From 84c08e6ed971f6903f1231c1593c709e47c01aba Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 7 May 2024 00:24:49 -0500 Subject: [PATCH 01/65] work around collide not returning false with a nul object fix a crash caused by having boundingBoxCollision on, while projecting the mouse so that there is nothing between it and a globalbounds object it would seem we're somehow ending up in a state of WorldEditor::collide returning true it hit somethging, but NULL as far as *what* until we properly fix this, doublecheck to make sure the hitObject isn't NULL before we start trying to reference membervars/methods --- Engine/source/gui/worldEditor/worldEditor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/gui/worldEditor/worldEditor.cpp b/Engine/source/gui/worldEditor/worldEditor.cpp index 888c629f4..b6effcf93 100644 --- a/Engine/source/gui/worldEditor/worldEditor.cpp +++ b/Engine/source/gui/worldEditor/worldEditor.cpp @@ -1999,7 +1999,7 @@ void WorldEditor::on3DMouseMove(const Gui3DMouseEvent & event) if ( !mHitObject ) { SceneObject *hitObj = NULL; - if ( collide(event, &hitObj) && !hitObj->isDeleted() && hitObj->isSelectionEnabled() && !objClassIgnored(hitObj) ) + if ( collide(event, &hitObj) && hitObj && !hitObj->isDeleted() && hitObj->isSelectionEnabled() && !objClassIgnored(hitObj) ) { mHitObject = hitObj; } @@ -2060,7 +2060,7 @@ void WorldEditor::on3DMouseDown(const Gui3DMouseEvent & event) } SceneObject *hitObj = NULL; - if ( collide( event, &hitObj ) && hitObj->isSelectionEnabled() && !objClassIgnored( hitObj ) ) + if ( collide( event, &hitObj ) && hitObj && hitObj->isSelectionEnabled() && !objClassIgnored( hitObj ) ) { mPossibleHitObject = hitObj; mNoMouseDrag = true; From 679f0ff0652200e94c1565ffd8f7fa1a86d6e98b Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 03:07:59 +0100 Subject: [PATCH 02/65] vhacd added vhacd not working atm --- Engine/lib/CMakeLists.txt | 2 +- Engine/source/CMakeLists.txt | 1 + Engine/source/ts/tsMeshFit.cpp | 117 +- Engine/source/ts/vhacd/VHACD.h | 8447 ++++++++++++++++++++++++++++++ Tools/CMake/torque_configs.cmake | 2 +- 5 files changed, 8513 insertions(+), 56 deletions(-) create mode 100644 Engine/source/ts/vhacd/VHACD.h diff --git a/Engine/lib/CMakeLists.txt b/Engine/lib/CMakeLists.txt index c6398956f..f101dba06 100644 --- a/Engine/lib/CMakeLists.txt +++ b/Engine/lib/CMakeLists.txt @@ -189,7 +189,7 @@ mark_as_advanced(PNG_PREFIX) add_subdirectory(tinyxml ${TORQUE_LIB_TARG_DIRECTORY}/tinyxml EXCLUDE_FROM_ALL) add_subdirectory(opcode ${TORQUE_LIB_TARG_DIRECTORY}/opcode EXCLUDE_FROM_ALL) add_subdirectory(pcre ${TORQUE_LIB_TARG_DIRECTORY}/pcre EXCLUDE_FROM_ALL) -add_subdirectory(convexDecomp ${TORQUE_LIB_TARG_DIRECTORY}/convexDecomp EXCLUDE_FROM_ALL) + add_subdirectory(squish ${TORQUE_LIB_TARG_DIRECTORY}/squish EXCLUDE_FROM_ALL) add_subdirectory(collada ${TORQUE_LIB_TARG_DIRECTORY}/collada EXCLUDE_FROM_ALL) add_subdirectory(glad ${TORQUE_LIB_TARG_DIRECTORY}/glad EXCLUDE_FROM_ALL) diff --git a/Engine/source/CMakeLists.txt b/Engine/source/CMakeLists.txt index 6f204ba32..e1a23a177 100644 --- a/Engine/source/CMakeLists.txt +++ b/Engine/source/CMakeLists.txt @@ -86,6 +86,7 @@ torqueAddSourceDirectories("gfx" "gfx/Null" "gfx/test" "gfx/bitmap" "gfx/bitmap/ # add the stb headers set(TORQUE_INCLUDE_DIRECTORIES ${TORQUE_INCLUDE_DIRECTORIES} "gfx/bitmap/loaders/stb") +set(TORQUE_INCLUDE_DIRECTORIES ${TORQUE_INCLUDE_DIRECTORIES} "ts/vhacd") if (TORQUE_OPENGL) torqueAddSourceDirectories("gfx/gl" "gfx/gl/sdl" "gfx/gl/tGL") diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 0beb2d9a2..d367c6b32 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -19,7 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- - #include "platform/platform.h" #include "console/consoleTypes.h" @@ -27,18 +26,12 @@ #include "ts/tsShapeConstruct.h" #include "console/engineAPI.h" -// define macros required for ConvexDecomp headers -#if defined( _WIN32 ) && !defined( WIN32 ) -#define WIN32 -#elif defined( __MACOSX__ ) && !defined( APPLE ) -#define APPLE -#endif - -#include "convexDecomp/NvFloatMath.h" -#include "convexDecomp/NvConvexDecomposition.h" -#include "convexDecomp/NvStanHull.h" - //----------------------------------------------------------------------------- + +#define ENABLE_VHACD_IMPLEMENTATION 1 +#define VHACD_DISABLE_THREADING 0 +#include + static const Point3F sFacePlanes[] = { Point3F( -1.0f, 0.0f, 0.0f ), Point3F( 1.0f, 0.0f, 0.0f ), @@ -109,18 +102,18 @@ public: void fitBox( U32 vertCount, const F32* verts ) { - CONVEX_DECOMPOSITION::fm_computeBestFitOBB( vertCount, verts, sizeof(F32)*3, (F32*)mBoxSides, (F32*)mBoxTransform ); + //FLOAT_MATH::fm_computeBestFitOBB( vertCount, verts, sizeof(F32)*3, (F32*)mBoxSides, (F32*)mBoxTransform, false ); mBoxTransform.transpose(); } void fitSphere( U32 vertCount, const F32* verts ) { - mSphereRadius = CONVEX_DECOMPOSITION::fm_computeBestFitSphere( vertCount, verts, sizeof(F32)*3, (F32*)mSphereCenter ); + //mSphereRadius = FLOAT_MATH::fm_computeBestFitSphere( vertCount, verts, sizeof(F32)*3, (F32*)mSphereCenter ); } void fitCapsule( U32 vertCount, const F32* verts ) { - CONVEX_DECOMPOSITION::fm_computeBestFitCapsule( vertCount, verts, sizeof(F32)*3, mCapRadius, mCapHeight, (F32*)mCapTransform ); + //FLOAT_MATH::fm_computeBestFitCapsule( vertCount, verts, sizeof(F32)*3, mCapRadius, mCapHeight, (F32*)mCapTransform ); mCapTransform.transpose(); } }; @@ -572,6 +565,7 @@ void MeshFit::fitK_DOP( const Vector& planes ) // Collect the intersection points of any 3 planes that lie inside // the maximum distances found above Vector points; + Vector pointIndices; for ( S32 i = 0; i < planes.size()-2; i++ ) { for ( S32 j = i+1; j < planes.size()-1; j++ ) @@ -599,32 +593,48 @@ void MeshFit::fitK_DOP( const Vector& planes ) } } - if ( addPoint ) - points.push_back( p ); + if (addPoint) + { + points.push_back(p); + pointIndices.push_back(points.size() - 1); + } } } } - // Create a convex hull from the point set - CONVEX_DECOMPOSITION::HullDesc hd; - hd.mVcount = points.size(); - hd.mVertices = (F32*)points.address(); - hd.mVertexStride = sizeof(Point3F); - hd.mMaxVertices = 64; - hd.mSkinWidth = 0.0f; + VHACD::IVHACD::Parameters p; + p.m_fillMode = VHACD::FillMode::FLOOD_FILL; + p.m_maxNumVerticesPerCH = 64; + p.m_shrinkWrap = true; + p.m_maxRecursionDepth = 64; + p.m_minimumVolumePercentErrorAllowed = 10; + p.m_resolution = 10000; + p.m_maxConvexHulls = 1; - CONVEX_DECOMPOSITION::HullLibrary hl; - CONVEX_DECOMPOSITION::HullResult result; - hl.CreateConvexHull( hd, result ); + VHACD::IVHACD* iface = VHACD::CreateVHACD(); + + iface->Compute((F32*)points.address(), points.size(), (U32*)pointIndices.address(), pointIndices.size(), p); + + // safety loop. + while (!iface->IsReady()) + { + Platform::sleep(1000); + } + + // we only get the 1 in dop? + VHACD::IVHACD::ConvexHull ch; + iface->GetConvexHull(0, ch); // Create TSMesh from convex hull mMeshes.increment(); MeshFit::Mesh& lastMesh = mMeshes.last(); lastMesh.type = MeshFit::Hull; lastMesh.transform.identity(); - lastMesh.tsmesh = createTriMesh(result.mOutputVertices, result.mNumOutputVertices, - result.mIndices, result.mNumFaces ); + lastMesh.tsmesh = createTriMesh((F32*)&ch.m_points, ch.m_points.size(), + (U32*)&ch.m_triangles, ch.m_triangles.size()); lastMesh.tsmesh->computeBounds(); + + iface->Release(); } //--------------------------- @@ -635,31 +645,30 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh const F32 SkinWidth = 0.0f; const F32 SplitThreshold = 2.0f; - CONVEX_DECOMPOSITION::iConvexDecomposition *ic = CONVEX_DECOMPOSITION::createConvexDecomposition(); + VHACD::IVHACD::Parameters p; + p.m_fillMode = VHACD::FillMode::FLOOD_FILL; + p.m_maxNumVerticesPerCH = maxHullVerts; + p.m_shrinkWrap = true; + p.m_maxRecursionDepth = 64; + p.m_minimumVolumePercentErrorAllowed = 10; + p.m_resolution = 10000; + p.m_maxConvexHulls = depth; - for ( S32 i = 0; i < mIndices.size(); i += 3 ) + VHACD::IVHACD* iface = VHACD::CreateVHACD(); + + iface->Compute((F32*)mVerts.address(), mVerts.size(), (U32*)mIndices.address(), mIndices.size(), p); + + // safety loop. + while (!iface->IsReady()) { - ic->addTriangle( (F32*)mVerts[mIndices[i]], - (F32*)mVerts[mIndices[i+1]], - (F32*)mVerts[mIndices[i+2]] ); + Platform::sleep(1000); } - ic->computeConvexDecomposition( - SkinWidth, - depth, - maxHullVerts, - concavityThreshold, - mergeThreshold, - SplitThreshold, - true, - false, - false ); - // Add a TSMesh for each hull - for ( S32 i = 0; i < ic->getHullCount(); i++ ) + for ( S32 i = 0; i < iface->GetNConvexHulls(); i++ ) { - CONVEX_DECOMPOSITION::ConvexHullResult result; - ic->getConvexHullResult( i, result ); + VHACD::IVHACD::ConvexHull ch; + iface->GetConvexHull(i, ch); eMeshType meshType = MeshFit::Hull; @@ -667,23 +676,23 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh if (( boxMaxError > 0 ) || ( sphereMaxError > 0 ) || ( capsuleMaxError > 0 )) { // Compute error between actual mesh and fitted primitives - F32 meshVolume = CONVEX_DECOMPOSITION::fm_computeMeshVolume( result.mVertices, result.mTcount, result.mIndices ); + F32 meshVolume = 10.0f; // FLOAT_MATH::fm_computeMeshVolume((F32*)&ch.m_points, ch.m_triangles.size(), (U32*)&ch.m_triangles); PrimFit primFitter; F32 boxError = 100.0f, sphereError = 100.0f, capsuleError = 100.0f; if ( boxMaxError > 0 ) { - primFitter.fitBox( result.mVcount, result.mVertices ); + primFitter.fitBox(ch.m_points.size(), (F32*)&ch.m_points); boxError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getBoxVolume() ) ); } if ( sphereMaxError > 0 ) { - primFitter.fitSphere( result.mVcount, result.mVertices ); + primFitter.fitSphere(ch.m_points.size(), (F32*)&ch.m_points); sphereError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getSphereVolume() ) ); } if ( capsuleMaxError > 0 ) { - primFitter.fitCapsule( result.mVcount, result.mVertices ); + primFitter.fitCapsule(ch.m_points.size(), (F32*)&ch.m_points); capsuleError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getCapsuleVolume() ) ); } @@ -722,12 +731,12 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh MeshFit::Mesh& lastMesh = mMeshes.last(); lastMesh.type = MeshFit::Hull; lastMesh.transform.identity(); - lastMesh.tsmesh = createTriMesh(result.mVertices, result.mVcount, result.mIndices, result.mTcount); + lastMesh.tsmesh = createTriMesh((F32*)&ch.m_points, ch.m_points.size(), (U32*)&ch.m_triangles, ch.m_triangles.size()); lastMesh.tsmesh->computeBounds(); } } - CONVEX_DECOMPOSITION::releaseConvexDecomposition( ic ); + iface->Release(); } //----------------------------------------------------------------------------- diff --git a/Engine/source/ts/vhacd/VHACD.h b/Engine/source/ts/vhacd/VHACD.h new file mode 100644 index 000000000..e8a3e73c4 --- /dev/null +++ b/Engine/source/ts/vhacd/VHACD.h @@ -0,0 +1,8447 @@ +/* Copyright (c) 2011 Khaled Mamou (kmamou at gmail dot com) + All rights reserved. + + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + 3. The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once + +#ifndef VHACD_H +# define VHACD_H + +// Please view this slide deck which describes usage and how the algorithm works. +// https://docs.google.com/presentation/d/1OZ4mtZYrGEC8qffqb8F7Le2xzufiqvaPpRbLHKKgTIM/edit?usp=sharing + +// VHACD is now a header only library. +// In just *one* of your CPP files *before* you include 'VHACD.h' you must declare +// #define ENABLE_VHACD_IMPLEMENTATION 1 +// This will compile the implementation code into your project. If you don't +// have this define, you will get link errors since the implementation code will +// not be present. If you define it more than once in your code base, you will get +// link errors due to a duplicate implementation. This is the same pattern used by +// ImGui and StbLib and other popular open source libraries. + +# define VHACD_VERSION_MAJOR 4 +# define VHACD_VERSION_MINOR 1 + +// Changes for version 4.1 +// +// Various minor tweaks mostly to the test application and some default values. + +// Changes for version 4.0 +// +// * The code has been significantly refactored to be cleaner and easier to maintain +// * All OpenCL related code removed +// * All Bullet code removed +// * All SIMD code removed +// * Old plane splitting code removed +// +// * The code is now delivered as a single header file 'VHACD.h' which has both the API +// * declaration as well as the implementation. Simply add '#define ENABLE_VHACD_IMPLEMENTATION 1' +// * to any CPP in your application prior to including 'VHACD.h'. Only do this in one CPP though. +// * If you do not have this define once, you will get link errors since the implementation code +// * will not be compiled in. If you have this define more than once, you are likely to get +// * duplicate symbol link errors. +// +// * Since the library is now delivered as a single header file, we do not provide binaries +// * or build scripts as these are not needed. +// +// * The old DebugView and test code has all been removed and replaced with a much smaller and +// * simpler test console application with some test meshes to work with. +// +// * The convex hull generation code has changed. The previous version came from Bullet. +// * However, the new version is courtesy of Julio Jerez, the author of the Newton +// * physics engine. His new version is faster and more numerically stable. +// +// * The code can now detect if the input mesh is, itself, already a convex object and +// * can early out. +// +// * Significant performance improvements have been made to the code and it is now much +// * faster, stable, and is easier to tune than previous versions. +// +// * A bug was fixed with the shrink wrapping code (project hull vertices) that could +// * sometime produce artifacts in the results. The new version uses a 'closest point' +// * algorithm that is more reliable. +// +// * You can now select which 'fill mode' to use. For perfectly closed meshes, the default +// * behavior using a flood fill generally works fine. However, some meshes have small +// * holes in them and therefore the flood fill will fail, treating the mesh as being +// * hollow. In these cases, you can use the 'raycast' fill option to determine which +// * parts of the voxelized mesh are 'inside' versus being 'outside'. Finally, there +// * are some rare instances where a user might actually want the mesh to be treated as +// * hollow, in which case you can pass in 'surface' only. +// * +// * A new optional virtual interface called 'IUserProfiler' was provided. +// * This allows the user to provide an optional profiling callback interface to assist in +// * diagnosing performance issues. This change was made by Danny Couture at Epic for the UE4 integration. +// * Some profiling macros were also declared in support of this feature. +// * +// * Another new optional virtual interface called 'IUserTaskRunner' was provided. +// * This interface is used to run logical 'tasks' in a background thread. If none is provided +// * then a default implementation using std::thread will be executed. +// * This change was made by Danny Couture at Epic to speed up the voxelization step. +// * + + + +// The history of V-HACD: +// +// The initial version was written by John W. Ratcliff and was called 'ACD' +// This version did not perform CSG operations on the source mesh, so if you +// recursed too deeply it would produce hollow results. +// +// The next version was written by Khaled Mamou and was called 'HACD' +// In this version Khaled tried to perform a CSG operation on the source +// mesh to produce more robust results. However, Khaled learned that the +// CSG library he was using had licensing issues so he started work on the +// next version. +// +// The next version was called 'V-HACD' because Khaled made the observation +// that plane splitting would be far easier to implement working in voxel space. +// +// V-HACD has been integrated into UE4, Blender, and a number of other projects. +// This new release, version4, is a significant refactor of the code to fix +// some bugs, improve performance, and to make the codebase easier to maintain +// going forward. + +#ifndef _STDINT +#include +#endif + +#ifndef _FUNCTIONAL_ +#include +#endif + +#ifndef _VECTOR_ +#include +#endif + +#ifndef _ARRAY_ +#include +#endif + +#ifndef _CMATH_ +#include +#endif + +#ifndef _ALGORITHM_ +#include +#endif + +namespace VHACD { + +struct Vertex +{ + double mX; + double mY; + double mZ; + + Vertex() = default; + Vertex(double x, double y, double z) : mX(x), mY(y), mZ(z) {} + + const double& operator[](size_t idx) const + { + switch(idx) + { + case 0: return mX; + case 1: return mY; + case 2: return mZ; + }; + return mX; + } +}; + +struct Triangle +{ + uint32_t mI0; + uint32_t mI1; + uint32_t mI2; + + Triangle() = default; + Triangle(uint32_t i0, uint32_t i1, uint32_t i2) : mI0(i0), mI1(i1), mI2(i2) {} +}; + +template +class Vector3 +{ +public: + /* + * Getters + */ + T& operator[](size_t i); + const T& operator[](size_t i) const; + T& GetX(); + T& GetY(); + T& GetZ(); + const T& GetX() const; + const T& GetY() const; + const T& GetZ() const; + + /* + * Normalize and norming + */ + T Normalize(); + Vector3 Normalized(); + T GetNorm() const; + T GetNormSquared() const; + int LongestAxis() const; + + /* + * Vector-vector operations + */ + Vector3& operator=(const Vector3& rhs); + Vector3& operator+=(const Vector3& rhs); + Vector3& operator-=(const Vector3& rhs); + + Vector3 CWiseMul(const Vector3& rhs) const; + Vector3 Cross(const Vector3& rhs) const; + T Dot(const Vector3& rhs) const; + Vector3 operator+(const Vector3& rhs) const; + Vector3 operator-(const Vector3& rhs) const; + + /* + * Vector-scalar operations + */ + Vector3& operator-=(T a); + Vector3& operator+=(T a); + Vector3& operator/=(T a); + Vector3& operator*=(T a); + + Vector3 operator*(T rhs) const; + Vector3 operator/(T rhs) const; + + /* + * Unary operations + */ + Vector3 operator-() const; + + /* + * Comparison operators + */ + bool operator<(const Vector3& rhs) const; + bool operator>(const Vector3& rhs) const; + + /* + * Returns true if all elements of *this are greater than or equal to all elements of rhs, coefficient wise + * LE is less than or equal + */ + bool CWiseAllGE(const Vector3& rhs) const; + bool CWiseAllLE(const Vector3& rhs) const; + + Vector3 CWiseMin(const Vector3& rhs) const; + Vector3 CWiseMax(const Vector3& rhs) const; + T MinCoeff() const; + T MaxCoeff() const; + + T MinCoeff(uint32_t& idx) const; + T MaxCoeff(uint32_t& idx) const; + + /* + * Constructors + */ + Vector3() = default; + Vector3(T a); + Vector3(T x, T y, T z); + Vector3(const Vector3& rhs); + ~Vector3() = default; + + template + Vector3(const Vector3& rhs); + + Vector3(const VHACD::Vertex&); + Vector3(const VHACD::Triangle&); + + operator VHACD::Vertex() const; + +private: + std::array m_data{ T(0.0) }; +}; + +typedef VHACD::Vector3 Vect3; + +struct BoundsAABB +{ + BoundsAABB() = default; + BoundsAABB(const std::vector& points); + BoundsAABB(const Vect3& min, + const Vect3& max); + + BoundsAABB Union(const BoundsAABB& b); + + bool Intersects(const BoundsAABB& b) const; + + double SurfaceArea() const; + double Volume() const; + + BoundsAABB Inflate(double ratio) const; + + VHACD::Vect3 ClosestPoint(const VHACD::Vect3& p) const; + + VHACD::Vect3& GetMin(); + VHACD::Vect3& GetMax(); + const VHACD::Vect3& GetMin() const; + const VHACD::Vect3& GetMax() const; + + VHACD::Vect3 GetSize() const; + VHACD::Vect3 GetCenter() const; + + VHACD::Vect3 m_min{ double(0.0) }; + VHACD::Vect3 m_max{ double(0.0) }; +}; + +/** +* This enumeration determines how the voxels as filled to create a solid +* object. The default should be 'FLOOD_FILL' which generally works fine +* for closed meshes. However, if the mesh is not watertight, then using +* RAYCAST_FILL may be preferable as it will determine if a voxel is part +* of the interior of the source mesh by raycasting around it. +* +* Finally, there are some cases where you might actually want a convex +* decomposition to treat the source mesh as being hollow. If that is the +* case you can pass in 'SURFACE_ONLY' and then the convex decomposition +* will converge only onto the 'skin' of the surface mesh. +*/ +enum class FillMode +{ + FLOOD_FILL, // This is the default behavior, after the voxelization step it uses a flood fill to determine 'inside' + // from 'outside'. However, meshes with holes can fail and create hollow results. + SURFACE_ONLY, // Only consider the 'surface', will create 'skins' with hollow centers. + RAYCAST_FILL, // Uses raycasting to determine inside from outside. +}; + +class IVHACD +{ +public: + /** + * This optional pure virtual interface is used to notify the caller of the progress + * of convex decomposition as well as a signal when it is complete when running in + * a background thread + */ + class IUserCallback + { + public: + virtual ~IUserCallback(){}; + + /** + * Notifies the application of the current state of the convex decomposition operation + * + * @param overallProgress : Total progress from 0-100% + * @param stageProgress : Progress of the current stage 0-100% + * @param stage : A text description of the current stage we are in + * @param operation : A text description of what operation is currently being performed. + */ + virtual void Update(const double overallProgress, + const double stageProgress, + const char* const stage, + const char* operation) = 0; + + // This is an optional user callback which is only called when running V-HACD asynchronously. + // This is a callback performed to notify the user that the + // convex decomposition background process is completed. This call back will occur from + // a different thread so the user should take that into account. + virtual void NotifyVHACDComplete() + { + } + }; + + /** + * Optional user provided pure virtual interface to be notified of warning or informational messages + */ + class IUserLogger + { + public: + virtual ~IUserLogger(){}; + virtual void Log(const char* const msg) = 0; + }; + + /** + * An optional user provided pure virtual interface to perform a background task. + * This was added by Danny Couture at Epic as they wanted to use their own + * threading system instead of the standard library version which is the default. + */ + class IUserTaskRunner + { + public: + virtual ~IUserTaskRunner(){}; + virtual void* StartTask(std::function func) = 0; + virtual void JoinTask(void* Task) = 0; + }; + + /** + * A simple class that represents a convex hull as a triangle mesh with + * double precision vertices. Polygons are not currently provided. + */ + class ConvexHull + { + public: + std::vector m_points; + std::vector m_triangles; + + double m_volume{ 0 }; // The volume of the convex hull + VHACD::Vect3 m_center{ 0, 0, 0 }; // The centroid of the convex hull + uint32_t m_meshId{ 0 }; // A unique id for this convex hull + VHACD::Vect3 mBmin; // Bounding box minimum of the AABB + VHACD::Vect3 mBmax; // Bounding box maximum of the AABB + }; + + /** + * This class provides the parameters controlling the convex decomposition operation + */ + class Parameters + { + public: + IUserCallback* m_callback{nullptr}; // Optional user provided callback interface for progress + IUserLogger* m_logger{nullptr}; // Optional user provided callback interface for log messages + IUserTaskRunner* m_taskRunner{nullptr}; // Optional user provided interface for creating tasks + uint32_t m_maxConvexHulls{ 64 }; // The maximum number of convex hulls to produce + uint32_t m_resolution{ 400000 }; // The voxel resolution to use + double m_minimumVolumePercentErrorAllowed{ 1 }; // if the voxels are within 1% of the volume of the hull, we consider this a close enough approximation + uint32_t m_maxRecursionDepth{ 10 }; // The maximum recursion depth + bool m_shrinkWrap{true}; // Whether or not to shrinkwrap the voxel positions to the source mesh on output + FillMode m_fillMode{ FillMode::FLOOD_FILL }; // How to fill the interior of the voxelized mesh + uint32_t m_maxNumVerticesPerCH{ 64 }; // The maximum number of vertices allowed in any output convex hull + bool m_asyncACD{ true }; // Whether or not to run asynchronously, taking advantage of additional cores + uint32_t m_minEdgeLength{ 2 }; // Once a voxel patch has an edge length of less than 4 on all 3 sides, we don't keep recursing + bool m_findBestPlane{ false }; // Whether or not to attempt to split planes along the best location. Experimental feature. False by default. + }; + + /** + * Will cause the convex decomposition operation to be canceled early. No results will be produced but the background operation will end as soon as it can. + */ + virtual void Cancel() = 0; + + /** + * Compute a convex decomposition of a triangle mesh using float vertices and the provided user parameters. + * + * @param points : The vertices of the source mesh as floats in the form of X1,Y1,Z1, X2,Y2,Z2,.. etc. + * @param countPoints : The number of vertices in the source mesh. + * @param triangles : The indices of triangles in the source mesh in the form of I1,I2,I3, .... + * @param countTriangles : The number of triangles in the source mesh + * @param params : The convex decomposition parameters to apply + * @return : Returns true if the convex decomposition operation can be started + */ + virtual bool Compute(const float* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) = 0; + + /** + * Compute a convex decomposition of a triangle mesh using double vertices and the provided user parameters. + * + * @param points : The vertices of the source mesh as floats in the form of X1,Y1,Z1, X2,Y2,Z2,.. etc. + * @param countPoints : The number of vertices in the source mesh. + * @param triangles : The indices of triangles in the source mesh in the form of I1,I2,I3, .... + * @param countTriangles : The number of triangles in the source mesh + * @param params : The convex decomposition parameters to apply + * @return : Returns true if the convex decomposition operation can be started + */ + virtual bool Compute(const double* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) = 0; + + /** + * Returns the number of convex hulls that were produced. + * + * @return : Returns the number of convex hulls produced, or zero if it failed or was canceled + */ + virtual uint32_t GetNConvexHulls() const = 0; + + /** + * Retrieves one of the convex hulls in the solution set + * + * @param index : Which convex hull to retrieve + * @param ch : The convex hull descriptor to return + * @return : Returns true if the convex hull exists and could be retrieved + */ + virtual bool GetConvexHull(const uint32_t index, + ConvexHull& ch) const = 0; + + /** + * Releases any memory allocated by the V-HACD class + */ + virtual void Clean() = 0; // release internally allocated memory + + /** + * Releases this instance of the V-HACD class + */ + virtual void Release() = 0; // release IVHACD + + // Will compute the center of mass of the convex hull decomposition results and return it + // in 'centerOfMass'. Returns false if the center of mass could not be computed. + virtual bool ComputeCenterOfMass(double centerOfMass[3]) const = 0; + + // In synchronous mode (non-multi-threaded) the state is always 'ready' + // In asynchronous mode, this returns true if the background thread is not still actively computing + // a new solution. In an asynchronous config the 'IsReady' call will report any update or log + // messages in the caller's current thread. + virtual bool IsReady() const + { + return true; + } + + /** + * At the request of LegionFu : out_look@foxmail.com + * This method will return which convex hull is closest to the source position. + * You can use this method to figure out, for example, which vertices in the original + * source mesh are best associated with which convex hull. + * + * @param pos : The input 3d position to test against + * + * @return : Returns which convex hull this position is closest to. + */ + virtual uint32_t findNearestConvexHull(const double pos[3], + double& distanceToHull) = 0; + +protected: + virtual ~IVHACD() + { + } +}; +/* + * Out of line definitions + */ + + template + T clamp(const T& v, const T& lo, const T& hi) + { + if (v < lo) + { + return lo; + } + if (v > hi) + { + return hi; + } + return v ; + } + +/* + * Getters + */ + template + inline T& Vector3::operator[](size_t i) + { + return m_data[i]; + } + + template + inline const T& Vector3::operator[](size_t i) const + { + return m_data[i]; + } + + template + inline T& Vector3::GetX() + { + return m_data[0]; + } + + template + inline T& Vector3::GetY() + { + return m_data[1]; + } + + template + inline T& Vector3::GetZ() + { + return m_data[2]; + } + + template + inline const T& Vector3::GetX() const + { + return m_data[0]; + } + + template + inline const T& Vector3::GetY() const + { + return m_data[1]; + } + + template + inline const T& Vector3::GetZ() const + { + return m_data[2]; + } + +/* + * Normalize and norming + */ + template + inline T Vector3::Normalize() + { + T n = GetNorm(); + if (n != T(0.0)) (*this) /= n; + return n; + } + + template + inline Vector3 Vector3::Normalized() + { + Vector3 ret = *this; + T n = GetNorm(); + if (n != T(0.0)) ret /= n; + return ret; + } + + template + inline T Vector3::GetNorm() const + { + return std::sqrt(GetNormSquared()); + } + + template + inline T Vector3::GetNormSquared() const + { + return this->Dot(*this); + } + + template + inline int Vector3::LongestAxis() const + { + auto it = std::max_element(m_data.begin(), m_data.end()); + return int(std::distance(m_data.begin(), it)); + } + +/* + * Vector-vector operations + */ + template + inline Vector3& Vector3::operator=(const Vector3& rhs) + { + GetX() = rhs.GetX(); + GetY() = rhs.GetY(); + GetZ() = rhs.GetZ(); + return *this; + } + + template + inline Vector3& Vector3::operator+=(const Vector3& rhs) + { + GetX() += rhs.GetX(); + GetY() += rhs.GetY(); + GetZ() += rhs.GetZ(); + return *this; + } + + template + inline Vector3& Vector3::operator-=(const Vector3& rhs) + { + GetX() -= rhs.GetX(); + GetY() -= rhs.GetY(); + GetZ() -= rhs.GetZ(); + return *this; + } + + template + inline Vector3 Vector3::CWiseMul(const Vector3& rhs) const + { + return Vector3(GetX() * rhs.GetX(), + GetY() * rhs.GetY(), + GetZ() * rhs.GetZ()); + } + + template + inline Vector3 Vector3::Cross(const Vector3& rhs) const + { + return Vector3(GetY() * rhs.GetZ() - GetZ() * rhs.GetY(), + GetZ() * rhs.GetX() - GetX() * rhs.GetZ(), + GetX() * rhs.GetY() - GetY() * rhs.GetX()); + } + + template + inline T Vector3::Dot(const Vector3& rhs) const + { + return GetX() * rhs.GetX() + + GetY() * rhs.GetY() + + GetZ() * rhs.GetZ(); + } + + template + inline Vector3 Vector3::operator+(const Vector3& rhs) const + { + return Vector3(GetX() + rhs.GetX(), + GetY() + rhs.GetY(), + GetZ() + rhs.GetZ()); + } + + template + inline Vector3 Vector3::operator-(const Vector3& rhs) const + { + return Vector3(GetX() - rhs.GetX(), + GetY() - rhs.GetY(), + GetZ() - rhs.GetZ()); + } + + template + inline Vector3 operator*(T lhs, const Vector3& rhs) + { + return Vector3(lhs * rhs.GetX(), + lhs * rhs.GetY(), + lhs * rhs.GetZ()); + } + +/* + * Vector-scalar operations + */ + template + inline Vector3& Vector3::operator-=(T a) + { + GetX() -= a; + GetY() -= a; + GetZ() -= a; + return *this; + } + + template + inline Vector3& Vector3::operator+=(T a) + { + GetX() += a; + GetY() += a; + GetZ() += a; + return *this; + } + + template + inline Vector3& Vector3::operator/=(T a) + { + GetX() /= a; + GetY() /= a; + GetZ() /= a; + return *this; + } + + template + inline Vector3& Vector3::operator*=(T a) + { + GetX() *= a; + GetY() *= a; + GetZ() *= a; + return *this; + } + + template + inline Vector3 Vector3::operator*(T rhs) const + { + return Vector3(GetX() * rhs, + GetY() * rhs, + GetZ() * rhs); + } + + template + inline Vector3 Vector3::operator/(T rhs) const + { + return Vector3(GetX() / rhs, + GetY() / rhs, + GetZ() / rhs); + } + +/* + * Unary operations + */ + template + inline Vector3 Vector3::operator-() const + { + return Vector3(-GetX(), + -GetY(), + -GetZ()); + } + +/* + * Comparison operators + */ + template + inline bool Vector3::operator<(const Vector3& rhs) const + { + if (GetX() == rhs.GetX()) + { + if (GetY() == rhs.GetY()) + { + return (GetZ() < rhs.GetZ()); + } + return (GetY() < rhs.GetY()); + } + return (GetX() < rhs.GetX()); + } + + template + inline bool Vector3::operator>(const Vector3& rhs) const + { + if (GetX() == rhs.GetX()) + { + if (GetY() == rhs.GetY()) + { + return (GetZ() > rhs.GetZ()); + } + return (GetY() > rhs.GetY()); + } + return (GetX() > rhs.GetZ()); + } + + template + inline bool Vector3::CWiseAllGE(const Vector3& rhs) const + { + return GetX() >= rhs.GetX() + && GetY() >= rhs.GetY() + && GetZ() >= rhs.GetZ(); + } + + template + inline bool Vector3::CWiseAllLE(const Vector3& rhs) const + { + return GetX() <= rhs.GetX() + && GetY() <= rhs.GetY() + && GetZ() <= rhs.GetZ(); + } + + template + inline Vector3 Vector3::CWiseMin(const Vector3& rhs) const + { + return Vector3(std::min(GetX(), rhs.GetX()), + std::min(GetY(), rhs.GetY()), + std::min(GetZ(), rhs.GetZ())); + } + + template + inline Vector3 Vector3::CWiseMax(const Vector3& rhs) const + { + return Vector3(std::max(GetX(), rhs.GetX()), + std::max(GetY(), rhs.GetY()), + std::max(GetZ(), rhs.GetZ())); + } + + template + inline T Vector3::MinCoeff() const + { + return *std::min_element(m_data.begin(), m_data.end()); + } + + template + inline T Vector3::MaxCoeff() const + { + return *std::max_element(m_data.begin(), m_data.end()); + } + + template + inline T Vector3::MinCoeff(uint32_t& idx) const + { + auto it = std::min_element(m_data.begin(), m_data.end()); + idx = uint32_t(std::distance(m_data.begin(), it)); + return *it; + } + + template + inline T Vector3::MaxCoeff(uint32_t& idx) const + { + auto it = std::max_element(m_data.begin(), m_data.end()); + idx = uint32_t(std::distance(m_data.begin(), it)); + return *it; + } + +/* + * Constructors + */ + template + inline Vector3::Vector3(T a) + : m_data{a, a, a} + { + } + + template + inline Vector3::Vector3(T x, T y, T z) + : m_data{x, y, z} + { + } + + template + inline Vector3::Vector3(const Vector3& rhs) + : m_data{rhs.m_data} + { + } + + template + template + inline Vector3::Vector3(const Vector3& rhs) + : m_data{T(rhs.GetX()), T(rhs.GetY()), T(rhs.GetZ())} + { + } + + template + inline Vector3::Vector3(const VHACD::Vertex& rhs) + : Vector3(rhs.mX, rhs.mY, rhs.mZ) + { + static_assert(std::is_same::value, "Vertex to Vector3 constructor only enabled for double"); + } + + template + inline Vector3::Vector3(const VHACD::Triangle& rhs) + : Vector3(rhs.mI0, rhs.mI1, rhs.mI2) + { + static_assert(std::is_same::value, "Triangle to Vector3 constructor only enabled for uint32_t"); + } + + template + inline Vector3::operator VHACD::Vertex() const + { + static_assert(std::is_same::value, "Vector3 to Vertex conversion only enable for double"); + return ::VHACD::Vertex( GetX(), GetY(), GetZ()); + } + +IVHACD* CreateVHACD(); // Create a synchronous (blocking) implementation of V-HACD +IVHACD* CreateVHACD_ASYNC(); // Create an asynchronous (non-blocking) implementation of V-HACD + +} // namespace VHACD + +#if ENABLE_VHACD_IMPLEMENTATION + +#include + +#ifndef _INC_MATH +#include +#endif + +#ifndef _INC_STDLIB +#include +#endif + +#ifndef _INC_STRING +#include +#endif + +#ifndef _INC_FLOAT +#include +#endif + +#ifndef _INC_LIMITS +#include +#endif + +#ifndef _ARRAY_ +#include +#endif + +#ifndef _ATOMIC_ +#include +#endif + +#ifndef _CHRONO_ +#include +#endif + +#ifndef _CONDITION_VARIABLE_ +#include +#endif + +#ifndef _DEQUE_ +#include +#endif + +#ifndef _FUTURE_ +#include +#endif + +#ifndef _IOSTREAM_ +#include +#endif + +#ifndef _LIST_ +#include +#endif + +#ifndef _MEMORY_ +#include +#endif + +#ifndef _MUTEX_ +#include +#endif + +#ifndef _QUEUE_ +#include +#endif + +#ifndef _THREAD_ +#include +#endif + +#ifndef _UNORDERED_MAP_ +#include +#endif + +#ifndef _UNORDERED_SET_ +#include +#endif + +#ifndef _UTILITY_ +#include +#endif + +#ifndef _VECTOR_ +#include +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4100 4127 4189 4244 4456 4701 4702 4996) +#endif // _MSC_VER + +#ifdef __GNUC__ +#pragma GCC diagnostic push +// Minimum set of warnings used for cleanup +// #pragma GCC diagnostic warning "-Wall" +// #pragma GCC diagnostic warning "-Wextra" +// #pragma GCC diagnostic warning "-Wpedantic" +// #pragma GCC diagnostic warning "-Wold-style-cast" +// #pragma GCC diagnostic warning "-Wnon-virtual-dtor" +// #pragma GCC diagnostic warning "-Wshadow" +#endif // __GNUC__ + +// Scoped Timer +namespace VHACD { + +class Timer +{ +public: + Timer() + : m_startTime(std::chrono::high_resolution_clock::now()) + { + } + + void Reset() + { + m_startTime = std::chrono::high_resolution_clock::now(); + } + + double GetElapsedSeconds() + { + auto s = PeekElapsedSeconds(); + Reset(); + return s; + } + + double PeekElapsedSeconds() + { + auto now = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = now - m_startTime; + return diff.count(); + } + +private: + std::chrono::time_point m_startTime; +}; + +class ScopedTime +{ +public: + ScopedTime(const char* action, + VHACD::IVHACD::IUserLogger* logger) + : m_action(action) + , m_logger(logger) + { + m_timer.Reset(); + } + + ~ScopedTime() + { + double dtime = m_timer.GetElapsedSeconds(); + if( m_logger ) + { + char scratch[512]; + snprintf(scratch, + sizeof(scratch),"%s took %0.5f seconds", + m_action, + dtime); + m_logger->Log(scratch); + } + } + + const char* m_action{ nullptr }; + Timer m_timer; + VHACD::IVHACD::IUserLogger* m_logger{ nullptr }; +}; +BoundsAABB::BoundsAABB(const std::vector& points) + : m_min(points[0]) + , m_max(points[0]) +{ + for (uint32_t i = 1; i < points.size(); ++i) + { + const VHACD::Vertex& p = points[i]; + m_min = m_min.CWiseMin(p); + m_max = m_max.CWiseMax(p); + } +} + +BoundsAABB::BoundsAABB(const VHACD::Vect3& min, + const VHACD::Vect3& max) + : m_min(min) + , m_max(max) +{ +} + +BoundsAABB BoundsAABB::Union(const BoundsAABB& b) +{ + return BoundsAABB(GetMin().CWiseMin(b.GetMin()), + GetMax().CWiseMax(b.GetMax())); +} + +bool VHACD::BoundsAABB::Intersects(const VHACD::BoundsAABB& b) const +{ + if ( ( GetMin().GetX() > b.GetMax().GetX()) + || (b.GetMin().GetX() > GetMax().GetX())) + return false; + if ( ( GetMin().GetY() > b.GetMax().GetY()) + || (b.GetMin().GetY() > GetMax().GetY())) + return false; + if ( ( GetMin().GetZ() > b.GetMax().GetZ()) + || (b.GetMin().GetZ() > GetMax().GetZ())) + return false; + return true; +} + +double BoundsAABB::SurfaceArea() const +{ + VHACD::Vect3 d = GetMax() - GetMin(); + return double(2.0) * (d.GetX() * d.GetY() + d.GetX() * d.GetZ() + d.GetY() * d.GetZ()); +} + +double VHACD::BoundsAABB::Volume() const +{ + VHACD::Vect3 d = GetMax() - GetMin(); + return d.GetX() * d.GetY() * d.GetZ(); +} + +BoundsAABB VHACD::BoundsAABB::Inflate(double ratio) const +{ + double inflate = (GetMin() - GetMax()).GetNorm() * double(0.5) * ratio; + return BoundsAABB(GetMin() - inflate, + GetMax() + inflate); +} + +VHACD::Vect3 VHACD::BoundsAABB::ClosestPoint(const VHACD::Vect3& p) const +{ + return p.CWiseMax(GetMin()).CWiseMin(GetMax()); +} + +VHACD::Vect3& VHACD::BoundsAABB::GetMin() +{ + return m_min; +} + +VHACD::Vect3& VHACD::BoundsAABB::GetMax() +{ + return m_max; +} + +inline const VHACD::Vect3& VHACD::BoundsAABB::GetMin() const +{ + return m_min; +} + +const VHACD::Vect3& VHACD::BoundsAABB::GetMax() const +{ + return m_max; +} + +VHACD::Vect3 VHACD::BoundsAABB::GetSize() const +{ + return GetMax() - GetMin(); +} + +VHACD::Vect3 VHACD::BoundsAABB::GetCenter() const +{ + return (GetMin() + GetMax()) * double(0.5); +} + +/* + * Relies on three way comparison, which std::sort doesn't use + */ +template +void Sort(T* const array, int elements) +{ + const int batchSize = 8; + int stack[1024][2]; + + stack[0][0] = 0; + stack[0][1] = elements - 1; + int stackIndex = 1; + const dCompareKey comparator; + while (stackIndex) + { + stackIndex--; + int lo = stack[stackIndex][0]; + int hi = stack[stackIndex][1]; + if ((hi - lo) > batchSize) + { + int mid = (lo + hi) >> 1; + if (comparator.Compare(array[lo], array[mid]) > 0) + { + std::swap(array[lo], + array[mid]); + } + if (comparator.Compare(array[mid], array[hi]) > 0) + { + std::swap(array[mid], + array[hi]); + } + if (comparator.Compare(array[lo], array[mid]) > 0) + { + std::swap(array[lo], + array[mid]); + } + int i = lo + 1; + int j = hi - 1; + const T pivot(array[mid]); + do + { + while (comparator.Compare(array[i], pivot) < 0) + { + i++; + } + while (comparator.Compare(array[j], pivot) > 0) + { + j--; + } + + if (i <= j) + { + std::swap(array[i], + array[j]); + i++; + j--; + } + } while (i <= j); + + if (i < hi) + { + stack[stackIndex][0] = i; + stack[stackIndex][1] = hi; + stackIndex++; + } + if (lo < j) + { + stack[stackIndex][0] = lo; + stack[stackIndex][1] = j; + stackIndex++; + } + assert(stackIndex < int(sizeof(stack) / (2 * sizeof(stack[0][0])))); + } + } + + int stride = batchSize + 1; + if (elements < stride) + { + stride = elements; + } + for (int i = 1; i < stride; ++i) + { + if (comparator.Compare(array[0], array[i]) > 0) + { + std::swap(array[0], + array[i]); + } + } + + for (int i = 1; i < elements; ++i) + { + int j = i; + const T tmp(array[i]); + for (; comparator.Compare(array[j - 1], tmp) > 0; --j) + { + assert(j > 0); + array[j] = array[j - 1]; + } + array[j] = tmp; + } +} + +/* +Maintaining comment due to attribution +Purpose: + +TRIANGLE_AREA_3D computes the area of a triangle in 3D. + +Modified: + +22 April 1999 + +Author: + +John Burkardt + +Parameters: + +Input, double X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, the (getX,getY,getZ) +coordinates of the corners of the triangle. + +Output, double TRIANGLE_AREA_3D, the area of the triangle. +*/ +double ComputeArea(const VHACD::Vect3& p1, + const VHACD::Vect3& p2, + const VHACD::Vect3& p3) +{ + /* + Find the projection of (P3-P1) onto (P2-P1). + */ + double base = (p2 - p1).GetNorm(); + /* + The height of the triangle is the length of (P3-P1) after its + projection onto (P2-P1) has been subtracted. + */ + double height; + if (base == double(0.0)) + { + height = double(0.0); + } + else + { + double dot = (p3 - p1).Dot(p2 - p1); + double alpha = dot / (base * base); + + VHACD::Vect3 a = p3 - p1 - alpha * (p2 - p1); + height = a.GetNorm(); + } + + return double(0.5) * base * height; +} + +bool ComputeCentroid(const std::vector& points, + const std::vector& indices, + VHACD::Vect3& center) + +{ + bool ret = false; + if (points.size()) + { + center = VHACD::Vect3(0); + + VHACD::Vect3 numerator(0); + double denominator = 0; + + for (uint32_t i = 0; i < indices.size(); i++) + { + uint32_t i1 = indices[i].mI0; + uint32_t i2 = indices[i].mI1; + uint32_t i3 = indices[i].mI2; + + const VHACD::Vect3& p1 = points[i1]; + const VHACD::Vect3& p2 = points[i2]; + const VHACD::Vect3& p3 = points[i3]; + + // Compute the average of the sum of the three positions + VHACD::Vect3 sum = (p1 + p2 + p3) / 3; + + // Compute the area of this triangle + double area = ComputeArea(p1, + p2, + p3); + + numerator += (sum * area); + + denominator += area; + } + double recip = 1 / denominator; + center = numerator * recip; + ret = true; + } + return ret; +} + +double Determinant3x3(const std::array& matrix, + double& error) +{ + double det = double(0.0); + error = double(0.0); + + double a01xa12 = matrix[0].GetY() * matrix[1].GetZ(); + double a02xa11 = matrix[0].GetZ() * matrix[1].GetY(); + error += (std::abs(a01xa12) + std::abs(a02xa11)) * std::abs(matrix[2].GetX()); + det += (a01xa12 - a02xa11) * matrix[2].GetX(); + + double a00xa12 = matrix[0].GetX() * matrix[1].GetZ(); + double a02xa10 = matrix[0].GetZ() * matrix[1].GetX(); + error += (std::abs(a00xa12) + std::abs(a02xa10)) * std::abs(matrix[2].GetY()); + det -= (a00xa12 - a02xa10) * matrix[2].GetY(); + + double a00xa11 = matrix[0].GetX() * matrix[1].GetY(); + double a01xa10 = matrix[0].GetY() * matrix[1].GetX(); + error += (std::abs(a00xa11) + std::abs(a01xa10)) * std::abs(matrix[2].GetZ()); + det += (a00xa11 - a01xa10) * matrix[2].GetZ(); + + return det; +} + +double ComputeMeshVolume(const std::vector& vertices, + const std::vector& indices) +{ + double volume = 0; + for (uint32_t i = 0; i < indices.size(); i++) + { + const std::array m = { + vertices[indices[i].mI0], + vertices[indices[i].mI1], + vertices[indices[i].mI2] + }; + double placeholder; + volume += Determinant3x3(m, + placeholder); + } + + volume *= (double(1.0) / double(6.0)); + if (volume < 0) + volume *= -1; + return volume; +} + +/* + * To minimize memory allocations while maintaining pointer stability. + * Used in KdTreeNode and ConvexHull, as both use tree data structures that rely on pointer stability + * Neither rely on random access or iteration + * They just dump elements into a memory pool, then refer to pointers to the elements + * All elements are default constructed in NodeStorage's m_nodes array + */ +template +class NodeBundle +{ + struct NodeStorage { + bool IsFull() const; + + T& GetNextNode(); + + std::size_t m_index; + std::array m_nodes; + }; + + std::list m_list; + typename std::list::iterator m_head{ m_list.end() }; + +public: + T& GetNextNode(); + + T& GetFirstNode(); + + void Clear(); +}; + +template +bool NodeBundle::NodeStorage::IsFull() const +{ + return m_index == MaxBundleSize; +} + +template +T& NodeBundle::NodeStorage::GetNextNode() +{ + assert(m_index < MaxBundleSize); + T& ret = m_nodes[m_index]; + m_index++; + return ret; +} + +template +T& NodeBundle::GetNextNode() +{ + /* + * || short circuits, so doesn't dereference if m_bundle == m_bundleHead.end() + */ + if ( m_head == m_list.end() + || m_head->IsFull()) + { + m_head = m_list.emplace(m_list.end()); + } + + return m_head->GetNextNode(); +} + +template +T& NodeBundle::GetFirstNode() +{ + assert(m_head != m_list.end()); + return m_list.front().m_nodes[0]; +} + +template +void NodeBundle::Clear() +{ + m_list.clear(); +} + +/* + * Returns index of highest set bit in x + */ +inline int dExp2(int x) +{ + int exp; + for (exp = -1; x; x >>= 1) + { + exp++; + } + return exp; +} + +/* + * Reverses the order of the bits in v and returns the result + * Does not put fill any of the bits higher than the highest bit in v + * Only used to calculate index of ndNormalMap::m_normal when tessellating a triangle + */ +inline int dBitReversal(int v, + int base) +{ + int x = 0; + int power = dExp2(base) - 1; + do + { + x += (v & 1) << power; + v >>= 1; + power--; + } while (v); + return x; +} + +class Googol +{ + #define VHACD_GOOGOL_SIZE 4 +public: + Googol() = default; + Googol(double value); + + operator double() const; + Googol operator+(const Googol &A) const; + Googol operator-(const Googol &A) const; + Googol operator*(const Googol &A) const; + Googol operator/ (const Googol &A) const; + + Googol& operator+= (const Googol &A); + Googol& operator-= (const Googol &A); + + bool operator>(const Googol &A) const; + bool operator>=(const Googol &A) const; + bool operator<(const Googol &A) const; + bool operator<=(const Googol &A) const; + bool operator==(const Googol &A) const; + bool operator!=(const Googol &A) const; + + Googol Abs() const; + Googol Floor() const; + Googol InvSqrt() const; + Googol Sqrt() const; + + void ToString(char* const string) const; + +private: + void NegateMantissa(std::array& mantissa) const; + void CopySignedMantissa(std::array& mantissa) const; + int NormalizeMantissa(std::array& mantissa) const; + void ShiftRightMantissa(std::array& mantissa, + int bits) const; + uint64_t CheckCarrier(uint64_t a, uint64_t b) const; + + int LeadingZeros(uint64_t a) const; + void ExtendedMultiply(uint64_t a, + uint64_t b, + uint64_t& high, + uint64_t& low) const; + void ScaleMantissa(uint64_t* out, + uint64_t scale) const; + + int m_sign{ 0 }; + int m_exponent{ 0 }; + std::array m_mantissa{ 0 }; + +public: + static Googol m_zero; + static Googol m_one; + static Googol m_two; + static Googol m_three; + static Googol m_half; +}; + +Googol Googol::m_zero(double(0.0)); +Googol Googol::m_one(double(1.0)); +Googol Googol::m_two(double(2.0)); +Googol Googol::m_three(double(3.0)); +Googol Googol::m_half(double(0.5)); + +Googol::Googol(double value) +{ + int exp; + double mantissa = fabs(frexp(value, &exp)); + + m_exponent = exp; + m_sign = (value >= 0) ? 0 : 1; + + m_mantissa[0] = uint64_t(double(uint64_t(1) << 62) * mantissa); +} + +Googol::operator double() const +{ + double mantissa = (double(1.0) / double(uint64_t(1) << 62)) * double(m_mantissa[0]); + mantissa = ldexp(mantissa, m_exponent) * (m_sign ? double(-1.0) : double(1.0)); + return mantissa; +} + +Googol Googol::operator+(const Googol &A) const +{ + Googol tmp; + if (m_mantissa[0] && A.m_mantissa[0]) + { + std::array mantissa0; + std::array mantissa1; + std::array mantissa; + + CopySignedMantissa(mantissa0); + A.CopySignedMantissa(mantissa1); + + int exponentDiff = m_exponent - A.m_exponent; + int exponent = m_exponent; + if (exponentDiff > 0) + { + ShiftRightMantissa(mantissa1, + exponentDiff); + } + else if (exponentDiff < 0) + { + exponent = A.m_exponent; + ShiftRightMantissa(mantissa0, + -exponentDiff); + } + + uint64_t carrier = 0; + for (int i = VHACD_GOOGOL_SIZE - 1; i >= 0; i--) + { + uint64_t m0 = mantissa0[i]; + uint64_t m1 = mantissa1[i]; + mantissa[i] = m0 + m1 + carrier; + carrier = CheckCarrier(m0, m1) | CheckCarrier(m0 + m1, carrier); + } + + int sign = 0; + if (int64_t(mantissa[0]) < 0) + { + sign = 1; + NegateMantissa(mantissa); + } + + int bits = NormalizeMantissa(mantissa); + if (bits <= (-64 * VHACD_GOOGOL_SIZE)) + { + tmp.m_sign = 0; + tmp.m_exponent = 0; + } + else + { + tmp.m_sign = sign; + tmp.m_exponent = int(exponent + bits); + } + + tmp.m_mantissa = mantissa; + } + else if (A.m_mantissa[0]) + { + tmp = A; + } + else + { + tmp = *this; + } + + return tmp; +} + +Googol Googol::operator-(const Googol &A) const +{ + Googol tmp(A); + tmp.m_sign = !tmp.m_sign; + return *this + tmp; +} + +Googol Googol::operator*(const Googol &A) const +{ + if (m_mantissa[0] && A.m_mantissa[0]) + { + std::array mantissaAcc{ 0 }; + for (int i = VHACD_GOOGOL_SIZE - 1; i >= 0; i--) + { + uint64_t a = m_mantissa[i]; + if (a) + { + uint64_t mantissaScale[2 * VHACD_GOOGOL_SIZE] = { 0 }; + A.ScaleMantissa(&mantissaScale[i], a); + + uint64_t carrier = 0; + for (int j = 0; j < 2 * VHACD_GOOGOL_SIZE; j++) + { + const int k = 2 * VHACD_GOOGOL_SIZE - 1 - j; + uint64_t m0 = mantissaAcc[k]; + uint64_t m1 = mantissaScale[k]; + mantissaAcc[k] = m0 + m1 + carrier; + carrier = CheckCarrier(m0, m1) | CheckCarrier(m0 + m1, carrier); + } + } + } + + uint64_t carrier = 0; + int bits = LeadingZeros(mantissaAcc[0]) - 2; + for (int i = 0; i < 2 * VHACD_GOOGOL_SIZE; i++) + { + const int k = 2 * VHACD_GOOGOL_SIZE - 1 - i; + uint64_t a = mantissaAcc[k]; + mantissaAcc[k] = (a << uint64_t(bits)) | carrier; + carrier = a >> uint64_t(64 - bits); + } + + int exp = m_exponent + A.m_exponent - (bits - 2); + + Googol tmp; + tmp.m_sign = m_sign ^ A.m_sign; + tmp.m_exponent = exp; + for (std::size_t i = 0; i < tmp.m_mantissa.size(); ++i) + { + tmp.m_mantissa[i] = mantissaAcc[i]; + } + + return tmp; + } + return Googol(double(0.0)); +} + +Googol Googol::operator/(const Googol &A) const +{ + Googol tmp(double(1.0) / A); + tmp = tmp * (m_two - A * tmp); + tmp = tmp * (m_two - A * tmp); + bool test = false; + int passes = 0; + do + { + passes++; + Googol tmp0(tmp); + tmp = tmp * (m_two - A * tmp); + test = tmp0 == tmp; + } while (test && (passes < (2 * VHACD_GOOGOL_SIZE))); + return (*this) * tmp; +} + +Googol& Googol::operator+=(const Googol &A) +{ + *this = *this + A; + return *this; +} + +Googol& Googol::operator-=(const Googol &A) +{ + *this = *this - A; + return *this; +} + +bool Googol::operator>(const Googol &A) const +{ + Googol tmp(*this - A); + return double(tmp) > double(0.0); +} + +bool Googol::operator>=(const Googol &A) const +{ + Googol tmp(*this - A); + return double(tmp) >= double(0.0); +} + +bool Googol::operator<(const Googol &A) const +{ + Googol tmp(*this - A); + return double(tmp) < double(0.0); +} + +bool Googol::operator<=(const Googol &A) const +{ + Googol tmp(*this - A); + return double(tmp) <= double(0.0); +} + +bool Googol::operator==(const Googol &A) const +{ + return m_sign == A.m_sign + && m_exponent == A.m_exponent + && m_mantissa == A.m_mantissa; +} + +bool Googol::operator!=(const Googol &A) const +{ + return !(*this == A); +} + +Googol Googol::Abs() const +{ + Googol tmp(*this); + tmp.m_sign = 0; + return tmp; +} + +Googol Googol::Floor() const +{ + if (m_exponent < 1) + { + return Googol(double(0.0)); + } + int bits = m_exponent + 2; + int start = 0; + while (bits >= 64) + { + bits -= 64; + start++; + } + + Googol tmp(*this); + for (int i = VHACD_GOOGOL_SIZE - 1; i > start; i--) + { + tmp.m_mantissa[i] = 0; + } + // some compilers do no like this and I do not know why is that + //uint64_t mask = (-1LL) << (64 - bits); + uint64_t mask(~0ULL); + mask <<= (64 - bits); + tmp.m_mantissa[start] &= mask; + return tmp; +} + +Googol Googol::InvSqrt() const +{ + const Googol& me = *this; + Googol x(double(1.0) / sqrt(me)); + + int test = 0; + int passes = 0; + do + { + passes++; + Googol tmp(x); + x = m_half * x * (m_three - me * x * x); + test = (x != tmp); + } while (test && (passes < (2 * VHACD_GOOGOL_SIZE))); + return x; +} + +Googol Googol::Sqrt() const +{ + return *this * InvSqrt(); +} + +void Googol::ToString(char* const string) const +{ + Googol tmp(*this); + Googol base(double(10.0)); + while (double(tmp) > double(1.0)) + { + tmp = tmp / base; + } + + int index = 0; + while (tmp.m_mantissa[0]) + { + tmp = tmp * base; + Googol digit(tmp.Floor()); + tmp -= digit; + double val = digit; + string[index] = char(val) + '0'; + index++; + } + string[index] = 0; +} + +void Googol::NegateMantissa(std::array& mantissa) const +{ + uint64_t carrier = 1; + for (size_t i = mantissa.size() - 1; i >= 0 && i < mantissa.size(); i--) + { + uint64_t a = ~mantissa[i] + carrier; + if (a) + { + carrier = 0; + } + mantissa[i] = a; + } +} + +void Googol::CopySignedMantissa(std::array& mantissa) const +{ + mantissa = m_mantissa; + if (m_sign) + { + NegateMantissa(mantissa); + } +} + +int Googol::NormalizeMantissa(std::array& mantissa) const +{ + int bits = 0; + if (int64_t(mantissa[0] * 2) < 0) + { + bits = 1; + ShiftRightMantissa(mantissa, 1); + } + else + { + while (!mantissa[0] && bits > (-64 * VHACD_GOOGOL_SIZE)) + { + bits -= 64; + for (int i = 1; i < VHACD_GOOGOL_SIZE; i++) { + mantissa[i - 1] = mantissa[i]; + } + mantissa[VHACD_GOOGOL_SIZE - 1] = 0; + } + + if (bits > (-64 * VHACD_GOOGOL_SIZE)) + { + int n = LeadingZeros(mantissa[0]) - 2; + if (n > 0) + { + uint64_t carrier = 0; + for (int i = VHACD_GOOGOL_SIZE - 1; i >= 0; i--) + { + uint64_t a = mantissa[i]; + mantissa[i] = (a << n) | carrier; + carrier = a >> (64 - n); + } + bits -= n; + } + else if (n < 0) + { + // this is very rare but it does happens, whee the leading zeros of the mantissa is an exact multiple of 64 + uint64_t carrier = 0; + int shift = -n; + for (int i = 0; i < VHACD_GOOGOL_SIZE; i++) + { + uint64_t a = mantissa[i]; + mantissa[i] = (a >> shift) | carrier; + carrier = a << (64 - shift); + } + bits -= n; + } + } + } + return bits; +} + +void Googol::ShiftRightMantissa(std::array& mantissa, + int bits) const +{ + uint64_t carrier = 0; + if (int64_t(mantissa[0]) < int64_t(0)) + { + carrier = uint64_t(-1); + } + + while (bits >= 64) + { + for (int i = VHACD_GOOGOL_SIZE - 2; i >= 0; i--) + { + mantissa[i + 1] = mantissa[i]; + } + mantissa[0] = carrier; + bits -= 64; + } + + if (bits > 0) + { + carrier <<= (64 - bits); + for (int i = 0; i < VHACD_GOOGOL_SIZE; i++) + { + uint64_t a = mantissa[i]; + mantissa[i] = (a >> bits) | carrier; + carrier = a << (64 - bits); + } + } +} + +uint64_t Googol::CheckCarrier(uint64_t a, uint64_t b) const +{ + return ((uint64_t(-1) - b) < a) ? uint64_t(1) : 0; +} + +int Googol::LeadingZeros(uint64_t a) const +{ + #define VHACD_COUNTBIT(mask, add) \ + do { \ + uint64_t test = a & mask; \ + n += test ? 0 : add; \ + a = test ? test : (a & ~mask); \ + } while (false) + + int n = 0; + VHACD_COUNTBIT(0xffffffff00000000LL, 32); + VHACD_COUNTBIT(0xffff0000ffff0000LL, 16); + VHACD_COUNTBIT(0xff00ff00ff00ff00LL, 8); + VHACD_COUNTBIT(0xf0f0f0f0f0f0f0f0LL, 4); + VHACD_COUNTBIT(0xccccccccccccccccLL, 2); + VHACD_COUNTBIT(0xaaaaaaaaaaaaaaaaLL, 1); + + return n; +} + +void Googol::ExtendedMultiply(uint64_t a, + uint64_t b, + uint64_t& high, + uint64_t& low) const +{ + uint64_t bLow = b & 0xffffffff; + uint64_t bHigh = b >> 32; + uint64_t aLow = a & 0xffffffff; + uint64_t aHigh = a >> 32; + + uint64_t l = bLow * aLow; + + uint64_t c1 = bHigh * aLow; + uint64_t c2 = bLow * aHigh; + uint64_t m = c1 + c2; + uint64_t carrier = CheckCarrier(c1, c2) << 32; + + uint64_t h = bHigh * aHigh + carrier; + + uint64_t ml = m << 32; + uint64_t ll = l + ml; + uint64_t mh = (m >> 32) + CheckCarrier(l, ml); + uint64_t hh = h + mh; + + low = ll; + high = hh; +} + +void Googol::ScaleMantissa(uint64_t* dst, + uint64_t scale) const +{ + uint64_t carrier = 0; + for (int i = VHACD_GOOGOL_SIZE - 1; i >= 0; i--) + { + if (m_mantissa[i]) + { + uint64_t low; + uint64_t high; + ExtendedMultiply(scale, + m_mantissa[i], + high, + low); + uint64_t acc = low + carrier; + carrier = CheckCarrier(low, + carrier); + carrier += high; + dst[i + 1] = acc; + } + else + { + dst[i + 1] = carrier; + carrier = 0; + } + + } + dst[0] = carrier; +} + +Googol Determinant3x3(const std::array, 3>& matrix) +{ + Googol det = double(0.0); + + Googol a01xa12 = matrix[0].GetY() * matrix[1].GetZ(); + Googol a02xa11 = matrix[0].GetZ() * matrix[1].GetY(); + det += (a01xa12 - a02xa11) * matrix[2].GetX(); + + Googol a00xa12 = matrix[0].GetX() * matrix[1].GetZ(); + Googol a02xa10 = matrix[0].GetZ() * matrix[1].GetX(); + det -= (a00xa12 - a02xa10) * matrix[2].GetY(); + + Googol a00xa11 = matrix[0].GetX() * matrix[1].GetY(); + Googol a01xa10 = matrix[0].GetY() * matrix[1].GetX(); + det += (a00xa11 - a01xa10) * matrix[2].GetZ(); + return det; +} + +class HullPlane : public VHACD::Vect3 +{ +public: + HullPlane(const HullPlane&) = default; + HullPlane(double x, + double y, + double z, + double w); + + HullPlane(const VHACD::Vect3& p, + double w); + + HullPlane(const VHACD::Vect3& p0, + const VHACD::Vect3& p1, + const VHACD::Vect3& p2); + + HullPlane Scale(double s) const; + + HullPlane& operator=(const HullPlane& rhs); + + double Evalue(const VHACD::Vect3 &point) const; + + double& GetW(); + const double& GetW() const; + +private: + double m_w; +}; + +HullPlane::HullPlane(double x, + double y, + double z, + double w) + : VHACD::Vect3(x, y, z) + , m_w(w) +{ +} + +HullPlane::HullPlane(const VHACD::Vect3& p, + double w) + : VHACD::Vect3(p) + , m_w(w) +{ +} + +HullPlane::HullPlane(const VHACD::Vect3& p0, + const VHACD::Vect3& p1, + const VHACD::Vect3& p2) + : VHACD::Vect3((p1 - p0).Cross(p2 - p0)) + , m_w(-Dot(p0)) +{ +} + +HullPlane HullPlane::Scale(double s) const +{ + return HullPlane(*this * s, + m_w * s); +} + +HullPlane& HullPlane::operator=(const HullPlane& rhs) +{ + GetX() = rhs.GetX(); + GetY() = rhs.GetY(); + GetZ() = rhs.GetZ(); + m_w = rhs.m_w; + return *this; +} + +double HullPlane::Evalue(const VHACD::Vect3& point) const +{ + return Dot(point) + m_w; +} + +double& HullPlane::GetW() +{ + return m_w; +} + +const double& HullPlane::GetW() const +{ + return m_w; +} + +class ConvexHullFace +{ +public: + ConvexHullFace() = default; + double Evalue(const std::vector& pointArray, + const VHACD::Vect3& point) const; + HullPlane GetPlaneEquation(const std::vector& pointArray, + bool& isValid) const; + + std::array m_index; +private: + int m_mark{ 0 }; + std::array::iterator, 3> m_twin; + + friend class ConvexHull; +}; + +double ConvexHullFace::Evalue(const std::vector& pointArray, + const VHACD::Vect3& point) const +{ + const VHACD::Vect3& p0 = pointArray[m_index[0]]; + const VHACD::Vect3& p1 = pointArray[m_index[1]]; + const VHACD::Vect3& p2 = pointArray[m_index[2]]; + + std::array matrix = { p2 - p0, p1 - p0, point - p0 }; + double error; + double det = Determinant3x3(matrix, + error); + + // the code use double, however the threshold for accuracy test is the machine precision of a float. + // by changing this to a smaller number, the code should run faster since many small test will be considered valid + // the precision must be a power of two no smaller than the machine precision of a double, (1<<48) + // float64(1<<30) can be a good value + + // double precision = double (1.0f) / double (1<<30); + double precision = double(1.0) / double(1 << 24); + double errbound = error * precision; + if (fabs(det) > errbound) + { + return det; + } + + const VHACD::Vector3 p0g = pointArray[m_index[0]]; + const VHACD::Vector3 p1g = pointArray[m_index[1]]; + const VHACD::Vector3 p2g = pointArray[m_index[2]]; + const VHACD::Vector3 pointg = point; + std::array, 3> exactMatrix = { p2g - p0g, p1g - p0g, pointg - p0g }; + return Determinant3x3(exactMatrix); +} + +HullPlane ConvexHullFace::GetPlaneEquation(const std::vector& pointArray, + bool& isvalid) const +{ + const VHACD::Vect3& p0 = pointArray[m_index[0]]; + const VHACD::Vect3& p1 = pointArray[m_index[1]]; + const VHACD::Vect3& p2 = pointArray[m_index[2]]; + HullPlane plane(p0, p1, p2); + + isvalid = false; + double mag2 = plane.Dot(plane); + if (mag2 > double(1.0e-16)) + { + isvalid = true; + plane = plane.Scale(double(1.0) / sqrt(mag2)); + } + return plane; +} + +class ConvexHullVertex : public VHACD::Vect3 +{ +public: + ConvexHullVertex() = default; + ConvexHullVertex(const ConvexHullVertex&) = default; + ConvexHullVertex& operator=(const ConvexHullVertex& rhs) = default; + using VHACD::Vect3::operator=; + + int m_mark{ 0 }; +}; + + +class ConvexHullAABBTreeNode +{ + #define VHACD_CONVEXHULL_3D_VERTEX_CLUSTER_SIZE 8 +public: + ConvexHullAABBTreeNode() = default; + ConvexHullAABBTreeNode(ConvexHullAABBTreeNode* parent); + + VHACD::Vect3 m_box[2]; + ConvexHullAABBTreeNode* m_left{ nullptr }; + ConvexHullAABBTreeNode* m_right{ nullptr }; + ConvexHullAABBTreeNode* m_parent{ nullptr }; + + size_t m_count; + std::array m_indices; +}; + +ConvexHullAABBTreeNode::ConvexHullAABBTreeNode(ConvexHullAABBTreeNode* parent) + : m_parent(parent) +{ +} + +class ConvexHull +{ + class ndNormalMap; + +public: + ConvexHull(const ConvexHull& source); + ConvexHull(const std::vector<::VHACD::Vertex>& vertexCloud, + double distTol, + int maxVertexCount = 0x7fffffff); + ~ConvexHull() = default; + + const std::vector& GetVertexPool() const; + + const std::list& GetList() const { return m_list; } + +private: + void BuildHull(const std::vector<::VHACD::Vertex>& vertexCloud, + double distTol, + int maxVertexCount); + + void GetUniquePoints(std::vector& points); + int InitVertexArray(std::vector& points, + NodeBundle& memoryPool); + + ConvexHullAABBTreeNode* BuildTreeNew(std::vector& points, + std::vector& memoryPool) const; + ConvexHullAABBTreeNode* BuildTreeOld(std::vector& points, + NodeBundle& memoryPool); + ConvexHullAABBTreeNode* BuildTreeRecurse(ConvexHullAABBTreeNode* const parent, + ConvexHullVertex* const points, + int count, + int baseIndex, + NodeBundle& memoryPool) const; + + std::list::iterator AddFace(int i0, + int i1, + int i2); + + void CalculateConvexHull3D(ConvexHullAABBTreeNode* vertexTree, + std::vector& points, + int count, + double distTol, + int maxVertexCount); + + int SupportVertex(ConvexHullAABBTreeNode** const tree, + const std::vector& points, + const VHACD::Vect3& dir, + const bool removeEntry = true) const; + double TetrahedrumVolume(const VHACD::Vect3& p0, + const VHACD::Vect3& p1, + const VHACD::Vect3& p2, + const VHACD::Vect3& p3) const; + + std::list m_list; + VHACD::Vect3 m_aabbP0{ 0 }; + VHACD::Vect3 m_aabbP1{ 0 }; + double m_diag{ 0.0 }; + std::vector m_points; +}; + +class ConvexHull::ndNormalMap +{ +public: + ndNormalMap(); + + static const ndNormalMap& GetNormalMap(); + + void TessellateTriangle(int level, + const VHACD::Vect3& p0, + const VHACD::Vect3& p1, + const VHACD::Vect3& p2, + int& count); + + std::array m_normal; + int m_count{ 128 }; +}; + +const ConvexHull::ndNormalMap& ConvexHull::ndNormalMap::GetNormalMap() +{ + static ndNormalMap normalMap; + return normalMap; +} + +void ConvexHull::ndNormalMap::TessellateTriangle(int level, + const VHACD::Vect3& p0, + const VHACD::Vect3& p1, + const VHACD::Vect3& p2, + int& count) +{ + if (level) + { + assert(fabs(p0.Dot(p0) - double(1.0)) < double(1.0e-4)); + assert(fabs(p1.Dot(p1) - double(1.0)) < double(1.0e-4)); + assert(fabs(p2.Dot(p2) - double(1.0)) < double(1.0e-4)); + VHACD::Vect3 p01(p0 + p1); + VHACD::Vect3 p12(p1 + p2); + VHACD::Vect3 p20(p2 + p0); + + p01 = p01 * (double(1.0) / p01.GetNorm()); + p12 = p12 * (double(1.0) / p12.GetNorm()); + p20 = p20 * (double(1.0) / p20.GetNorm()); + + assert(fabs(p01.GetNormSquared() - double(1.0)) < double(1.0e-4)); + assert(fabs(p12.GetNormSquared() - double(1.0)) < double(1.0e-4)); + assert(fabs(p20.GetNormSquared() - double(1.0)) < double(1.0e-4)); + + TessellateTriangle(level - 1, p0, p01, p20, count); + TessellateTriangle(level - 1, p1, p12, p01, count); + TessellateTriangle(level - 1, p2, p20, p12, count); + TessellateTriangle(level - 1, p01, p12, p20, count); + } + else + { + /* + * This is just m_normal[index] = n.Normalized(), but due to tiny floating point errors, causes + * different outputs, so I'm leaving it + */ + HullPlane n(p0, p1, p2); + n = n.Scale(double(1.0) / n.GetNorm()); + n.GetW() = double(0.0); + int index = dBitReversal(count, + int(m_normal.size())); + m_normal[index] = n; + count++; + assert(count <= int(m_normal.size())); + } +} + +ConvexHull::ndNormalMap::ndNormalMap() +{ + VHACD::Vect3 p0(double( 1.0), double( 0.0), double( 0.0)); + VHACD::Vect3 p1(double(-1.0), double( 0.0), double( 0.0)); + VHACD::Vect3 p2(double( 0.0), double( 1.0), double( 0.0)); + VHACD::Vect3 p3(double( 0.0), double(-1.0), double( 0.0)); + VHACD::Vect3 p4(double( 0.0), double( 0.0), double( 1.0)); + VHACD::Vect3 p5(double( 0.0), double( 0.0), double(-1.0)); + + int count = 0; + int subdivisions = 2; + TessellateTriangle(subdivisions, p4, p0, p2, count); + TessellateTriangle(subdivisions, p0, p5, p2, count); + TessellateTriangle(subdivisions, p5, p1, p2, count); + TessellateTriangle(subdivisions, p1, p4, p2, count); + TessellateTriangle(subdivisions, p0, p4, p3, count); + TessellateTriangle(subdivisions, p5, p0, p3, count); + TessellateTriangle(subdivisions, p1, p5, p3, count); + TessellateTriangle(subdivisions, p4, p1, p3, count); +} + +ConvexHull::ConvexHull(const std::vector<::VHACD::Vertex>& vertexCloud, + double distTol, + int maxVertexCount) +{ + if (vertexCloud.size() >= 4) + { + BuildHull(vertexCloud, + distTol, + maxVertexCount); + } +} + +const std::vector& ConvexHull::GetVertexPool() const +{ + return m_points; +} + +void ConvexHull::BuildHull(const std::vector<::VHACD::Vertex>& vertexCloud, + double distTol, + int maxVertexCount) +{ + size_t treeCount = vertexCloud.size() / (VHACD_CONVEXHULL_3D_VERTEX_CLUSTER_SIZE >> 1); + treeCount = std::max(treeCount, size_t(4)) * 2; + + std::vector points(vertexCloud.size()); + /* + * treePool provides a memory pool for the AABB tree + * Each node is either a leaf or non-leaf node + * Non-leaf nodes have up to 8 vertices + * Vertices are specified by the m_indices array and are accessed via the points array + * + * Later on in ConvexHull::SupportVertex, the tree is used directly + * It differentiates between ConvexHullAABBTreeNode and ConvexHull3DPointCluster by whether the m_left and m_right + * pointers are null or not + * + * Pointers have to be stable + */ + NodeBundle treePool; + for (size_t i = 0; i < vertexCloud.size(); ++i) + { + points[i] = VHACD::Vect3(vertexCloud[i]); + } + int count = InitVertexArray(points, + treePool); + + if (m_points.size() >= 4) + { + CalculateConvexHull3D(&treePool.GetFirstNode(), + points, + count, + distTol, + maxVertexCount); + } +} + +void ConvexHull::GetUniquePoints(std::vector& points) +{ + class CompareVertex + { + public: + int Compare(const ConvexHullVertex& elementA, const ConvexHullVertex& elementB) const + { + for (int i = 0; i < 3; i++) + { + if (elementA[i] < elementB[i]) + { + return -1; + } + else if (elementA[i] > elementB[i]) + { + return 1; + } + } + return 0; + } + }; + + int count = int(points.size()); + Sort(points.data(), + count); + + int indexCount = 0; + CompareVertex compareVertex; + for (int i = 1; i < count; ++i) + { + for (; i < count; ++i) + { + if (compareVertex.Compare(points[indexCount], points[i])) + { + indexCount++; + points[indexCount] = points[i]; + break; + } + } + } + points.resize(indexCount + 1); +} + +ConvexHullAABBTreeNode* ConvexHull::BuildTreeRecurse(ConvexHullAABBTreeNode* const parent, + ConvexHullVertex* const points, + int count, + int baseIndex, + NodeBundle& memoryPool) const +{ + ConvexHullAABBTreeNode* tree = nullptr; + + assert(count); + VHACD::Vect3 minP( double(1.0e15)); + VHACD::Vect3 maxP(-double(1.0e15)); + if (count <= VHACD_CONVEXHULL_3D_VERTEX_CLUSTER_SIZE) + { + ConvexHullAABBTreeNode& clump = memoryPool.GetNextNode(); + + clump.m_count = count; + for (int i = 0; i < count; ++i) + { + clump.m_indices[i] = i + baseIndex; + + const VHACD::Vect3& p = points[i]; + minP = minP.CWiseMin(p); + maxP = maxP.CWiseMax(p); + } + + clump.m_left = nullptr; + clump.m_right = nullptr; + tree = &clump; + } + else + { + VHACD::Vect3 median(0); + VHACD::Vect3 varian(0); + for (int i = 0; i < count; ++i) + { + const VHACD::Vect3& p = points[i]; + minP = minP.CWiseMin(p); + maxP = maxP.CWiseMax(p); + median += p; + varian += p.CWiseMul(p); + } + + varian = varian * double(count) - median.CWiseMul(median); + int index = 0; + double maxVarian = double(-1.0e10); + for (int i = 0; i < 3; ++i) + { + if (varian[i] > maxVarian) + { + index = i; + maxVarian = varian[i]; + } + } + VHACD::Vect3 center(median * (double(1.0) / double(count))); + + double test = center[index]; + + int i0 = 0; + int i1 = count - 1; + do + { + for (; i0 <= i1; i0++) + { + double val = points[i0][index]; + if (val > test) + { + break; + } + } + + for (; i1 >= i0; i1--) + { + double val = points[i1][index]; + if (val < test) + { + break; + } + } + + if (i0 < i1) + { + std::swap(points[i0], + points[i1]); + i0++; + i1--; + } + } while (i0 <= i1); + + if (i0 == 0) + { + i0 = count / 2; + } + if (i0 >= (count - 1)) + { + i0 = count / 2; + } + + tree = &memoryPool.GetNextNode(); + + assert(i0); + assert(count - i0); + + tree->m_left = BuildTreeRecurse(tree, + points, + i0, + baseIndex, + memoryPool); + tree->m_right = BuildTreeRecurse(tree, + &points[i0], + count - i0, + i0 + baseIndex, + memoryPool); + } + + assert(tree); + tree->m_parent = parent; + /* + * WARNING: Changing the compiler conversion of 1.0e-3f changes the results of the convex decomposition + * Inflate the tree's bounding box slightly + */ + tree->m_box[0] = minP - VHACD::Vect3(double(1.0e-3f)); + tree->m_box[1] = maxP + VHACD::Vect3(double(1.0e-3f)); + return tree; +} + +ConvexHullAABBTreeNode* ConvexHull::BuildTreeOld(std::vector& points, + NodeBundle& memoryPool) +{ + GetUniquePoints(points); + int count = int(points.size()); + if (count < 4) + { + return nullptr; + } + return BuildTreeRecurse(nullptr, + points.data(), + count, + 0, + memoryPool); +} + +ConvexHullAABBTreeNode* ConvexHull::BuildTreeNew(std::vector& points, + std::vector& memoryPool) const +{ + class dCluster + { + public: + VHACD::Vect3 m_sum{ double(0.0) }; + VHACD::Vect3 m_sum2{ double(0.0) }; + int m_start{ 0 }; + int m_count{ 0 }; + }; + + dCluster firstCluster; + firstCluster.m_count = int(points.size()); + + for (int i = 0; i < firstCluster.m_count; ++i) + { + const VHACD::Vect3& p = points[i]; + firstCluster.m_sum += p; + firstCluster.m_sum2 += p.CWiseMul(p); + } + + int baseCount = 0; + const int clusterSize = 16; + + if (firstCluster.m_count > clusterSize) + { + dCluster spliteStack[128]; + spliteStack[0] = firstCluster; + size_t stack = 1; + + while (stack) + { + stack--; + dCluster cluster (spliteStack[stack]); + + const VHACD::Vect3 origin(cluster.m_sum * (double(1.0) / cluster.m_count)); + const VHACD::Vect3 variance2(cluster.m_sum2 * (double(1.0) / cluster.m_count) - origin.CWiseMul(origin)); + double maxVariance2 = variance2.MaxCoeff(); + + if ( (cluster.m_count <= clusterSize) + || (stack > (sizeof(spliteStack) / sizeof(spliteStack[0]) - 4)) + || (maxVariance2 < 1.e-4f)) + { + // no sure if this is beneficial, + // the array is so small that seem too much overhead + //int maxIndex = 0; + //double min_x = 1.0e20f; + //for (int i = 0; i < cluster.m_count; ++i) + //{ + // if (points[cluster.m_start + i].getX() < min_x) + // { + // maxIndex = i; + // min_x = points[cluster.m_start + i].getX(); + // } + //} + //Swap(points[cluster.m_start], points[cluster.m_start + maxIndex]); + // + //for (int i = 2; i < cluster.m_count; ++i) + //{ + // int j = i; + // ConvexHullVertex tmp(points[cluster.m_start + i]); + // for (; points[cluster.m_start + j - 1].getX() > tmp.getX(); --j) + // { + // assert(j > 0); + // points[cluster.m_start + j] = points[cluster.m_start + j - 1]; + // } + // points[cluster.m_start + j] = tmp; + //} + + int count = cluster.m_count; + for (int i = cluster.m_count - 1; i > 0; --i) + { + for (int j = i - 1; j >= 0; --j) + { + VHACD::Vect3 error(points[cluster.m_start + j] - points[cluster.m_start + i]); + double mag2 = error.Dot(error); + if (mag2 < double(1.0e-6)) + { + points[cluster.m_start + j] = points[cluster.m_start + i]; + count--; + break; + } + } + } + + assert(baseCount <= cluster.m_start); + for (int i = 0; i < count; ++i) + { + points[baseCount] = points[cluster.m_start + i]; + baseCount++; + } + } + else + { + const int firstSortAxis = variance2.LongestAxis(); + double axisVal = origin[firstSortAxis]; + + int i0 = 0; + int i1 = cluster.m_count - 1; + + const int start = cluster.m_start; + while (i0 < i1) + { + while ( (points[start + i0][firstSortAxis] <= axisVal) + && (i0 < i1)) + { + ++i0; + }; + + while ( (points[start + i1][firstSortAxis] > axisVal) + && (i0 < i1)) + { + --i1; + } + + assert(i0 <= i1); + if (i0 < i1) + { + std::swap(points[start + i0], + points[start + i1]); + ++i0; + --i1; + } + } + + while ( (points[start + i0][firstSortAxis] <= axisVal) + && (i0 < cluster.m_count)) + { + ++i0; + }; + + #ifdef _DEBUG + for (int i = 0; i < i0; ++i) + { + assert(points[start + i][firstSortAxis] <= axisVal); + } + + for (int i = i0; i < cluster.m_count; ++i) + { + assert(points[start + i][firstSortAxis] > axisVal); + } + #endif + + VHACD::Vect3 xc(0); + VHACD::Vect3 x2c(0); + for (int i = 0; i < i0; ++i) + { + const VHACD::Vect3& x = points[start + i]; + xc += x; + x2c += x.CWiseMul(x); + } + + dCluster cluster_i1(cluster); + cluster_i1.m_start = start + i0; + cluster_i1.m_count = cluster.m_count - i0; + cluster_i1.m_sum -= xc; + cluster_i1.m_sum2 -= x2c; + spliteStack[stack] = cluster_i1; + assert(cluster_i1.m_count > 0); + stack++; + + dCluster cluster_i0(cluster); + cluster_i0.m_start = start; + cluster_i0.m_count = i0; + cluster_i0.m_sum = xc; + cluster_i0.m_sum2 = x2c; + assert(cluster_i0.m_count > 0); + spliteStack[stack] = cluster_i0; + stack++; + } + } + } + + points.resize(baseCount); + if (baseCount < 4) + { + return nullptr; + } + + VHACD::Vect3 sum(0); + VHACD::Vect3 sum2(0); + VHACD::Vect3 minP(double( 1.0e15)); + VHACD::Vect3 maxP(double(-1.0e15)); + class dTreeBox + { + public: + VHACD::Vect3 m_min; + VHACD::Vect3 m_max; + VHACD::Vect3 m_sum; + VHACD::Vect3 m_sum2; + ConvexHullAABBTreeNode* m_parent; + ConvexHullAABBTreeNode** m_child; + int m_start; + int m_count; + }; + + for (int i = 0; i < baseCount; ++i) + { + const VHACD::Vect3& p = points[i]; + sum += p; + sum2 += p.CWiseMul(p); + minP = minP.CWiseMin(p); + maxP = maxP.CWiseMax(p); + } + + dTreeBox treeBoxStack[128]; + treeBoxStack[0].m_start = 0; + treeBoxStack[0].m_count = baseCount; + treeBoxStack[0].m_sum = sum; + treeBoxStack[0].m_sum2 = sum2; + treeBoxStack[0].m_min = minP; + treeBoxStack[0].m_max = maxP; + treeBoxStack[0].m_child = nullptr; + treeBoxStack[0].m_parent = nullptr; + + int stack = 1; + ConvexHullAABBTreeNode* root = nullptr; + while (stack) + { + stack--; + dTreeBox box(treeBoxStack[stack]); + if (box.m_count <= VHACD_CONVEXHULL_3D_VERTEX_CLUSTER_SIZE) + { + assert(memoryPool.size() != memoryPool.capacity() + && "memoryPool is going to be reallocated, pointers will be invalid"); + memoryPool.emplace_back(); + ConvexHullAABBTreeNode& clump = memoryPool.back(); + + clump.m_count = box.m_count; + for (int i = 0; i < box.m_count; ++i) + { + clump.m_indices[i] = i + box.m_start; + } + clump.m_box[0] = box.m_min; + clump.m_box[1] = box.m_max; + + if (box.m_child) + { + *box.m_child = &clump; + } + + if (!root) + { + root = &clump; + } + } + else + { + const VHACD::Vect3 origin(box.m_sum * (double(1.0) / box.m_count)); + const VHACD::Vect3 variance2(box.m_sum2 * (double(1.0) / box.m_count) - origin.CWiseMul(origin)); + + int firstSortAxis = 0; + if ((variance2.GetY() >= variance2.GetX()) && (variance2.GetY() >= variance2.GetZ())) + { + firstSortAxis = 1; + } + else if ((variance2.GetZ() >= variance2.GetX()) && (variance2.GetZ() >= variance2.GetY())) + { + firstSortAxis = 2; + } + double axisVal = origin[firstSortAxis]; + + int i0 = 0; + int i1 = box.m_count - 1; + + const int start = box.m_start; + while (i0 < i1) + { + while ((points[start + i0][firstSortAxis] <= axisVal) && (i0 < i1)) + { + ++i0; + }; + + while ((points[start + i1][firstSortAxis] > axisVal) && (i0 < i1)) + { + --i1; + } + + assert(i0 <= i1); + if (i0 < i1) + { + std::swap(points[start + i0], + points[start + i1]); + ++i0; + --i1; + } + } + + while ((points[start + i0][firstSortAxis] <= axisVal) && (i0 < box.m_count)) + { + ++i0; + }; + + #ifdef _DEBUG + for (int i = 0; i < i0; ++i) + { + assert(points[start + i][firstSortAxis] <= axisVal); + } + + for (int i = i0; i < box.m_count; ++i) + { + assert(points[start + i][firstSortAxis] > axisVal); + } + #endif + + assert(memoryPool.size() != memoryPool.capacity() + && "memoryPool is going to be reallocated, pointers will be invalid"); + memoryPool.emplace_back(); + ConvexHullAABBTreeNode& node = memoryPool.back(); + + node.m_box[0] = box.m_min; + node.m_box[1] = box.m_max; + if (box.m_child) + { + *box.m_child = &node; + } + + if (!root) + { + root = &node; + } + + { + VHACD::Vect3 xc(0); + VHACD::Vect3 x2c(0); + VHACD::Vect3 p0(double( 1.0e15)); + VHACD::Vect3 p1(double(-1.0e15)); + for (int i = i0; i < box.m_count; ++i) + { + const VHACD::Vect3& p = points[start + i]; + xc += p; + x2c += p.CWiseMul(p); + p0 = p0.CWiseMin(p); + p1 = p1.CWiseMax(p); + } + + dTreeBox cluster_i1(box); + cluster_i1.m_start = start + i0; + cluster_i1.m_count = box.m_count - i0; + cluster_i1.m_sum = xc; + cluster_i1.m_sum2 = x2c; + cluster_i1.m_min = p0; + cluster_i1.m_max = p1; + cluster_i1.m_parent = &node; + cluster_i1.m_child = &node.m_right; + treeBoxStack[stack] = cluster_i1; + assert(cluster_i1.m_count > 0); + stack++; + } + + { + VHACD::Vect3 xc(0); + VHACD::Vect3 x2c(0); + VHACD::Vect3 p0(double( 1.0e15)); + VHACD::Vect3 p1(double(-1.0e15)); + for (int i = 0; i < i0; ++i) + { + const VHACD::Vect3& p = points[start + i]; + xc += p; + x2c += p.CWiseMul(p); + p0 = p0.CWiseMin(p); + p1 = p1.CWiseMax(p); + } + + dTreeBox cluster_i0(box); + cluster_i0.m_start = start; + cluster_i0.m_count = i0; + cluster_i0.m_min = p0; + cluster_i0.m_max = p1; + cluster_i0.m_sum = xc; + cluster_i0.m_sum2 = x2c; + cluster_i0.m_parent = &node; + cluster_i0.m_child = &node.m_left; + assert(cluster_i0.m_count > 0); + treeBoxStack[stack] = cluster_i0; + stack++; + } + } + } + + return root; +} + +int ConvexHull::SupportVertex(ConvexHullAABBTreeNode** const treePointer, + const std::vector& points, + const VHACD::Vect3& dirPlane, + const bool removeEntry) const +{ +#define VHACD_STACK_DEPTH_3D 64 + double aabbProjection[VHACD_STACK_DEPTH_3D]; + ConvexHullAABBTreeNode* stackPool[VHACD_STACK_DEPTH_3D]; + + VHACD::Vect3 dir(dirPlane); + + int index = -1; + int stack = 1; + stackPool[0] = *treePointer; + aabbProjection[0] = double(1.0e20); + double maxProj = double(-1.0e20); + int ix = (dir[0] > double(0.0)) ? 1 : 0; + int iy = (dir[1] > double(0.0)) ? 1 : 0; + int iz = (dir[2] > double(0.0)) ? 1 : 0; + while (stack) + { + stack--; + double boxSupportValue = aabbProjection[stack]; + if (boxSupportValue > maxProj) + { + ConvexHullAABBTreeNode* me = stackPool[stack]; + + /* + * If the node is not a leaf node... + */ + if (me->m_left && me->m_right) + { + const VHACD::Vect3 leftSupportPoint(me->m_left->m_box[ix].GetX(), + me->m_left->m_box[iy].GetY(), + me->m_left->m_box[iz].GetZ()); + double leftSupportDist = leftSupportPoint.Dot(dir); + + const VHACD::Vect3 rightSupportPoint(me->m_right->m_box[ix].GetX(), + me->m_right->m_box[iy].GetY(), + me->m_right->m_box[iz].GetZ()); + double rightSupportDist = rightSupportPoint.Dot(dir); + + /* + * ...push the shorter side first + * So we can explore the tree in the larger side first + */ + if (rightSupportDist >= leftSupportDist) + { + aabbProjection[stack] = leftSupportDist; + stackPool[stack] = me->m_left; + stack++; + assert(stack < VHACD_STACK_DEPTH_3D); + aabbProjection[stack] = rightSupportDist; + stackPool[stack] = me->m_right; + stack++; + assert(stack < VHACD_STACK_DEPTH_3D); + } + else + { + aabbProjection[stack] = rightSupportDist; + stackPool[stack] = me->m_right; + stack++; + assert(stack < VHACD_STACK_DEPTH_3D); + aabbProjection[stack] = leftSupportDist; + stackPool[stack] = me->m_left; + stack++; + assert(stack < VHACD_STACK_DEPTH_3D); + } + } + /* + * If it is a node... + */ + else + { + ConvexHullAABBTreeNode* cluster = me; + for (size_t i = 0; i < cluster->m_count; ++i) + { + const ConvexHullVertex& p = points[cluster->m_indices[i]]; + assert(p.GetX() >= cluster->m_box[0].GetX()); + assert(p.GetX() <= cluster->m_box[1].GetX()); + assert(p.GetY() >= cluster->m_box[0].GetY()); + assert(p.GetY() <= cluster->m_box[1].GetY()); + assert(p.GetZ() >= cluster->m_box[0].GetZ()); + assert(p.GetZ() <= cluster->m_box[1].GetZ()); + if (!p.m_mark) + { + //assert(p.m_w == double(0.0f)); + double dist = p.Dot(dir); + if (dist > maxProj) + { + maxProj = dist; + index = cluster->m_indices[i]; + } + } + else if (removeEntry) + { + cluster->m_indices[i] = cluster->m_indices[cluster->m_count - 1]; + cluster->m_count = cluster->m_count - 1; + i--; + } + } + + if (cluster->m_count == 0) + { + ConvexHullAABBTreeNode* const parent = cluster->m_parent; + if (parent) + { + ConvexHullAABBTreeNode* const sibling = (parent->m_left != cluster) ? parent->m_left : parent->m_right; + assert(sibling != cluster); + ConvexHullAABBTreeNode* const grandParent = parent->m_parent; + if (grandParent) + { + sibling->m_parent = grandParent; + if (grandParent->m_right == parent) + { + grandParent->m_right = sibling; + } + else + { + grandParent->m_left = sibling; + } + } + else + { + sibling->m_parent = nullptr; + *treePointer = sibling; + } + } + } + } + } + } + + assert(index != -1); + return index; +} + +double ConvexHull::TetrahedrumVolume(const VHACD::Vect3& p0, + const VHACD::Vect3& p1, + const VHACD::Vect3& p2, + const VHACD::Vect3& p3) const +{ + const VHACD::Vect3 p1p0(p1 - p0); + const VHACD::Vect3 p2p0(p2 - p0); + const VHACD::Vect3 p3p0(p3 - p0); + return p3p0.Dot(p1p0.Cross(p2p0)); +} + +int ConvexHull::InitVertexArray(std::vector& points, + NodeBundle& memoryPool) +// std::vector& memoryPool) +{ +#if 1 + ConvexHullAABBTreeNode* tree = BuildTreeOld(points, + memoryPool); +#else + ConvexHullAABBTreeNode* tree = BuildTreeNew(points, (char**)&memoryPool, maxMemSize); +#endif + int count = int(points.size()); + if (count < 4) + { + m_points.resize(0); + return 0; + } + + m_points.resize(count); + m_aabbP0 = tree->m_box[0]; + m_aabbP1 = tree->m_box[1]; + + VHACD::Vect3 boxSize(tree->m_box[1] - tree->m_box[0]); + m_diag = boxSize.GetNorm(); + const ndNormalMap& normalMap = ndNormalMap::GetNormalMap(); + + int index0 = SupportVertex(&tree, + points, + normalMap.m_normal[0]); + m_points[0] = points[index0]; + points[index0].m_mark = 1; + + bool validTetrahedrum = false; + VHACD::Vect3 e1(double(0.0)); + for (int i = 1; i < normalMap.m_count; ++i) + { + int index = SupportVertex(&tree, + points, + normalMap.m_normal[i]); + assert(index >= 0); + + e1 = points[index] - m_points[0]; + double error2 = e1.GetNormSquared(); + if (error2 > (double(1.0e-4) * m_diag * m_diag)) + { + m_points[1] = points[index]; + points[index].m_mark = 1; + validTetrahedrum = true; + break; + } + } + if (!validTetrahedrum) + { + m_points.resize(0); + assert(0); + return count; + } + + validTetrahedrum = false; + VHACD::Vect3 e2(double(0.0)); + VHACD::Vect3 normal(double(0.0)); + for (int i = 2; i < normalMap.m_count; ++i) + { + int index = SupportVertex(&tree, + points, + normalMap.m_normal[i]); + assert(index >= 0); + e2 = points[index] - m_points[0]; + normal = e1.Cross(e2); + double error2 = normal.GetNorm(); + if (error2 > (double(1.0e-4) * m_diag * m_diag)) + { + m_points[2] = points[index]; + points[index].m_mark = 1; + validTetrahedrum = true; + break; + } + } + + if (!validTetrahedrum) + { + m_points.resize(0); + assert(0); + return count; + } + + // find the largest possible tetrahedron + validTetrahedrum = false; + VHACD::Vect3 e3(double(0.0)); + + index0 = SupportVertex(&tree, + points, + normal); + e3 = points[index0] - m_points[0]; + double err2 = normal.Dot(e3); + if (fabs(err2) > (double(1.0e-6) * m_diag * m_diag)) + { + // we found a valid tetrahedral, about and start build the hull by adding the rest of the points + m_points[3] = points[index0]; + points[index0].m_mark = 1; + validTetrahedrum = true; + } + if (!validTetrahedrum) + { + VHACD::Vect3 n(-normal); + int index = SupportVertex(&tree, + points, + n); + e3 = points[index] - m_points[0]; + double error2 = normal.Dot(e3); + if (fabs(error2) > (double(1.0e-6) * m_diag * m_diag)) + { + // we found a valid tetrahedral, about and start build the hull by adding the rest of the points + m_points[3] = points[index]; + points[index].m_mark = 1; + validTetrahedrum = true; + } + } + if (!validTetrahedrum) + { + for (int i = 3; i < normalMap.m_count; ++i) + { + int index = SupportVertex(&tree, + points, + normalMap.m_normal[i]); + assert(index >= 0); + + //make sure the volume of the fist tetrahedral is no negative + e3 = points[index] - m_points[0]; + double error2 = normal.Dot(e3); + if (fabs(error2) > (double(1.0e-6) * m_diag * m_diag)) + { + // we found a valid tetrahedral, about and start build the hull by adding the rest of the points + m_points[3] = points[index]; + points[index].m_mark = 1; + validTetrahedrum = true; + break; + } + } + } + if (!validTetrahedrum) + { + // the points do not form a convex hull + m_points.resize(0); + return count; + } + + m_points.resize(4); + double volume = TetrahedrumVolume(m_points[0], + m_points[1], + m_points[2], + m_points[3]); + if (volume > double(0.0)) + { + std::swap(m_points[2], + m_points[3]); + } + assert(TetrahedrumVolume(m_points[0], m_points[1], m_points[2], m_points[3]) < double(0.0)); + return count; +} + +std::list::iterator ConvexHull::AddFace(int i0, + int i1, + int i2) +{ + ConvexHullFace face; + face.m_index[0] = i0; + face.m_index[1] = i1; + face.m_index[2] = i2; + + std::list::iterator node = m_list.emplace(m_list.end(), face); + return node; +} + +void ConvexHull::CalculateConvexHull3D(ConvexHullAABBTreeNode* vertexTree, + std::vector& points, + int count, + double distTol, + int maxVertexCount) +{ + distTol = fabs(distTol) * m_diag; + std::list::iterator f0Node = AddFace(0, 1, 2); + std::list::iterator f1Node = AddFace(0, 2, 3); + std::list::iterator f2Node = AddFace(2, 1, 3); + std::list::iterator f3Node = AddFace(1, 0, 3); + + ConvexHullFace& f0 = *f0Node; + ConvexHullFace& f1 = *f1Node; + ConvexHullFace& f2 = *f2Node; + ConvexHullFace& f3 = *f3Node; + + f0.m_twin[0] = f3Node; + f0.m_twin[1] = f2Node; + f0.m_twin[2] = f1Node; + + f1.m_twin[0] = f0Node; + f1.m_twin[1] = f2Node; + f1.m_twin[2] = f3Node; + + f2.m_twin[0] = f0Node; + f2.m_twin[1] = f3Node; + f2.m_twin[2] = f1Node; + + f3.m_twin[0] = f0Node; + f3.m_twin[1] = f1Node; + f3.m_twin[2] = f2Node; + + std::list::iterator> boundaryFaces; + boundaryFaces.push_back(f0Node); + boundaryFaces.push_back(f1Node); + boundaryFaces.push_back(f2Node); + boundaryFaces.push_back(f3Node); + + m_points.resize(count); + + count -= 4; + maxVertexCount -= 4; + int currentIndex = 4; + + /* + * Some are iterators into boundaryFaces, others into m_list + */ + std::vector::iterator> stack; + std::vector::iterator> coneList; + std::vector::iterator> deleteList; + + stack.reserve(1024 + count); + coneList.reserve(1024 + count); + deleteList.reserve(1024 + count); + + while (boundaryFaces.size() && count && (maxVertexCount > 0)) + { + // my definition of the optimal convex hull of a given vertex count, + // is the convex hull formed by a subset of the input vertex that minimizes the volume difference + // between the perfect hull formed from all input vertex and the hull of the sub set of vertex. + // When using a priority heap this algorithms will generate the an optimal of a fix vertex count. + // Since all Newton's tools do not have a limit on the point count of a convex hull, I can use either a stack or a queue. + // a stack maximize construction speed, a Queue tend to maximize the volume of the generated Hull approaching a perfect Hull. + // For now we use a queue. + // For general hulls it does not make a difference if we use a stack, queue, or a priority heap. + // perfect optimal hull only apply for when build hull of a limited vertex count. + // + // Also when building Hulls of a limited vertex count, this function runs in constant time. + // yes that is correct, it does not makes a difference if you build a N point hull from 100 vertex + // or from 100000 vertex input array. + + // using a queue (some what slower by better hull when reduced vertex count is desired) + bool isvalid; + std::list::iterator faceNode = boundaryFaces.back(); + ConvexHullFace& face = *faceNode; + HullPlane planeEquation(face.GetPlaneEquation(m_points, isvalid)); + + int index = 0; + double dist = 0; + VHACD::Vect3 p; + if (isvalid) + { + index = SupportVertex(&vertexTree, + points, + planeEquation); + p = points[index]; + dist = planeEquation.Evalue(p); + } + + if ( isvalid + && (dist >= distTol) + && (face.Evalue(m_points, p) < double(0.0))) + { + stack.push_back(faceNode); + + deleteList.clear(); + while (stack.size()) + { + std::list::iterator node1 = stack.back(); + ConvexHullFace& face1 = *node1; + + stack.pop_back(); + + if (!face1.m_mark && (face1.Evalue(m_points, p) < double(0.0))) + { + #ifdef _DEBUG + for (const auto node : deleteList) + { + assert(node != node1); + } + #endif + + deleteList.push_back(node1); + face1.m_mark = 1; + for (std::list::iterator& twinNode : face1.m_twin) + { + ConvexHullFace& twinFace = *twinNode; + if (!twinFace.m_mark) + { + stack.push_back(twinNode); + } + } + } + } + + m_points[currentIndex] = points[index]; + points[index].m_mark = 1; + + coneList.clear(); + for (std::list::iterator node1 : deleteList) + { + ConvexHullFace& face1 = *node1; + assert(face1.m_mark == 1); + for (std::size_t j0 = 0; j0 < face1.m_twin.size(); ++j0) + { + std::list::iterator twinNode = face1.m_twin[j0]; + ConvexHullFace& twinFace = *twinNode; + if (!twinFace.m_mark) + { + std::size_t j1 = (j0 == 2) ? 0 : j0 + 1; + std::list::iterator newNode = AddFace(currentIndex, + face1.m_index[j0], + face1.m_index[j1]); + boundaryFaces.push_front(newNode); + ConvexHullFace& newFace = *newNode; + + newFace.m_twin[1] = twinNode; + for (std::size_t k = 0; k < twinFace.m_twin.size(); ++k) + { + if (twinFace.m_twin[k] == node1) + { + twinFace.m_twin[k] = newNode; + } + } + coneList.push_back(newNode); + } + } + } + + for (std::size_t i = 0; i < coneList.size() - 1; ++i) + { + std::list::iterator nodeA = coneList[i]; + ConvexHullFace& faceA = *nodeA; + assert(faceA.m_mark == 0); + for (std::size_t j = i + 1; j < coneList.size(); j++) + { + std::list::iterator nodeB = coneList[j]; + ConvexHullFace& faceB = *nodeB; + assert(faceB.m_mark == 0); + if (faceA.m_index[2] == faceB.m_index[1]) + { + faceA.m_twin[2] = nodeB; + faceB.m_twin[0] = nodeA; + break; + } + } + + for (std::size_t j = i + 1; j < coneList.size(); j++) + { + std::list::iterator nodeB = coneList[j]; + ConvexHullFace& faceB = *nodeB; + assert(faceB.m_mark == 0); + if (faceA.m_index[1] == faceB.m_index[2]) + { + faceA.m_twin[0] = nodeB; + faceB.m_twin[2] = nodeA; + break; + } + } + } + + for (std::list::iterator node : deleteList) + { + auto it = std::find(boundaryFaces.begin(), + boundaryFaces.end(), + node); + if (it != boundaryFaces.end()) + { + boundaryFaces.erase(it); + } + m_list.erase(node); + } + + maxVertexCount--; + currentIndex++; + count--; + } + else + { + auto it = std::find(boundaryFaces.begin(), + boundaryFaces.end(), + faceNode); + if (it != boundaryFaces.end()) + { + boundaryFaces.erase(it); + } + } + } + m_points.resize(currentIndex); +} + +//*********************************************************************************************** +// End of ConvexHull generation code by Julio Jerez +//*********************************************************************************************** + +class KdTreeNode; + +enum Axes +{ + X_AXIS = 0, + Y_AXIS = 1, + Z_AXIS = 2 +}; + +class KdTreeFindNode +{ +public: + KdTreeFindNode() = default; + + KdTreeNode* m_node{ nullptr }; + double m_distance{ 0.0 }; +}; + +class KdTree +{ +public: + KdTree() = default; + + const VHACD::Vertex& GetPosition(uint32_t index) const; + + uint32_t Search(const VHACD::Vect3& pos, + double radius, + uint32_t maxObjects, + KdTreeFindNode* found) const; + + uint32_t Add(const VHACD::Vertex& v); + + KdTreeNode& GetNewNode(uint32_t index); + + uint32_t GetNearest(const VHACD::Vect3& pos, + double radius, + bool& _found) const; // returns the nearest possible neighbor's index. + + const std::vector& GetVertices() const; + std::vector&& TakeVertices(); + + uint32_t GetVCount() const; + +private: + KdTreeNode* m_root{ nullptr }; + NodeBundle m_bundle; + + std::vector m_vertices; +}; + +class KdTreeNode +{ +public: + KdTreeNode() = default; + KdTreeNode(uint32_t index); + + void Add(KdTreeNode& node, + Axes dim, + const KdTree& iface); + + uint32_t GetIndex() const; + + void Search(Axes axis, + const VHACD::Vect3& pos, + double radius, + uint32_t& count, + uint32_t maxObjects, + KdTreeFindNode* found, + const KdTree& iface); + +private: + uint32_t m_index = 0; + KdTreeNode* m_left = nullptr; + KdTreeNode* m_right = nullptr; +}; + +const VHACD::Vertex& KdTree::GetPosition(uint32_t index) const +{ + assert(index < m_vertices.size()); + return m_vertices[index]; +} + +uint32_t KdTree::Search(const VHACD::Vect3& pos, + double radius, + uint32_t maxObjects, + KdTreeFindNode* found) const +{ + if (!m_root) + return 0; + uint32_t count = 0; + m_root->Search(X_AXIS, pos, radius, count, maxObjects, found, *this); + return count; +} + +uint32_t KdTree::Add(const VHACD::Vertex& v) +{ + uint32_t ret = uint32_t(m_vertices.size()); + m_vertices.emplace_back(v); + KdTreeNode& node = GetNewNode(ret); + if (m_root) + { + m_root->Add(node, + X_AXIS, + *this); + } + else + { + m_root = &node; + } + return ret; +} + +KdTreeNode& KdTree::GetNewNode(uint32_t index) +{ + KdTreeNode& node = m_bundle.GetNextNode(); + node = KdTreeNode(index); + return node; +} + +uint32_t KdTree::GetNearest(const VHACD::Vect3& pos, + double radius, + bool& _found) const // returns the nearest possible neighbor's index. +{ + uint32_t ret = 0; + + _found = false; + KdTreeFindNode found; + uint32_t count = Search(pos, radius, 1, &found); + if (count) + { + KdTreeNode* node = found.m_node; + ret = node->GetIndex(); + _found = true; + } + return ret; +} + +const std::vector& KdTree::GetVertices() const +{ + return m_vertices; +} + +std::vector&& KdTree::TakeVertices() +{ + return std::move(m_vertices); +} + +uint32_t KdTree::GetVCount() const +{ + return uint32_t(m_vertices.size()); +} + +KdTreeNode::KdTreeNode(uint32_t index) + : m_index(index) +{ +} + +void KdTreeNode::Add(KdTreeNode& node, + Axes dim, + const KdTree& tree) +{ + Axes axis = X_AXIS; + uint32_t idx = 0; + switch (dim) + { + case X_AXIS: + idx = 0; + axis = Y_AXIS; + break; + case Y_AXIS: + idx = 1; + axis = Z_AXIS; + break; + case Z_AXIS: + idx = 2; + axis = X_AXIS; + break; + } + + const VHACD::Vertex& nodePosition = tree.GetPosition(node.m_index); + const VHACD::Vertex& position = tree.GetPosition(m_index); + if (nodePosition[idx] <= position[idx]) + { + if (m_left) + m_left->Add(node, axis, tree); + else + m_left = &node; + } + else + { + if (m_right) + m_right->Add(node, axis, tree); + else + m_right = &node; + } +} + +uint32_t KdTreeNode::GetIndex() const +{ + return m_index; +} + +void KdTreeNode::Search(Axes axis, + const VHACD::Vect3& pos, + double radius, + uint32_t& count, + uint32_t maxObjects, + KdTreeFindNode* found, + const KdTree& iface) +{ + const VHACD::Vect3 position = iface.GetPosition(m_index); + + const VHACD::Vect3 d = pos - position; + + KdTreeNode* search1 = 0; + KdTreeNode* search2 = 0; + + uint32_t idx = 0; + switch (axis) + { + case X_AXIS: + idx = 0; + axis = Y_AXIS; + break; + case Y_AXIS: + idx = 1; + axis = Z_AXIS; + break; + case Z_AXIS: + idx = 2; + axis = X_AXIS; + break; + } + + if (d[idx] <= 0) // JWR if we are to the left + { + search1 = m_left; // JWR then search to the left + if (-d[idx] < radius) // JWR if distance to the right is less than our search radius, continue on the right + // as well. + search2 = m_right; + } + else + { + search1 = m_right; // JWR ok, we go down the left tree + if (d[idx] < radius) // JWR if the distance from the right is less than our search radius + search2 = m_left; + } + + double r2 = radius * radius; + double m = d.GetNormSquared(); + + if (m < r2) + { + switch (count) + { + case 0: + { + found[count].m_node = this; + found[count].m_distance = m; + break; + } + case 1: + { + if (m < found[0].m_distance) + { + if (maxObjects == 1) + { + found[0].m_node = this; + found[0].m_distance = m; + } + else + { + found[1] = found[0]; + found[0].m_node = this; + found[0].m_distance = m; + } + } + else if (maxObjects > 1) + { + found[1].m_node = this; + found[1].m_distance = m; + } + break; + } + default: + { + bool inserted = false; + + for (uint32_t i = 0; i < count; i++) + { + if (m < found[i].m_distance) // if this one is closer than a pre-existing one... + { + // insertion sort... + uint32_t scan = count; + if (scan >= maxObjects) + scan = maxObjects - 1; + for (uint32_t j = scan; j > i; j--) + { + found[j] = found[j - 1]; + } + found[i].m_node = this; + found[i].m_distance = m; + inserted = true; + break; + } + } + + if (!inserted && count < maxObjects) + { + found[count].m_node = this; + found[count].m_distance = m; + } + } + break; + } + + count++; + + if (count > maxObjects) + { + count = maxObjects; + } + } + + + if (search1) + search1->Search(axis, pos, radius, count, maxObjects, found, iface); + + if (search2) + search2->Search(axis, pos, radius, count, maxObjects, found, iface); +} + +class VertexIndex +{ +public: + VertexIndex(double granularity, + bool snapToGrid); + + VHACD::Vect3 SnapToGrid(VHACD::Vect3 p); + + uint32_t GetIndex(VHACD::Vect3 p, + bool& newPos); + + const std::vector& GetVertices() const; + + std::vector&& TakeVertices(); + + uint32_t GetVCount() const; + + bool SaveAsObj(const char* fname, + uint32_t tcount, + uint32_t* indices) + { + bool ret = false; + + FILE* fph = fopen(fname, "wb"); + if (fph) + { + ret = true; + + const std::vector& v = GetVertices(); + for (uint32_t i = 0; i < v.size(); ++i) + { + fprintf(fph, "v %0.9f %0.9f %0.9f\r\n", + v[i].mX, + v[i].mY, + v[i].mZ); + } + + for (uint32_t i = 0; i < tcount; i++) + { + uint32_t i1 = *indices++; + uint32_t i2 = *indices++; + uint32_t i3 = *indices++; + fprintf(fph, "f %d %d %d\r\n", + i1 + 1, + i2 + 1, + i3 + 1); + } + fclose(fph); + } + + return ret; + } + +private: + bool m_snapToGrid : 1; + double m_granularity; + KdTree m_KdTree; +}; + +VertexIndex::VertexIndex(double granularity, + bool snapToGrid) + : m_snapToGrid(snapToGrid) + , m_granularity(granularity) +{ +} + +VHACD::Vect3 VertexIndex::SnapToGrid(VHACD::Vect3 p) +{ + for (int i = 0; i < 3; ++i) + { + double m = fmod(p[i], m_granularity); + p[i] -= m; + } + return p; +} + +uint32_t VertexIndex::GetIndex(VHACD::Vect3 p, + bool& newPos) +{ + uint32_t ret; + + newPos = false; + + if (m_snapToGrid) + { + p = SnapToGrid(p); + } + + bool found; + ret = m_KdTree.GetNearest(p, m_granularity, found); + if (!found) + { + newPos = true; + ret = m_KdTree.Add(VHACD::Vertex(p.GetX(), p.GetY(), p.GetZ())); + } + + return ret; +} + +const std::vector& VertexIndex::GetVertices() const +{ + return m_KdTree.GetVertices(); +} + +std::vector&& VertexIndex::TakeVertices() +{ + return std::move(m_KdTree.TakeVertices()); +} + +uint32_t VertexIndex::GetVCount() const +{ + return m_KdTree.GetVCount(); +} + +/* + * A wrapper class for 3 10 bit integers packed into a 32 bit integer + * Layout is [PAD][X][Y][Z] + * Pad is bits 31-30, X is 29-20, Y is 19-10, and Z is 9-0 + */ +class Voxel +{ + /* + * Specify all of them for consistency + */ + static constexpr int VoxelBitsZStart = 0; + static constexpr int VoxelBitsYStart = 10; + static constexpr int VoxelBitsXStart = 20; + static constexpr int VoxelBitMask = 0x03FF; // bits 0 through 9 inclusive +public: + Voxel() = default; + + Voxel(uint32_t index); + + Voxel(uint32_t x, + uint32_t y, + uint32_t z); + + bool operator==(const Voxel &v) const; + + VHACD::Vector3 GetVoxel() const; + + uint32_t GetX() const; + uint32_t GetY() const; + uint32_t GetZ() const; + + uint32_t GetVoxelAddress() const; + +private: + uint32_t m_voxel{ 0 }; +}; + +Voxel::Voxel(uint32_t index) + : m_voxel(index) +{ +} + +Voxel::Voxel(uint32_t x, + uint32_t y, + uint32_t z) + : m_voxel((x << VoxelBitsXStart) | (y << VoxelBitsYStart) | (z << VoxelBitsZStart)) +{ + assert(x < 1024 && "Voxel constructed with X outside of range"); + assert(y < 1024 && "Voxel constructed with Y outside of range"); + assert(z < 1024 && "Voxel constructed with Z outside of range"); +} + +bool Voxel::operator==(const Voxel& v) const +{ + return m_voxel == v.m_voxel; +} + +VHACD::Vector3 Voxel::GetVoxel() const +{ + return VHACD::Vector3(GetX(), GetY(), GetZ()); +} + +uint32_t Voxel::GetX() const +{ + return (m_voxel >> VoxelBitsXStart) & VoxelBitMask; +} + +uint32_t Voxel::GetY() const +{ + return (m_voxel >> VoxelBitsYStart) & VoxelBitMask; +} + +uint32_t Voxel::GetZ() const +{ + return (m_voxel >> VoxelBitsZStart) & VoxelBitMask; +} + +uint32_t Voxel::GetVoxelAddress() const +{ + return m_voxel; +} + +struct SimpleMesh +{ + std::vector m_vertices; + std::vector m_indices; +}; + +/*======================== 0-tests ========================*/ +inline bool IntersectRayAABB(const VHACD::Vect3& start, + const VHACD::Vect3& dir, + const VHACD::BoundsAABB& bounds, + double& t) +{ + //! calculate candidate plane on each axis + bool inside = true; + VHACD::Vect3 ta(double(-1.0)); + + //! use unrolled loops + for (uint32_t i = 0; i < 3; ++i) + { + if (start[i] < bounds.GetMin()[i]) + { + if (dir[i] != double(0.0)) + ta[i] = (bounds.GetMin()[i] - start[i]) / dir[i]; + inside = false; + } + else if (start[i] > bounds.GetMax()[i]) + { + if (dir[i] != double(0.0)) + ta[i] = (bounds.GetMax()[i] - start[i]) / dir[i]; + inside = false; + } + } + + //! if point inside all planes + if (inside) + { + t = double(0.0); + return true; + } + + //! we now have t values for each of possible intersection planes + //! find the maximum to get the intersection point + uint32_t taxis; + double tmax = ta.MaxCoeff(taxis); + + if (tmax < double(0.0)) + return false; + + //! check that the intersection point lies on the plane we picked + //! we don't test the axis of closest intersection for precision reasons + + //! no eps for now + double eps = double(0.0); + + VHACD::Vect3 hit = start + dir * tmax; + + if (( hit.GetX() < bounds.GetMin().GetX() - eps + || hit.GetX() > bounds.GetMax().GetX() + eps) + && taxis != 0) + return false; + if (( hit.GetY() < bounds.GetMin().GetY() - eps + || hit.GetY() > bounds.GetMax().GetY() + eps) + && taxis != 1) + return false; + if (( hit.GetZ() < bounds.GetMin().GetZ() - eps + || hit.GetZ() > bounds.GetMax().GetZ() + eps) + && taxis != 2) + return false; + + //! output results + t = tmax; + + return true; +} + +// Moller and Trumbore's method +inline bool IntersectRayTriTwoSided(const VHACD::Vect3& p, + const VHACD::Vect3& dir, + const VHACD::Vect3& a, + const VHACD::Vect3& b, + const VHACD::Vect3& c, + double& t, + double& u, + double& v, + double& w, + double& sign, + VHACD::Vect3* normal) +{ + VHACD::Vect3 ab = b - a; + VHACD::Vect3 ac = c - a; + VHACD::Vect3 n = ab.Cross(ac); + + double d = -dir.Dot(n); + double ood = double(1.0) / d; // No need to check for division by zero here as infinity arithmetic will save us... + VHACD::Vect3 ap = p - a; + + t = ap.Dot(n) * ood; + if (t < double(0.0)) + { + return false; + } + + VHACD::Vect3 e = -dir.Cross(ap); + v = ac.Dot(e) * ood; + if (v < double(0.0) || v > double(1.0)) // ...here... + { + return false; + } + w = -ab.Dot(e) * ood; + if (w < double(0.0) || v + w > double(1.0)) // ...and here + { + return false; + } + + u = double(1.0) - v - w; + if (normal) + { + *normal = n; + } + + sign = d; + + return true; +} + +// RTCD 5.1.5, page 142 +inline VHACD::Vect3 ClosestPointOnTriangle(const VHACD::Vect3& a, + const VHACD::Vect3& b, + const VHACD::Vect3& c, + const VHACD::Vect3& p, + double& v, + double& w) +{ + VHACD::Vect3 ab = b - a; + VHACD::Vect3 ac = c - a; + VHACD::Vect3 ap = p - a; + + double d1 = ab.Dot(ap); + double d2 = ac.Dot(ap); + if ( d1 <= double(0.0) + && d2 <= double(0.0)) + { + v = double(0.0); + w = double(0.0); + return a; + } + + VHACD::Vect3 bp = p - b; + double d3 = ab.Dot(bp); + double d4 = ac.Dot(bp); + if ( d3 >= double(0.0) + && d4 <= d3) + { + v = double(1.0); + w = double(0.0); + return b; + } + + double vc = d1 * d4 - d3 * d2; + if ( vc <= double(0.0) + && d1 >= double(0.0) + && d3 <= double(0.0)) + { + v = d1 / (d1 - d3); + w = double(0.0); + return a + v * ab; + } + + VHACD::Vect3 cp = p - c; + double d5 = ab.Dot(cp); + double d6 = ac.Dot(cp); + if (d6 >= double(0.0) && d5 <= d6) + { + v = double(0.0); + w = double(1.0); + return c; + } + + double vb = d5 * d2 - d1 * d6; + if ( vb <= double(0.0) + && d2 >= double(0.0) + && d6 <= double(0.0)) + { + v = double(0.0); + w = d2 / (d2 - d6); + return a + w * ac; + } + + double va = d3 * d6 - d5 * d4; + if ( va <= double(0.0) + && (d4 - d3) >= double(0.0) + && (d5 - d6) >= double(0.0)) + { + w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + v = double(1.0) - w; + return b + w * (c - b); + } + + double denom = double(1.0) / (va + vb + vc); + v = vb * denom; + w = vc * denom; + return a + ab * v + ac * w; +} + +class AABBTree +{ +public: + AABBTree() = default; + AABBTree(AABBTree&&) = default; + AABBTree& operator=(AABBTree&&) = default; + + AABBTree(const std::vector& vertices, + const std::vector& indices); + + bool TraceRay(const VHACD::Vect3& start, + const VHACD::Vect3& to, + double& outT, + double& faceSign, + VHACD::Vect3& hitLocation) const; + + bool TraceRay(const VHACD::Vect3& start, + const VHACD::Vect3& dir, + uint32_t& insideCount, + uint32_t& outsideCount) const; + + bool TraceRay(const VHACD::Vect3& start, + const VHACD::Vect3& dir, + double& outT, + double& u, + double& v, + double& w, + double& faceSign, + uint32_t& faceIndex) const; + + VHACD::Vect3 GetCenter() const; + VHACD::Vect3 GetMinExtents() const; + VHACD::Vect3 GetMaxExtents() const; + + bool GetClosestPointWithinDistance(const VHACD::Vect3& point, + double maxDistance, + VHACD::Vect3& closestPoint) const; + +private: + struct Node + { + union + { + uint32_t m_children; + uint32_t m_numFaces{ 0 }; + }; + + uint32_t* m_faces{ nullptr }; + VHACD::BoundsAABB m_extents; + }; + + struct FaceSorter + { + FaceSorter(const std::vector& positions, + const std::vector& indices, + uint32_t axis); + + bool operator()(uint32_t lhs, uint32_t rhs) const; + + double GetCentroid(uint32_t face) const; + + const std::vector& m_vertices; + const std::vector& m_indices; + uint32_t m_axis; + }; + + // partition the objects and return the number of objects in the lower partition + uint32_t PartitionMedian(Node& n, + uint32_t* faces, + uint32_t numFaces); + uint32_t PartitionSAH(Node& n, + uint32_t* faces, + uint32_t numFaces); + + void Build(); + + void BuildRecursive(uint32_t nodeIndex, + uint32_t* faces, + uint32_t numFaces); + + void TraceRecursive(uint32_t nodeIndex, + const VHACD::Vect3& start, + const VHACD::Vect3& dir, + double& outT, + double& u, + double& v, + double& w, + double& faceSign, + uint32_t& faceIndex) const; + + + bool GetClosestPointWithinDistance(const VHACD::Vect3& point, + const double maxDis, + double& dis, + double& v, + double& w, + uint32_t& faceIndex, + VHACD::Vect3& closest) const; + + void GetClosestPointWithinDistanceSqRecursive(uint32_t nodeIndex, + const VHACD::Vect3& point, + double& outDisSq, + double& outV, + double& outW, + uint32_t& outFaceIndex, + VHACD::Vect3& closest) const; + + VHACD::BoundsAABB CalculateFaceBounds(uint32_t* faces, + uint32_t numFaces); + + // track the next free node + uint32_t m_freeNode; + + const std::vector* m_vertices{ nullptr }; + const std::vector* m_indices{ nullptr }; + + std::vector m_faces; + std::vector m_nodes; + std::vector m_faceBounds; + + // stats + uint32_t m_treeDepth{ 0 }; + uint32_t m_innerNodes{ 0 }; + uint32_t m_leafNodes{ 0 }; + + uint32_t s_depth{ 0 }; +}; + +AABBTree::FaceSorter::FaceSorter(const std::vector& positions, + const std::vector& indices, + uint32_t axis) + : m_vertices(positions) + , m_indices(indices) + , m_axis(axis) +{ +} + +inline bool AABBTree::FaceSorter::operator()(uint32_t lhs, + uint32_t rhs) const +{ + double a = GetCentroid(lhs); + double b = GetCentroid(rhs); + + if (a == b) + { + return lhs < rhs; + } + else + { + return a < b; + } +} + +inline double AABBTree::FaceSorter::GetCentroid(uint32_t face) const +{ + const VHACD::Vect3& a = m_vertices[m_indices[face].mI0]; + const VHACD::Vect3& b = m_vertices[m_indices[face].mI1]; + const VHACD::Vect3& c = m_vertices[m_indices[face].mI2]; + + return (a[m_axis] + b[m_axis] + c[m_axis]) / double(3.0); +} + +AABBTree::AABBTree(const std::vector& vertices, + const std::vector& indices) + : m_vertices(&vertices) + , m_indices(&indices) +{ + Build(); +} + +bool AABBTree::TraceRay(const VHACD::Vect3& start, + const VHACD::Vect3& to, + double& outT, + double& faceSign, + VHACD::Vect3& hitLocation) const +{ + VHACD::Vect3 dir = to - start; + double distance = dir.Normalize(); + double u, v, w; + uint32_t faceIndex; + bool hit = TraceRay(start, + dir, + outT, + u, + v, + w, + faceSign, + faceIndex); + if (hit) + { + hitLocation = start + dir * outT; + } + + if (hit && outT > distance) + { + hit = false; + } + return hit; +} + +bool AABBTree::TraceRay(const VHACD::Vect3& start, + const VHACD::Vect3& dir, + uint32_t& insideCount, + uint32_t& outsideCount) const +{ + double outT, u, v, w, faceSign; + uint32_t faceIndex; + bool hit = TraceRay(start, + dir, + outT, + u, + v, + w, + faceSign, + faceIndex); + if (hit) + { + if (faceSign >= 0) + { + insideCount++; + } + else + { + outsideCount++; + } + } + return hit; +} + +bool AABBTree::TraceRay(const VHACD::Vect3& start, + const VHACD::Vect3& dir, + double& outT, + double& u, + double& v, + double& w, + double& faceSign, + uint32_t& faceIndex) const +{ + outT = FLT_MAX; + TraceRecursive(0, + start, + dir, + outT, + u, + v, + w, + faceSign, + faceIndex); + return (outT != FLT_MAX); +} + +VHACD::Vect3 AABBTree::GetCenter() const +{ + return m_nodes[0].m_extents.GetCenter(); +} + +VHACD::Vect3 AABBTree::GetMinExtents() const +{ + return m_nodes[0].m_extents.GetMin(); +} + +VHACD::Vect3 AABBTree::GetMaxExtents() const +{ + return m_nodes[0].m_extents.GetMax(); +} + +bool AABBTree::GetClosestPointWithinDistance(const VHACD::Vect3& point, + double maxDistance, + VHACD::Vect3& closestPoint) const +{ + double dis, v, w; + uint32_t faceIndex; + bool hit = GetClosestPointWithinDistance(point, + maxDistance, + dis, + v, + w, + faceIndex, + closestPoint); + return hit; +} + +// partition faces around the median face +uint32_t AABBTree::PartitionMedian(Node& n, + uint32_t* faces, + uint32_t numFaces) +{ + FaceSorter predicate(*m_vertices, + *m_indices, + n.m_extents.GetSize().LongestAxis()); + std::nth_element(faces, + faces + numFaces / 2, + faces + numFaces, + predicate); + + return numFaces / 2; +} + +// partition faces based on the surface area heuristic +uint32_t AABBTree::PartitionSAH(Node&, + uint32_t* faces, + uint32_t numFaces) +{ + uint32_t bestAxis = 0; + uint32_t bestIndex = 0; + double bestCost = FLT_MAX; + + for (uint32_t a = 0; a < 3; ++a) + { + // sort faces by centroids + FaceSorter predicate(*m_vertices, + *m_indices, + a); + std::sort(faces, + faces + numFaces, + predicate); + + // two passes over data to calculate upper and lower bounds + std::vector cumulativeLower(numFaces); + std::vector cumulativeUpper(numFaces); + + VHACD::BoundsAABB lower; + VHACD::BoundsAABB upper; + + for (uint32_t i = 0; i < numFaces; ++i) + { + lower.Union(m_faceBounds[faces[i]]); + upper.Union(m_faceBounds[faces[numFaces - i - 1]]); + + cumulativeLower[i] = lower.SurfaceArea(); + cumulativeUpper[numFaces - i - 1] = upper.SurfaceArea(); + } + + double invTotalSA = double(1.0) / cumulativeUpper[0]; + + // test all split positions + for (uint32_t i = 0; i < numFaces - 1; ++i) + { + double pBelow = cumulativeLower[i] * invTotalSA; + double pAbove = cumulativeUpper[i] * invTotalSA; + + double cost = double(0.125) + (pBelow * i + pAbove * (numFaces - i)); + if (cost <= bestCost) + { + bestCost = cost; + bestIndex = i; + bestAxis = a; + } + } + } + + // re-sort by best axis + FaceSorter predicate(*m_vertices, + *m_indices, + bestAxis); + std::sort(faces, + faces + numFaces, + predicate); + + return bestIndex + 1; +} + +void AABBTree::Build() +{ + const uint32_t numFaces = uint32_t(m_indices->size()); + + // build initial list of faces + m_faces.reserve(numFaces); + + // calculate bounds of each face and store + m_faceBounds.reserve(numFaces); + + std::vector stack; + for (uint32_t i = 0; i < numFaces; ++i) + { + VHACD::BoundsAABB top = CalculateFaceBounds(&i, + 1); + + m_faces.push_back(i); + m_faceBounds.push_back(top); + } + + m_nodes.reserve(uint32_t(numFaces * double(1.5))); + + // allocate space for all the nodes + m_freeNode = 1; + + // start building + BuildRecursive(0, + m_faces.data(), + numFaces); + + assert(s_depth == 0); +} + +void AABBTree::BuildRecursive(uint32_t nodeIndex, + uint32_t* faces, + uint32_t numFaces) +{ + const uint32_t kMaxFacesPerLeaf = 6; + + // if we've run out of nodes allocate some more + if (nodeIndex >= m_nodes.size()) + { + uint32_t s = std::max(uint32_t(double(1.5) * m_nodes.size()), 512U); + m_nodes.resize(s); + } + + // a reference to the current node, need to be careful here as this reference may become invalid if array is resized + Node& n = m_nodes[nodeIndex]; + + // track max tree depth + ++s_depth; + m_treeDepth = std::max(m_treeDepth, s_depth); + + n.m_extents = CalculateFaceBounds(faces, + numFaces); + + // calculate bounds of faces and add node + if (numFaces <= kMaxFacesPerLeaf) + { + n.m_faces = faces; + n.m_numFaces = numFaces; + + ++m_leafNodes; + } + else + { + ++m_innerNodes; + + // face counts for each branch + const uint32_t leftCount = PartitionMedian(n, faces, numFaces); + // const uint32_t leftCount = PartitionSAH(n, faces, numFaces); + const uint32_t rightCount = numFaces - leftCount; + + // alloc 2 nodes + m_nodes[nodeIndex].m_children = m_freeNode; + + // allocate two nodes + m_freeNode += 2; + + // split faces in half and build each side recursively + BuildRecursive(m_nodes[nodeIndex].m_children + 0, faces, leftCount); + BuildRecursive(m_nodes[nodeIndex].m_children + 1, faces + leftCount, rightCount); + } + + --s_depth; +} + +void AABBTree::TraceRecursive(uint32_t nodeIndex, + const VHACD::Vect3& start, + const VHACD::Vect3& dir, + double& outT, + double& outU, + double& outV, + double& outW, + double& faceSign, + uint32_t& faceIndex) const +{ + const Node& node = m_nodes[nodeIndex]; + + if (node.m_faces == NULL) + { + // find closest node + const Node& leftChild = m_nodes[node.m_children + 0]; + const Node& rightChild = m_nodes[node.m_children + 1]; + + double dist[2] = { FLT_MAX, FLT_MAX }; + + IntersectRayAABB(start, + dir, + leftChild.m_extents, + dist[0]); + IntersectRayAABB(start, + dir, + rightChild.m_extents, + dist[1]); + + uint32_t closest = 0; + uint32_t furthest = 1; + + if (dist[1] < dist[0]) + { + closest = 1; + furthest = 0; + } + + if (dist[closest] < outT) + { + TraceRecursive(node.m_children + closest, + start, + dir, + outT, + outU, + outV, + outW, + faceSign, + faceIndex); + } + + if (dist[furthest] < outT) + { + TraceRecursive(node.m_children + furthest, + start, + dir, + outT, + outU, + outV, + outW, + faceSign, + faceIndex); + } + } + else + { + double t, u, v, w, s; + + for (uint32_t i = 0; i < node.m_numFaces; ++i) + { + uint32_t indexStart = node.m_faces[i]; + + const VHACD::Vect3& a = (*m_vertices)[(*m_indices)[indexStart].mI0]; + const VHACD::Vect3& b = (*m_vertices)[(*m_indices)[indexStart].mI1]; + const VHACD::Vect3& c = (*m_vertices)[(*m_indices)[indexStart].mI2]; + if (IntersectRayTriTwoSided(start, dir, a, b, c, t, u, v, w, s, NULL)) + { + if (t < outT) + { + outT = t; + outU = u; + outV = v; + outW = w; + faceSign = s; + faceIndex = node.m_faces[i]; + } + } + } + } +} + +bool AABBTree::GetClosestPointWithinDistance(const VHACD::Vect3& point, + const double maxDis, + double& dis, + double& v, + double& w, + uint32_t& faceIndex, + VHACD::Vect3& closest) const +{ + dis = maxDis; + faceIndex = uint32_t(~0); + double disSq = dis * dis; + + GetClosestPointWithinDistanceSqRecursive(0, + point, + disSq, + v, + w, + faceIndex, + closest); + dis = sqrt(disSq); + + return (faceIndex < (~(static_cast(0)))); +} + +void AABBTree::GetClosestPointWithinDistanceSqRecursive(uint32_t nodeIndex, + const VHACD::Vect3& point, + double& outDisSq, + double& outV, + double& outW, + uint32_t& outFaceIndex, + VHACD::Vect3& closestPoint) const +{ + const Node& node = m_nodes[nodeIndex]; + + if (node.m_faces == nullptr) + { + // find closest node + const Node& leftChild = m_nodes[node.m_children + 0]; + const Node& rightChild = m_nodes[node.m_children + 1]; + + // double dist[2] = { FLT_MAX, FLT_MAX }; + VHACD::Vect3 lp = leftChild.m_extents.ClosestPoint(point); + VHACD::Vect3 rp = rightChild.m_extents.ClosestPoint(point); + + + uint32_t closest = 0; + uint32_t furthest = 1; + double dcSq = (point - lp).GetNormSquared(); + double dfSq = (point - rp).GetNormSquared(); + if (dfSq < dcSq) + { + closest = 1; + furthest = 0; + std::swap(dfSq, dcSq); + } + + if (dcSq < outDisSq) + { + GetClosestPointWithinDistanceSqRecursive(node.m_children + closest, + point, + outDisSq, + outV, + outW, + outFaceIndex, + closestPoint); + } + + if (dfSq < outDisSq) + { + GetClosestPointWithinDistanceSqRecursive(node.m_children + furthest, + point, + outDisSq, + outV, + outW, + outFaceIndex, + closestPoint); + } + } + else + { + + double v, w; + for (uint32_t i = 0; i < node.m_numFaces; ++i) + { + uint32_t indexStart = node.m_faces[i]; + + const VHACD::Vect3& a = (*m_vertices)[(*m_indices)[indexStart].mI0]; + const VHACD::Vect3& b = (*m_vertices)[(*m_indices)[indexStart].mI1]; + const VHACD::Vect3& c = (*m_vertices)[(*m_indices)[indexStart].mI2]; + + VHACD::Vect3 cp = ClosestPointOnTriangle(a, b, c, point, v, w); + double disSq = (cp - point).GetNormSquared(); + + if (disSq < outDisSq) + { + closestPoint = cp; + outDisSq = disSq; + outV = v; + outW = w; + outFaceIndex = node.m_faces[i]; + } + } + } +} + +VHACD::BoundsAABB AABBTree::CalculateFaceBounds(uint32_t* faces, + uint32_t numFaces) +{ + VHACD::Vect3 minExtents( FLT_MAX); + VHACD::Vect3 maxExtents(-FLT_MAX); + + // calculate face bounds + for (uint32_t i = 0; i < numFaces; ++i) + { + VHACD::Vect3 a = (*m_vertices)[(*m_indices)[faces[i]].mI0]; + VHACD::Vect3 b = (*m_vertices)[(*m_indices)[faces[i]].mI1]; + VHACD::Vect3 c = (*m_vertices)[(*m_indices)[faces[i]].mI2]; + + minExtents = a.CWiseMin(minExtents); + maxExtents = a.CWiseMax(maxExtents); + + minExtents = b.CWiseMin(minExtents); + maxExtents = b.CWiseMax(maxExtents); + + minExtents = c.CWiseMin(minExtents); + maxExtents = c.CWiseMax(maxExtents); + } + + return VHACD::BoundsAABB(minExtents, + maxExtents); +} + +enum class VoxelValue : uint8_t +{ + PRIMITIVE_UNDEFINED = 0, + PRIMITIVE_OUTSIDE_SURFACE_TOWALK = 1, + PRIMITIVE_OUTSIDE_SURFACE = 2, + PRIMITIVE_INSIDE_SURFACE = 3, + PRIMITIVE_ON_SURFACE = 4 +}; + +class Volume +{ +public: + void Voxelize(const std::vector& points, + const std::vector& triangles, + const size_t dim, + FillMode fillMode, + const AABBTree& aabbTree); + + void RaycastFill(const AABBTree& aabbTree); + + void SetVoxel(const size_t i, + const size_t j, + const size_t k, + VoxelValue value); + + VoxelValue& GetVoxel(const size_t i, + const size_t j, + const size_t k); + + const VoxelValue& GetVoxel(const size_t i, + const size_t j, + const size_t k) const; + + const std::vector& GetSurfaceVoxels() const; + const std::vector& GetInteriorVoxels() const; + + double GetScale() const; + const VHACD::BoundsAABB& GetBounds() const; + const VHACD::Vector3& GetDimensions() const; + + VHACD::BoundsAABB m_bounds; + double m_scale{ 1.0 }; + VHACD::Vector3 m_dim{ 0 }; + size_t m_numVoxelsOnSurface{ 0 }; + size_t m_numVoxelsInsideSurface{ 0 }; + size_t m_numVoxelsOutsideSurface{ 0 }; + std::vector m_data; +private: + + void MarkOutsideSurface(const size_t i0, + const size_t j0, + const size_t k0, + const size_t i1, + const size_t j1, + const size_t k1); + void FillOutsideSurface(); + + void FillInsideSurface(); + + std::vector m_surfaceVoxels; + std::vector m_interiorVoxels; +}; + +bool PlaneBoxOverlap(const VHACD::Vect3& normal, + const VHACD::Vect3& vert, + const VHACD::Vect3& maxbox) +{ + int32_t q; + VHACD::Vect3 vmin; + VHACD::Vect3 vmax; + double v; + for (q = 0; q < 3; q++) + { + v = vert[q]; + if (normal[q] > double(0.0)) + { + vmin[q] = -maxbox[q] - v; + vmax[q] = maxbox[q] - v; + } + else + { + vmin[q] = maxbox[q] - v; + vmax[q] = -maxbox[q] - v; + } + } + if (normal.Dot(vmin) > double(0.0)) + return false; + if (normal.Dot(vmax) >= double(0.0)) + return true; + return false; +} + +bool AxisTest(double a, double b, double fa, double fb, + double v0, double v1, double v2, double v3, + double boxHalfSize1, double boxHalfSize2) +{ + double p0 = a * v0 + b * v1; + double p1 = a * v2 + b * v3; + + double min = std::min(p0, p1); + double max = std::max(p0, p1); + + double rad = fa * boxHalfSize1 + fb * boxHalfSize2; + if (min > rad || max < -rad) + { + return false; + } + + return true; +} + +bool TriBoxOverlap(const VHACD::Vect3& boxCenter, + const VHACD::Vect3& boxHalfSize, + const VHACD::Vect3& triVer0, + const VHACD::Vect3& triVer1, + const VHACD::Vect3& triVer2) +{ + /* use separating axis theorem to test overlap between triangle and box */ + /* need to test for overlap in these directions: */ + /* 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle */ + /* we do not even need to test these) */ + /* 2) normal of the triangle */ + /* 3) crossproduct(edge from tri, {x,y,z}-direction) */ + /* this gives 3x3=9 more tests */ + + VHACD::Vect3 v0 = triVer0 - boxCenter; + VHACD::Vect3 v1 = triVer1 - boxCenter; + VHACD::Vect3 v2 = triVer2 - boxCenter; + VHACD::Vect3 e0 = v1 - v0; + VHACD::Vect3 e1 = v2 - v1; + VHACD::Vect3 e2 = v0 - v2; + + /* This is the fastest branch on Sun */ + /* move everything so that the boxcenter is in (0,0,0) */ + + /* Bullet 3: */ + /* test the 9 tests first (this was faster) */ + double fex = fabs(e0[0]); + double fey = fabs(e0[1]); + double fez = fabs(e0[2]); + + /* + * These should use Get*() instead of subscript for consistency, but the function calls are long enough already + */ + if (!AxisTest( e0[2], -e0[1], fez, fey, v0[1], v0[2], v2[1], v2[2], boxHalfSize[1], boxHalfSize[2])) return 0; // X01 + if (!AxisTest(-e0[2], e0[0], fez, fex, v0[0], v0[2], v2[0], v2[2], boxHalfSize[0], boxHalfSize[2])) return 0; // Y02 + if (!AxisTest( e0[1], -e0[0], fey, fex, v1[0], v1[1], v2[0], v2[1], boxHalfSize[0], boxHalfSize[1])) return 0; // Z12 + + fex = fabs(e1[0]); + fey = fabs(e1[1]); + fez = fabs(e1[2]); + + if (!AxisTest( e1[2], -e1[1], fez, fey, v0[1], v0[2], v2[1], v2[2], boxHalfSize[1], boxHalfSize[2])) return 0; // X01 + if (!AxisTest(-e1[2], e1[0], fez, fex, v0[0], v0[2], v2[0], v2[2], boxHalfSize[0], boxHalfSize[2])) return 0; // Y02 + if (!AxisTest( e1[1], -e1[0], fey, fex, v0[0], v0[1], v1[0], v1[1], boxHalfSize[0], boxHalfSize[2])) return 0; // Z0 + + fex = fabs(e2[0]); + fey = fabs(e2[1]); + fez = fabs(e2[2]); + + if (!AxisTest( e2[2], -e2[1], fez, fey, v0[1], v0[2], v1[1], v1[2], boxHalfSize[1], boxHalfSize[2])) return 0; // X2 + if (!AxisTest(-e2[2], e2[0], fez, fex, v0[0], v0[2], v1[0], v1[2], boxHalfSize[0], boxHalfSize[2])) return 0; // Y1 + if (!AxisTest( e2[1], -e2[0], fey, fex, v1[0], v1[1], v2[0], v2[1], boxHalfSize[0], boxHalfSize[1])) return 0; // Z12 + + /* Bullet 1: */ + /* first test overlap in the {x,y,z}-directions */ + /* find min, max of the triangle each direction, and test for overlap in */ + /* that direction -- this is equivalent to testing a minimal AABB around */ + /* the triangle against the AABB */ + + /* test in 0-direction */ + double min = std::min({v0.GetX(), v1.GetX(), v2.GetX()}); + double max = std::max({v0.GetX(), v1.GetX(), v2.GetX()}); + if (min > boxHalfSize[0] || max < -boxHalfSize[0]) + return false; + + /* test in 1-direction */ + min = std::min({v0.GetY(), v1.GetY(), v2.GetY()}); + max = std::max({v0.GetY(), v1.GetY(), v2.GetY()}); + if (min > boxHalfSize[1] || max < -boxHalfSize[1]) + return false; + + /* test in getZ-direction */ + min = std::min({v0.GetZ(), v1.GetZ(), v2.GetZ()}); + max = std::max({v0.GetZ(), v1.GetZ(), v2.GetZ()}); + if (min > boxHalfSize[2] || max < -boxHalfSize[2]) + return false; + + /* Bullet 2: */ + /* test if the box intersects the plane of the triangle */ + /* compute plane equation of triangle: normal*x+d=0 */ + VHACD::Vect3 normal = e0.Cross(e1); + + if (!PlaneBoxOverlap(normal, v0, boxHalfSize)) + return false; + return true; /* box and triangle overlaps */ +} + +void Volume::Voxelize(const std::vector& points, + const std::vector& indices, + const size_t dimensions, + FillMode fillMode, + const AABBTree& aabbTree) +{ + double a = std::pow(dimensions, 0.33); + size_t dim = a * double(1.5); + dim = std::max(dim, size_t(32)); + + if (points.size() == 0) + { + return; + } + + m_bounds = BoundsAABB(points); + + VHACD::Vect3 d = m_bounds.GetSize(); + double r; + // Equal comparison is important here to avoid taking the last branch when d[0] == d[1] with d[2] being the smallest + // dimension. That would lead to dimensions in i and j to be a lot bigger than expected and make the amount of + // voxels in the volume totally unmanageable. + if (d[0] >= d[1] && d[0] >= d[2]) + { + r = d[0]; + m_dim[0] = uint32_t(dim); + m_dim[1] = uint32_t(2 + static_cast(dim * d[1] / d[0])); + m_dim[2] = uint32_t(2 + static_cast(dim * d[2] / d[0])); + } + else if (d[1] >= d[0] && d[1] >= d[2]) + { + r = d[1]; + m_dim[1] = uint32_t(dim); + m_dim[0] = uint32_t(2 + static_cast(dim * d[0] / d[1])); + m_dim[2] = uint32_t(2 + static_cast(dim * d[2] / d[1])); + } + else + { + r = d[2]; + m_dim[2] = uint32_t(dim); + m_dim[0] = uint32_t(2 + static_cast(dim * d[0] / d[2])); + m_dim[1] = uint32_t(2 + static_cast(dim * d[1] / d[2])); + } + + m_scale = r / (dim - 1); + double invScale = (dim - 1) / r; + + m_data = std::vector(m_dim[0] * m_dim[1] * m_dim[2], + VoxelValue::PRIMITIVE_UNDEFINED); + m_numVoxelsOnSurface = 0; + m_numVoxelsInsideSurface = 0; + m_numVoxelsOutsideSurface = 0; + + VHACD::Vect3 p[3]; + VHACD::Vect3 boxcenter; + VHACD::Vect3 pt; + const VHACD::Vect3 boxhalfsize(double(0.5)); + for (size_t t = 0; t < indices.size(); ++t) + { + size_t i0, j0, k0; + size_t i1, j1, k1; + VHACD::Vector3 tri = indices[t]; + for (int32_t c = 0; c < 3; ++c) + { + pt = points[tri[c]]; + + p[c] = (pt - m_bounds.GetMin()) * invScale; + + size_t i = static_cast(p[c][0] + double(0.5)); + size_t j = static_cast(p[c][1] + double(0.5)); + size_t k = static_cast(p[c][2] + double(0.5)); + + assert(i < m_dim[0] && i >= 0 && j < m_dim[1] && j >= 0 && k < m_dim[2] && k >= 0); + + if (c == 0) + { + i0 = i1 = i; + j0 = j1 = j; + k0 = k1 = k; + } + else + { + i0 = std::min(i0, i); + j0 = std::min(j0, j); + k0 = std::min(k0, k); + + i1 = std::max(i1, i); + j1 = std::max(j1, j); + k1 = std::max(k1, k); + } + } + if (i0 > 0) + --i0; + if (j0 > 0) + --j0; + if (k0 > 0) + --k0; + if (i1 < m_dim[0]) + ++i1; + if (j1 < m_dim[1]) + ++j1; + if (k1 < m_dim[2]) + ++k1; + for (size_t i_id = i0; i_id < i1; ++i_id) + { + boxcenter[0] = uint32_t(i_id); + for (size_t j_id = j0; j_id < j1; ++j_id) + { + boxcenter[1] = uint32_t(j_id); + for (size_t k_id = k0; k_id < k1; ++k_id) + { + boxcenter[2] = uint32_t(k_id); + bool res = TriBoxOverlap(boxcenter, + boxhalfsize, + p[0], + p[1], + p[2]); + VoxelValue& value = GetVoxel(i_id, + j_id, + k_id); + if ( res + && value == VoxelValue::PRIMITIVE_UNDEFINED) + { + value = VoxelValue::PRIMITIVE_ON_SURFACE; + ++m_numVoxelsOnSurface; + m_surfaceVoxels.emplace_back(uint32_t(i_id), + uint32_t(j_id), + uint32_t(k_id)); + } + } + } + } + } + + if (fillMode == FillMode::SURFACE_ONLY) + { + const size_t i0_local = m_dim[0]; + const size_t j0_local = m_dim[1]; + const size_t k0_local = m_dim[2]; + for (size_t i_id = 0; i_id < i0_local; ++i_id) + { + for (size_t j_id = 0; j_id < j0_local; ++j_id) + { + for (size_t k_id = 0; k_id < k0_local; ++k_id) + { + const VoxelValue& voxel = GetVoxel(i_id, + j_id, + k_id); + if (voxel != VoxelValue::PRIMITIVE_ON_SURFACE) + { + SetVoxel(i_id, + j_id, + k_id, + VoxelValue::PRIMITIVE_OUTSIDE_SURFACE); + } + } + } + } + } + else if (fillMode == FillMode::FLOOD_FILL) + { + /* + * Marking the outside edges of the voxel cube to be outside surfaces to walk + */ + MarkOutsideSurface(0, 0, 0, m_dim[0], m_dim[1], 1); + MarkOutsideSurface(0, 0, m_dim[2] - 1, m_dim[0], m_dim[1], m_dim[2]); + MarkOutsideSurface(0, 0, 0, m_dim[0], 1, m_dim[2]); + MarkOutsideSurface(0, m_dim[1] - 1, 0, m_dim[0], m_dim[1], m_dim[2]); + MarkOutsideSurface(0, 0, 0, 1, m_dim[1], m_dim[2]); + MarkOutsideSurface(m_dim[0] - 1, 0, 0, m_dim[0], m_dim[1], m_dim[2]); + FillOutsideSurface(); + FillInsideSurface(); + } + else if (fillMode == FillMode::RAYCAST_FILL) + { + RaycastFill(aabbTree); + } +} + +void Volume::RaycastFill(const AABBTree& aabbTree) +{ + const uint32_t i0 = m_dim[0]; + const uint32_t j0 = m_dim[1]; + const uint32_t k0 = m_dim[2]; + + size_t maxSize = i0 * j0 * k0; + + std::vector temp; + temp.reserve(maxSize); + uint32_t count{ 0 }; + m_numVoxelsInsideSurface = 0; + for (uint32_t i = 0; i < i0; ++i) + { + for (uint32_t j = 0; j < j0; ++j) + { + for (uint32_t k = 0; k < k0; ++k) + { + VoxelValue& voxel = GetVoxel(i, j, k); + if (voxel != VoxelValue::PRIMITIVE_ON_SURFACE) + { + VHACD::Vect3 start = VHACD::Vect3(i, j, k) * m_scale + m_bounds.GetMin(); + + uint32_t insideCount = 0; + uint32_t outsideCount = 0; + + VHACD::Vect3 directions[6] = { + VHACD::Vect3( 1, 0, 0), + VHACD::Vect3(-1, 0, 0), // this was 1, 0, 0 in the original code, but looks wrong + VHACD::Vect3( 0, 1, 0), + VHACD::Vect3( 0, -1, 0), + VHACD::Vect3( 0, 0, 1), + VHACD::Vect3( 0, 0, -1) + }; + + for (uint32_t r = 0; r < 6; r++) + { + aabbTree.TraceRay(start, + directions[r], + insideCount, + outsideCount); + // Early out if we hit the outside of the mesh + if (outsideCount) + { + break; + } + // Early out if we accumulated 3 inside hits + if (insideCount >= 3) + { + break; + } + } + + if (outsideCount == 0 && insideCount >= 3) + { + voxel = VoxelValue::PRIMITIVE_INSIDE_SURFACE; + temp.emplace_back(i, j, k); + count++; + m_numVoxelsInsideSurface++; + } + else + { + voxel = VoxelValue::PRIMITIVE_OUTSIDE_SURFACE; + } + } + } + } + } + + if (count) + { + m_interiorVoxels = std::move(temp); + } +} + +void Volume::SetVoxel(const size_t i, + const size_t j, + const size_t k, + VoxelValue value) +{ + assert(i < m_dim[0] || i >= 0); + assert(j < m_dim[1] || j >= 0); + assert(k < m_dim[2] || k >= 0); + + m_data[k + j * m_dim[2] + i * m_dim[1] * m_dim[2]] = value; +} + +VoxelValue& Volume::GetVoxel(const size_t i, + const size_t j, + const size_t k) +{ + assert(i < m_dim[0] || i >= 0); + assert(j < m_dim[1] || j >= 0); + assert(k < m_dim[2] || k >= 0); + return m_data[k + j * m_dim[2] + i * m_dim[1] * m_dim[2]]; +} + +const VoxelValue& Volume::GetVoxel(const size_t i, + const size_t j, + const size_t k) const +{ + assert(i < m_dim[0] || i >= 0); + assert(j < m_dim[1] || j >= 0); + assert(k < m_dim[2] || k >= 0); + return m_data[k + j * m_dim[2] + i * m_dim[1] * m_dim[2]]; +} + +const std::vector& Volume::GetSurfaceVoxels() const +{ + return m_surfaceVoxels; +} + +const std::vector& Volume::GetInteriorVoxels() const +{ + return m_interiorVoxels; +} + +double Volume::GetScale() const +{ + return m_scale; +} + +const VHACD::BoundsAABB& Volume::GetBounds() const +{ + return m_bounds; +} + +const VHACD::Vector3& Volume::GetDimensions() const +{ + return m_dim; +} + +void Volume::MarkOutsideSurface(const size_t i0, + const size_t j0, + const size_t k0, + const size_t i1, + const size_t j1, + const size_t k1) +{ + for (size_t i = i0; i < i1; ++i) + { + for (size_t j = j0; j < j1; ++j) + { + for (size_t k = k0; k < k1; ++k) + { + VoxelValue& v = GetVoxel(i, j, k); + if (v == VoxelValue::PRIMITIVE_UNDEFINED) + { + v = VoxelValue::PRIMITIVE_OUTSIDE_SURFACE_TOWALK; + } + } + } + } +} + +inline void WalkForward(int64_t start, + int64_t end, + VoxelValue* ptr, + int64_t stride, + int64_t maxDistance) +{ + for (int64_t i = start, count = 0; + count < maxDistance && i < end && *ptr == VoxelValue::PRIMITIVE_UNDEFINED; + ++i, ptr += stride, ++count) + { + *ptr = VoxelValue::PRIMITIVE_OUTSIDE_SURFACE_TOWALK; + } +} + +inline void WalkBackward(int64_t start, + int64_t end, + VoxelValue* ptr, + int64_t stride, + int64_t maxDistance) +{ + for (int64_t i = start, count = 0; + count < maxDistance && i >= end && *ptr == VoxelValue::PRIMITIVE_UNDEFINED; + --i, ptr -= stride, ++count) + { + *ptr = VoxelValue::PRIMITIVE_OUTSIDE_SURFACE_TOWALK; + } +} + +void Volume::FillOutsideSurface() +{ + size_t voxelsWalked = 0; + const int64_t i0 = m_dim[0]; + const int64_t j0 = m_dim[1]; + const int64_t k0 = m_dim[2]; + + // Avoid striding too far in each direction to stay in L1 cache as much as possible. + // The cache size required for the walk is roughly (4 * walkDistance * 64) since + // the k direction doesn't count as it's walking byte per byte directly in a cache lines. + // ~16k is required for a walk distance of 64 in each directions. + const size_t walkDistance = 64; + + // using the stride directly instead of calling GetVoxel for each iterations saves + // a lot of multiplications and pipeline stalls due to data dependencies on imul. + const size_t istride = &GetVoxel(1, 0, 0) - &GetVoxel(0, 0, 0); + const size_t jstride = &GetVoxel(0, 1, 0) - &GetVoxel(0, 0, 0); + const size_t kstride = &GetVoxel(0, 0, 1) - &GetVoxel(0, 0, 0); + + // It might seem counter intuitive to go over the whole voxel range multiple times + // but since we do the run in memory order, it leaves us with far fewer cache misses + // than a BFS algorithm and it has the additional benefit of not requiring us to + // store and manipulate a fifo for recursion that might become huge when the number + // of voxels is large. + // This will outperform the BFS algorithm by several orders of magnitude in practice. + do + { + voxelsWalked = 0; + for (int64_t i = 0; i < i0; ++i) + { + for (int64_t j = 0; j < j0; ++j) + { + for (int64_t k = 0; k < k0; ++k) + { + VoxelValue& voxel = GetVoxel(i, j, k); + if (voxel == VoxelValue::PRIMITIVE_OUTSIDE_SURFACE_TOWALK) + { + voxelsWalked++; + voxel = VoxelValue::PRIMITIVE_OUTSIDE_SURFACE; + + // walk in each direction to mark other voxel that should be walked. + // this will generate a 3d pattern that will help the overall + // algorithm converge faster while remaining cache friendly. + WalkForward(k + 1, k0, &voxel + kstride, kstride, walkDistance); + WalkBackward(k - 1, 0, &voxel - kstride, kstride, walkDistance); + + WalkForward(j + 1, j0, &voxel + jstride, jstride, walkDistance); + WalkBackward(j - 1, 0, &voxel - jstride, jstride, walkDistance); + + WalkForward(i + 1, i0, &voxel + istride, istride, walkDistance); + WalkBackward(i - 1, 0, &voxel - istride, istride, walkDistance); + } + } + } + } + + m_numVoxelsOutsideSurface += voxelsWalked; + } while (voxelsWalked != 0); +} + +void Volume::FillInsideSurface() +{ + const uint32_t i0 = uint32_t(m_dim[0]); + const uint32_t j0 = uint32_t(m_dim[1]); + const uint32_t k0 = uint32_t(m_dim[2]); + + size_t maxSize = i0 * j0 * k0; + + std::vector temp; + temp.reserve(maxSize); + uint32_t count{ 0 }; + + for (uint32_t i = 0; i < i0; ++i) + { + for (uint32_t j = 0; j < j0; ++j) + { + for (uint32_t k = 0; k < k0; ++k) + { + VoxelValue& v = GetVoxel(i, j, k); + if (v == VoxelValue::PRIMITIVE_UNDEFINED) + { + v = VoxelValue::PRIMITIVE_INSIDE_SURFACE; + temp.emplace_back(i, j, k); + count++; + ++m_numVoxelsInsideSurface; + } + } + } + } + + if ( count ) + { + m_interiorVoxels = std::move(temp); + } +} + +//****************************************************************************************** +// ShrinkWrap helper class +//****************************************************************************************** +// This is a code snippet which 'shrinkwraps' a convex hull +// to a source mesh. +// +// It is a somewhat complicated algorithm. It works as follows: +// +// * Step #1 : Compute the mean unit normal vector for each vertex in the convex hull +// * Step #2 : For each vertex in the conex hull we project is slightly outwards along the mean normal vector +// * Step #3 : We then raycast from this slightly extruded point back into the opposite direction of the mean normal vector +// resulting in a raycast from slightly beyond the vertex in the hull into the source mesh we are trying +// to 'shrink wrap' against +// * Step #4 : If the raycast fails we leave the original vertex alone +// * Step #5 : If the raycast hits a backface we leave the original vertex alone +// * Step #6 : If the raycast hits too far away (no more than a certain threshold distance) we live it alone +// * Step #7 : If the point we hit on the source mesh is not still within the convex hull, we reject it. +// * Step #8 : If all of the previous conditions are met, then we take the raycast hit location as the 'new position' +// * Step #9 : Once all points have been projected, if possible, we need to recompute the convex hull again based on these shrinkwrapped points +// * Step #10 : In theory that should work.. let's see... + +//*********************************************************************************************** +// QuickHull implementation +//*********************************************************************************************** + +////////////////////////////////////////////////////////////////////////// +// Quickhull base class holding the hull during construction +////////////////////////////////////////////////////////////////////////// +class QuickHull +{ +public: + uint32_t ComputeConvexHull(const std::vector& vertices, + uint32_t maxHullVertices); + + const std::vector& GetVertices() const; + const std::vector& GetIndices() const; + +private: + std::vector m_vertices; + std::vector m_indices; +}; + +uint32_t QuickHull::ComputeConvexHull(const std::vector& vertices, + uint32_t maxHullVertices) +{ + m_indices.clear(); + + VHACD::ConvexHull ch(vertices, + double(0.0001), + maxHullVertices); + + auto& vlist = ch.GetVertexPool(); + if ( !vlist.empty() ) + { + size_t vcount = vlist.size(); + m_vertices.resize(vcount); + std::copy(vlist.begin(), + vlist.end(), + m_vertices.begin()); + } + + for (std::list::const_iterator node = ch.GetList().begin(); node != ch.GetList().end(); ++node) + { + const VHACD::ConvexHullFace& face = *node; + m_indices.emplace_back(face.m_index[0], + face.m_index[1], + face.m_index[2]); + } + + return uint32_t(m_indices.size()); +} + +const std::vector& QuickHull::GetVertices() const +{ + return m_vertices; +} + +const std::vector& QuickHull::GetIndices() const +{ + return m_indices; +} + +//****************************************************************************************** +// Implementation of the ShrinkWrap function +//****************************************************************************************** + +void ShrinkWrap(SimpleMesh& sourceConvexHull, + const AABBTree& aabbTree, + uint32_t maxHullVertexCount, + double distanceThreshold, + bool doShrinkWrap) +{ + std::vector verts; // New verts for the new convex hull + verts.reserve(sourceConvexHull.m_vertices.size()); + // Examine each vertex and see if it is within the voxel distance. + // If it is, then replace the point with the shrinkwrapped / projected point + for (uint32_t j = 0; j < sourceConvexHull.m_vertices.size(); j++) + { + VHACD::Vertex& p = sourceConvexHull.m_vertices[j]; + if (doShrinkWrap) + { + VHACD::Vect3 closest; + if (aabbTree.GetClosestPointWithinDistance(p, distanceThreshold, closest)) + { + p = closest; + } + } + verts.emplace_back(p); + } + // Final step is to recompute the convex hull + VHACD::QuickHull qh; + uint32_t tcount = qh.ComputeConvexHull(verts, + maxHullVertexCount); + if (tcount) + { + sourceConvexHull.m_vertices = qh.GetVertices(); + sourceConvexHull.m_indices = qh.GetIndices(); + } +} + +//******************************************************************************************************************** + +#if !VHACD_DISABLE_THREADING + +//******************************************************************************************************************** +// Definition of the ThreadPool +//******************************************************************************************************************** + +class ThreadPool { + public: + ThreadPool(); + ThreadPool(int worker); + ~ThreadPool(); + template + auto enqueue(F&& f, Args&& ... args) +#ifndef __cpp_lib_is_invocable + -> std::future< typename std::result_of< F( Args... ) >::type>; +#else + -> std::future< typename std::invoke_result_t>; +#endif + private: + std::vector workers; + std::deque> tasks; + std::mutex task_mutex; + std::condition_variable cv; + bool closed; + int count; +}; + +ThreadPool::ThreadPool() + : ThreadPool(1) +{ +} + +ThreadPool::ThreadPool(int worker) + : closed(false) + , count(0) +{ + workers.reserve(worker); + for(int i=0; i lock(this->task_mutex); + while(true) + { + while (this->tasks.empty()) + { + if (this->closed) + { + return; + } + this->cv.wait(lock); + } + auto task = this->tasks.front(); + this->tasks.pop_front(); + lock.unlock(); + task(); + lock.lock(); + } + } + ); + } +} + +template +auto ThreadPool::enqueue(F&& f, Args&& ... args) +#ifndef __cpp_lib_is_invocable + -> std::future< typename std::result_of< F( Args... ) >::type> +#else + -> std::future< typename std::invoke_result_t> +#endif +{ + +#ifndef __cpp_lib_is_invocable + using return_type = typename std::result_of< F( Args... ) >::type; +#else + using return_type = typename std::invoke_result_t< F, Args... >; +#endif + auto task = std::make_shared > ( + std::bind(std::forward(f), std::forward(args)...) + ); + auto result = task->get_future(); + + { + std::unique_lock lock(task_mutex); + if (!closed) + { + tasks.emplace_back([task] + { + (*task)(); + }); + cv.notify_one(); + } + } + + return result; +} + +ThreadPool::~ThreadPool() { + { + std::unique_lock lock(task_mutex); + closed = true; + } + cv.notify_all(); + for (auto && worker : workers) + { + worker.join(); + } +} +#endif + +enum class Stages +{ + COMPUTE_BOUNDS_OF_INPUT_MESH, + REINDEXING_INPUT_MESH, + CREATE_RAYCAST_MESH, + VOXELIZING_INPUT_MESH, + BUILD_INITIAL_CONVEX_HULL, + PERFORMING_DECOMPOSITION, + INITIALIZING_CONVEX_HULLS_FOR_MERGING, + COMPUTING_COST_MATRIX, + MERGING_CONVEX_HULLS, + FINALIZING_RESULTS, + NUM_STAGES +}; + +class VHACDCallbacks +{ +public: + virtual void ProgressUpdate(Stages stage, + double stageProgress, + const char *operation) = 0; + virtual bool IsCanceled() const = 0; + + virtual ~VHACDCallbacks() = default; +}; + +enum class SplitAxis +{ + X_AXIS_NEGATIVE, + X_AXIS_POSITIVE, + Y_AXIS_NEGATIVE, + Y_AXIS_POSITIVE, + Z_AXIS_NEGATIVE, + Z_AXIS_POSITIVE, +}; + +// This class represents a collection of voxels, the convex hull +// which surrounds them, and a triangle mesh representation of those voxels +class VoxelHull +{ +public: + + // This method constructs a new VoxelHull based on a plane split of the parent + // convex hull + VoxelHull(const VoxelHull& parent, + SplitAxis axis, + uint32_t splitLoc); + + // Here we construct the initial convex hull around the + // entire voxel set + VoxelHull(Volume& voxels, + const IVHACD::Parameters ¶ms, + VHACDCallbacks *callbacks); + + ~VoxelHull() = default; + + // Helper method to refresh the min/max voxel bounding region + void MinMaxVoxelRegion(const Voxel &v); + + void BuildRaycastMesh(); + + // We now compute the convex hull relative to a triangle mesh generated + // from the voxels + void ComputeConvexHull(); + + // Returns true if this convex hull should be considered done + bool IsComplete(); + + + // Convert a voxel position into it's correct double precision location + VHACD::Vect3 GetPoint(const int32_t x, + const int32_t y, + const int32_t z, + const double scale, + const VHACD::Vect3& bmin) const; + + // Sees if we have already got an index for this voxel position. + // If the voxel position has already been indexed, we just return + // that index value. + // If not, then we convert it into the floating point position and + // add it to the index map + uint32_t GetVertexIndex(const VHACD::Vector3& p); + + // This method will convert the voxels into an actual indexed triangle mesh of boxes + // This serves two purposes. + // The primary purpose is so that when we compute a convex hull it considered all of the points + // for each voxel, not just the center point. If you don't do this, then the hulls don't fit the + // mesh accurately enough. + // The second reason we convert it into a triangle mesh is so that we can do raycasting against it + // to search for the best splitting plane fairly quickly. That algorithm will be discussed in the + // method which computes the best splitting plane. + void BuildVoxelMesh(); + + // Convert a single voxel position into an actual 3d box mesh comprised + // of 12 triangles + void AddVoxelBox(const Voxel &v); + + // Add the triangle represented by these 3 indices into the 'box' set of vertices + // to the output mesh + void AddTri(const std::array, 8>& box, + uint32_t i1, + uint32_t i2, + uint32_t i3); + + // Here we convert from voxel space to a 3d position, index it, and add + // the triangle positions and indices for the output mesh + void AddTriangle(const VHACD::Vector3& p1, + const VHACD::Vector3& p2, + const VHACD::Vector3& p3); + + // When computing the split plane, we start by simply + // taking the midpoint of the longest side. However, + // we can also search the surface and look for the greatest + // spot of concavity and use that as the split location. + // This will make the convex decomposition more efficient + // as it will tend to cut across the greatest point of + // concavity on the surface. + SplitAxis ComputeSplitPlane(uint32_t& location); + + VHACD::Vect3 GetPosition(const VHACD::Vector3& ip) const; + + double Raycast(const VHACD::Vector3& p1, + const VHACD::Vector3& p2) const; + + bool FindConcavity(uint32_t idx, + uint32_t& splitLoc); + + // Finding the greatest area of concavity.. + bool FindConcavityX(uint32_t& splitLoc); + + // Finding the greatest area of concavity.. + bool FindConcavityY(uint32_t& splitLoc); + + // Finding the greatest area of concavity.. + bool FindConcavityZ(uint32_t& splitLoc); + + // This operation is performed in a background thread. + // It splits the voxels by a plane + void PerformPlaneSplit(); + + // Used only for debugging. Saves the voxelized mesh to disk + // Optionally saves the original source mesh as well for comparison + void SaveVoxelMesh(const SimpleMesh& inputMesh, + bool saveVoxelMesh, + bool saveSourceMesh); + + void SaveOBJ(const char* fname, + const VoxelHull* h); + + void SaveOBJ(const char* fname); + +private: + void WriteOBJ(FILE* fph, + const std::vector& vertices, + const std::vector& indices, + uint32_t baseIndex); +public: + + SplitAxis m_axis{ SplitAxis::X_AXIS_NEGATIVE }; + Volume* m_voxels{ nullptr }; // The voxelized data set + double m_voxelScale{ 0 }; // Size of a single voxel + double m_voxelScaleHalf{ 0 }; // 1/2 of the size of a single voxel + VHACD::BoundsAABB m_voxelBounds; + VHACD::Vect3 m_voxelAdjust; // Minimum coordinates of the voxel space, with adjustment + uint32_t m_depth{ 0 }; // How deep in the recursion of the binary tree this hull is + uint32_t m_index{ 0 }; // Each convex hull is given a unique id to distinguish it from the others + double m_volumeError{ 0 }; // The percentage error from the convex hull volume vs. the voxel volume + double m_voxelVolume{ 0 }; // The volume of the voxels + double m_hullVolume{ 0 }; // The volume of the enclosing convex hull + + std::unique_ptr m_convexHull{ nullptr }; // The convex hull which encloses this set of voxels. + std::vector m_surfaceVoxels; // The voxels which are on the surface of the source mesh. + std::vector m_newSurfaceVoxels; // Voxels which are on the surface as a result of a plane split + std::vector m_interiorVoxels; // Voxels which are part of the interior of the hull + + std::unique_ptr m_hullA{ nullptr }; // hull resulting from one side of the plane split + std::unique_ptr m_hullB{ nullptr }; // hull resulting from the other side of the plane split + + // Defines the coordinates this convex hull comprises within the voxel volume + // of the entire source + VHACD::Vector3 m_1{ 0 }; + VHACD::Vector3 m_2{ 0 }; + AABBTree m_AABBTree; + std::unordered_map m_voxelIndexMap; // Maps from a voxel coordinate space into a vertex index space + std::vector m_vertices; + std::vector m_indices; + static uint32_t m_voxelHullCount; + IVHACD::Parameters m_params; + VHACDCallbacks* m_callbacks{ nullptr }; +}; + +uint32_t VoxelHull::m_voxelHullCount = 0; + +VoxelHull::VoxelHull(const VoxelHull& parent, + SplitAxis axis, + uint32_t splitLoc) + : m_axis(axis) + , m_voxels(parent.m_voxels) + , m_voxelScale(m_voxels->GetScale()) + , m_voxelScaleHalf(m_voxelScale * double(0.5)) + , m_voxelBounds(m_voxels->GetBounds()) + , m_voxelAdjust(m_voxelBounds.GetMin() - m_voxelScaleHalf) + , m_depth(parent.m_depth + 1) + , m_index(++m_voxelHullCount) + , m_1(parent.m_1) + , m_2(parent.m_2) + , m_params(parent.m_params) +{ + // Default copy the voxel region from the parent, but values will + // be adjusted next based on the split axis and location + switch ( m_axis ) + { + case SplitAxis::X_AXIS_NEGATIVE: + m_2.GetX() = splitLoc; + break; + case SplitAxis::X_AXIS_POSITIVE: + m_1.GetX() = splitLoc + 1; + break; + case SplitAxis::Y_AXIS_NEGATIVE: + m_2.GetY() = splitLoc; + break; + case SplitAxis::Y_AXIS_POSITIVE: + m_1.GetY() = splitLoc + 1; + break; + case SplitAxis::Z_AXIS_NEGATIVE: + m_2.GetZ() = splitLoc; + break; + case SplitAxis::Z_AXIS_POSITIVE: + m_1.GetZ() = splitLoc + 1; + break; + } + + // First, we copy all of the interior voxels from our parent + // which intersect our region + for (auto& i : parent.m_interiorVoxels) + { + VHACD::Vector3 v = i.GetVoxel(); + if (v.CWiseAllGE(m_1) && v.CWiseAllLE(m_2)) + { + bool newSurface = false; + switch ( m_axis ) + { + case SplitAxis::X_AXIS_NEGATIVE: + if ( v.GetX() == splitLoc ) + { + newSurface = true; + } + break; + case SplitAxis::X_AXIS_POSITIVE: + if ( v.GetX() == m_1.GetX() ) + { + newSurface = true; + } + break; + case SplitAxis::Y_AXIS_NEGATIVE: + if ( v.GetY() == splitLoc ) + { + newSurface = true; + } + break; + case SplitAxis::Y_AXIS_POSITIVE: + if ( v.GetY() == m_1.GetY() ) + { + newSurface = true; + } + break; + case SplitAxis::Z_AXIS_NEGATIVE: + if ( v.GetZ() == splitLoc ) + { + newSurface = true; + } + break; + case SplitAxis::Z_AXIS_POSITIVE: + if ( v.GetZ() == m_1.GetZ() ) + { + newSurface = true; + } + break; + } + // If his interior voxels lie directly on the split plane then + // these become new surface voxels for our patch + if ( newSurface ) + { + m_newSurfaceVoxels.push_back(i); + } + else + { + m_interiorVoxels.push_back(i); + } + } + } + // Next we copy all of the surface voxels which intersect our region + for (auto& i : parent.m_surfaceVoxels) + { + VHACD::Vector3 v = i.GetVoxel(); + if (v.CWiseAllGE(m_1) && v.CWiseAllLE(m_2)) + { + m_surfaceVoxels.push_back(i); + } + } + // Our parent's new surface voxels become our new surface voxels so long as they intersect our region + for (auto& i : parent.m_newSurfaceVoxels) + { + VHACD::Vector3 v = i.GetVoxel(); + if (v.CWiseAllGE(m_1) && v.CWiseAllLE(m_2)) + { + m_newSurfaceVoxels.push_back(i); + } + } + + // Recompute the min-max bounding box which would be different after the split occurs + m_1 = VHACD::Vector3(0x7FFFFFFF); + m_2 = VHACD::Vector3(0); + for (auto& i : m_surfaceVoxels) + { + MinMaxVoxelRegion(i); + } + for (auto& i : m_newSurfaceVoxels) + { + MinMaxVoxelRegion(i); + } + for (auto& i : m_interiorVoxels) + { + MinMaxVoxelRegion(i); + } + + BuildVoxelMesh(); + BuildRaycastMesh(); // build a raycast mesh of the voxel mesh + ComputeConvexHull(); +} + +VoxelHull::VoxelHull(Volume& voxels, + const IVHACD::Parameters& params, + VHACDCallbacks* callbacks) + : m_voxels(&voxels) + , m_voxelScale(m_voxels->GetScale()) + , m_voxelScaleHalf(m_voxelScale * double(0.5)) + , m_voxelBounds(m_voxels->GetBounds()) + , m_voxelAdjust(m_voxelBounds.GetMin() - m_voxelScaleHalf) + , m_index(++m_voxelHullCount) + // Here we get a copy of all voxels which lie on the surface mesh + , m_surfaceVoxels(m_voxels->GetSurfaceVoxels()) + // Now we get a copy of all voxels which are considered part of the 'interior' of the source mesh + , m_interiorVoxels(m_voxels->GetInteriorVoxels()) + , m_2(m_voxels->GetDimensions() - 1) + , m_params(params) + , m_callbacks(callbacks) +{ + BuildVoxelMesh(); + BuildRaycastMesh(); // build a raycast mesh of the voxel mesh + ComputeConvexHull(); +} + +void VoxelHull::MinMaxVoxelRegion(const Voxel& v) +{ + VHACD::Vector3 x = v.GetVoxel(); + m_1 = m_1.CWiseMin(x); + m_2 = m_2.CWiseMax(x); +} + +void VoxelHull::BuildRaycastMesh() +{ + // Create a raycast mesh representation of the voxelized surface mesh + if ( !m_indices.empty() ) + { + m_AABBTree = AABBTree(m_vertices, + m_indices); + } +} + +void VoxelHull::ComputeConvexHull() +{ + if ( !m_vertices.empty() ) + { + // we compute the convex hull as follows... + VHACD::QuickHull qh; + uint32_t tcount = qh.ComputeConvexHull(m_vertices, + uint32_t(m_vertices.size())); + if ( tcount ) + { + m_convexHull = std::unique_ptr(new IVHACD::ConvexHull); + + m_convexHull->m_points = qh.GetVertices(); + m_convexHull->m_triangles = qh.GetIndices(); + + VHACD::ComputeCentroid(m_convexHull->m_points, + m_convexHull->m_triangles, + m_convexHull->m_center); + m_convexHull->m_volume = VHACD::ComputeMeshVolume(m_convexHull->m_points, + m_convexHull->m_triangles); + } + } + if ( m_convexHull ) + { + m_hullVolume = m_convexHull->m_volume; + } + // This is the volume of a single voxel + double singleVoxelVolume = m_voxelScale * m_voxelScale * m_voxelScale; + size_t voxelCount = m_interiorVoxels.size() + m_newSurfaceVoxels.size() + m_surfaceVoxels.size(); + m_voxelVolume = singleVoxelVolume * double(voxelCount); + double diff = fabs(m_hullVolume - m_voxelVolume); + m_volumeError = (diff * 100) / m_voxelVolume; +} + +bool VoxelHull::IsComplete() +{ + bool ret = false; + if ( m_convexHull == nullptr ) + { + ret = true; + } + else if ( m_volumeError < m_params.m_minimumVolumePercentErrorAllowed ) + { + ret = true; + } + else if ( m_depth > m_params.m_maxRecursionDepth ) + { + ret = true; + } + else + { + // We compute the voxel width on all 3 axes and see if they are below the min threshold size + VHACD::Vector3 d = m_2 - m_1; + if ( d.GetX() <= m_params.m_minEdgeLength && + d.GetY() <= m_params.m_minEdgeLength && + d.GetZ() <= m_params.m_minEdgeLength ) + { + ret = true; + } + } + return ret; +} + +VHACD::Vect3 VoxelHull::GetPoint(const int32_t x, + const int32_t y, + const int32_t z, + const double scale, + const VHACD::Vect3& bmin) const +{ + return VHACD::Vect3(x * scale + bmin.GetX(), + y * scale + bmin.GetY(), + z * scale + bmin.GetZ()); +} + +uint32_t VoxelHull::GetVertexIndex(const VHACD::Vector3& p) +{ + uint32_t ret = 0; + uint32_t address = (p.GetX() << 20) | (p.GetY() << 10) | p.GetZ(); + auto found = m_voxelIndexMap.find(address); + if ( found != m_voxelIndexMap.end() ) + { + ret = found->second; + } + else + { + VHACD::Vect3 vertex = GetPoint(p.GetX(), + p.GetY(), + p.GetZ(), + m_voxelScale, + m_voxelAdjust); + ret = uint32_t(m_voxelIndexMap.size()); + m_voxelIndexMap[address] = ret; + m_vertices.emplace_back(vertex); + } + return ret; +} + +void VoxelHull::BuildVoxelMesh() +{ + // When we build the triangle mesh we do *not* need the interior voxels, only the ones + // which lie upon the logical surface of the mesh. + // Each time we perform a plane split, voxels which are along the splitting plane become + // 'new surface voxels'. + + for (auto& i : m_surfaceVoxels) + { + AddVoxelBox(i); + } + for (auto& i : m_newSurfaceVoxels) + { + AddVoxelBox(i); + } +} + +void VoxelHull::AddVoxelBox(const Voxel &v) +{ + // The voxel position of the upper left corner of the box + VHACD::Vector3 bmin(v.GetX(), + v.GetY(), + v.GetZ()); + // The voxel position of the lower right corner of the box + VHACD::Vector3 bmax(bmin.GetX() + 1, + bmin.GetY() + 1, + bmin.GetZ() + 1); + + // Build the set of 8 voxel positions representing + // the coordinates of the box + std::array, 8> box{{ + { bmin.GetX(), bmin.GetY(), bmin.GetZ() }, + { bmax.GetX(), bmin.GetY(), bmin.GetZ() }, + { bmax.GetX(), bmax.GetY(), bmin.GetZ() }, + { bmin.GetX(), bmax.GetY(), bmin.GetZ() }, + { bmin.GetX(), bmin.GetY(), bmax.GetZ() }, + { bmax.GetX(), bmin.GetY(), bmax.GetZ() }, + { bmax.GetX(), bmax.GetY(), bmax.GetZ() }, + { bmin.GetX(), bmax.GetY(), bmax.GetZ() } + }}; + + // Now add the 12 triangles comprising the 3d box + AddTri(box, 2, 1, 0); + AddTri(box, 3, 2, 0); + + AddTri(box, 7, 2, 3); + AddTri(box, 7, 6, 2); + + AddTri(box, 5, 1, 2); + AddTri(box, 5, 2, 6); + + AddTri(box, 5, 4, 1); + AddTri(box, 4, 0, 1); + + AddTri(box, 4, 6, 7); + AddTri(box, 4, 5, 6); + + AddTri(box, 4, 7, 0); + AddTri(box, 7, 3, 0); +} + +void VoxelHull::AddTri(const std::array, 8>& box, + uint32_t i1, + uint32_t i2, + uint32_t i3) +{ + AddTriangle(box[i1], box[i2], box[i3]); +} + +void VoxelHull::AddTriangle(const VHACD::Vector3& p1, + const VHACD::Vector3& p2, + const VHACD::Vector3& p3) +{ + uint32_t i1 = GetVertexIndex(p1); + uint32_t i2 = GetVertexIndex(p2); + uint32_t i3 = GetVertexIndex(p3); + + m_indices.emplace_back(i1, i2, i3); +} + +SplitAxis VoxelHull::ComputeSplitPlane(uint32_t& location) +{ + SplitAxis ret = SplitAxis::X_AXIS_NEGATIVE; + + VHACD::Vector3 d = m_2 - m_1; + + if ( d.GetX() >= d.GetY() && d.GetX() >= d.GetZ() ) + { + ret = SplitAxis::X_AXIS_NEGATIVE; + location = (m_2.GetX() + 1 + m_1.GetX()) / 2; + uint32_t edgeLoc; + if ( m_params.m_findBestPlane && FindConcavityX(edgeLoc) ) + { + location = edgeLoc; + } + } + else if ( d.GetY() >= d.GetX() && d.GetY() >= d.GetZ() ) + { + ret = SplitAxis::Y_AXIS_NEGATIVE; + location = (m_2.GetY() + 1 + m_1.GetY()) / 2; + uint32_t edgeLoc; + if ( m_params.m_findBestPlane && FindConcavityY(edgeLoc) ) + { + location = edgeLoc; + } + } + else + { + ret = SplitAxis::Z_AXIS_NEGATIVE; + location = (m_2.GetZ() + 1 + m_1.GetZ()) / 2; + uint32_t edgeLoc; + if ( m_params.m_findBestPlane && FindConcavityZ(edgeLoc) ) + { + location = edgeLoc; + } + } + + return ret; +} + +VHACD::Vect3 VoxelHull::GetPosition(const VHACD::Vector3& ip) const +{ + return GetPoint(ip.GetX(), + ip.GetY(), + ip.GetZ(), + m_voxelScale, + m_voxelAdjust); +} + +double VoxelHull::Raycast(const VHACD::Vector3& p1, + const VHACD::Vector3& p2) const +{ + double ret; + VHACD::Vect3 from = GetPosition(p1); + VHACD::Vect3 to = GetPosition(p2); + + double outT; + double faceSign; + VHACD::Vect3 hitLocation; + if (m_AABBTree.TraceRay(from, to, outT, faceSign, hitLocation)) + { + ret = (from - hitLocation).GetNorm(); + } + else + { + ret = 0; // if it doesn't hit anything, just assign it to zero. + } + + return ret; +} + +bool VoxelHull::FindConcavity(uint32_t idx, + uint32_t& splitLoc) +{ + bool ret = false; + + int32_t d = (m_2[idx] - m_1[idx]) + 1; // The length of the getX axis in voxel space + + uint32_t idx1; + uint32_t idx2; + uint32_t idx3; + switch (idx) + { + case 0: // X + idx1 = 0; + idx2 = 1; + idx3 = 2; + break; + case 1: // Y + idx1 = 1; + idx2 = 0; + idx3 = 2; + break; + case 2: + idx1 = 2; + idx2 = 1; + idx3 = 0; + break; + default: + /* + * To silence uninitialized variable warnings + */ + idx1 = 0; + idx2 = 0; + idx3 = 0; + assert(0 && "findConcavity::idx must be 0, 1, or 2"); + break; + } + + // We will compute the edge error on the XY plane and the XZ plane + // searching for the greatest location of concavity + std::vector edgeError1 = std::vector(d); + std::vector edgeError2 = std::vector(d); + + // Counter of number of voxel samples on the XY plane we have accumulated + uint32_t index1 = 0; + + // Compute Edge Error on the XY plane + for (uint32_t i0 = m_1[idx1]; i0 <= m_2[idx1]; i0++) + { + double errorTotal = 0; + // We now perform a raycast from the sides inward on the XY plane to + // determine the total error (distance of the surface from the sides) + // along this getX position. + for (uint32_t i1 = m_1[idx2]; i1 <= m_2[idx2]; i1++) + { + VHACD::Vector3 p1; + VHACD::Vector3 p2; + switch (idx) + { + case 0: + { + p1 = VHACD::Vector3(i0, i1, m_1.GetZ() - 2); + p2 = VHACD::Vector3(i0, i1, m_2.GetZ() + 2); + break; + } + case 1: + { + p1 = VHACD::Vector3(i1, i0, m_1.GetZ() - 2); + p2 = VHACD::Vector3(i1, i0, m_2.GetZ() + 2); + break; + } + case 2: + { + p1 = VHACD::Vector3(m_1.GetX() - 2, i1, i0); + p2 = VHACD::Vector3(m_2.GetX() + 2, i1, i0); + break; + } + } + + double e1 = Raycast(p1, p2); + double e2 = Raycast(p2, p1); + + errorTotal = errorTotal + e1 + e2; + } + // The total amount of edge error along this voxel location + edgeError1[index1] = errorTotal; + index1++; + } + + // Compute edge error along the XZ plane + uint32_t index2 = 0; + + for (uint32_t i0 = m_1[idx1]; i0 <= m_2[idx1]; i0++) + { + double errorTotal = 0; + + for (uint32_t i1 = m_1[idx3]; i1 <= m_2[idx3]; i1++) + { + VHACD::Vector3 p1; + VHACD::Vector3 p2; + switch (idx) + { + case 0: + { + p1 = VHACD::Vector3(i0, m_1.GetY() - 2, i1); + p2 = VHACD::Vector3(i0, m_2.GetY() + 2, i1); + break; + } + case 1: + { + p1 = VHACD::Vector3(m_1.GetX() - 2, i0, i1); + p2 = VHACD::Vector3(m_2.GetX() + 2, i0, i1); + break; + } + case 2: + { + p1 = VHACD::Vector3(i1, m_1.GetY() - 2, i0); + p2 = VHACD::Vector3(i1, m_2.GetY() + 2, i0); + break; + } + } + + double e1 = Raycast(p1, p2); // raycast from one side to the interior + double e2 = Raycast(p2, p1); // raycast from the other side to the interior + + errorTotal = errorTotal + e1 + e2; + } + edgeError2[index2] = errorTotal; + index2++; + } + + + // we now compute the first derivative to find the greatest spot of concavity on the XY plane + double maxDiff = 0; + uint32_t maxC = 0; + for (uint32_t x = 1; x < index1; x++) + { + if ( edgeError1[x] > 0 && edgeError1[x - 1] > 0 ) + { + double diff = abs(edgeError1[x] - edgeError1[x - 1]); + if ( diff > maxDiff ) + { + maxDiff = diff; + maxC = x-1; + } + } + } + + // Now see if there is a greater concavity on the XZ plane + for (uint32_t x = 1; x < index2; x++) + { + if ( edgeError2[x] > 0 && edgeError2[x - 1] > 0 ) + { + double diff = abs(edgeError2[x] - edgeError2[x - 1]); + if ( diff > maxDiff ) + { + maxDiff = diff; + maxC = x - 1; + } + } + } + + splitLoc = maxC + m_1[idx1]; + + // we do not allow an edge split if it is too close to the ends + if ( splitLoc > (m_1[idx1] + 4) + && splitLoc < (m_2[idx1] - 4) ) + { + ret = true; + } + + return ret; +} + +// Finding the greatest area of concavity.. +bool VoxelHull::FindConcavityX(uint32_t& splitLoc) +{ + return FindConcavity(0, splitLoc); +} + +// Finding the greatest area of concavity.. +bool VoxelHull::FindConcavityY(uint32_t& splitLoc) +{ + return FindConcavity(1, splitLoc); +} + +// Finding the greatest area of concavity.. +bool VoxelHull::FindConcavityZ(uint32_t &splitLoc) +{ + return FindConcavity(2, splitLoc); +} + +void VoxelHull::PerformPlaneSplit() +{ + if ( IsComplete() ) + { + } + else + { + uint32_t splitLoc; + SplitAxis axis = ComputeSplitPlane(splitLoc); + switch ( axis ) + { + case SplitAxis::X_AXIS_NEGATIVE: + case SplitAxis::X_AXIS_POSITIVE: + // Split on the getX axis at this split location + m_hullA = std::unique_ptr(new VoxelHull(*this, SplitAxis::X_AXIS_NEGATIVE, splitLoc)); + m_hullB = std::unique_ptr(new VoxelHull(*this, SplitAxis::X_AXIS_POSITIVE, splitLoc)); + break; + case SplitAxis::Y_AXIS_NEGATIVE: + case SplitAxis::Y_AXIS_POSITIVE: + // Split on the 1 axis at this split location + m_hullA = std::unique_ptr(new VoxelHull(*this, SplitAxis::Y_AXIS_NEGATIVE, splitLoc)); + m_hullB = std::unique_ptr(new VoxelHull(*this, SplitAxis::Y_AXIS_POSITIVE, splitLoc)); + break; + case SplitAxis::Z_AXIS_NEGATIVE: + case SplitAxis::Z_AXIS_POSITIVE: + // Split on the getZ axis at this split location + m_hullA = std::unique_ptr(new VoxelHull(*this, SplitAxis::Z_AXIS_NEGATIVE, splitLoc)); + m_hullB = std::unique_ptr(new VoxelHull(*this, SplitAxis::Z_AXIS_POSITIVE, splitLoc)); + break; + } + } +} + +void VoxelHull::SaveVoxelMesh(const SimpleMesh &inputMesh, + bool saveVoxelMesh, + bool saveSourceMesh) +{ + char scratch[512]; + snprintf(scratch, + sizeof(scratch), + "voxel-mesh-%03d.obj", + m_index); + FILE *fph = fopen(scratch, + "wb"); + if ( fph ) + { + uint32_t baseIndex = 1; + if ( saveVoxelMesh ) + { + WriteOBJ(fph, + m_vertices, + m_indices, + baseIndex); + baseIndex += uint32_t(m_vertices.size()); + } + if ( saveSourceMesh ) + { + WriteOBJ(fph, + inputMesh.m_vertices, + inputMesh.m_indices, + baseIndex); + } + fclose(fph); + } +} + +void VoxelHull::SaveOBJ(const char* fname, + const VoxelHull* h) +{ + FILE *fph = fopen(fname,"wb"); + if ( fph ) + { + uint32_t baseIndex = 1; + WriteOBJ(fph, + m_vertices, + m_indices, + baseIndex); + + baseIndex += uint32_t(m_vertices.size()); + + WriteOBJ(fph, + h->m_vertices, + h->m_indices, + baseIndex); + fclose(fph); + } +} + +void VoxelHull::SaveOBJ(const char *fname) +{ + FILE *fph = fopen(fname, "wb"); + if ( fph ) + { + printf("Saving '%s' with %d vertices and %d triangles\n", + fname, + uint32_t(m_vertices.size()), + uint32_t(m_indices.size())); + WriteOBJ(fph, + m_vertices, + m_indices, + 1); + fclose(fph); + } +} + +void VoxelHull::WriteOBJ(FILE* fph, + const std::vector& vertices, + const std::vector& indices, + uint32_t baseIndex) +{ + if (!fph) + { + return; + } + + for (size_t i = 0; i < vertices.size(); ++i) + { + const VHACD::Vertex& v = vertices[i]; + fprintf(fph, "v %0.9f %0.9f %0.9f\n", + v.mX, + v.mY, + v.mZ); + } + + for (size_t i = 0; i < indices.size(); ++i) + { + const VHACD::Triangle& t = indices[i]; + fprintf(fph, "f %d %d %d\n", + t.mI0 + baseIndex, + t.mI1 + baseIndex, + t.mI2 + baseIndex); + } +} + +class VHACDImpl; + +// This class represents a single task to compute the volume error +// of two convex hulls combined +class CostTask +{ +public: + VHACDImpl* m_this{ nullptr }; + IVHACD::ConvexHull* m_hullA{ nullptr }; + IVHACD::ConvexHull* m_hullB{ nullptr }; + double m_concavity{ 0 }; // concavity of the two combined + std::future m_future; +}; + +class HullPair +{ +public: + HullPair() = default; + HullPair(uint32_t hullA, + uint32_t hullB, + double concavity); + + bool operator<(const HullPair &h) const; + + uint32_t m_hullA{ 0 }; + uint32_t m_hullB{ 0 }; + double m_concavity{ 0 }; +}; + +HullPair::HullPair(uint32_t hullA, + uint32_t hullB, + double concavity) + : m_hullA(hullA) + , m_hullB(hullB) + , m_concavity(concavity) +{ +} + +bool HullPair::operator<(const HullPair &h) const +{ + return m_concavity > h.m_concavity ? true : false; +} + +// void jobCallback(void* userPtr); + +class VHACDImpl : public IVHACD, public VHACDCallbacks +{ + // Don't consider more than 100,000 convex hulls. + static constexpr uint32_t MaxConvexHullFragments{ 100000 }; +public: + VHACDImpl() = default; + + /* + * Overrides VHACD::IVHACD + */ + ~VHACDImpl() override + { + Clean(); + } + + void Cancel() override final; + + bool Compute(const float* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) override final; + + bool Compute(const double* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) override final; + + uint32_t GetNConvexHulls() const override final; + + bool GetConvexHull(const uint32_t index, + ConvexHull& ch) const override final; + + void Clean() override final; // release internally allocated memory + + void Release() override final; + + // Will compute the center of mass of the convex hull decomposition results and return it + // in 'centerOfMass'. Returns false if the center of mass could not be computed. + bool ComputeCenterOfMass(double centerOfMass[3]) const override final; + + // In synchronous mode (non-multi-threaded) the state is always 'ready' + // In asynchronous mode, this returns true if the background thread is not still actively computing + // a new solution. In an asynchronous config the 'IsReady' call will report any update or log + // messages in the caller's current thread. + bool IsReady(void) const override final; + + /** + * At the request of LegionFu : out_look@foxmail.com + * This method will return which convex hull is closest to the source position. + * You can use this method to figure out, for example, which vertices in the original + * source mesh are best associated with which convex hull. + * + * @param pos : The input 3d position to test against + * + * @return : Returns which convex hull this position is closest to. + */ + uint32_t findNearestConvexHull(const double pos[3], + double& distanceToHull) override final; + +// private: + bool Compute(const std::vector& points, + const std::vector& triangles, + const Parameters& params); + + // Take the source position, normalize it, and then convert it into an index position + uint32_t GetIndex(VHACD::VertexIndex& vi, + const VHACD::Vertex& p); + + // This copies the input mesh while scaling the input positions + // to fit into a normalized unit cube. It also re-indexes all of the + // vertex positions in case they weren't clean coming in. + void CopyInputMesh(const std::vector& points, + const std::vector& triangles); + + void ScaleOutputConvexHull(ConvexHull &ch); + + void AddCostToPriorityQueue(CostTask& task); + + void ReleaseConvexHull(ConvexHull* ch); + + void PerformConvexDecomposition(); + + double ComputeConvexHullVolume(const ConvexHull& sm); + + double ComputeVolume4(const VHACD::Vect3& a, + const VHACD::Vect3& b, + const VHACD::Vect3& c, + const VHACD::Vect3& d); + + double ComputeConcavity(double volumeSeparate, + double volumeCombined, + double volumeMesh); + + // See if we can compute the cost without having to actually merge convex hulls. + // If the axis aligned bounding boxes (slightly inflated) of the two convex hulls + // do not intersect, then we don't need to actually compute the merged convex hull + // volume. + bool DoFastCost(CostTask& mt); + + void PerformMergeCostTask(CostTask& mt); + + ConvexHull* ComputeReducedConvexHull(const ConvexHull& ch, + uint32_t maxVerts, + bool projectHullVertices); + + // Take the points in convex hull A and the points in convex hull B and generate + // a new convex hull on the combined set of points. + // Once completed, we create a SimpleMesh instance to hold the triangle mesh + // and we compute an inflated AABB for it. + ConvexHull* ComputeCombinedConvexHull(const ConvexHull& sm1, + const ConvexHull& sm2); + + + ConvexHull* GetHull(uint32_t index); + + bool RemoveHull(uint32_t index); + + ConvexHull* CopyConvexHull(const ConvexHull& source); + + const char* GetStageName(Stages stage) const; + + /* + * Overrides VHACD::VHACDCallbacks + */ + void ProgressUpdate(Stages stage, + double stageProgress, + const char* operation) override final; + + bool IsCanceled() const override final; + + std::atomic m_canceled{ false }; + Parameters m_params; // Convex decomposition parameters + + std::vector m_convexHulls; // Finalized convex hulls + std::vector> m_voxelHulls; // completed voxel hulls + std::vector> m_pendingHulls; + + std::vector> m_trees; + VHACD::AABBTree m_AABBTree; + VHACD::Volume m_voxelize; + VHACD::Vect3 m_center; + double m_scale{ double(1.0) }; + double m_recipScale{ double(1.0) }; + SimpleMesh m_inputMesh; // re-indexed and normalized input mesh + std::vector m_vertices; + std::vector m_indices; + + double m_overallHullVolume{ double(0.0) }; + double m_voxelScale{ double(0.0) }; + double m_voxelHalfScale{ double(0.0) }; + VHACD::Vect3 m_voxelBmin; + VHACD::Vect3 m_voxelBmax; + uint32_t m_meshId{ 0 }; + std::priority_queue m_hullPairQueue; +#if !VHACD_DISABLE_THREADING + std::unique_ptr m_threadPool{ nullptr }; +#endif + std::unordered_map m_hulls; + + double m_overallProgress{ double(0.0) }; + double m_stageProgress{ double(0.0) }; + double m_operationProgress{ double(0.0) }; +}; + +void VHACDImpl::Cancel() +{ + m_canceled = true; +} + +bool VHACDImpl::Compute(const float* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) +{ + std::vector v; + v.reserve(countPoints); + for (uint32_t i = 0; i < countPoints; ++i) + { + v.emplace_back(points[i * 3 + 0], + points[i * 3 + 1], + points[i * 3 + 2]); + } + + std::vector t; + t.reserve(countTriangles); + for (uint32_t i = 0; i < countTriangles; ++i) + { + t.emplace_back(triangles[i * 3 + 0], + triangles[i * 3 + 1], + triangles[i * 3 + 2]); + } + + return Compute(v, t, params); +} + +bool VHACDImpl::Compute(const double* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) +{ + std::vector v; + v.reserve(countPoints); + for (uint32_t i = 0; i < countPoints; ++i) + { + v.emplace_back(points[i * 3 + 0], + points[i * 3 + 1], + points[i * 3 + 2]); + } + + std::vector t; + t.reserve(countTriangles); + for (uint32_t i = 0; i < countTriangles; ++i) + { + t.emplace_back(triangles[i * 3 + 0], + triangles[i * 3 + 1], + triangles[i * 3 + 2]); + } + + return Compute(v, t, params); +} + +uint32_t VHACDImpl::GetNConvexHulls() const +{ + return uint32_t(m_convexHulls.size()); +} + +bool VHACDImpl::GetConvexHull(const uint32_t index, + ConvexHull& ch) const +{ + bool ret = false; + + if ( index < uint32_t(m_convexHulls.size() )) + { + ch = *m_convexHulls[index]; + ret = true; + } + + return ret; +} + +void VHACDImpl::Clean() +{ +#if !VHACD_DISABLE_THREADING + m_threadPool = nullptr; +#endif + + m_trees.clear(); + + for (auto& ch : m_convexHulls) + { + ReleaseConvexHull(ch); + } + m_convexHulls.clear(); + + for (auto& ch : m_hulls) + { + ReleaseConvexHull(ch.second); + } + m_hulls.clear(); + + m_voxelHulls.clear(); + + m_pendingHulls.clear(); + + m_vertices.clear(); + m_indices.clear(); +} + +void VHACDImpl::Release() +{ + delete this; +} + +bool VHACDImpl::ComputeCenterOfMass(double centerOfMass[3]) const +{ + bool ret = false; + + return ret; +} + +bool VHACDImpl::IsReady() const +{ + return true; +} + +uint32_t VHACDImpl::findNearestConvexHull(const double pos[3], + double& distanceToHull) +{ + uint32_t ret = 0; // The default return code is zero + + uint32_t hullCount = GetNConvexHulls(); + distanceToHull = 0; + // First, make sure that we have valid and completed results + if ( hullCount ) + { + // See if we already have AABB trees created for each convex hull + if ( m_trees.empty() ) + { + // For each convex hull, we generate an AABB tree for fast closest point queries + for (uint32_t i = 0; i < hullCount; i++) + { + VHACD::IVHACD::ConvexHull ch; + GetConvexHull(i,ch); + // Pass the triangle mesh to create an AABB tree instance based on it. + m_trees.emplace_back(new AABBTree(ch.m_points, + ch.m_triangles)); + } + } + // We now compute the closest point to each convex hull and save the nearest one + double closest = 1e99; + for (uint32_t i = 0; i < hullCount; i++) + { + std::unique_ptr& t = m_trees[i]; + if ( t ) + { + VHACD::Vect3 closestPoint; + VHACD::Vect3 position(pos[0], + pos[1], + pos[2]); + if ( t->GetClosestPointWithinDistance(position, 1e99, closestPoint)) + { + VHACD::Vect3 d = position - closestPoint; + double distanceSquared = d.GetNormSquared(); + if ( distanceSquared < closest ) + { + closest = distanceSquared; + ret = i; + } + } + } + } + distanceToHull = sqrt(closest); // compute the distance to the nearest convex hull + } + + return ret; +} + +bool VHACDImpl::Compute(const std::vector& points, + const std::vector& triangles, + const Parameters& params) +{ + bool ret = false; + + m_params = params; + m_canceled = false; + + Clean(); // release any previous results +#if !VHACD_DISABLE_THREADING + if ( m_params.m_asyncACD ) + { + m_threadPool = std::unique_ptr(new ThreadPool(8)); + } +#endif + CopyInputMesh(points, + triangles); + if ( !m_canceled ) + { + // We now recursively perform convex decomposition until complete + PerformConvexDecomposition(); + } + + if ( m_canceled ) + { + Clean(); + ret = false; + if ( m_params.m_logger ) + { + m_params.m_logger->Log("VHACD operation canceled before it was complete."); + } + } + else + { + ret = true; + } +#if !VHACD_DISABLE_THREADING + m_threadPool = nullptr; +#endif + return ret; +} + +uint32_t VHACDImpl::GetIndex(VHACD::VertexIndex& vi, + const VHACD::Vertex& p) +{ + VHACD::Vect3 pos = (VHACD::Vect3(p) - m_center) * m_recipScale; + bool newPos; + uint32_t ret = vi.GetIndex(pos, + newPos); + return ret; +} + +void VHACDImpl::CopyInputMesh(const std::vector& points, + const std::vector& triangles) +{ + m_vertices.clear(); + m_indices.clear(); + m_indices.reserve(triangles.size()); + + // First we must find the bounding box of this input vertices and normalize them into a unit-cube + VHACD::Vect3 bmin( FLT_MAX); + VHACD::Vect3 bmax(-FLT_MAX); + ProgressUpdate(Stages::COMPUTE_BOUNDS_OF_INPUT_MESH, + 0, + "ComputingBounds"); + for (uint32_t i = 0; i < points.size(); i++) + { + const VHACD::Vertex& p = points[i]; + + bmin = bmin.CWiseMin(p); + bmax = bmax.CWiseMax(p); + } + ProgressUpdate(Stages::COMPUTE_BOUNDS_OF_INPUT_MESH, + 100, + "ComputingBounds"); + + m_center = (bmax + bmin) * double(0.5); + + VHACD::Vect3 scale = bmax - bmin; + m_scale = scale.MaxCoeff(); + + m_recipScale = m_scale > double(0.0) ? double(1.0) / m_scale : double(0.0); + + { + VHACD::VertexIndex vi = VHACD::VertexIndex(double(0.001), false); + + uint32_t dcount = 0; + + for (uint32_t i = 0; i < triangles.size() && !m_canceled; ++i) + { + const VHACD::Triangle& t = triangles[i]; + const VHACD::Vertex& p1 = points[t.mI0]; + const VHACD::Vertex& p2 = points[t.mI1]; + const VHACD::Vertex& p3 = points[t.mI2]; + + uint32_t i1 = GetIndex(vi, p1); + uint32_t i2 = GetIndex(vi, p2); + uint32_t i3 = GetIndex(vi, p3); + + if ( i1 == i2 || i1 == i3 || i2 == i3 ) + { + dcount++; + } + else + { + m_indices.emplace_back(i1, i2, i3); + } + } + + if ( dcount ) + { + if ( m_params.m_logger ) + { + char scratch[512]; + snprintf(scratch, + sizeof(scratch), + "Skipped %d degenerate triangles", dcount); + m_params.m_logger->Log(scratch); + } + } + + m_vertices = vi.TakeVertices(); + } + + // Create the raycast mesh + if ( !m_canceled ) + { + ProgressUpdate(Stages::CREATE_RAYCAST_MESH, + 0, + "Building RaycastMesh"); + m_AABBTree = VHACD::AABBTree(m_vertices, + m_indices); + ProgressUpdate(Stages::CREATE_RAYCAST_MESH, + 100, + "RaycastMesh completed"); + } + if ( !m_canceled ) + { + ProgressUpdate(Stages::VOXELIZING_INPUT_MESH, + 0, + "Voxelizing Input Mesh"); + m_voxelize = VHACD::Volume(); + m_voxelize.Voxelize(m_vertices, + m_indices, + m_params.m_resolution, + m_params.m_fillMode, + m_AABBTree); + m_voxelScale = m_voxelize.GetScale(); + m_voxelHalfScale = m_voxelScale * double(0.5); + m_voxelBmin = m_voxelize.GetBounds().GetMin(); + m_voxelBmax = m_voxelize.GetBounds().GetMax(); + ProgressUpdate(Stages::VOXELIZING_INPUT_MESH, + 100, + "Voxelization complete"); + } + + m_inputMesh.m_vertices = m_vertices; + m_inputMesh.m_indices = m_indices; + if ( !m_canceled ) + { + ProgressUpdate(Stages::BUILD_INITIAL_CONVEX_HULL, + 0, + "Build initial ConvexHull"); + std::unique_ptr vh = std::unique_ptr(new VoxelHull(m_voxelize, + m_params, + this)); + if ( vh->m_convexHull ) + { + m_overallHullVolume = vh->m_convexHull->m_volume; + } + m_pendingHulls.push_back(std::move(vh)); + ProgressUpdate(Stages::BUILD_INITIAL_CONVEX_HULL, + 100, + "Initial ConvexHull complete"); + } +} + +void VHACDImpl::ScaleOutputConvexHull(ConvexHull& ch) +{ + for (uint32_t i = 0; i < ch.m_points.size(); i++) + { + VHACD::Vect3 p = ch.m_points[i]; + p = (p * m_scale) + m_center; + ch.m_points[i] = p; + } + ch.m_volume = ComputeConvexHullVolume(ch); // get the combined volume + VHACD::BoundsAABB b(ch.m_points); + ch.mBmin = b.GetMin(); + ch.mBmax = b.GetMax(); + ComputeCentroid(ch.m_points, + ch.m_triangles, + ch.m_center); +} + +void VHACDImpl::AddCostToPriorityQueue(CostTask& task) +{ + HullPair hp(task.m_hullA->m_meshId, + task.m_hullB->m_meshId, + task.m_concavity); + m_hullPairQueue.push(hp); +} + +void VHACDImpl::ReleaseConvexHull(ConvexHull* ch) +{ + if ( ch ) + { + delete ch; + } +} + +void jobCallback(std::unique_ptr& userPtr) +{ + userPtr->PerformPlaneSplit(); +} + +void computeMergeCostTask(CostTask& ptr) +{ + ptr.m_this->PerformMergeCostTask(ptr); +} + +void VHACDImpl::PerformConvexDecomposition() +{ + { + ScopedTime st("Convex Decomposition", + m_params.m_logger); + double maxHulls = pow(2, m_params.m_maxRecursionDepth); + // We recursively split convex hulls until we can + // no longer recurse further. + Timer t; + + while ( !m_pendingHulls.empty() && !m_canceled ) + { + size_t count = m_pendingHulls.size() + m_voxelHulls.size(); + double e = t.PeekElapsedSeconds(); + if ( e >= double(0.1) ) + { + t.Reset(); + double stageProgress = (double(count) * double(100.0)) / maxHulls; + ProgressUpdate(Stages::PERFORMING_DECOMPOSITION, + stageProgress, + "Performing recursive decomposition of convex hulls"); + } + // First we make a copy of the hulls we are processing + std::vector> oldList = std::move(m_pendingHulls); + // For each hull we want to split, we either + // immediately perform the plane split or we post it as + // a job to be performed in a background thread + std::vector> futures(oldList.size()); + uint32_t futureCount = 0; + for (auto& i : oldList) + { + if ( i->IsComplete() || count > MaxConvexHullFragments ) + { + } + else + { +#if !VHACD_DISABLE_THREADING + if ( m_threadPool ) + { + futures[futureCount] = m_threadPool->enqueue([&i] + { + jobCallback(i); + }); + futureCount++; + } + else +#endif + { + i->PerformPlaneSplit(); + } + } + } + // Wait for any outstanding jobs to complete in the background threads + if ( futureCount ) + { + for (uint32_t i = 0; i < futureCount; i++) + { + futures[i].get(); + } + } + // Now, we rebuild the pending convex hulls list by + // adding the two children to the output list if + // we need to recurse them further + for (auto& vh : oldList) + { + if ( vh->IsComplete() || count > MaxConvexHullFragments ) + { + if ( vh->m_convexHull ) + { + m_voxelHulls.push_back(std::move(vh)); + } + } + else + { + if ( vh->m_hullA ) + { + m_pendingHulls.push_back(std::move(vh->m_hullA)); + } + if ( vh->m_hullB ) + { + m_pendingHulls.push_back(std::move(vh->m_hullB)); + } + } + } + } + } + + if ( !m_canceled ) + { + // Give each convex hull a unique guid + m_meshId = 0; + m_hulls.clear(); + + // Build the convex hull id map + std::vector hulls; + + ProgressUpdate(Stages::INITIALIZING_CONVEX_HULLS_FOR_MERGING, + 0, + "Initializing ConvexHulls"); + for (auto& vh : m_voxelHulls) + { + if ( m_canceled ) + { + break; + } + ConvexHull* ch = CopyConvexHull(*vh->m_convexHull); + m_meshId++; + ch->m_meshId = m_meshId; + m_hulls[m_meshId] = ch; + // Compute the volume of the convex hull + ch->m_volume = ComputeConvexHullVolume(*ch); + // Compute the AABB of the convex hull + VHACD::BoundsAABB b = VHACD::BoundsAABB(ch->m_points).Inflate(double(0.1)); + ch->mBmin = b.GetMin(); + ch->mBmax = b.GetMax(); + + ComputeCentroid(ch->m_points, + ch->m_triangles, + ch->m_center); + + hulls.push_back(ch); + } + ProgressUpdate(Stages::INITIALIZING_CONVEX_HULLS_FOR_MERGING, + 100, + "ConvexHull initialization complete"); + + m_voxelHulls.clear(); + + // here we merge convex hulls as needed until the match the + // desired maximum hull count. + size_t hullCount = hulls.size(); + + if ( hullCount > m_params.m_maxConvexHulls && !m_canceled) + { + size_t costMatrixSize = ((hullCount * hullCount) - hullCount) >> 1; + std::vector tasks; + tasks.reserve(costMatrixSize); + + ScopedTime st("Computing the Cost Matrix", + m_params.m_logger); + // First thing we need to do is compute the cost matrix + // This is computed as the volume error of any two convex hulls + // combined + ProgressUpdate(Stages::COMPUTING_COST_MATRIX, + 0, + "Computing Hull Merge Cost Matrix"); + for (size_t i = 1; i < hullCount && !m_canceled; i++) + { + ConvexHull* chA = hulls[i]; + + for (size_t j = 0; j < i && !m_canceled; j++) + { + ConvexHull* chB = hulls[j]; + + CostTask t; + t.m_hullA = chA; + t.m_hullB = chB; + t.m_this = this; + + if ( DoFastCost(t) ) + { + } + else + { + tasks.push_back(std::move(t)); + CostTask* task = &tasks.back(); +#if !VHACD_DISABLE_THREADING + if ( m_threadPool ) + { + task->m_future = m_threadPool->enqueue([task] + { + computeMergeCostTask(*task); + }); + } +#endif + } + } + } + + if ( !m_canceled ) + { +#if !VHACD_DISABLE_THREADING + if ( m_threadPool ) + { + for (CostTask& task : tasks) + { + task.m_future.get(); + } + + for (CostTask& task : tasks) + { + AddCostToPriorityQueue(task); + } + } + else +#endif + { + for (CostTask& task : tasks) + { + PerformMergeCostTask(task); + AddCostToPriorityQueue(task); + } + } + ProgressUpdate(Stages::COMPUTING_COST_MATRIX, + 100, + "Finished cost matrix"); + } + + if ( !m_canceled ) + { + ScopedTime stMerging("Merging Convex Hulls", + m_params.m_logger); + Timer t; + // Now that we know the cost to merge each hull, we can begin merging them. + bool cancel = false; + + uint32_t maxMergeCount = uint32_t(m_hulls.size()) - m_params.m_maxConvexHulls; + uint32_t startCount = uint32_t(m_hulls.size()); + + while ( !cancel + && m_hulls.size() > m_params.m_maxConvexHulls + && !m_hullPairQueue.empty() + && !m_canceled) + { + double e = t.PeekElapsedSeconds(); + if ( e >= double(0.1) ) + { + t.Reset(); + uint32_t hullsProcessed = startCount - uint32_t(m_hulls.size() ); + double stageProgress = double(hullsProcessed * 100) / double(maxMergeCount); + ProgressUpdate(Stages::MERGING_CONVEX_HULLS, + stageProgress, + "Merging Convex Hulls"); + } + + HullPair hp = m_hullPairQueue.top(); + m_hullPairQueue.pop(); + + // It is entirely possible that the hull pair queue can + // have references to convex hulls that are no longer valid + // because they were previously merged. So we check for this + // and if either hull referenced in this pair no longer + // exists, then we skip it. + + // Look up this pair of hulls by ID + ConvexHull* ch1 = GetHull(hp.m_hullA); + ConvexHull* ch2 = GetHull(hp.m_hullB); + + // If both hulls are still valid, then we merge them, delete the old + // two hulls and recompute the cost matrix for the new combined hull + // we have created + if ( ch1 && ch2 ) + { + // This is the convex hull which results from combining the + // vertices in the two source hulls + ConvexHull* combinedHull = ComputeCombinedConvexHull(*ch1, + *ch2); + // The two old convex hulls are going to get removed + RemoveHull(hp.m_hullA); + RemoveHull(hp.m_hullB); + + m_meshId++; + combinedHull->m_meshId = m_meshId; + tasks.clear(); + tasks.reserve(m_hulls.size()); + + // Compute the cost between this new merged hull + // and all existing convex hulls and then + // add that to the priority queue + for (auto& i : m_hulls) + { + if ( m_canceled ) + { + break; + } + ConvexHull* secondHull = i.second; + CostTask t; + t.m_hullA = combinedHull; + t.m_hullB = secondHull; + t.m_this = this; + if ( DoFastCost(t) ) + { + } + else + { + tasks.push_back(std::move(t)); + } + } + m_hulls[combinedHull->m_meshId] = combinedHull; + // See how many merge cost tasks were posted + // If there are 8 or more and we are running asynchronously, then do them that way. +#if !VHACD_DISABLE_THREADING + if ( m_threadPool && tasks.size() >= 2) + { + for (CostTask& task : tasks) + { + task.m_future = m_threadPool->enqueue([&task] + { + computeMergeCostTask(task); + }); + } + + for (CostTask& task : tasks) + { + task.m_future.get(); + } + } + else +#endif + { + for (CostTask& task : tasks) + { + PerformMergeCostTask(task); + } + } + + for (CostTask& task : tasks) + { + AddCostToPriorityQueue(task); + } + } + } + // Ok...once we are done, we copy the results! + m_meshId -= 0; + ProgressUpdate(Stages::FINALIZING_RESULTS, + 0, + "Finalizing results"); + for (auto& i : m_hulls) + { + if ( m_canceled ) + { + break; + } + ConvexHull* ch = i.second; + // We now must reduce the convex hull + if ( ch->m_points.size() > m_params.m_maxNumVerticesPerCH || m_params.m_shrinkWrap) + { + ConvexHull* reduce = ComputeReducedConvexHull(*ch, + m_params.m_maxNumVerticesPerCH, + m_params.m_shrinkWrap); + ReleaseConvexHull(ch); + ch = reduce; + } + ScaleOutputConvexHull(*ch); + ch->m_meshId = m_meshId; + m_meshId++; + m_convexHulls.push_back(ch); + } + m_hulls.clear(); // since the hulls were moved into the output list, we don't need to delete them from this container + ProgressUpdate(Stages::FINALIZING_RESULTS, + 100, + "Finalized results complete"); + } + } + else + { + ProgressUpdate(Stages::FINALIZING_RESULTS, + 0, + "Finalizing results"); + m_meshId = 0; + for (auto& ch : hulls) + { + // We now must reduce the convex hull + if ( ch->m_points.size() > m_params.m_maxNumVerticesPerCH || m_params.m_shrinkWrap ) + { + ConvexHull* reduce = ComputeReducedConvexHull(*ch, + m_params.m_maxNumVerticesPerCH, + m_params.m_shrinkWrap); + ReleaseConvexHull(ch); + ch = reduce; + } + ScaleOutputConvexHull(*ch); + ch->m_meshId = m_meshId; + m_meshId++; + m_convexHulls.push_back(ch); + } + m_hulls.clear(); + ProgressUpdate(Stages::FINALIZING_RESULTS, + 100, + "Finalized results"); + } + } +} + +double VHACDImpl::ComputeConvexHullVolume(const ConvexHull& sm) +{ + double totalVolume = 0; + VHACD::Vect3 bary(0, 0, 0); + for (uint32_t i = 0; i < sm.m_points.size(); i++) + { + VHACD::Vect3 p(sm.m_points[i]); + bary += p; + } + bary /= double(sm.m_points.size()); + + for (uint32_t i = 0; i < sm.m_triangles.size(); i++) + { + uint32_t i1 = sm.m_triangles[i].mI0; + uint32_t i2 = sm.m_triangles[i].mI1; + uint32_t i3 = sm.m_triangles[i].mI2; + + VHACD::Vect3 ver0(sm.m_points[i1]); + VHACD::Vect3 ver1(sm.m_points[i2]); + VHACD::Vect3 ver2(sm.m_points[i3]); + + totalVolume += ComputeVolume4(ver0, + ver1, + ver2, + bary); + + } + totalVolume = totalVolume / double(6.0); + return totalVolume; +} + +double VHACDImpl::ComputeVolume4(const VHACD::Vect3& a, + const VHACD::Vect3& b, + const VHACD::Vect3& c, + const VHACD::Vect3& d) +{ + VHACD::Vect3 ad = a - d; + VHACD::Vect3 bd = b - d; + VHACD::Vect3 cd = c - d; + VHACD::Vect3 bcd = bd.Cross(cd); + double dot = ad.Dot(bcd); + return dot; +} + +double VHACDImpl::ComputeConcavity(double volumeSeparate, + double volumeCombined, + double volumeMesh) +{ + return fabs(volumeSeparate - volumeCombined) / volumeMesh; +} + +bool VHACDImpl::DoFastCost(CostTask& mt) +{ + bool ret = false; + + ConvexHull* ch1 = mt.m_hullA; + ConvexHull* ch2 = mt.m_hullB; + + VHACD::BoundsAABB ch1b(ch1->mBmin, + ch1->mBmax); + VHACD::BoundsAABB ch2b(ch2->mBmin, + ch2->mBmax); + if (!ch1b.Intersects(ch2b)) + { + VHACD::BoundsAABB b = ch1b.Union(ch2b); + + double combinedVolume = b.Volume(); + double concavity = ComputeConcavity(ch1->m_volume + ch2->m_volume, + combinedVolume, + m_overallHullVolume); + HullPair hp(ch1->m_meshId, + ch2->m_meshId, + concavity); + m_hullPairQueue.push(hp); + ret = true; + } + return ret; +} + +void VHACDImpl::PerformMergeCostTask(CostTask& mt) +{ + ConvexHull* ch1 = mt.m_hullA; + ConvexHull* ch2 = mt.m_hullB; + + double volume1 = ch1->m_volume; + double volume2 = ch2->m_volume; + + ConvexHull* combined = ComputeCombinedConvexHull(*ch1, + *ch2); // Build the combined convex hull + double combinedVolume = ComputeConvexHullVolume(*combined); // get the combined volume + mt.m_concavity = ComputeConcavity(volume1 + volume2, + combinedVolume, + m_overallHullVolume); + ReleaseConvexHull(combined); +} + +IVHACD::ConvexHull* VHACDImpl::ComputeReducedConvexHull(const ConvexHull& ch, + uint32_t maxVerts, + bool projectHullVertices) +{ + SimpleMesh sourceConvexHull; + + sourceConvexHull.m_vertices = ch.m_points; + sourceConvexHull.m_indices = ch.m_triangles; + + ShrinkWrap(sourceConvexHull, + m_AABBTree, + maxVerts, + m_voxelScale * 4, + projectHullVertices); + + ConvexHull *ret = new ConvexHull; + + ret->m_points = sourceConvexHull.m_vertices; + ret->m_triangles = sourceConvexHull.m_indices; + + VHACD::BoundsAABB b = VHACD::BoundsAABB(ret->m_points).Inflate(double(0.1)); + ret->mBmin = b.GetMin(); + ret->mBmax = b.GetMax(); + ComputeCentroid(ret->m_points, + ret->m_triangles, + ret->m_center); + + ret->m_volume = ComputeConvexHullVolume(*ret); + + // Return the convex hull + return ret; +} + +IVHACD::ConvexHull* VHACDImpl::ComputeCombinedConvexHull(const ConvexHull& sm1, + const ConvexHull& sm2) +{ + uint32_t vcount = uint32_t(sm1.m_points.size() + sm2.m_points.size()); // Total vertices from both hulls + std::vector vertices(vcount); + auto it = std::copy(sm1.m_points.begin(), + sm1.m_points.end(), + vertices.begin()); + std::copy(sm2.m_points.begin(), + sm2.m_points.end(), + it); + + VHACD::QuickHull qh; + qh.ComputeConvexHull(vertices, + vcount); + + ConvexHull* ret = new ConvexHull; + ret->m_points = qh.GetVertices(); + ret->m_triangles = qh.GetIndices(); + + ret->m_volume = ComputeConvexHullVolume(*ret); + + VHACD::BoundsAABB b = VHACD::BoundsAABB(qh.GetVertices()).Inflate(double(0.1)); + ret->mBmin = b.GetMin(); + ret->mBmax = b.GetMax(); + ComputeCentroid(ret->m_points, + ret->m_triangles, + ret->m_center); + + // Return the convex hull + return ret; +} + +IVHACD::ConvexHull* VHACDImpl::GetHull(uint32_t index) +{ + ConvexHull* ret = nullptr; + + auto found = m_hulls.find(index); + if ( found != m_hulls.end() ) + { + ret = found->second; + } + + return ret; +} + +bool VHACDImpl::RemoveHull(uint32_t index) +{ + bool ret = false; + auto found = m_hulls.find(index); + if ( found != m_hulls.end() ) + { + ret = true; + ReleaseConvexHull(found->second); + m_hulls.erase(found); + } + return ret; +} + +IVHACD::ConvexHull* VHACDImpl::CopyConvexHull(const ConvexHull& source) +{ + ConvexHull *ch = new ConvexHull; + *ch = source; + + return ch; +} + +const char* VHACDImpl::GetStageName(Stages stage) const +{ + const char *ret = "unknown"; + switch ( stage ) + { + case Stages::COMPUTE_BOUNDS_OF_INPUT_MESH: + ret = "COMPUTE_BOUNDS_OF_INPUT_MESH"; + break; + case Stages::REINDEXING_INPUT_MESH: + ret = "REINDEXING_INPUT_MESH"; + break; + case Stages::CREATE_RAYCAST_MESH: + ret = "CREATE_RAYCAST_MESH"; + break; + case Stages::VOXELIZING_INPUT_MESH: + ret = "VOXELIZING_INPUT_MESH"; + break; + case Stages::BUILD_INITIAL_CONVEX_HULL: + ret = "BUILD_INITIAL_CONVEX_HULL"; + break; + case Stages::PERFORMING_DECOMPOSITION: + ret = "PERFORMING_DECOMPOSITION"; + break; + case Stages::INITIALIZING_CONVEX_HULLS_FOR_MERGING: + ret = "INITIALIZING_CONVEX_HULLS_FOR_MERGING"; + break; + case Stages::COMPUTING_COST_MATRIX: + ret = "COMPUTING_COST_MATRIX"; + break; + case Stages::MERGING_CONVEX_HULLS: + ret = "MERGING_CONVEX_HULLS"; + break; + case Stages::FINALIZING_RESULTS: + ret = "FINALIZING_RESULTS"; + break; + case Stages::NUM_STAGES: + // Should be unreachable, here to silence enumeration value not handled in switch warnings + // GCC/Clang's -Wswitch + break; + } + return ret; +} + +void VHACDImpl::ProgressUpdate(Stages stage, + double stageProgress, + const char* operation) +{ + if ( m_params.m_callback ) + { + double overallProgress = (double(stage) * 100) / double(Stages::NUM_STAGES); + const char *s = GetStageName(stage); + m_params.m_callback->Update(overallProgress, + stageProgress, + s, + operation); + } +} + +bool VHACDImpl::IsCanceled() const +{ + return m_canceled; +} + +IVHACD* CreateVHACD(void) +{ + VHACDImpl *ret = new VHACDImpl; + return static_cast< IVHACD *>(ret); +} + +IVHACD* CreateVHACD(void); + +#if !VHACD_DISABLE_THREADING + +class LogMessage +{ +public: + double m_overallProgress{ double(-1.0) }; + double m_stageProgress{ double(-1.0) }; + std::string m_stage; + std::string m_operation; +}; + +class VHACDAsyncImpl : public VHACD::IVHACD, + public VHACD::IVHACD::IUserCallback, + VHACD::IVHACD::IUserLogger, + VHACD::IVHACD::IUserTaskRunner +{ +public: + VHACDAsyncImpl() = default; + + ~VHACDAsyncImpl() override; + + void Cancel() override final; + + bool Compute(const float* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) override final; + + bool Compute(const double* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) override final; + + bool GetConvexHull(const uint32_t index, + VHACD::IVHACD::ConvexHull& ch) const override final; + + uint32_t GetNConvexHulls() const override final; + + void Clean() override final; // release internally allocated memory + + void Release() override final; // release IVHACD + + // Will compute the center of mass of the convex hull decomposition results and return it + // in 'centerOfMass'. Returns false if the center of mass could not be computed. + bool ComputeCenterOfMass(double centerOfMass[3]) const override; + + bool IsReady() const override final; + + /** + * At the request of LegionFu : out_look@foxmail.com + * This method will return which convex hull is closest to the source position. + * You can use this method to figure out, for example, which vertices in the original + * source mesh are best associated with which convex hull. + * + * @param pos : The input 3d position to test against + * + * @return : Returns which convex hull this position is closest to. + */ + uint32_t findNearestConvexHull(const double pos[3], + double& distanceToHull) override final; + + void Update(const double overallProgress, + const double stageProgress, + const char* const stage, + const char *operation) override final; + + void Log(const char* const msg) override final; + + void* StartTask(std::function func) override; + + void JoinTask(void* Task) override; + + bool Compute(const Parameters params); + + bool ComputeNow(const std::vector& points, + const std::vector& triangles, + const Parameters& _desc); + + // As a convenience for the calling application we only send it update and log messages from it's own main + // thread. This reduces the complexity burden on the caller by making sure it only has to deal with log + // messages in it's main application thread. + void ProcessPendingMessages() const; + +private: + VHACD::VHACDImpl m_VHACD; + std::vector m_vertices; + std::vector m_indices; + VHACD::IVHACD::IUserCallback* m_callback{ nullptr }; + VHACD::IVHACD::IUserLogger* m_logger{ nullptr }; + VHACD::IVHACD::IUserTaskRunner* m_taskRunner{ nullptr }; + void* m_task{ nullptr }; + std::atomic m_running{ false }; + std::atomic m_cancel{ false }; + + // Thread safe caching mechanism for messages and update status. + // This is so that caller always gets messages in his own thread + // Member variables are marked as 'mutable' since the message dispatch function + // is called from const query methods. + mutable std::mutex m_messageMutex; + mutable std::vector m_messages; + mutable std::atomic m_haveMessages{ false }; +}; + +VHACDAsyncImpl::~VHACDAsyncImpl() +{ + Cancel(); +} + +void VHACDAsyncImpl::Cancel() +{ + m_cancel = true; + m_VHACD.Cancel(); + + if (m_task) + { + m_taskRunner->JoinTask(m_task); // Wait for the thread to fully exit before we delete the instance + m_task = nullptr; + } + m_cancel = false; // clear the cancel semaphore +} + +bool VHACDAsyncImpl::Compute(const float* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) +{ + m_vertices.reserve(countPoints); + for (uint32_t i = 0; i < countPoints; ++i) + { + m_vertices.emplace_back(points[i * 3 + 0], + points[i * 3 + 1], + points[i * 3 + 2]); + } + + m_indices.reserve(countTriangles); + for (uint32_t i = 0; i < countTriangles; ++i) + { + m_indices.emplace_back(triangles[i * 3 + 0], + triangles[i * 3 + 1], + triangles[i * 3 + 2]); + } + + return Compute(params); +} + +bool VHACDAsyncImpl::Compute(const double* const points, + const uint32_t countPoints, + const uint32_t* const triangles, + const uint32_t countTriangles, + const Parameters& params) +{ + // We need to copy the input vertices and triangles into our own buffers so we can operate + // on them safely from the background thread. + // Can't be local variables due to being asynchronous + m_vertices.reserve(countPoints); + for (uint32_t i = 0; i < countPoints; ++i) + { + m_vertices.emplace_back(points[i * 3 + 0], + points[i * 3 + 1], + points[i * 3 + 2]); + } + + m_indices.reserve(countTriangles); + for (uint32_t i = 0; i < countTriangles; ++i) + { + m_indices.emplace_back(triangles[i * 3 + 0], + triangles[i * 3 + 1], + triangles[i * 3 + 2]); + } + + return Compute(params); +} + +bool VHACDAsyncImpl::GetConvexHull(const uint32_t index, + VHACD::IVHACD::ConvexHull& ch) const +{ + return m_VHACD.GetConvexHull(index, + ch); +} + +uint32_t VHACDAsyncImpl::GetNConvexHulls() const +{ + ProcessPendingMessages(); + return m_VHACD.GetNConvexHulls(); +} + +void VHACDAsyncImpl::Clean() +{ + Cancel(); + m_VHACD.Clean(); +} + +void VHACDAsyncImpl::Release() +{ + delete this; +} + +bool VHACDAsyncImpl::ComputeCenterOfMass(double centerOfMass[3]) const +{ + bool ret = false; + + centerOfMass[0] = 0; + centerOfMass[1] = 0; + centerOfMass[2] = 0; + + if (IsReady()) + { + ret = m_VHACD.ComputeCenterOfMass(centerOfMass); + } + return ret; +} + +bool VHACDAsyncImpl::IsReady() const +{ + ProcessPendingMessages(); + return !m_running; +} + +uint32_t VHACDAsyncImpl::findNearestConvexHull(const double pos[3], + double& distanceToHull) +{ + uint32_t ret = 0; // The default return code is zero + + distanceToHull = 0; + // First, make sure that we have valid and completed results + if (IsReady() ) + { + ret = m_VHACD.findNearestConvexHull(pos,distanceToHull); + } + + return ret; +} + +void VHACDAsyncImpl::Update(const double overallProgress, + const double stageProgress, + const char* const stage, + const char* operation) +{ + m_messageMutex.lock(); + LogMessage m; + m.m_operation = std::string(operation); + m.m_overallProgress = overallProgress; + m.m_stageProgress = stageProgress; + m.m_stage = std::string(stage); + m_messages.push_back(m); + m_haveMessages = true; + m_messageMutex.unlock(); +} + +void VHACDAsyncImpl::Log(const char* const msg) +{ + m_messageMutex.lock(); + LogMessage m; + m.m_operation = std::string(msg); + m_haveMessages = true; + m_messages.push_back(m); + m_messageMutex.unlock(); +} + +void* VHACDAsyncImpl::StartTask(std::function func) +{ + return new std::thread(func); +} + +void VHACDAsyncImpl::JoinTask(void* Task) +{ + std::thread* t = static_cast(Task); + t->join(); + delete t; +} + +bool VHACDAsyncImpl::Compute(Parameters params) +{ + Cancel(); // if we previously had a solution running; cancel it. + + m_taskRunner = params.m_taskRunner ? params.m_taskRunner : this; + params.m_taskRunner = m_taskRunner; + + m_running = true; + m_task = m_taskRunner->StartTask([this, params]() { + ComputeNow(m_vertices, + m_indices, + params); + // If we have a user provided callback and the user did *not* call 'cancel' we notify him that the + // task is completed. However..if the user selected 'cancel' we do not send a completed notification event. + if (params.m_callback && !m_cancel) + { + params.m_callback->NotifyVHACDComplete(); + } + m_running = false; + }); + return true; +} + +bool VHACDAsyncImpl::ComputeNow(const std::vector& points, + const std::vector& triangles, + const Parameters& _desc) +{ + uint32_t ret = 0; + + Parameters desc; + m_callback = _desc.m_callback; + m_logger = _desc.m_logger; + + desc = _desc; + // Set our intercepting callback interfaces if non-null + desc.m_callback = _desc.m_callback ? this : nullptr; + desc.m_logger = _desc.m_logger ? this : nullptr; + + // If not task runner provided, then use the default one + if (desc.m_taskRunner == nullptr) + { + desc.m_taskRunner = this; + } + + bool ok = m_VHACD.Compute(points, + triangles, + desc); + if (ok) + { + ret = m_VHACD.GetNConvexHulls(); + } + + return ret ? true : false; +} + +void VHACDAsyncImpl::ProcessPendingMessages() const +{ + if (m_cancel) + { + return; + } + if ( m_haveMessages ) + { + m_messageMutex.lock(); + for (auto& i : m_messages) + { + if ( i.m_overallProgress == -1 ) + { + if ( m_logger ) + { + m_logger->Log(i.m_operation.c_str()); + } + } + else if ( m_callback ) + { + m_callback->Update(i.m_overallProgress, + i.m_stageProgress, + i.m_stage.c_str(), + i.m_operation.c_str()); + } + } + m_messages.clear(); + m_haveMessages = false; + m_messageMutex.unlock(); + } +} + +IVHACD* CreateVHACD_ASYNC() +{ + VHACDAsyncImpl* m = new VHACDAsyncImpl; + return static_cast(m); +} +#endif + +} // namespace VHACD + +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif // __GNUC__ + +#endif // ENABLE_VHACD_IMPLEMENTATION + +#endif // VHACD_H diff --git a/Tools/CMake/torque_configs.cmake b/Tools/CMake/torque_configs.cmake index 0e7cf9521..113710b5e 100644 --- a/Tools/CMake/torque_configs.cmake +++ b/Tools/CMake/torque_configs.cmake @@ -15,7 +15,7 @@ set(TORQUE_COMPILE_DEFINITIONS ICE_NO_DLL PCRE_STATIC TORQUE_ADVANCED_LIGHTING T # All link libraries. Modules should append to this the path to specify additional link libraries (.a, .lib, .dylib, .so) set(TORQUE_LINK_LIBRARIES tinyxml collada squish opcode assimp FLAC FLAC++ ogg vorbis - vorbisfile vorbisenc opus sndfile SDL2 glad pcre convexDecomp zlib) + vorbisfile vorbisenc opus sndfile SDL2 glad pcre zlib) if(TORQUE_TESTING) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} gtest gmock) From eb33fe04af14db09489e4586b8d61b3e1c2ea859 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 14:43:56 +0100 Subject: [PATCH 03/65] working vhacd renamed ThreadPool to TorqueThreadPool to avoid conflics fixed data transmission between stages of convexDecome and trimesh creation TODO: re-add our own functions for generating sphere/cylinder/box --- Engine/lib/Torque_postBuild.cmake | 2 +- Engine/source/CMakeLists.txt | 3 +- Engine/source/app/mainLoop.cpp | 8 +-- Engine/source/gfx/bitmap/imageUtils.cpp | 4 +- Engine/source/gfx/video/theoraTexture.cpp | 2 +- Engine/source/gfx/video/theoraTexture.h | 2 +- .../platform/async/asyncBufferedStream.h | 10 +-- .../source/platform/async/asyncPacketStream.h | 6 +- Engine/source/platform/platformNetAsync.cpp | 8 +-- Engine/source/platform/threads/threadPool.cpp | 62 +++++++++---------- Engine/source/platform/threads/threadPool.h | 18 +++--- .../platform/threads/threadPoolAsyncIO.h | 2 +- Engine/source/platformWin32/winRedbook.cpp | 2 + Engine/source/sfx/sfxInternal.h | 6 +- Engine/source/ts/tsMeshFit.cpp | 56 ++++++++++++++--- 15 files changed, 116 insertions(+), 75 deletions(-) diff --git a/Engine/lib/Torque_postBuild.cmake b/Engine/lib/Torque_postBuild.cmake index d8430e112..7c4f3fb26 100644 --- a/Engine/lib/Torque_postBuild.cmake +++ b/Engine/lib/Torque_postBuild.cmake @@ -2,7 +2,7 @@ # When on Windows, we need to link against winsock and windows codecs if (WIN32) - set(TORQUE_LINK_WINDOWS ${TORQUE_LINK_WINDOWS} WS2_32.LIB windowscodecs.lib) + set(TORQUE_LINK_WINDOWS ${TORQUE_LINK_WINDOWS} WS2_32.LIB windowscodecs.lib winmm.lib) if (TORQUE_D3D11) set(TORQUE_LINK_WINDOWS ${TORQUE_LINK_WINDOWS} dxguid.lib) endif (TORQUE_D3D11) diff --git a/Engine/source/CMakeLists.txt b/Engine/source/CMakeLists.txt index e1a23a177..ef6a3ab40 100644 --- a/Engine/source/CMakeLists.txt +++ b/Engine/source/CMakeLists.txt @@ -51,7 +51,7 @@ torqueAddSourceDirectories("app" "app/net") # Handle console torqueAddSourceDirectories("console") torqueAddSourceDirectories("console/torquescript") - +set(TORQUE_INCLUDE_DIRECTORIES ${TORQUE_INCLUDE_DIRECTORIES} "ts/vhacd") # Handle Platform torqueAddSourceDirectories("platform" "platform/threads" "platform/async" "platform/input" "platform/output") @@ -86,7 +86,6 @@ torqueAddSourceDirectories("gfx" "gfx/Null" "gfx/test" "gfx/bitmap" "gfx/bitmap/ # add the stb headers set(TORQUE_INCLUDE_DIRECTORIES ${TORQUE_INCLUDE_DIRECTORIES} "gfx/bitmap/loaders/stb") -set(TORQUE_INCLUDE_DIRECTORIES ${TORQUE_INCLUDE_DIRECTORIES} "ts/vhacd") if (TORQUE_OPENGL) torqueAddSourceDirectories("gfx/gl" "gfx/gl/sdl" "gfx/gl/tGL") diff --git a/Engine/source/app/mainLoop.cpp b/Engine/source/app/mainLoop.cpp index 2acb64845..c2b037252 100644 --- a/Engine/source/app/mainLoop.cpp +++ b/Engine/source/app/mainLoop.cpp @@ -262,7 +262,7 @@ void StandardMainLoop::init() RedBook::init(); Platform::initConsole(); - ThreadPool::GlobalThreadPool::createSingleton(); + TorqueThreadPool::GlobalThreadPool::createSingleton(); // Set engineAPI initialized to true engineAPI::gIsInitialized = true; @@ -293,7 +293,7 @@ void StandardMainLoop::init() Con::setVariable("TorqueScriptFileExtension", TORQUE_SCRIPT_EXTENSION); - Con::addVariable( "_forceAllMainThread", TypeBool, &ThreadPool::getForceAllMainThread(), "Force all work items to execute on main thread. turns this into a single-threaded system. Primarily useful to find whether malfunctions are caused by parallel execution or not.\n" + Con::addVariable( "_forceAllMainThread", TypeBool, &TorqueThreadPool::getForceAllMainThread(), "Force all work items to execute on main thread. turns this into a single-threaded system. Primarily useful to find whether malfunctions are caused by parallel execution or not.\n" "@ingroup platform" ); #if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE ) @@ -351,7 +351,7 @@ void StandardMainLoop::shutdown() EngineModuleManager::shutdownSystem(); - ThreadPool::GlobalThreadPool::deleteSingleton(); + TorqueThreadPool::GlobalThreadPool::deleteSingleton(); #ifdef TORQUE_ENABLE_VFS closeEmbeddedVFSArchive(); @@ -636,7 +636,7 @@ bool StandardMainLoop::doMainLoop() if(!Process::processEvents()) keepRunning = false; - ThreadPool::processMainThreadWorkItems(); + TorqueThreadPool::processMainThreadWorkItems(); Sampler::endFrame(); ConsoleValue::resetConversionBuffer(); PROFILE_END_NAMED(MainLoop); diff --git a/Engine/source/gfx/bitmap/imageUtils.cpp b/Engine/source/gfx/bitmap/imageUtils.cpp index 3426eecd3..1b289d31d 100644 --- a/Engine/source/gfx/bitmap/imageUtils.cpp +++ b/Engine/source/gfx/bitmap/imageUtils.cpp @@ -65,7 +65,7 @@ namespace ImageUtil } //Thread work job for compression - struct CompressJob : public ThreadPool::WorkItem + struct CompressJob : public TorqueThreadPool::WorkItem { S32 width; S32 height; @@ -124,7 +124,7 @@ namespace ImageUtil srcDDS->mFlags.set(DDSFile::CompressedData); //grab global thread pool - ThreadPool* pThreadPool = &ThreadPool::GLOBAL(); + TorqueThreadPool* pThreadPool = &TorqueThreadPool::GLOBAL(); if (cubemap) { diff --git a/Engine/source/gfx/video/theoraTexture.cpp b/Engine/source/gfx/video/theoraTexture.cpp index 83063e3be..3b1403a1b 100644 --- a/Engine/source/gfx/video/theoraTexture.cpp +++ b/Engine/source/gfx/video/theoraTexture.cpp @@ -336,7 +336,7 @@ void TheoraTexture::_onTextureEvent( GFXTexCallbackCode code ) { // Blast out work items and then release all texture locks. - ThreadPool::GLOBAL().flushWorkItems(); + TorqueThreadPool::GLOBAL().flushWorkItems(); mAsyncState->getFrameStream()->releaseTextureLocks(); // The Theora decoder does not implement seeking at the moment, diff --git a/Engine/source/gfx/video/theoraTexture.h b/Engine/source/gfx/video/theoraTexture.h index 8c0748068..899673ad4 100644 --- a/Engine/source/gfx/video/theoraTexture.h +++ b/Engine/source/gfx/video/theoraTexture.h @@ -177,7 +177,7 @@ class TheoraTexture : private IOutputStream< TheoraTextureFrame* >, /// FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream, - ThreadPool::Context* context ); + TorqueThreadPool::Context* context ); }; /// Stream filter that turns a stream of OggTheoraFrames into a buffered background stream of TheoraTextureFrame diff --git a/Engine/source/platform/async/asyncBufferedStream.h b/Engine/source/platform/async/asyncBufferedStream.h index 8c421c5c9..15e44a693 100644 --- a/Engine/source/platform/async/asyncBufferedStream.h +++ b/Engine/source/platform/async/asyncBufferedStream.h @@ -107,7 +107,7 @@ class AsyncBufferedInputStream : public IInputStreamFilter< T, Stream >, ElementList mBufferedElements; /// The thread pool to which read items are queued. - ThreadPool* mThreadPool; + TorqueThreadPool* mThreadPool; /// The thread context used for prioritizing read items in the pool. ThreadContext* mThreadContext; @@ -132,7 +132,7 @@ class AsyncBufferedInputStream : public IInputStreamFilter< T, Stream >, U32 numSourceElementsToRead = 0, U32 numReadAhead = DEFAULT_STREAM_LOOKAHEAD, bool isLooping = false, - ThreadPool* pool = &ThreadPool::GLOBAL(), + TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(), ThreadContext* context = ThreadContext::ROOT_CONTEXT() ); virtual ~AsyncBufferedInputStream(); @@ -162,7 +162,7 @@ AsyncBufferedInputStream< T, Stream >::AsyncBufferedInputStream U32 numSourceElementsToRead, U32 numReadAhead, bool isLooping, - ThreadPool* threadPool, + TorqueThreadPool* threadPool, ThreadContext* threadContext ) : Parent( stream ), mIsLooping( isLooping ), @@ -321,7 +321,7 @@ class AsyncBufferedReadItem : public ThreadWorkItem /// AsyncBufferedReadItem( const AsyncStreamRef& asyncStream, - ThreadPool::Context* context = NULL + TorqueThreadPool::Context* context = NULL ) : Parent( context ), mAsyncStream( asyncStream ), @@ -376,7 +376,7 @@ class AsyncSingleBufferedInputStream : public AsyncBufferedInputStream< T, Strea U32 numSourceElementsToRead = 0, U32 numReadAhead = Parent::DEFAULT_STREAM_LOOKAHEAD, bool isLooping = false, - ThreadPool* pool = &ThreadPool::GLOBAL(), + TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(), ThreadContext* context = ThreadContext::ROOT_CONTEXT() ) : Parent( stream, numSourceElementsToRead, diff --git a/Engine/source/platform/async/asyncPacketStream.h b/Engine/source/platform/async/asyncPacketStream.h index 374118784..517990a41 100644 --- a/Engine/source/platform/async/asyncPacketStream.h +++ b/Engine/source/platform/async/asyncPacketStream.h @@ -113,7 +113,7 @@ class AsyncPacketBufferedInputStream : public AsyncBufferedInputStream< Packet*, PacketReadItem( const ThreadSafeRef< AsyncPacketBufferedInputStream< Stream, Packet > >& asyncStream, PacketType* packet, U32 numElements, - ThreadPool::Context* context = NULL ) + TorqueThreadPool::Context* context = NULL ) : Parent( asyncStream->getSourceStream(), numElements, 0, *packet, false, 0, context ), mAsyncStream( asyncStream ), mPacket( packet ) {} @@ -227,7 +227,7 @@ class AsyncPacketBufferedInputStream : public AsyncBufferedInputStream< Packet*, U32 numSourceElementsToRead = 0, U32 numReadAhead = Parent::DEFAULT_STREAM_LOOKAHEAD, bool isLooping = false, - ThreadPool* pool = &ThreadPool::GLOBAL(), + TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(), ThreadContext* context = ThreadContext::ROOT_CONTEXT() ); /// @return the size of stream packets returned by this stream in number of elements. @@ -241,7 +241,7 @@ AsyncPacketBufferedInputStream< Stream, Packet >::AsyncPacketBufferedInputStream U32 numSourceElementsToRead, U32 numReadAhead, bool isLooping, - ThreadPool* threadPool, + TorqueThreadPool* threadPool, ThreadContext* threadContext ) : Parent( stream, numSourceElementsToRead, numReadAhead, isLooping, threadPool, threadContext ), mPacketSize( packetSize ), diff --git a/Engine/source/platform/platformNetAsync.cpp b/Engine/source/platform/platformNetAsync.cpp index 1a2a33df2..57873a312 100644 --- a/Engine/source/platform/platformNetAsync.cpp +++ b/Engine/source/platform/platformNetAsync.cpp @@ -65,11 +65,11 @@ struct NetAsync::NameLookupRequest /// Work item issued to the thread pool for each lookup request. -struct NetAsync::NameLookupWorkItem : public ThreadPool::WorkItem +struct NetAsync::NameLookupWorkItem : public TorqueThreadPool::WorkItem { - typedef ThreadPool::WorkItem Parent; + typedef TorqueThreadPool::WorkItem Parent; - NameLookupWorkItem( NameLookupRequest& request, ThreadPool::Context* context = 0 ) + NameLookupWorkItem( NameLookupRequest& request, TorqueThreadPool::Context* context = 0 ) : Parent( context ), mRequest( request ) { @@ -133,7 +133,7 @@ void NetAsync::queueLookup(const char* remoteAddr, NetSocket socket) dStrncpy(lookupRequest.remoteAddr, remoteAddr, sizeof(lookupRequest.remoteAddr)); ThreadSafeRef< NameLookupWorkItem > workItem( new NameLookupWorkItem( lookupRequest ) ); - ThreadPool::GLOBAL().queueWorkItem( workItem ); + TorqueThreadPool::GLOBAL().queueWorkItem( workItem ); } bool NetAsync::checkLookup(NetSocket socket, void* out_h_addr, diff --git a/Engine/source/platform/threads/threadPool.cpp b/Engine/source/platform/threads/threadPool.cpp index 42f262726..a3e4f9266 100644 --- a/Engine/source/platform/threads/threadPool.cpp +++ b/Engine/source/platform/threads/threadPool.cpp @@ -34,11 +34,11 @@ // ThreadPool::Context. //============================================================================= -ThreadPool::Context ThreadPool::Context::smRootContext( "ROOT", NULL, 1.0 ); +TorqueThreadPool::Context TorqueThreadPool::Context::smRootContext( "ROOT", NULL, 1.0 ); //-------------------------------------------------------------------------- -ThreadPool::Context::Context( const char* name, ThreadPool::Context* parent, F32 priorityBias ) +TorqueThreadPool::Context::Context( const char* name, TorqueThreadPool::Context* parent, F32 priorityBias ) : mParent( parent ), mName( name ), mChildren( 0 ), @@ -55,7 +55,7 @@ ThreadPool::Context::Context( const char* name, ThreadPool::Context* parent, F32 //-------------------------------------------------------------------------- -ThreadPool::Context::~Context() +TorqueThreadPool::Context::~Context() { if( mParent ) for( Context* context = mParent->mChildren, *prev = 0; context != 0; prev = context, context = context->mSibling ) @@ -70,7 +70,7 @@ ThreadPool::Context::~Context() //-------------------------------------------------------------------------- -ThreadPool::Context* ThreadPool::Context::getChild( const char* name ) +TorqueThreadPool::Context* TorqueThreadPool::Context::getChild( const char* name ) { for( Context* child = getChildren(); child != 0; child = child->getSibling() ) if( dStricmp( child->getName(), name ) == 0 ) @@ -80,7 +80,7 @@ ThreadPool::Context* ThreadPool::Context::getChild( const char* name ) //-------------------------------------------------------------------------- -F32 ThreadPool::Context::getAccumulatedPriorityBias() +F32 TorqueThreadPool::Context::getAccumulatedPriorityBias() { if( !mAccumulatedPriorityBias ) updateAccumulatedPriorityBiases(); @@ -89,7 +89,7 @@ F32 ThreadPool::Context::getAccumulatedPriorityBias() //-------------------------------------------------------------------------- -void ThreadPool::Context::setPriorityBias( F32 value ) +void TorqueThreadPool::Context::setPriorityBias( F32 value ) { mPriorityBias = value; mAccumulatedPriorityBias = 0.0; @@ -97,7 +97,7 @@ void ThreadPool::Context::setPriorityBias( F32 value ) //-------------------------------------------------------------------------- -void ThreadPool::Context::updateAccumulatedPriorityBiases() +void TorqueThreadPool::Context::updateAccumulatedPriorityBiases() { // Update our own priority bias. @@ -117,7 +117,7 @@ void ThreadPool::Context::updateAccumulatedPriorityBiases() //-------------------------------------------------------------------------- -void ThreadPool::WorkItem::process() +void TorqueThreadPool::WorkItem::process() { execute(); mExecuted = true; @@ -125,14 +125,14 @@ void ThreadPool::WorkItem::process() //-------------------------------------------------------------------------- -bool ThreadPool::WorkItem::isCancellationRequested() +bool TorqueThreadPool::WorkItem::isCancellationRequested() { return false; } //-------------------------------------------------------------------------- -bool ThreadPool::WorkItem::cancellationPoint() +bool TorqueThreadPool::WorkItem::cancellationPoint() { if( isCancellationRequested() ) { @@ -145,7 +145,7 @@ bool ThreadPool::WorkItem::cancellationPoint() //-------------------------------------------------------------------------- -F32 ThreadPool::WorkItem::getPriority() +F32 TorqueThreadPool::WorkItem::getPriority() { return 1.0; } @@ -160,7 +160,7 @@ F32 ThreadPool::WorkItem::getPriority() /// @see ThreadSafePriorityQueueWithUpdate /// @see ThreadPool::WorkItem /// -struct ThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > +struct TorqueThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > { typedef ThreadSafeRef< WorkItem > Parent; @@ -172,7 +172,7 @@ struct ThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > F32 getPriority(); }; -inline bool ThreadPool::WorkItemWrapper::isAlive() +inline bool TorqueThreadPool::WorkItemWrapper::isAlive() { WorkItem* item = ptr(); if( !item ) @@ -186,7 +186,7 @@ inline bool ThreadPool::WorkItemWrapper::isAlive() return true; } -inline F32 ThreadPool::WorkItemWrapper::getPriority() +inline F32 TorqueThreadPool::WorkItemWrapper::getPriority() { WorkItem* item = ptr(); AssertFatal( item != 0, "ThreadPool::WorkItemWrapper::getPriority - called on dead item" ); @@ -201,20 +201,20 @@ inline F32 ThreadPool::WorkItemWrapper::getPriority() /// /// -struct ThreadPool::WorkerThread : public Thread +struct TorqueThreadPool::WorkerThread : public Thread { - WorkerThread( ThreadPool* pool, U32 index ); + WorkerThread( TorqueThreadPool* pool, U32 index ); WorkerThread* getNext(); void run( void* arg = 0 ) override; private: U32 mIndex; - ThreadPool* mPool; + TorqueThreadPool* mPool; WorkerThread* mNext; }; -ThreadPool::WorkerThread::WorkerThread( ThreadPool* pool, U32 index ) +TorqueThreadPool::WorkerThread::WorkerThread( TorqueThreadPool* pool, U32 index ) : mIndex( index ), mPool( pool ) { @@ -224,12 +224,12 @@ ThreadPool::WorkerThread::WorkerThread( ThreadPool* pool, U32 index ) pool->mThreads = this; } -inline ThreadPool::WorkerThread* ThreadPool::WorkerThread::getNext() +inline TorqueThreadPool::WorkerThread* TorqueThreadPool::WorkerThread::getNext() { return mNext; } -void ThreadPool::WorkerThread::run( void* arg ) +void TorqueThreadPool::WorkerThread::run( void* arg ) { #ifdef TORQUE_DEBUG { @@ -300,13 +300,13 @@ void ThreadPool::WorkerThread::run( void* arg ) // ThreadPool. //============================================================================= -bool ThreadPool::smForceAllMainThread; -U32 ThreadPool::smMainThreadTimeMS; -ThreadPool::QueueType ThreadPool::smMainThreadQueue; +bool TorqueThreadPool::smForceAllMainThread; +U32 TorqueThreadPool::smMainThreadTimeMS; +TorqueThreadPool::QueueType TorqueThreadPool::smMainThreadQueue; //-------------------------------------------------------------------------- -ThreadPool::ThreadPool( const char* name, U32 numThreads ) +TorqueThreadPool::TorqueThreadPool( const char* name, U32 numThreads ) : mName( name ), mNumThreads( numThreads ), mNumThreadsAwake( 0 ), @@ -347,14 +347,14 @@ ThreadPool::ThreadPool( const char* name, U32 numThreads ) //-------------------------------------------------------------------------- -ThreadPool::~ThreadPool() +TorqueThreadPool::~TorqueThreadPool() { shutdown(); } //-------------------------------------------------------------------------- -void ThreadPool::shutdown() +void TorqueThreadPool::shutdown() { const U32 numThreads = mNumThreads; @@ -387,7 +387,7 @@ void ThreadPool::shutdown() //-------------------------------------------------------------------------- -void ThreadPool::queueWorkItem( WorkItem* item ) +void TorqueThreadPool::queueWorkItem( WorkItem* item ) { bool executeRightAway = ( getForceAllMainThread() ); #ifdef DEBUG_SPEW @@ -410,7 +410,7 @@ void ThreadPool::queueWorkItem( WorkItem* item ) //-------------------------------------------------------------------------- -void ThreadPool::flushWorkItems( S32 timeOut ) +void TorqueThreadPool::flushWorkItems( S32 timeOut ) { AssertFatal( mNumThreads, "ThreadPool::flushWorkItems() - no worker threads in pool" ); @@ -432,7 +432,7 @@ void ThreadPool::flushWorkItems( S32 timeOut ) } } -void ThreadPool::waitForAllItems( S32 timeOut ) +void TorqueThreadPool::waitForAllItems( S32 timeOut ) { U32 endTime = 0; if( timeOut != -1 ) @@ -454,14 +454,14 @@ void ThreadPool::waitForAllItems( S32 timeOut ) //-------------------------------------------------------------------------- -void ThreadPool::queueWorkItemOnMainThread( WorkItem* item ) +void TorqueThreadPool::queueWorkItemOnMainThread( WorkItem* item ) { smMainThreadQueue.insert( item->getPriority(), item ); } //-------------------------------------------------------------------------- -void ThreadPool::processMainThreadWorkItems() +void TorqueThreadPool::processMainThreadWorkItems() { AssertFatal( ThreadManager::isMainThread(), "ThreadPool::processMainThreadWorkItems - this function must only be called on the main thread" ); diff --git a/Engine/source/platform/threads/threadPool.h b/Engine/source/platform/threads/threadPool.h index 5fb8cc60b..59abc8620 100644 --- a/Engine/source/platform/threads/threadPool.h +++ b/Engine/source/platform/threads/threadPool.h @@ -70,7 +70,7 @@ /// automatically being released once the last concurrent work item has been /// processed or discarded. /// -class ThreadPool +class TorqueThreadPool { public: @@ -298,9 +298,9 @@ class ThreadPool /// will be based on the number of CPU cores available. /// /// @param numThreads Number of threads to create or zero for default. - ThreadPool( const char* name, U32 numThreads = 0 ); + TorqueThreadPool( const char* name, U32 numThreads = 0 ); - ~ThreadPool(); + ~TorqueThreadPool(); /// Manually shutdown threads outside of static destructors. void shutdown(); @@ -397,16 +397,16 @@ class ThreadPool } /// Return the global thread pool singleton. - static ThreadPool& GLOBAL(); + static TorqueThreadPool& GLOBAL(); }; -typedef ThreadPool::Context ThreadContext; -typedef ThreadPool::WorkItem ThreadWorkItem; +typedef TorqueThreadPool::Context ThreadContext; +typedef TorqueThreadPool::WorkItem ThreadWorkItem; -struct ThreadPool::GlobalThreadPool : public ThreadPool, public ManagedSingleton< GlobalThreadPool > +struct TorqueThreadPool::GlobalThreadPool : public TorqueThreadPool, public ManagedSingleton< GlobalThreadPool > { - typedef ThreadPool Parent; + typedef TorqueThreadPool Parent; GlobalThreadPool() : Parent( "GLOBAL" ) {} @@ -415,7 +415,7 @@ struct ThreadPool::GlobalThreadPool : public ThreadPool, public ManagedSingleton static const char* getSingletonName() { return "GlobalThreadPool"; } }; -inline ThreadPool& ThreadPool::GLOBAL() +inline TorqueThreadPool& TorqueThreadPool::GLOBAL() { return *( GlobalThreadPool::instance() ); } diff --git a/Engine/source/platform/threads/threadPoolAsyncIO.h b/Engine/source/platform/threads/threadPoolAsyncIO.h index 35738d091..7e78f1752 100644 --- a/Engine/source/platform/threads/threadPoolAsyncIO.h +++ b/Engine/source/platform/threads/threadPoolAsyncIO.h @@ -54,7 +54,7 @@ /// /// @param T Type of elements being streamed. template< typename T, class Stream > -class AsyncIOItem : public ThreadPool::WorkItem +class AsyncIOItem : public TorqueThreadPool::WorkItem { public: diff --git a/Engine/source/platformWin32/winRedbook.cpp b/Engine/source/platformWin32/winRedbook.cpp index c363d69a7..83f62392b 100644 --- a/Engine/source/platformWin32/winRedbook.cpp +++ b/Engine/source/platformWin32/winRedbook.cpp @@ -24,6 +24,8 @@ #include "platform/platformRedBook.h" #include "core/strings/unicode.h" #include "core/strings/stringFunctions.h" +#include +#include class Win32RedBookDevice : public RedBookDevice { diff --git a/Engine/source/sfx/sfxInternal.h b/Engine/source/sfx/sfxInternal.h index 7d2c49df2..e5109727a 100644 --- a/Engine/source/sfx/sfxInternal.h +++ b/Engine/source/sfx/sfxInternal.h @@ -358,11 +358,11 @@ enum /// @note Don't use this directly but rather use THREAD_POOL() instead. /// This way, the sound code may be easily switched to using a common /// pool later on. -class SFXThreadPool : public ThreadPool, public ManagedSingleton< SFXThreadPool > +class SFXThreadPool : public TorqueThreadPool, public ManagedSingleton< SFXThreadPool > { public: - typedef ThreadPool Parent; + typedef TorqueThreadPool Parent; /// Create a ThreadPool called "SFX" with two threads. SFXThreadPool() @@ -399,7 +399,7 @@ extern ThreadSafeRef< SFXBufferProcessList > gBufferUpdateList; extern ThreadSafeDeque< SFXBuffer* > gDeadBufferList; /// Return the thread pool used for SFX work. -inline ThreadPool& THREAD_POOL() +inline TorqueThreadPool& THREAD_POOL() { return *( SFXThreadPool::instance() ); } diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index d367c6b32..98975ca60 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -26,12 +26,12 @@ #include "ts/tsShapeConstruct.h" #include "console/engineAPI.h" -//----------------------------------------------------------------------------- - #define ENABLE_VHACD_IMPLEMENTATION 1 #define VHACD_DISABLE_THREADING 0 #include +//----------------------------------------------------------------------------- + static const Point3F sFacePlanes[] = { Point3F( -1.0f, 0.0f, 0.0f ), Point3F( 1.0f, 0.0f, 0.0f ), @@ -613,7 +613,7 @@ void MeshFit::fitK_DOP( const Vector& planes ) VHACD::IVHACD* iface = VHACD::CreateVHACD(); - iface->Compute((F32*)points.address(), points.size(), (U32*)pointIndices.address(), pointIndices.size(), p); + iface->Compute((F32*)points.address(), points.size(), (U32*)pointIndices.address(), pointIndices.size() / 3, p); // safety loop. while (!iface->IsReady()) @@ -630,11 +630,31 @@ void MeshFit::fitK_DOP( const Vector& planes ) MeshFit::Mesh& lastMesh = mMeshes.last(); lastMesh.type = MeshFit::Hull; lastMesh.transform.identity(); - lastMesh.tsmesh = createTriMesh((F32*)&ch.m_points, ch.m_points.size(), - (U32*)&ch.m_triangles, ch.m_triangles.size()); + + U32* indices = new U32[ch.m_triangles.size() * 3]; + for (U32 i = 0; i < ch.m_triangles.size(); i++) + { + indices[i * 3 + 0] = ch.m_triangles[i].mI0; + indices[i * 3 + 1] = ch.m_triangles[i].mI1; + indices[i * 3 + 2] = ch.m_triangles[i].mI2; + } + + F32* resultPts = new F32[ch.m_points.size() * 3]; + for (U32 i = 0; i < ch.m_points.size(); i++) + { + resultPts[i * 3 + 0] = ch.m_points[i].mX; + resultPts[i * 3 + 1] = ch.m_points[i].mY; + resultPts[i * 3 + 2] = ch.m_points[i].mZ; + } + + lastMesh.tsmesh = createTriMesh(resultPts, ch.m_points.size(), + indices, ch.m_triangles.size()); lastMesh.tsmesh->computeBounds(); iface->Release(); + + delete[] resultPts; + delete[] indices; } //--------------------------- @@ -654,9 +674,9 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh p.m_resolution = 10000; p.m_maxConvexHulls = depth; - VHACD::IVHACD* iface = VHACD::CreateVHACD(); + VHACD::IVHACD* iface = VHACD::CreateVHACD_ASYNC(); - iface->Compute((F32*)mVerts.address(), mVerts.size(), (U32*)mIndices.address(), mIndices.size(), p); + iface->Compute((F32*)mVerts.address(), mVerts.size(), mIndices.address(), mIndices.size() / 3, p); // safety loop. while (!iface->IsReady()) @@ -731,8 +751,28 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh MeshFit::Mesh& lastMesh = mMeshes.last(); lastMesh.type = MeshFit::Hull; lastMesh.transform.identity(); - lastMesh.tsmesh = createTriMesh((F32*)&ch.m_points, ch.m_points.size(), (U32*)&ch.m_triangles, ch.m_triangles.size()); + + U32* indices = new U32[ch.m_triangles.size() * 3]; + for (U32 i = 0; i < ch.m_triangles.size(); i++) + { + indices[i * 3 + 0] = ch.m_triangles[i].mI0; + indices[i * 3 + 1] = ch.m_triangles[i].mI1; + indices[i * 3 + 2] = ch.m_triangles[i].mI2; + } + + F32* points = new F32[ch.m_points.size() * 3]; + for (U32 i = 0; i < ch.m_points.size(); i++) + { + points[i * 3 + 0] = ch.m_points[i].mX; + points[i * 3 + 1] = ch.m_points[i].mY; + points[i * 3 + 2] = ch.m_points[i].mZ; + } + + lastMesh.tsmesh = createTriMesh(points, ch.m_points.size(), indices, ch.m_triangles.size()); lastMesh.tsmesh->computeBounds(); + + delete[] points; + delete[] indices; } } From 109d8c06e9bcf0197f7aca204b0853ac19642fb4 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 15:13:03 +0100 Subject: [PATCH 04/65] final cleanup final cleanup removal of the old convexDecomp library ADDDED: library for the floatmath from v-hacd resource, required for fit sphere/capsule/box functions --- Engine/lib/CMakeLists.txt | 1 + Engine/lib/convexDecomp/CMakeLists.txt | 7 - Engine/lib/convexDecomp/NvConcavityVolume.cpp | 252 - Engine/lib/convexDecomp/NvConcavityVolume.h | 78 - .../convexDecomp/NvConvexDecomposition.cpp | 788 --- .../lib/convexDecomp/NvConvexDecomposition.h | 111 - Engine/lib/convexDecomp/NvFloatMath.cpp | 74 - Engine/lib/convexDecomp/NvFloatMath.h | 586 -- Engine/lib/convexDecomp/NvFloatMath.inl | 5607 ----------------- Engine/lib/convexDecomp/NvHashMap.h | 1905 ------ .../convexDecomp/NvMeshIslandGeneration.cpp | 783 --- .../lib/convexDecomp/NvMeshIslandGeneration.h | 91 - Engine/lib/convexDecomp/NvRayCast.cpp | 153 - Engine/lib/convexDecomp/NvRayCast.h | 79 - .../lib/convexDecomp/NvRemoveTjunctions.cpp | 713 --- Engine/lib/convexDecomp/NvRemoveTjunctions.h | 110 - Engine/lib/convexDecomp/NvSimpleTypes.h | 189 - Engine/lib/convexDecomp/NvSplitMesh.cpp | 224 - Engine/lib/convexDecomp/NvSplitMesh.h | 88 - Engine/lib/convexDecomp/NvStanHull.cpp | 3464 ---------- Engine/lib/convexDecomp/NvStanHull.h | 201 - Engine/lib/convexDecomp/NvThreadConfig.cpp | 511 -- Engine/lib/convexDecomp/NvThreadConfig.h | 119 - Engine/lib/convexDecomp/NvUserMemAlloc.h | 81 - Engine/lib/convexDecomp/readme.txt | 38 - Engine/lib/convexDecomp/wavefront.cpp | 852 --- Engine/lib/convexDecomp/wavefront.h | 77 - Engine/lib/convexMath/CMakeLists.txt | 3 + Engine/lib/convexMath/FloatMath.cpp | 17 + Engine/lib/convexMath/FloatMath.h | 525 ++ Engine/lib/convexMath/FloatMath.inl | 5280 ++++++++++++++++ Engine/source/ts/tsMeshFit.cpp | 31 +- Tools/CMake/torque_configs.cmake | 2 +- 33 files changed, 5851 insertions(+), 17189 deletions(-) delete mode 100644 Engine/lib/convexDecomp/CMakeLists.txt delete mode 100644 Engine/lib/convexDecomp/NvConcavityVolume.cpp delete mode 100644 Engine/lib/convexDecomp/NvConcavityVolume.h delete mode 100644 Engine/lib/convexDecomp/NvConvexDecomposition.cpp delete mode 100644 Engine/lib/convexDecomp/NvConvexDecomposition.h delete mode 100644 Engine/lib/convexDecomp/NvFloatMath.cpp delete mode 100644 Engine/lib/convexDecomp/NvFloatMath.h delete mode 100644 Engine/lib/convexDecomp/NvFloatMath.inl delete mode 100644 Engine/lib/convexDecomp/NvHashMap.h delete mode 100644 Engine/lib/convexDecomp/NvMeshIslandGeneration.cpp delete mode 100644 Engine/lib/convexDecomp/NvMeshIslandGeneration.h delete mode 100644 Engine/lib/convexDecomp/NvRayCast.cpp delete mode 100644 Engine/lib/convexDecomp/NvRayCast.h delete mode 100644 Engine/lib/convexDecomp/NvRemoveTjunctions.cpp delete mode 100644 Engine/lib/convexDecomp/NvRemoveTjunctions.h delete mode 100644 Engine/lib/convexDecomp/NvSimpleTypes.h delete mode 100644 Engine/lib/convexDecomp/NvSplitMesh.cpp delete mode 100644 Engine/lib/convexDecomp/NvSplitMesh.h delete mode 100644 Engine/lib/convexDecomp/NvStanHull.cpp delete mode 100644 Engine/lib/convexDecomp/NvStanHull.h delete mode 100644 Engine/lib/convexDecomp/NvThreadConfig.cpp delete mode 100644 Engine/lib/convexDecomp/NvThreadConfig.h delete mode 100644 Engine/lib/convexDecomp/NvUserMemAlloc.h delete mode 100644 Engine/lib/convexDecomp/readme.txt delete mode 100644 Engine/lib/convexDecomp/wavefront.cpp delete mode 100644 Engine/lib/convexDecomp/wavefront.h create mode 100644 Engine/lib/convexMath/CMakeLists.txt create mode 100644 Engine/lib/convexMath/FloatMath.cpp create mode 100644 Engine/lib/convexMath/FloatMath.h create mode 100644 Engine/lib/convexMath/FloatMath.inl diff --git a/Engine/lib/CMakeLists.txt b/Engine/lib/CMakeLists.txt index f101dba06..1784b3685 100644 --- a/Engine/lib/CMakeLists.txt +++ b/Engine/lib/CMakeLists.txt @@ -112,6 +112,7 @@ mark_as_advanced(SDL_XINPUT) add_subdirectory(sdl ${TORQUE_LIB_TARG_DIRECTORY}/sdl2 EXCLUDE_FROM_ALL) add_subdirectory(nativeFileDialogs ${TORQUE_LIB_TARG_DIRECTORY}/nfd EXCLUDE_FROM_ALL) +add_subdirectory(convexMath ${TORQUE_LIB_TARG_DIRECTORY}/convexMath EXCLUDE_FROM_ALL) # Assimp advanced_option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF) diff --git a/Engine/lib/convexDecomp/CMakeLists.txt b/Engine/lib/convexDecomp/CMakeLists.txt deleted file mode 100644 index 0a58bb89f..000000000 --- a/Engine/lib/convexDecomp/CMakeLists.txt +++ /dev/null @@ -1,7 +0,0 @@ -file(GLOB CONVEX_DECOMP_SOURCES "*.cpp") -add_library(convexDecomp STATIC ${CONVEX_DECOMP_SOURCES}) -target_include_directories(convexDecomp PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -if (UNIX AND NOT APPLE) - target_compile_definitions(convexDecomp PUBLIC LINUX) -endif (UNIX AND NOT APPLE) \ No newline at end of file diff --git a/Engine/lib/convexDecomp/NvConcavityVolume.cpp b/Engine/lib/convexDecomp/NvConcavityVolume.cpp deleted file mode 100644 index 4cda8811f..000000000 --- a/Engine/lib/convexDecomp/NvConcavityVolume.cpp +++ /dev/null @@ -1,252 +0,0 @@ -/* - -NvConcavityVolume.cpp : This is a code snippet that computes the volume of concavity of a traingle mesh. - -*/ -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#define SHOW_DEBUG 0 -#if SHOW_DEBUG -#include "RenderDebug.h" -#endif -#include "NvConcavityVolume.h" -#include "NvFloatMath.h" -#include "NvRayCast.h" -#include - -#pragma warning(disable:4100 4189 4505 4127 4101) - -namespace CONVEX_DECOMPOSITION -{ - -bool raycast(const NxF32 *p1,const NxF32 *normal,NxF32 *dest,iRayCast *cast_hull,iRayCast *cast_mesh) -{ - bool ret = true; - - NxF32 hit_hull[3]; - NxF32 hit_hullNormal[3]; - - NxF32 hit_mesh[3]; - NxF32 hit_meshNormal[3]; - - bool hitHull = cast_hull->castRay(p1,normal,hit_hull,hit_hullNormal); - bool hitMesh = cast_mesh->castRay(p1,normal,hit_mesh,hit_meshNormal); - - if ( hitMesh ) - { - float dot = fm_dot(normal,hit_meshNormal); - if ( dot < 0 ) // skip if we hit an internal face of the mesh when projection out towards the convex hull. - { - ret = false; - } - else - { - NxF32 d1 = fm_distanceSquared(p1,hit_mesh); - NxF32 d2 = fm_distanceSquared(p1,hit_hull); - if ( d1 < d2 ) - { - dest[0] = hit_mesh[0]; - dest[1] = hit_mesh[1]; - dest[2] = hit_mesh[2]; - } - else - { - dest[0] = hit_hull[0]; - dest[1] = hit_hull[1]; - dest[2] = hit_hull[2]; - } - } - } - else if ( hitHull ) - { - dest[0] = hit_hull[0]; - dest[1] = hit_hull[1]; - dest[2] = hit_hull[2]; - } - else - { - ret = false; - } - - - return ret; -} - -void addTri(NxU32 *indices,NxU32 i1,NxU32 i2,NxU32 i3,NxU32 &tcount) -{ - indices[tcount*3+0] = i1; - indices[tcount*3+1] = i2; - indices[tcount*3+2] = i3; - tcount++; -} - -NxF32 computeConcavityVolume(NxU32 vcount_hull, - const NxF32 *vertices_hull, - NxU32 tcount_hull, - const NxU32 *indices_hull, - NxU32 vcount_mesh, - const NxF32 *vertices_mesh, - NxU32 tcount_mesh, - const NxU32 *indices_mesh) -{ - NxF32 total_volume = 0; - -#if SHOW_DEBUG - NVSHARE::gRenderDebug->pushRenderState(); - NVSHARE::gRenderDebug->setCurrentDisplayTime(150.0f); -#endif - - iRayCast *cast_hull = createRayCast(vertices_hull,tcount_hull,indices_hull); - iRayCast *cast_mesh = createRayCast(vertices_mesh,tcount_mesh,indices_mesh); - - - const NxU32 *indices = indices_mesh; -#if 0 - static NxU32 index = 0; - NxU32 i = index++; - indices = &indices[i*3]; -#else - for (NxU32 i=0; isetCurrentColor(0x0000FF,0xFFFFFF); - NVSHARE::gRenderDebug->addToCurrentState(NVSHARE::DebugRenderState::SolidWireShaded); - - for (NxU32 i=0; iDebugTri(p1,p2,p3); - } -#endif - } - indices+=3; - } -#if SHOW_DEBUG - NVSHARE::gRenderDebug->popRenderState(); -#endif - - releaseRayCast(cast_hull); - releaseRayCast(cast_mesh); - - return total_volume; -} - -}; // end of namespace diff --git a/Engine/lib/convexDecomp/NvConcavityVolume.h b/Engine/lib/convexDecomp/NvConcavityVolume.h deleted file mode 100644 index 18bbed073..000000000 --- a/Engine/lib/convexDecomp/NvConcavityVolume.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef NV_CONCAVITY_H - -#define NV_CONCAVITY_H - -/* - -NvConcavityVolume.h : This is a code snippet that computes the volume of concavity of a traingle mesh. - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "NvUserMemAlloc.h" - -namespace CONVEX_DECOMPOSITION -{ - -// computes the 'volume of concavity' of a triangle mesh projected against its surrounding convex hull. - -NxF32 computeConcavityVolume(NxU32 vcount_hull, - const NxF32 *vertices_hull, - NxU32 tcount_hull, - const NxU32 *indices_hull, - NxU32 vcount_mesh, - const NxF32 *vertices_mesh, - NxU32 tcount_mesh, - const NxU32 *indices_mesh); - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvConvexDecomposition.cpp b/Engine/lib/convexDecomp/NvConvexDecomposition.cpp deleted file mode 100644 index 386ac497c..000000000 --- a/Engine/lib/convexDecomp/NvConvexDecomposition.cpp +++ /dev/null @@ -1,788 +0,0 @@ - -/* - -NvConvexDecomposition.cpp : The main interface to the convex decomposition library. - -*/ - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include -#include -#include -#include -#include - -#include "NvConvexDecomposition.h" -#include "NvHashMap.h" -#include "NvFloatMath.h" -#include "NvRemoveTjunctions.h" -#include "NvMeshIslandGeneration.h" -#include "NvStanHull.h" -#include "NvConcavityVolume.h" -#include "NvSplitMesh.h" -#include "NvThreadConfig.h" - - -#pragma warning(disable:4996 4100 4189) - -namespace CONVEX_DECOMPOSITION -{ - - -#define GRANULARITY 0.0000000001f - -typedef CONVEX_DECOMPOSITION::Array< NxU32 > NxU32Array; - -class ConvexHull : public Memalloc -{ -public: - ConvexHull(NxU32 vcount,const NxF32 *vertices,NxU32 tcount,const NxU32 *indices) - { - mTested = false; - mVcount = vcount; - mTcount = tcount; - mVertices = 0; - mIndices = 0; - mHullVolume = 0; - if ( vcount ) - { - mVertices = (NxF32 *)MEMALLOC_MALLOC(sizeof(NxF32)*3*vcount); - memcpy(mVertices,vertices,sizeof(NxF32)*3*vcount); - } - if ( tcount ) - { - mIndices = (NxU32 *)MEMALLOC_MALLOC(sizeof(NxU32)*3*tcount); - memcpy(mIndices,indices,sizeof(NxU32)*3*tcount); - } - if ( mVcount && mTcount ) - { - mHullVolume = fm_computeMeshVolume( mVertices, mTcount, mIndices); - } - } - - ~ConvexHull(void) - { - reset(); - } - - void reset(void) - { - MEMALLOC_FREE(mVertices); - MEMALLOC_FREE(mIndices); - mVertices = 0; - mIndices = 0; - mVcount = 0; - mTcount = 0; - mHullVolume = 0; - } - - // return true if merging this hull with the 'mergeHull' produces a new convex hull which is no greater in volume than the - // mergeThresholdPercentage - bool canMerge(ConvexHull *mergeHull,NxF32 mergeThresholdPercent,NxU32 maxVertices,NxF32 skinWidth,NxF32 &percent) - { - bool ret = false; - - if ( mHullVolume > 0 && mergeHull->mHullVolume > 0 ) - { - NxU32 combineVcount = mVcount + mergeHull->mVcount; - NxF32 *vertices = (NxF32 *)MEMALLOC_MALLOC(sizeof(NxF32)*combineVcount*3); - NxF32 *dest = vertices; - const NxF32 *source = mVertices; - - for (NxU32 i=0; imVertices; - for (NxU32 i=0; imVcount; i++) - { - dest[0] = source[0]; - dest[1] = source[1]; - dest[2] = source[2]; - dest+=3; - source+=3; - } - - // create the combined convex hull. - HullDesc hd; - hd.mVcount = combineVcount; - hd.mVertices = vertices; - hd.mVertexStride = sizeof(NxF32)*3; - hd.mMaxVertices = maxVertices; - hd.mSkinWidth = skinWidth; - HullLibrary hl; - HullResult result; - hl.CreateConvexHull(hd,result); - - NxF32 combinedVolume = fm_computeMeshVolume(result.mOutputVertices, result.mNumFaces, result.mIndices ); - NxF32 seperateVolume = mHullVolume+mergeHull->mHullVolume; - - NxF32 percentMerge = 100 - (seperateVolume*100 / combinedVolume ); - - if ( percentMerge <= mergeThresholdPercent ) - { - percent = percentMerge; - ret = true; - } - MEMALLOC_FREE(vertices); - hl.ReleaseResult(result); - } - return ret; - } - - void merge(ConvexHull *mergeHull,NxU32 maxVertices,NxF32 skinWidth) - { - NxU32 combineVcount = mVcount + mergeHull->mVcount; - NxF32 *vertices = (NxF32 *)MEMALLOC_MALLOC(sizeof(NxF32)*combineVcount*3); - NxF32 *dest = vertices; - const NxF32 *source = mVertices; - - for (NxU32 i=0; imVertices; - for (NxU32 i=0; imVcount; i++) - { - dest[0] = source[0]; - dest[1] = source[1]; - dest[2] = source[2]; - dest+=3; - source+=3; - } - - // create the combined convex hull. - HullDesc hd; - hd.mVcount = combineVcount; - hd.mVertices = vertices; - hd.mVertexStride = sizeof(NxF32)*3; - hd.mMaxVertices = maxVertices; - hd.mSkinWidth = skinWidth; - HullLibrary hl; - HullResult result; - hl.CreateConvexHull(hd,result); - - reset(); - mergeHull->reset(); - mergeHull->mTested = true; // it's been tested. - mVcount = result.mNumOutputVertices; - mVertices = (NxF32 *)MEMALLOC_MALLOC(sizeof(NxF32)*3*mVcount); - memcpy(mVertices,result.mOutputVertices,sizeof(NxF32)*3*mVcount); - mTcount = result.mNumFaces; - mIndices = (NxU32 *)MEMALLOC_MALLOC(sizeof(NxU32)*mTcount*3); - memcpy(mIndices, result.mIndices, sizeof(NxU32)*mTcount*3); - - MEMALLOC_FREE(vertices); - hl.ReleaseResult(result); - } - - void setTested(bool state) - { - mTested = state; - } - - bool beenTested(void) const { return mTested; }; - - bool mTested; - NxF32 mHullVolume; - NxU32 mVcount; - NxF32 *mVertices; - NxU32 mTcount; - NxU32 *mIndices; -}; - -typedef Array< ConvexHull *> ConvexHullVector; - -class ConvexDecomposition : public iConvexDecomposition, public CONVEX_DECOMPOSITION::Memalloc, public ThreadInterface -{ -public: - ConvexDecomposition(void) - { - mVertexIndex = 0; - mComplete = false; - mCancel = false; - mThread = 0; - } - - ~ConvexDecomposition(void) - { - wait(); - reset(); - if ( mThread ) - { - tc_releaseThread(mThread); - } - } - - void wait(void) const - { - while ( mThread && !mComplete ); - } - - virtual void reset(void) // reset the input mesh data. - { - wait(); - if ( mVertexIndex ) - { - fm_releaseVertexIndex(mVertexIndex); - mVertexIndex = 0; - } - mIndices.clear(); - ConvexHullVector::Iterator i; - for (i=mHulls.begin(); i!=mHulls.end(); ++i) - { - ConvexHull *ch = (*i); - delete ch; - } - mHulls.clear(); - } - - virtual bool addTriangle(const NxF32 *p1,const NxF32 *p2,const NxF32 *p3) - { - bool ret = true; - wait(); - if ( mVertexIndex == 0 ) - { - mVertexIndex = fm_createVertexIndex(GRANULARITY,false); - } - - bool newPos; - NxU32 i1 = mVertexIndex->getIndex(p1,newPos); - NxU32 i2 = mVertexIndex->getIndex(p2,newPos); - NxU32 i3 = mVertexIndex->getIndex(p3,newPos); - - if ( i1 == i2 || i1 == i3 || i2 == i3 ) - { - ret = false; // triangle is degenerate - } - else - { - mIndices.pushBack(i1); - mIndices.pushBack(i2); - mIndices.pushBack(i3); - } - return ret; - } - - ConvexHull * getNonTested(void) const - { - ConvexHull *ret = 0; - for (NxU32 i=0; ibeenTested() ) - { - ret = ch; - break; - } - } - return ret; - } - - virtual NxU32 computeConvexDecomposition(NxF32 skinWidth, - NxU32 decompositionDepth, - NxU32 maxHullVertices, - NxF32 concavityThresholdPercent, - NxF32 mergeThresholdPercent, - NxF32 volumeSplitThresholdPercent, - bool useInitialIslandGeneration, - bool useIslandGeneration, - bool useThreads) - { - NxU32 ret = 0; - - if ( mThread ) - return 0; - - if ( mVertexIndex ) - { - - mSkinWidth = skinWidth; - mDecompositionDepth = decompositionDepth; - mMaxHullVertices = maxHullVertices; - mConcavityThresholdPercent = concavityThresholdPercent; - mMergeThresholdPercent = mergeThresholdPercent; - mVolumeSplitThresholdPercent = volumeSplitThresholdPercent; - mUseInitialIslandGeneration = useInitialIslandGeneration; - mUseIslandGeneration = false; // Not currently supported. useIslandGeneration; - mComplete = false; - mCancel = false; - - if ( useThreads ) - { - mThread = tc_createThread(this); - } - else - { - threadMain(); - ret = getHullCount(); - } - } - return ret; - } - - void performConvexDecomposition(NxU32 vcount, - const NxF32 *vertices, - NxU32 tcount, - const NxU32 *indices, - NxF32 skinWidth, - NxU32 decompositionDepth, - NxU32 maxHullVertices, - NxF32 concavityThresholdPercent, - NxF32 mergeThresholdPercent, - NxF32 volumeSplitThresholdPercent, - bool useInitialIslandGeneration, - bool useIslandGeneration, - NxU32 depth) - { - if ( mCancel ) return; - if ( depth >= decompositionDepth ) return; - - RemoveTjunctionsDesc desc; - desc.mVcount = vcount; - desc.mVertices = vertices; - desc.mTcount = tcount; - desc.mIndices = indices; - -#if 0 - RemoveTjunctions *rt = createRemoveTjunctions(); - rt->removeTjunctions(desc); -#else - - desc.mTcountOut = desc.mTcount; - desc.mIndicesOut = desc.mIndices; - -#endif - // ok..we now have a clean mesh without any tjunctions. - bool island = (depth == 0 ) ? useInitialIslandGeneration : useIslandGeneration; - if ( island ) - { - MeshIslandGeneration *mi = createMeshIslandGeneration(); - NxU32 icount = mi->islandGenerate(desc.mTcountOut,desc.mIndicesOut,desc.mVertices); - for (NxU32 i=0; igetIsland(i,tcount); - - baseConvexDecomposition(desc.mVcount,desc.mVertices, - tcount,indices, - skinWidth, - decompositionDepth, - maxHullVertices, - concavityThresholdPercent, - mergeThresholdPercent, - volumeSplitThresholdPercent, - useInitialIslandGeneration, - useIslandGeneration,depth); - } - releaseMeshIslandGeneration(mi); - } - else - { - baseConvexDecomposition(desc.mVcount,desc.mVertices,desc.mTcountOut, - desc.mIndicesOut, - skinWidth, - decompositionDepth, - maxHullVertices, - concavityThresholdPercent, - mergeThresholdPercent, - volumeSplitThresholdPercent, - useInitialIslandGeneration, - useIslandGeneration,depth); - } -#if 0 - releaseRemoveTjunctions(rt); -#endif - } - - virtual void baseConvexDecomposition(NxU32 vcount, - const NxF32 *vertices, - NxU32 tcount, - const NxU32 *indices, - NxF32 skinWidth, - NxU32 decompositionDepth, - NxU32 maxHullVertices, - NxF32 concavityThresholdPercent, - NxF32 mergeThresholdPercent, - NxF32 volumeSplitThresholdPercent, - bool useInitialIslandGeneration, - bool useIslandGeneration, - NxU32 depth) - { - - if ( mCancel ) return; - - bool split = false; // by default we do not split - - - NxU32 *out_indices = (NxU32 *)MEMALLOC_MALLOC( sizeof(NxU32)*tcount*3 ); - NxF32 *out_vertices = (NxF32 *)MEMALLOC_MALLOC( sizeof(NxF32)*3*vcount ); - - NxU32 out_vcount = fm_copyUniqueVertices( vcount, vertices, out_vertices, tcount, indices, out_indices ); - // get a copy of only the unique vertices which are actually being used. - - HullDesc hd; - hd.mVcount = out_vcount; - hd.mVertices = out_vertices; - hd.mVertexStride = sizeof(NxF32)*3; - hd.mMaxVertices = maxHullVertices; - hd.mSkinWidth = skinWidth; - HullLibrary hl; - HullResult result; - hl.CreateConvexHull(hd,result); - - NxF32 meshVolume = fm_computeMeshVolume(result.mOutputVertices, result.mNumFaces, result.mIndices ); - - if ( (depth+1) < decompositionDepth ) - { - // compute the volume of this mesh... - NxF32 percentVolume = (meshVolume*100)/mOverallMeshVolume; // what percentage of the overall mesh volume are we? - if ( percentVolume > volumeSplitThresholdPercent ) // this piece must be greater thant he volume split threshold percent - { - // ok..now we will compute the concavity... - NxF32 concave_volume = computeConcavityVolume(result.mNumOutputVertices, result.mOutputVertices, result.mNumFaces, result.mIndices, out_vcount, out_vertices, tcount, out_indices ); - NxF32 concave_percent = (concave_volume*100) / meshVolume; - if ( concave_percent >= concavityThresholdPercent ) - { - // ready to do split here.. - split = true; - } - } - } - - if ( !split ) - { - saveConvexHull(result.mNumOutputVertices,result.mOutputVertices,result.mNumFaces,result.mIndices); - } - - // Compute the best fit plane relative to the computed convex hull. - NxF32 plane[4]; - bool ok = fm_computeSplitPlane(result.mNumOutputVertices,result.mOutputVertices,result.mNumFaces,result.mIndices,plane); - assert(ok); - - hl.ReleaseResult(result); - MEMALLOC_FREE(out_indices); - MEMALLOC_FREE(out_vertices); - - if ( split ) - { - iSplitMesh *sm = createSplitMesh(); - - NvSplitMesh n; - n.mVcount = vcount; - n.mVertices = vertices; - n.mTcount = tcount; - n.mIndices = indices; - if ( ok ) - { - NvSplitMesh leftMesh; - NvSplitMesh rightMesh; - - sm->splitMesh(n,leftMesh,rightMesh,plane,GRANULARITY); - - if ( leftMesh.mTcount ) - { - performConvexDecomposition(leftMesh.mVcount, - leftMesh.mVertices, - leftMesh.mTcount, - leftMesh.mIndices, - skinWidth, - decompositionDepth, - maxHullVertices, - concavityThresholdPercent, - mergeThresholdPercent, - volumeSplitThresholdPercent, - useInitialIslandGeneration, - useIslandGeneration, - depth+1); - - } - if ( rightMesh.mTcount ) - { - performConvexDecomposition(rightMesh.mVcount, - rightMesh.mVertices, - rightMesh.mTcount, - rightMesh.mIndices, - skinWidth, - decompositionDepth, - maxHullVertices, - concavityThresholdPercent, - mergeThresholdPercent, - volumeSplitThresholdPercent, - useInitialIslandGeneration, - useIslandGeneration, - depth+1); - } - } - releaseSplitMesh(sm); - } - } - - // Copies only the vertices which are actually used. - // Then computes the convex hull around these used vertices. - // Next computes the volume of this convex hull. - // Frees up scratch memory and returns the volume of the convex hull around the source triangle mesh. - NxF32 computeHullMeshVolume(NxU32 vcount,const NxF32 *vertices,NxU32 tcount,const NxU32 *indices,NxU32 maxVertices,NxF32 skinWidth) - { - if ( mCancel ) return 0; - // first thing we should do is compute the overall mesh volume. - NxU32 *out_indices = (NxU32 *)MEMALLOC_MALLOC( sizeof(NxU32)*tcount*3 ); - NxF32 *out_vertices = (NxF32 *)MEMALLOC_MALLOC( sizeof(NxF32)*3*vcount ); - - NxU32 out_vcount = fm_copyUniqueVertices( vcount, vertices, out_vertices, tcount, indices, out_indices ); - // get a copy of only the unique vertices which are actually being used. - - HullDesc hd; - hd.mVcount = out_vcount; - hd.mVertices = out_vertices; - hd.mVertexStride = sizeof(NxF32)*3; - hd.mMaxVertices = maxVertices; - hd.mSkinWidth = skinWidth; - HullLibrary hl; - HullResult result; - hl.CreateConvexHull(hd,result); - - NxF32 volume = fm_computeMeshVolume(result.mOutputVertices, result.mNumFaces, result.mIndices ); - - hl.ReleaseResult(result); - MEMALLOC_FREE(out_indices); - MEMALLOC_FREE(out_vertices); - - return volume; - } - - - virtual bool isComputeComplete(void) // if building the convex hulls in a background thread, this returns true if it is complete. - { - bool ret = true; - - if ( mThread ) - { - ret = mComplete; - if ( ret ) - { - tc_releaseThread(mThread); - mThread = 0; - } - } - - return ret; - } - - - virtual NxU32 getHullCount(void) - { - NxU32 hullCount = 0; - wait(); - if ( mCancel ) - { - reset(); - } - for (NxU32 i=0; imTcount ) - { - hullCount++; - } - } - return hullCount; - } - - virtual bool getConvexHullResult(NxU32 hullIndex,ConvexHullResult &result) - { - bool ret = false; - - wait(); - NxU32 index = 0; - for (NxU32 i=0; imTcount ) - { - if ( hullIndex == index ) - { - ret = true; - result.mVcount = ch->mVcount; - result.mTcount = ch->mTcount; - result.mVertices = ch->mVertices; - result.mIndices = ch->mIndices; - break; - } - index++; - } - } - - return ret; - } - - void saveConvexHull(NxU32 vcount,const NxF32 *vertices,NxU32 tcount,const NxU32 *indices) - { - ConvexHull *ch = MEMALLOC_NEW(ConvexHull)(vcount,vertices,tcount,indices); - mHulls.pushBack(ch); - } - - virtual void threadMain(void) - { - mOverallMeshVolume = computeHullMeshVolume( mVertexIndex->getVcount(), - mVertexIndex->getVerticesFloat(), - mIndices.size()/3, - &mIndices[0], - mMaxHullVertices, mSkinWidth ); - - performConvexDecomposition(mVertexIndex->getVcount(),mVertexIndex->getVerticesFloat(), - mIndices.size()/3,&mIndices[0], - mSkinWidth, - mDecompositionDepth, - mMaxHullVertices, - mConcavityThresholdPercent, - mMergeThresholdPercent, - mVolumeSplitThresholdPercent, - mUseInitialIslandGeneration, - mUseIslandGeneration,0); - - if ( mHulls.size() && !mCancel ) - { - // While convex hulls can be merged... - ConvexHull *ch = getNonTested(); - while ( ch && !mCancel ) - { - // Sort all convex hulls by volume, largest to smallest. - NxU32 hullCount = mHulls.size(); - ConvexHull *bestHull = 0; - NxF32 bestPercent = 100; - - for (NxU32 i=0; ibeenTested() && mergeHull != ch ) - { - NxF32 percent; - if ( ch->canMerge(mergeHull,mMergeThresholdPercent,mMaxHullVertices,mSkinWidth,percent) ) - { - if ( percent < bestPercent ) - { - bestHull = mergeHull; - bestPercent = percent; - } - } - } - } - - if ( bestHull ) - { - ch->merge(bestHull,mMaxHullVertices,mSkinWidth); - } - else - { - ch->setTested(true); - } - - ch = getNonTested(); - } - } - mComplete = true; - } - - virtual bool cancelCompute(void) // cause background thread computation to abort early. Will return no results. Use 'isComputeComplete' to confirm the thread is done. - { - bool ret = false; - - if ( mThread && !mComplete ) - { - mCancel = true; - ret = true; - } - - return ret; - } - -private: - bool mComplete; - bool mCancel; - fm_VertexIndex *mVertexIndex; - NxU32Array mIndices; - NxF32 mOverallMeshVolume; - ConvexHullVector mHulls; - Thread *mThread; - - NxF32 mSkinWidth; - NxU32 mDecompositionDepth; - NxU32 mMaxHullVertices; - NxF32 mConcavityThresholdPercent; - NxF32 mMergeThresholdPercent; - NxF32 mVolumeSplitThresholdPercent; - bool mUseInitialIslandGeneration; - bool mUseIslandGeneration; - -}; - - -iConvexDecomposition * createConvexDecomposition(void) -{ - ConvexDecomposition *cd = MEMALLOC_NEW(ConvexDecomposition); - return static_cast< iConvexDecomposition *>(cd); - -} - -void releaseConvexDecomposition(iConvexDecomposition *ic) -{ - ConvexDecomposition *cd = static_cast< ConvexDecomposition *>(ic); - delete cd; -} - -}; // end of namespace diff --git a/Engine/lib/convexDecomp/NvConvexDecomposition.h b/Engine/lib/convexDecomp/NvConvexDecomposition.h deleted file mode 100644 index bf5ded4b1..000000000 --- a/Engine/lib/convexDecomp/NvConvexDecomposition.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef CONVEX_DECOMPOSITION_H - -#define CONVEX_DECOMPOSITION_H - -/* - -NvConvexDecomposition.h : The main interface to the convex decomposition library. - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "NvSimpleTypes.h" - -namespace CONVEX_DECOMPOSITION -{ - -struct ConvexHullResult -{ - NxU32 mVcount; // number of vertices. - NxF32 *mVertices; // vertex positions. - NxU32 mTcount; // number of triangles. - NxU32 *mIndices; // indexed triangle list. -}; - -class iConvexDecomposition -{ -public: - virtual void reset(void) = 0; // reset the input mesh data. - - virtual bool addTriangle(const NxF32 *p1,const NxF32 *p2,const NxF32 *p3) = 0; // add the input mesh one triangle at a time. - - virtual NxU32 computeConvexDecomposition(NxF32 skinWidth=0, // Skin width on the convex hulls generated - NxU32 decompositionDepth=8, // recursion depth for convex decomposition. - NxU32 maxHullVertices=64, // maximum number of vertices in output convex hulls. - NxF32 concavityThresholdPercent=0.1f, // The percentage of concavity allowed without causing a split to occur. - NxF32 mergeThresholdPercent=30.0f, // The percentage of volume difference allowed to merge two convex hulls. - NxF32 volumeSplitThresholdPercent=0.1f, // The percentage of the total volume of the object above which splits will still occur. - bool useInitialIslandGeneration=true, // whether or not to perform initial island generation on the input mesh. - bool useIslandGeneration=false, // Whether or not to perform island generation at each split. Currently disabled due to bug in RemoveTjunctions - bool useBackgroundThread=true) = 0; // Whether or not to compute the convex decomposition in a background thread, the default is true. - - virtual bool isComputeComplete(void) = 0; // if building the convex hulls in a background thread, this returns true if it is complete. - - virtual bool cancelCompute(void) = 0; // cause background thread computation to abort early. Will return no results. Use 'isComputeComplete' to confirm the thread is done. - - - virtual NxU32 getHullCount(void) = 0; // returns the number of convex hulls produced. - virtual bool getConvexHullResult(NxU32 hullIndex,ConvexHullResult &result) = 0; // returns each convex hull result. - -protected: - virtual ~iConvexDecomposition(void) - { - } - -}; - - -iConvexDecomposition * createConvexDecomposition(void); -void releaseConvexDecomposition(iConvexDecomposition *ic); - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvFloatMath.cpp b/Engine/lib/convexDecomp/NvFloatMath.cpp deleted file mode 100644 index 91979b1ca..000000000 --- a/Engine/lib/convexDecomp/NvFloatMath.cpp +++ /dev/null @@ -1,74 +0,0 @@ -// a set of routines that let you do common 3d math -// operations without any vector, matrix, or quaternion -// classes or templates. -// -// a vector (or point) is a 'NxF32 *' to 3 floating point numbers. -// a matrix is a 'NxF32 *' to an array of 16 floating point numbers representing a 4x4 transformation matrix compatible with D3D or OGL -// a quaternion is a 'NxF32 *' to 4 floats representing a quaternion x,y,z,w -// -// - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include -#include -#include -#include -#include -#include - -#include "NvFloatMath.h" - -#define REAL NxF32 - -#include "NvFloatMath.inl" - -#undef REAL -#define REAL NxF64 - -#include "NvFloatMath.inl" diff --git a/Engine/lib/convexDecomp/NvFloatMath.h b/Engine/lib/convexDecomp/NvFloatMath.h deleted file mode 100644 index 3b4a885e6..000000000 --- a/Engine/lib/convexDecomp/NvFloatMath.h +++ /dev/null @@ -1,586 +0,0 @@ -#ifndef NV_FLOAT_MATH_H - -#define NV_FLOAT_MATH_H - -#include "NvUserMemAlloc.h" - -// a set of routines that let you do common 3d math -// operations without any vector, matrix, or quaternion -// classes or templates. -// -// a vector (or point) is a 'NxF32 *' to 3 floating point numbers. -// a matrix is a 'NxF32 *' to an array of 16 floating point numbers representing a 4x4 transformation matrix compatible with D3D or OGL -// a quaternion is a 'NxF32 *' to 4 floats representing a quaternion x,y,z,w -// -// - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include - -namespace CONVEX_DECOMPOSITION -{ - -enum FM_ClipState -{ - FMCS_XMIN = (1<<0), - FMCS_XMAX = (1<<1), - FMCS_YMIN = (1<<2), - FMCS_YMAX = (1<<3), - FMCS_ZMIN = (1<<4), - FMCS_ZMAX = (1<<5), -}; - -enum FM_Axis -{ - FM_XAXIS = (1<<0), - FM_YAXIS = (1<<1), - FM_ZAXIS = (1<<2) -}; - -enum LineSegmentType -{ - LS_START, - LS_MIDDLE, - LS_END -}; - - -const NxF32 FM_PI = 3.1415926535897932384626433832795028841971693993751f; -const NxF32 FM_DEG_TO_RAD = ((2.0f * FM_PI) / 360.0f); -const NxF32 FM_RAD_TO_DEG = (360.0f / (2.0f * FM_PI)); - -//***************** Float versions -//*** -//*** vectors are assumed to be 3 floats or 3 doubles representing X, Y, Z -//*** quaternions are assumed to be 4 floats or 4 doubles representing X,Y,Z,W -//*** matrices are assumed to be 16 floats or 16 doubles representing a standard D3D or OpenGL style 4x4 matrix -//*** bounding volumes are expressed as two sets of 3 floats/NxF64 representing bmin(x,y,z) and bmax(x,y,z) -//*** Plane equations are assumed to be 4 floats or 4 doubles representing Ax,By,Cz,D - -FM_Axis fm_getDominantAxis(const NxF32 normal[3]); -FM_Axis fm_getDominantAxis(const NxF64 normal[3]); - -void fm_decomposeTransform(const NxF32 local_transform[16],NxF32 trans[3],NxF32 rot[4],NxF32 scale[3]); -void fm_decomposeTransform(const NxF64 local_transform[16],NxF64 trans[3],NxF64 rot[4],NxF64 scale[3]); - -void fm_multiplyTransform(const NxF32 *pA,const NxF32 *pB,NxF32 *pM); -void fm_multiplyTransform(const NxF64 *pA,const NxF64 *pB,NxF64 *pM); - -void fm_inverseTransform(const NxF32 matrix[16],NxF32 inverse_matrix[16]); -void fm_inverseTransform(const NxF64 matrix[16],NxF64 inverse_matrix[16]); - -void fm_identity(NxF32 matrix[16]); // set 4x4 matrix to identity. -void fm_identity(NxF64 matrix[16]); // set 4x4 matrix to identity. - -void fm_inverseRT(const NxF32 matrix[16], const NxF32 pos[3], NxF32 t[3]); // inverse rotate translate the point. -void fm_inverseRT(const NxF64 matrix[16],const NxF64 pos[3],NxF64 t[3]); // inverse rotate translate the point. - -void fm_transform(const NxF32 matrix[16], const NxF32 pos[3], NxF32 t[3]); // rotate and translate this point. -void fm_transform(const NxF64 matrix[16],const NxF64 pos[3],NxF64 t[3]); // rotate and translate this point. - -NxF32 fm_getDeterminant(const NxF32 matrix[16]); -NxF64 fm_getDeterminant(const NxF64 matrix[16]); - -void fm_getSubMatrix(NxI32 ki,NxI32 kj,NxF32 pDst[16],const NxF32 matrix[16]); -void fm_getSubMatrix(NxI32 ki,NxI32 kj,NxF64 pDst[16],const NxF32 matrix[16]); - -void fm_rotate(const NxF32 matrix[16],const NxF32 pos[3],NxF32 t[3]); // only rotate the point by a 4x4 matrix, don't translate. -void fm_rotate(const NxF64 matri[16],const NxF64 pos[3],NxF64 t[3]); // only rotate the point by a 4x4 matrix, don't translate. - -void fm_eulerToMatrix(NxF32 ax,NxF32 ay,NxF32 az,NxF32 matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) -void fm_eulerToMatrix(NxF64 ax,NxF64 ay,NxF64 az,NxF64 matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) - -void fm_getAABB(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 bmin[3],NxF32 bmax[3]); -void fm_getAABB(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF64 bmin[3],NxF64 bmax[3]); - -void fm_getAABBCenter(const NxF32 bmin[3],const NxF32 bmax[3],NxF32 center[3]); -void fm_getAABBCenter(const NxF64 bmin[3],const NxF64 bmax[3],NxF64 center[3]); - -void fm_eulerToQuat(NxF32 x,NxF32 y,NxF32 z,NxF32 quat[4]); // convert euler angles to quaternion. -void fm_eulerToQuat(NxF64 x,NxF64 y,NxF64 z,NxF64 quat[4]); // convert euler angles to quaternion. - -void fm_quatToEuler(const NxF32 quat[4],NxF32 &ax,NxF32 &ay,NxF32 &az); -void fm_quatToEuler(const NxF64 quat[4],NxF64 &ax,NxF64 &ay,NxF64 &az); - -void fm_eulerToQuat(const NxF32 euler[3],NxF32 quat[4]); // convert euler angles to quaternion. Angles must be radians not degrees! -void fm_eulerToQuat(const NxF64 euler[3],NxF64 quat[4]); // convert euler angles to quaternion. - -void fm_scale(NxF32 x,NxF32 y,NxF32 z,NxF32 matrix[16]); // apply scale to the matrix. -void fm_scale(NxF64 x,NxF64 y,NxF64 z,NxF64 matrix[16]); // apply scale to the matrix. - -void fm_eulerToQuatDX(NxF32 x,NxF32 y,NxF32 z,NxF32 quat[4]); // convert euler angles to quaternion using the fucked up DirectX method -void fm_eulerToQuatDX(NxF64 x,NxF64 y,NxF64 z,NxF64 quat[4]); // convert euler angles to quaternion using the fucked up DirectX method - -void fm_eulerToMatrixDX(NxF32 x,NxF32 y,NxF32 z,NxF32 matrix[16]); // convert euler angles to quaternion using the fucked up DirectX method. -void fm_eulerToMatrixDX(NxF64 x,NxF64 y,NxF64 z,NxF64 matrix[16]); // convert euler angles to quaternion using the fucked up DirectX method. - -void fm_quatToMatrix(const NxF32 quat[4],NxF32 matrix[16]); // convert quaterinion rotation to matrix, translation set to zero. -void fm_quatToMatrix(const NxF64 quat[4],NxF64 matrix[16]); // convert quaterinion rotation to matrix, translation set to zero. - -void fm_quatRotate(const NxF32 quat[4],const NxF32 v[3],NxF32 r[3]); // rotate a vector directly by a quaternion. -void fm_quatRotate(const NxF64 quat[4],const NxF64 v[3],NxF64 r[3]); // rotate a vector directly by a quaternion. - -void fm_getTranslation(const NxF32 matrix[16],NxF32 t[3]); -void fm_getTranslation(const NxF64 matrix[16],NxF64 t[3]); - -void fm_setTranslation(const NxF32 *translation,NxF32 matrix[16]); -void fm_setTranslation(const NxF64 *translation,NxF64 matrix[16]); - -void fm_multiplyQuat(const NxF32 *qa,const NxF32 *qb,NxF32 *quat); -void fm_multiplyQuat(const NxF64 *qa,const NxF64 *qb,NxF64 *quat); - -void fm_matrixToQuat(const NxF32 matrix[16],NxF32 quat[4]); // convert the 3x3 portion of a 4x4 matrix into a quaterion as x,y,z,w -void fm_matrixToQuat(const NxF64 matrix[16],NxF64 quat[4]); // convert the 3x3 portion of a 4x4 matrix into a quaterion as x,y,z,w - -NxF32 fm_sphereVolume(NxF32 radius); // return's the volume of a sphere of this radius (4/3 PI * R cubed ) -NxF64 fm_sphereVolume(NxF64 radius); // return's the volume of a sphere of this radius (4/3 PI * R cubed ) - -NxF32 fm_cylinderVolume(NxF32 radius,NxF32 h); -NxF64 fm_cylinderVolume(NxF64 radius,NxF64 h); - -NxF32 fm_capsuleVolume(NxF32 radius,NxF32 h); -NxF64 fm_capsuleVolume(NxF64 radius,NxF64 h); - -NxF32 fm_distance(const NxF32 p1[3],const NxF32 p2[3]); -NxF64 fm_distance(const NxF64 p1[3],const NxF64 p2[3]); - -NxF32 fm_distanceSquared(const NxF32 p1[3],const NxF32 p2[3]); -NxF64 fm_distanceSquared(const NxF64 p1[3],const NxF64 p2[3]); - -NxF32 fm_distanceSquaredXZ(const NxF32 p1[3],const NxF32 p2[3]); -NxF64 fm_distanceSquaredXZ(const NxF64 p1[3],const NxF64 p2[3]); - -NxF32 fm_computePlane(const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3],NxF32 *n); // return D -NxF64 fm_computePlane(const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3],NxF64 *n); // return D - -NxF32 fm_distToPlane(const NxF32 plane[4],const NxF32 pos[3]); // computes the distance of this point from the plane. -NxF64 fm_distToPlane(const NxF64 plane[4],const NxF64 pos[3]); // computes the distance of this point from the plane. - -NxF32 fm_dot(const NxF32 p1[3],const NxF32 p2[3]); -NxF64 fm_dot(const NxF64 p1[3],const NxF64 p2[3]); - -void fm_cross(NxF32 cross[3],const NxF32 a[3],const NxF32 b[3]); -void fm_cross(NxF64 cross[3],const NxF64 a[3],const NxF64 b[3]); - -void fm_computeNormalVector(NxF32 n[3],const NxF32 p1[3],const NxF32 p2[3]); // as P2-P1 normalized. -void fm_computeNormalVector(NxF64 n[3],const NxF64 p1[3],const NxF64 p2[3]); // as P2-P1 normalized. - -bool fm_computeWindingOrder(const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3]); // returns true if the triangle is clockwise. -bool fm_computeWindingOrder(const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3]); // returns true if the triangle is clockwise. - -NxF32 fm_normalize(NxF32 n[3]); // normalize this vector and return the distance -NxF64 fm_normalize(NxF64 n[3]); // normalize this vector and return the distance - -void fm_matrixMultiply(const NxF32 A[16],const NxF32 B[16],NxF32 dest[16]); -void fm_matrixMultiply(const NxF64 A[16],const NxF64 B[16],NxF64 dest[16]); - -void fm_composeTransform(const NxF32 position[3],const NxF32 quat[4],const NxF32 scale[3],NxF32 matrix[16]); -void fm_composeTransform(const NxF64 position[3],const NxF64 quat[4],const NxF64 scale[3],NxF64 matrix[16]); - -NxF32 fm_computeArea(const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3]); -NxF64 fm_computeArea(const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3]); - -void fm_lerp(const NxF32 p1[3],const NxF32 p2[3],NxF32 dest[3],NxF32 lerpValue); -void fm_lerp(const NxF64 p1[3],const NxF64 p2[3],NxF64 dest[3],NxF64 lerpValue); - -bool fm_insideTriangleXZ(const NxF32 test[3],const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3]); -bool fm_insideTriangleXZ(const NxF64 test[3],const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3]); - -bool fm_insideAABB(const NxF32 pos[3],const NxF32 bmin[3],const NxF32 bmax[3]); -bool fm_insideAABB(const NxF64 pos[3],const NxF64 bmin[3],const NxF64 bmax[3]); - -bool fm_insideAABB(const NxF32 obmin[3],const NxF32 obmax[3],const NxF32 tbmin[3],const NxF32 tbmax[3]); // test if bounding box tbmin/tmbax is fully inside obmin/obmax -bool fm_insideAABB(const NxF64 obmin[3],const NxF64 obmax[3],const NxF64 tbmin[3],const NxF64 tbmax[3]); // test if bounding box tbmin/tmbax is fully inside obmin/obmax - -NxU32 fm_clipTestPoint(const NxF32 bmin[3],const NxF32 bmax[3],const NxF32 pos[3]); -NxU32 fm_clipTestPoint(const NxF64 bmin[3],const NxF64 bmax[3],const NxF64 pos[3]); - -NxU32 fm_clipTestPointXZ(const NxF32 bmin[3],const NxF32 bmax[3],const NxF32 pos[3]); // only tests X and Z, not Y -NxU32 fm_clipTestPointXZ(const NxF64 bmin[3],const NxF64 bmax[3],const NxF64 pos[3]); // only tests X and Z, not Y - - -NxU32 fm_clipTestAABB(const NxF32 bmin[3],const NxF32 bmax[3],const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3],NxU32 &andCode); -NxU32 fm_clipTestAABB(const NxF64 bmin[3],const NxF64 bmax[3],const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3],NxU32 &andCode); - - -bool fm_lineTestAABBXZ(const NxF32 p1[3],const NxF32 p2[3],const NxF32 bmin[3],const NxF32 bmax[3],NxF32 &time); -bool fm_lineTestAABBXZ(const NxF64 p1[3],const NxF64 p2[3],const NxF64 bmin[3],const NxF64 bmax[3],NxF64 &time); - -bool fm_lineTestAABB(const NxF32 p1[3],const NxF32 p2[3],const NxF32 bmin[3],const NxF32 bmax[3],NxF32 &time); -bool fm_lineTestAABB(const NxF64 p1[3],const NxF64 p2[3],const NxF64 bmin[3],const NxF64 bmax[3],NxF64 &time); - - -void fm_initMinMax(const NxF32 p[3],NxF32 bmin[3],NxF32 bmax[3]); -void fm_initMinMax(const NxF64 p[3],NxF64 bmin[3],NxF64 bmax[3]); - -void fm_initMinMax(NxF32 bmin[3],NxF32 bmax[3]); -void fm_initMinMax(NxF64 bmin[3],NxF64 bmax[3]); - -void fm_minmax(const NxF32 p[3],NxF32 bmin[3],NxF32 bmax[3]); // accmulate to a min-max value -void fm_minmax(const NxF64 p[3],NxF64 bmin[3],NxF64 bmax[3]); // accmulate to a min-max value - - -NxF32 fm_solveX(const NxF32 plane[4],NxF32 y,NxF32 z); // solve for X given this plane equation and the other two components. -NxF64 fm_solveX(const NxF64 plane[4],NxF64 y,NxF64 z); // solve for X given this plane equation and the other two components. - -NxF32 fm_solveY(const NxF32 plane[4],NxF32 x,NxF32 z); // solve for Y given this plane equation and the other two components. -NxF64 fm_solveY(const NxF64 plane[4],NxF64 x,NxF64 z); // solve for Y given this plane equation and the other two components. - -NxF32 fm_solveZ(const NxF32 plane[4],NxF32 x,NxF32 y); // solve for Z given this plane equation and the other two components. -NxF64 fm_solveZ(const NxF64 plane[4],NxF64 x,NxF64 y); // solve for Z given this plane equation and the other two components. - -bool fm_computeBestFitPlane(NxU32 vcount, // number of input data points - const NxF32 *points, // starting address of points array. - NxU32 vstride, // stride between input points. - const NxF32 *weights, // *optional point weighting values. - NxU32 wstride, // weight stride for each vertex. - NxF32 plane[4]); - -bool fm_computeBestFitPlane(NxU32 vcount, // number of input data points - const NxF64 *points, // starting address of points array. - NxU32 vstride, // stride between input points. - const NxF64 *weights, // *optional point weighting values. - NxU32 wstride, // weight stride for each vertex. - NxF64 plane[4]); - - -NxF32 fm_computeBestFitAABB(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 bmin[3],NxF32 bmax[3]); // returns the diagonal distance -NxF64 fm_computeBestFitAABB(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF64 bmin[3],NxF64 bmax[3]); // returns the diagonal distance - -NxF32 fm_computeBestFitSphere(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 center[3]); -NxF64 fm_computeBestFitSphere(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF64 center[3]); - -bool fm_lineSphereIntersect(const NxF32 center[3],NxF32 radius,const NxF32 p1[3],const NxF32 p2[3],NxF32 intersect[3]); -bool fm_lineSphereIntersect(const NxF64 center[3],NxF64 radius,const NxF64 p1[3],const NxF64 p2[3],NxF64 intersect[3]); - -bool fm_intersectRayAABB(const NxF32 bmin[3],const NxF32 bmax[3],const NxF32 pos[3],const NxF32 dir[3],NxF32 intersect[3]); -bool fm_intersectLineSegmentAABB(const NxF32 bmin[3],const NxF32 bmax[3],const NxF32 p1[3],const NxF32 p2[3],NxF32 intersect[3]); - -bool fm_lineIntersectsTriangle(const NxF32 rayStart[3],const NxF32 rayEnd[3],const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3],NxF32 sect[3]); -bool fm_lineIntersectsTriangle(const NxF64 rayStart[3],const NxF64 rayEnd[3],const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3],NxF64 sect[3]); - -bool fm_rayIntersectsTriangle(const NxF32 origin[3],const NxF32 dir[3],const NxF32 v0[3],const NxF32 v1[3],const NxF32 v2[3],NxF32 &t); -bool fm_rayIntersectsTriangle(const NxF64 origin[3],const NxF64 dir[3],const NxF64 v0[3],const NxF64 v1[3],const NxF64 v2[3],NxF64 &t); - -bool fm_raySphereIntersect(const NxF32 center[3],NxF32 radius,const NxF32 pos[3],const NxF32 dir[3],NxF32 distance,NxF32 intersect[3]); -bool fm_raySphereIntersect(const NxF64 center[3],NxF64 radius,const NxF64 pos[3],const NxF64 dir[3],NxF64 distance,NxF64 intersect[3]); - -void fm_catmullRom(NxF32 out_vector[3],const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3],const NxF32 *p4, const NxF32 s); -void fm_catmullRom(NxF64 out_vector[3],const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3],const NxF64 *p4, const NxF64 s); - -bool fm_intersectAABB(const NxF32 bmin1[3],const NxF32 bmax1[3],const NxF32 bmin2[3],const NxF32 bmax2[3]); -bool fm_intersectAABB(const NxF64 bmin1[3],const NxF64 bmax1[3],const NxF64 bmin2[3],const NxF64 bmax2[3]); - - -// computes the rotation quaternion to go from unit-vector v0 to unit-vector v1 -void fm_rotationArc(const NxF32 v0[3],const NxF32 v1[3],NxF32 quat[4]); -void fm_rotationArc(const NxF64 v0[3],const NxF64 v1[3],NxF64 quat[4]); - -NxF32 fm_distancePointLineSegment(const NxF32 Point[3],const NxF32 LineStart[3],const NxF32 LineEnd[3],NxF32 intersection[3],LineSegmentType &type,NxF32 epsilon); -NxF64 fm_distancePointLineSegment(const NxF64 Point[3],const NxF64 LineStart[3],const NxF64 LineEnd[3],NxF64 intersection[3],LineSegmentType &type,NxF64 epsilon); - - -bool fm_colinear(const NxF64 p1[3],const NxF64 p2[3],const NxF64 p3[3],NxF64 epsilon=0.999); // true if these three points in a row are co-linear -bool fm_colinear(const NxF32 p1[3],const NxF32 p2[3],const NxF32 p3[3],NxF32 epsilon=0.999f); - -bool fm_colinear(const NxF32 a1[3],const NxF32 a2[3],const NxF32 b1[3],const NxF32 b2[3],NxF32 epsilon=0.999f); // true if these two line segments are co-linear. -bool fm_colinear(const NxF64 a1[3],const NxF64 a2[3],const NxF64 b1[3],const NxF64 b2[3],NxF64 epsilon=0.999); // true if these two line segments are co-linear. - -enum IntersectResult -{ - IR_DONT_INTERSECT, - IR_DO_INTERSECT, - IR_COINCIDENT, - IR_PARALLEL, -}; - -IntersectResult fm_intersectLineSegments2d(const NxF32 a1[3], const NxF32 a2[3], const NxF32 b1[3], const NxF32 b2[3], NxF32 intersectionPoint[3]); -IntersectResult fm_intersectLineSegments2d(const NxF64 a1[3],const NxF64 a2[3],const NxF64 b1[3],const NxF64 b2[3],NxF64 intersectionPoint[3]); - -IntersectResult fm_intersectLineSegments2dTime(const NxF32 a1[3], const NxF32 a2[3], const NxF32 b1[3], const NxF32 b2[3],NxF32 &t1,NxF32 &t2); -IntersectResult fm_intersectLineSegments2dTime(const NxF64 a1[3],const NxF64 a2[3],const NxF64 b1[3],const NxF64 b2[3],NxF64 &t1,NxF64 &t2); - -// Plane-Triangle splitting - -enum PlaneTriResult -{ - PTR_ON_PLANE, - PTR_FRONT, - PTR_BACK, - PTR_SPLIT, -}; - -PlaneTriResult fm_planeTriIntersection(const NxF32 plane[4], // the plane equation in Ax+By+Cz+D format - const NxF32 *triangle, // the source triangle. - NxU32 tstride, // stride in bytes of the input and output *vertices* - NxF32 epsilon, // the co-planer epsilon value. - NxF32 *front, // the triangle in front of the - NxU32 &fcount, // number of vertices in the 'front' triangle - NxF32 *back, // the triangle in back of the plane - NxU32 &bcount); // the number of vertices in the 'back' triangle. - - -PlaneTriResult fm_planeTriIntersection(const NxF64 plane[4], // the plane equation in Ax+By+Cz+D format - const NxF64 *triangle, // the source triangle. - NxU32 tstride, // stride in bytes of the input and output *vertices* - NxF64 epsilon, // the co-planer epsilon value. - NxF64 *front, // the triangle in front of the - NxU32 &fcount, // number of vertices in the 'front' triangle - NxF64 *back, // the triangle in back of the plane - NxU32 &bcount); // the number of vertices in the 'back' triangle. - - -void fm_intersectPointPlane(const NxF32 p1[3],const NxF32 p2[3],NxF32 *split,const NxF32 plane[4]); -void fm_intersectPointPlane(const NxF64 p1[3],const NxF64 p2[3],NxF64 *split,const NxF64 plane[4]); - -PlaneTriResult fm_getSidePlane(const NxF32 p[3],const NxF32 plane[4],NxF32 epsilon); -PlaneTriResult fm_getSidePlane(const NxF64 p[3],const NxF64 plane[4],NxF64 epsilon); - - -void fm_computeBestFitOBB(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 *sides,NxF32 matrix[16],bool bruteForce=true); -void fm_computeBestFitOBB(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF64 *sides,NxF64 matrix[16],bool bruteForce=true); - -void fm_computeBestFitOBB(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 *sides,NxF32 pos[3],NxF32 quat[4],bool bruteForce=true); -void fm_computeBestFitOBB(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF64 *sides,NxF64 pos[3],NxF64 quat[4],bool bruteForce=true); - -void fm_computeBestFitABB(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 *sides,NxF32 pos[3]); -void fm_computeBestFitABB(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF64 *sides,NxF64 pos[3]); - - -//** Note, if the returned capsule height is less than zero, then you must represent it is a sphere of size radius. -void fm_computeBestFitCapsule(NxU32 vcount,const NxF32 *points,NxU32 pstride,NxF32 &radius,NxF32 &height,NxF32 matrix[16],bool bruteForce=true); -void fm_computeBestFitCapsule(NxU32 vcount,const NxF64 *points,NxU32 pstride,NxF32 &radius,NxF32 &height,NxF64 matrix[16],bool bruteForce=true); - - -void fm_planeToMatrix(const NxF32 plane[4],NxF32 matrix[16]); // convert a plane equation to a 4x4 rotation matrix. Reference vector is 0,1,0 -void fm_planeToQuat(const NxF32 plane[4],NxF32 quat[4],NxF32 pos[3]); // convert a plane equation to a quaternion and translation - -void fm_planeToMatrix(const NxF64 plane[4],NxF64 matrix[16]); // convert a plane equation to a 4x4 rotation matrix -void fm_planeToQuat(const NxF64 plane[4],NxF64 quat[4],NxF64 pos[3]); // convert a plane equation to a quaternion and translation - -inline void fm_doubleToFloat3(const NxF64 p[3],NxF32 t[3]) { t[0] = (NxF32) p[0]; t[1] = (NxF32)p[1]; t[2] = (NxF32)p[2]; }; -inline void fm_floatToDouble3(const NxF32 p[3],NxF64 t[3]) { t[0] = (NxF64)p[0]; t[1] = (NxF64)p[1]; t[2] = (NxF64)p[2]; }; - - -void fm_eulerMatrix(NxF32 ax,NxF32 ay,NxF32 az,NxF32 matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) -void fm_eulerMatrix(NxF64 ax,NxF64 ay,NxF64 az,NxF64 matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) - - -NxF32 fm_computeMeshVolume(const NxF32 *vertices,NxU32 tcount,const NxU32 *indices); -NxF64 fm_computeMeshVolume(const NxF64 *vertices,NxU32 tcount,const NxU32 *indices); - - -#define FM_DEFAULT_GRANULARITY 0.001f // 1 millimeter is the default granularity - -class fm_VertexIndex -{ -public: - virtual NxU32 getIndex(const NxF32 pos[3],bool &newPos) = 0; // get welded index for this NxF32 vector[3] - virtual NxU32 getIndex(const NxF64 pos[3],bool &newPos) = 0; // get welded index for this NxF64 vector[3] - virtual const NxF32 * getVerticesFloat(void) const = 0; - virtual const NxF64 * getVerticesDouble(void) const = 0; - virtual const NxF32 * getVertexFloat(NxU32 index) const = 0; - virtual const NxF64 * getVertexDouble(NxU32 index) const = 0; - virtual NxU32 getVcount(void) const = 0; - virtual bool isDouble(void) const = 0; - virtual bool saveAsObj(const char *fname,NxU32 tcount,NxU32 *indices) = 0; -}; - -fm_VertexIndex * fm_createVertexIndex(NxF64 granularity,bool snapToGrid); // create an indexed vertex system for doubles -fm_VertexIndex * fm_createVertexIndex(NxF32 granularity,bool snapToGrid); // create an indexed vertext system for floats -void fm_releaseVertexIndex(fm_VertexIndex *vindex); - - - -#if 0 // currently disabled - -class fm_LineSegment -{ -public: - fm_LineSegment(void) - { - mE1 = mE2 = 0; - } - - fm_LineSegment(NxU32 e1,NxU32 e2) - { - mE1 = e1; - mE2 = e2; - } - - NxU32 mE1; - NxU32 mE2; -}; - - -// LineSweep *only* supports doublees. As a geometric operation it needs as much precision as possible. -class fm_LineSweep -{ -public: - - virtual fm_LineSegment * performLineSweep(const fm_LineSegment *segments, - NxU32 icount, - const NxF64 *planeEquation, - fm_VertexIndex *pool, - NxU32 &scount) = 0; - - -}; - -fm_LineSweep * fm_createLineSweep(void); -void fm_releaseLineSweep(fm_LineSweep *sweep); - -#endif - -class fm_Triangulate -{ -public: - virtual const NxF64 * triangulate3d(NxU32 pcount, - const NxF64 *points, - NxU32 vstride, - NxU32 &tcount, - bool consolidate, - NxF64 epsilon) = 0; - - virtual const NxF32 * triangulate3d(NxU32 pcount, - const NxF32 *points, - NxU32 vstride, - NxU32 &tcount, - bool consolidate, - NxF32 epsilon) = 0; -}; - -fm_Triangulate * fm_createTriangulate(void); -void fm_releaseTriangulate(fm_Triangulate *t); - - -const NxF32 * fm_getPoint(const NxF32 *points,NxU32 pstride,NxU32 index); -const NxF64 * fm_getPoint(const NxF64 *points,NxU32 pstride,NxU32 index); - -bool fm_insideTriangle(NxF32 Ax, NxF32 Ay,NxF32 Bx, NxF32 By,NxF32 Cx, NxF32 Cy,NxF32 Px, NxF32 Py); -bool fm_insideTriangle(NxF64 Ax, NxF64 Ay,NxF64 Bx, NxF64 By,NxF64 Cx, NxF64 Cy,NxF64 Px, NxF64 Py); -NxF32 fm_areaPolygon2d(NxU32 pcount,const NxF32 *points,NxU32 pstride); -NxF64 fm_areaPolygon2d(NxU32 pcount,const NxF64 *points,NxU32 pstride); - -bool fm_pointInsidePolygon2d(NxU32 pcount,const NxF32 *points,NxU32 pstride,const NxF32 *point,NxU32 xindex=0,NxU32 yindex=1); -bool fm_pointInsidePolygon2d(NxU32 pcount,const NxF64 *points,NxU32 pstride,const NxF64 *point,NxU32 xindex=0,NxU32 yindex=1); - -NxU32 fm_consolidatePolygon(NxU32 pcount,const NxF32 *points,NxU32 pstride,NxF32 *dest,NxF32 epsilon=0.999999f); // collapses co-linear edges. -NxU32 fm_consolidatePolygon(NxU32 pcount,const NxF64 *points,NxU32 pstride,NxF64 *dest,NxF64 epsilon=0.999999); // collapses co-linear edges. - - -bool fm_computeSplitPlane(NxU32 vcount,const NxF64 *vertices,NxU32 tcount,const NxU32 *indices,NxF64 *plane); -bool fm_computeSplitPlane(NxU32 vcount,const NxF32 *vertices,NxU32 tcount,const NxU32 *indices,NxF32 *plane); - -void fm_nearestPointInTriangle(const NxF32 *pos,const NxF32 *p1,const NxF32 *p2,const NxF32 *p3,NxF32 *nearest); -void fm_nearestPointInTriangle(const NxF64 *pos,const NxF64 *p1,const NxF64 *p2,const NxF64 *p3,NxF64 *nearest); - -NxF32 fm_areaTriangle(const NxF32 *p1,const NxF32 *p2,const NxF32 *p3); -NxF64 fm_areaTriangle(const NxF64 *p1,const NxF64 *p2,const NxF64 *p3); - -void fm_subtract(const NxF32 *A,const NxF32 *B,NxF32 *diff); // compute A-B and store the result in 'diff' -void fm_subtract(const NxF64 *A,const NxF64 *B,NxF64 *diff); // compute A-B and store the result in 'diff' - -void fm_multiply(NxF32 *A,NxF32 scaler); -void fm_multiply(NxF64 *A,NxF64 scaler); - -void fm_add(const NxF32 *A,const NxF32 *B,NxF32 *sum); -void fm_add(const NxF64 *A,const NxF64 *B,NxF64 *sum); - -void fm_copy3(const NxF32 *source,NxF32 *dest); -void fm_copy3(const NxF64 *source,NxF64 *dest); - -// re-indexes an indexed triangle mesh but drops unused vertices. The output_indices can be the same pointer as the input indices. -// the output_vertices can point to the input vertices if you desire. The output_vertices buffer should be at least the same size -// is the input buffer. The routine returns the new vertex count after re-indexing. -NxU32 fm_copyUniqueVertices(NxU32 vcount,const NxF32 *input_vertices,NxF32 *output_vertices,NxU32 tcount,const NxU32 *input_indices,NxU32 *output_indices); -NxU32 fm_copyUniqueVertices(NxU32 vcount,const NxF64 *input_vertices,NxF64 *output_vertices,NxU32 tcount,const NxU32 *input_indices,NxU32 *output_indices); - -bool fm_isMeshCoplanar(NxU32 tcount,const NxU32 *indices,const NxF32 *vertices,bool doubleSided); // returns true if this collection of indexed triangles are co-planar! -bool fm_isMeshCoplanar(NxU32 tcount,const NxU32 *indices,const NxF64 *vertices,bool doubleSided); // returns true if this collection of indexed triangles are co-planar! - -bool fm_samePlane(const NxF32 p1[4],const NxF32 p2[4],NxF32 normalEpsilon=0.01f,NxF32 dEpsilon=0.001f,bool doubleSided=false); // returns true if these two plane equations are identical within an epsilon -bool fm_samePlane(const NxF64 p1[4],const NxF64 p2[4],NxF64 normalEpsilon=0.01,NxF64 dEpsilon=0.001,bool doubleSided=false); - -void fm_OBBtoAABB(const NxF32 obmin[3],const NxF32 obmax[3],const NxF32 matrix[16],NxF32 abmin[3],NxF32 abmax[3]); - -// a utility class that will tesseleate a mesh. -class fm_Tesselate -{ -public: - virtual const NxU32 * tesselate(fm_VertexIndex *vindex,NxU32 tcount,const NxU32 *indices,NxF32 longEdge,NxU32 maxDepth,NxU32 &outcount) = 0; -}; - -fm_Tesselate * fm_createTesselate(void); -void fm_releaseTesselate(fm_Tesselate *t); - -void fm_computeMeanNormals(NxU32 vcount, // the number of vertices - const NxF32 *vertices, // the base address of the vertex position data. - NxU32 vstride, // the stride between position data. - NxF32 *normals, // the base address of the destination for mean vector normals - NxU32 nstride, // the stride between normals - NxU32 tcount, // the number of triangles - const NxU32 *indices); // the triangle indices - -void fm_computeMeanNormals(NxU32 vcount, // the number of vertices - const NxF64 *vertices, // the base address of the vertex position data. - NxU32 vstride, // the stride between position data. - NxF64 *normals, // the base address of the destination for mean vector normals - NxU32 nstride, // the stride between normals - NxU32 tcount, // the number of triangles - const NxU32 *indices); // the triangle indices - - -bool fm_isValidTriangle(const NxF32 *p1,const NxF32 *p2,const NxF32 *p3,NxF32 epsilon=0.00001f); -bool fm_isValidTriangle(const NxF64 *p1,const NxF64 *p2,const NxF64 *p3,NxF64 epsilon=0.00001f); - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvFloatMath.inl b/Engine/lib/convexDecomp/NvFloatMath.inl deleted file mode 100644 index b8731013a..000000000 --- a/Engine/lib/convexDecomp/NvFloatMath.inl +++ /dev/null @@ -1,5607 +0,0 @@ -// a set of routines that let you do common 3d math -// operations without any vector, matrix, or quaternion -// classes or templates. -// -// a vector (or point) is a 'NxF32 *' to 3 floating point numbers. -// a matrix is a 'NxF32 *' to an array of 16 floating point numbers representing a 4x4 transformation matrix compatible with D3D or OGL -// a quaternion is a 'NxF32 *' to 4 floats representing a quaternion x,y,z,w -// -// -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#pragma warning(disable:4996) - -#include "NvUserMemAlloc.h" -#include "NvHashMap.h" - -namespace CONVEX_DECOMPOSITION -{ - -void fm_inverseRT(const REAL matrix[16],const REAL pos[3],REAL t[3]) // inverse rotate translate the point. -{ - - REAL _x = pos[0] - matrix[3*4+0]; - REAL _y = pos[1] - matrix[3*4+1]; - REAL _z = pos[2] - matrix[3*4+2]; - - // Multiply inverse-translated source vector by inverted rotation transform - - t[0] = (matrix[0*4+0] * _x) + (matrix[0*4+1] * _y) + (matrix[0*4+2] * _z); - t[1] = (matrix[1*4+0] * _x) + (matrix[1*4+1] * _y) + (matrix[1*4+2] * _z); - t[2] = (matrix[2*4+0] * _x) + (matrix[2*4+1] * _y) + (matrix[2*4+2] * _z); - -} - -REAL fm_getDeterminant(const REAL matrix[16]) -{ - REAL tempv[3]; - REAL p0[3]; - REAL p1[3]; - REAL p2[3]; - - - p0[0] = matrix[0*4+0]; - p0[1] = matrix[0*4+1]; - p0[2] = matrix[0*4+2]; - - p1[0] = matrix[1*4+0]; - p1[1] = matrix[1*4+1]; - p1[2] = matrix[1*4+2]; - - p2[0] = matrix[2*4+0]; - p2[1] = matrix[2*4+1]; - p2[2] = matrix[2*4+2]; - - fm_cross(tempv,p1,p2); - - return fm_dot(p0,tempv); - -} - -REAL fm_squared(REAL x) { return x*x; }; - -void fm_decomposeTransform(const REAL local_transform[16],REAL trans[3],REAL rot[4],REAL scale[3]) -{ - - trans[0] = local_transform[12]; - trans[1] = local_transform[13]; - trans[2] = local_transform[14]; - - scale[0] = sqrt(fm_squared(local_transform[0*4+0]) + fm_squared(local_transform[0*4+1]) + fm_squared(local_transform[0*4+2])); - scale[1] = sqrt(fm_squared(local_transform[1*4+0]) + fm_squared(local_transform[1*4+1]) + fm_squared(local_transform[1*4+2])); - scale[2] = sqrt(fm_squared(local_transform[2*4+0]) + fm_squared(local_transform[2*4+1]) + fm_squared(local_transform[2*4+2])); - - REAL m[16]; - memcpy(m,local_transform,sizeof(REAL)*16); - - REAL sx = 1.0f / scale[0]; - REAL sy = 1.0f / scale[1]; - REAL sz = 1.0f / scale[2]; - - m[0*4+0]*=sx; - m[0*4+1]*=sx; - m[0*4+2]*=sx; - - m[1*4+0]*=sy; - m[1*4+1]*=sy; - m[1*4+2]*=sy; - - m[2*4+0]*=sz; - m[2*4+1]*=sz; - m[2*4+2]*=sz; - - fm_matrixToQuat(m,rot); - -} - -void fm_getSubMatrix(NxI32 ki,NxI32 kj,REAL pDst[16],const REAL matrix[16]) -{ - NxI32 row, col; - NxI32 dstCol = 0, dstRow = 0; - - for ( col = 0; col < 4; col++ ) - { - if ( col == kj ) - { - continue; - } - for ( dstRow = 0, row = 0; row < 4; row++ ) - { - if ( row == ki ) - { - continue; - } - pDst[dstCol*4+dstRow] = matrix[col*4+row]; - dstRow++; - } - dstCol++; - } -} - -void fm_inverseTransform(const REAL matrix[16],REAL inverse_matrix[16]) -{ - REAL determinant = fm_getDeterminant(matrix); - determinant = 1.0f / determinant; - for (NxI32 i = 0; i < 4; i++ ) - { - for (NxI32 j = 0; j < 4; j++ ) - { - NxI32 sign = 1 - ( ( i + j ) % 2 ) * 2; - REAL subMat[16]; - fm_identity(subMat); - fm_getSubMatrix( i, j, subMat, matrix ); - REAL subDeterminant = fm_getDeterminant(subMat); - inverse_matrix[i*4+j] = ( subDeterminant * sign ) * determinant; - } - } -} - -void fm_identity(REAL matrix[16]) // set 4x4 matrix to identity. -{ - matrix[0*4+0] = 1; - matrix[1*4+1] = 1; - matrix[2*4+2] = 1; - matrix[3*4+3] = 1; - - matrix[1*4+0] = 0; - matrix[2*4+0] = 0; - matrix[3*4+0] = 0; - - matrix[0*4+1] = 0; - matrix[2*4+1] = 0; - matrix[3*4+1] = 0; - - matrix[0*4+2] = 0; - matrix[1*4+2] = 0; - matrix[3*4+2] = 0; - - matrix[0*4+3] = 0; - matrix[1*4+3] = 0; - matrix[2*4+3] = 0; - -} - -void fm_quatToEuler(const REAL quat[4],REAL &ax,REAL &ay,REAL &az) -{ - REAL x = quat[0]; - REAL y = quat[1]; - REAL z = quat[2]; - REAL w = quat[3]; - - REAL sint = (2.0f * w * y) - (2.0f * x * z); - REAL cost_temp = 1.0f - (sint * sint); - REAL cost = 0; - - if ( (REAL)fabs(cost_temp) > 0.001f ) - { - cost = sqrt( cost_temp ); - } - - REAL sinv, cosv, sinf, cosf; - if ( (REAL)fabs(cost) > 0.001f ) - { - cost = 1.0f / cost; - sinv = ((2.0f * y * z) + (2.0f * w * x)) * cost; - cosv = (1.0f - (2.0f * x * x) - (2.0f * y * y)) * cost; - sinf = ((2.0f * x * y) + (2.0f * w * z)) * cost; - cosf = (1.0f - (2.0f * y * y) - (2.0f * z * z)) * cost; - } - else - { - sinv = (2.0f * w * x) - (2.0f * y * z); - cosv = 1.0f - (2.0f * x * x) - (2.0f * z * z); - sinf = 0; - cosf = 1.0f; - } - - // compute output rotations - ax = atan2( sinv, cosv ); - ay = atan2( sint, cost ); - az = atan2( sinf, cosf ); - -} - -void fm_eulerToMatrix(REAL ax,REAL ay,REAL az,REAL *matrix) // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) -{ - REAL quat[4]; - fm_eulerToQuat(ax,ay,az,quat); - fm_quatToMatrix(quat,matrix); -} - -void fm_getAABB(NxU32 vcount,const REAL *points,NxU32 pstride,REAL *bmin,REAL *bmax) -{ - - const NxU8 *source = (const NxU8 *) points; - - bmin[0] = points[0]; - bmin[1] = points[1]; - bmin[2] = points[2]; - - bmax[0] = points[0]; - bmax[1] = points[1]; - bmax[2] = points[2]; - - - for (NxU32 i=1; i bmax[0] ) bmax[0] = p[0]; - if ( p[1] > bmax[1] ) bmax[1] = p[1]; - if ( p[2] > bmax[2] ) bmax[2] = p[2]; - - } -} - -void fm_eulerToQuat(const REAL *euler,REAL *quat) // convert euler angles to quaternion. -{ - fm_eulerToQuat(euler[0],euler[1],euler[2],quat); -} - -void fm_eulerToQuat(REAL roll,REAL pitch,REAL yaw,REAL *quat) // convert euler angles to quaternion. -{ - roll *= 0.5f; - pitch *= 0.5f; - yaw *= 0.5f; - - REAL cr = cos(roll); - REAL cp = cos(pitch); - REAL cy = cos(yaw); - - REAL sr = sin(roll); - REAL sp = sin(pitch); - REAL sy = sin(yaw); - - REAL cpcy = cp * cy; - REAL spsy = sp * sy; - REAL spcy = sp * cy; - REAL cpsy = cp * sy; - - quat[0] = ( sr * cpcy - cr * spsy); - quat[1] = ( cr * spcy + sr * cpsy); - quat[2] = ( cr * cpsy - sr * spcy); - quat[3] = cr * cpcy + sr * spsy; -} - -void fm_quatToMatrix(const REAL *quat,REAL *matrix) // convert quaterinion rotation to matrix, zeros out the translation component. -{ - - REAL xx = quat[0]*quat[0]; - REAL yy = quat[1]*quat[1]; - REAL zz = quat[2]*quat[2]; - REAL xy = quat[0]*quat[1]; - REAL xz = quat[0]*quat[2]; - REAL yz = quat[1]*quat[2]; - REAL wx = quat[3]*quat[0]; - REAL wy = quat[3]*quat[1]; - REAL wz = quat[3]*quat[2]; - - matrix[0*4+0] = 1 - 2 * ( yy + zz ); - matrix[1*4+0] = 2 * ( xy - wz ); - matrix[2*4+0] = 2 * ( xz + wy ); - - matrix[0*4+1] = 2 * ( xy + wz ); - matrix[1*4+1] = 1 - 2 * ( xx + zz ); - matrix[2*4+1] = 2 * ( yz - wx ); - - matrix[0*4+2] = 2 * ( xz - wy ); - matrix[1*4+2] = 2 * ( yz + wx ); - matrix[2*4+2] = 1 - 2 * ( xx + yy ); - - matrix[3*4+0] = matrix[3*4+1] = matrix[3*4+2] = (REAL) 0.0f; - matrix[0*4+3] = matrix[1*4+3] = matrix[2*4+3] = (REAL) 0.0f; - matrix[3*4+3] =(REAL) 1.0f; - -} - - -void fm_quatRotate(const REAL *quat,const REAL *v,REAL *r) // rotate a vector directly by a quaternion. -{ - REAL left[4]; - - left[0] = quat[3]*v[0] + quat[1]*v[2] - v[1]*quat[2]; - left[1] = quat[3]*v[1] + quat[2]*v[0] - v[2]*quat[0]; - left[2] = quat[3]*v[2] + quat[0]*v[1] - v[0]*quat[1]; - left[3] = - quat[0]*v[0] - quat[1]*v[1] - quat[2]*v[2]; - - r[0] = (left[3]*-quat[0]) + (quat[3]*left[0]) + (left[1]*-quat[2]) - (-quat[1]*left[2]); - r[1] = (left[3]*-quat[1]) + (quat[3]*left[1]) + (left[2]*-quat[0]) - (-quat[2]*left[0]); - r[2] = (left[3]*-quat[2]) + (quat[3]*left[2]) + (left[0]*-quat[1]) - (-quat[0]*left[1]); - -} - - -void fm_getTranslation(const REAL *matrix,REAL *t) -{ - t[0] = matrix[3*4+0]; - t[1] = matrix[3*4+1]; - t[2] = matrix[3*4+2]; -} - -void fm_matrixToQuat(const REAL *matrix,REAL *quat) // convert the 3x3 portion of a 4x4 matrix into a quaterion as x,y,z,w -{ - - REAL tr = matrix[0*4+0] + matrix[1*4+1] + matrix[2*4+2]; - - // check the diagonal - - if (tr > 0.0f ) - { - REAL s = (REAL) sqrt ( (NxF64) (tr + 1.0f) ); - quat[3] = s * 0.5f; - s = 0.5f / s; - quat[0] = (matrix[1*4+2] - matrix[2*4+1]) * s; - quat[1] = (matrix[2*4+0] - matrix[0*4+2]) * s; - quat[2] = (matrix[0*4+1] - matrix[1*4+0]) * s; - - } - else - { - // diagonal is negative - NxI32 nxt[3] = {1, 2, 0}; - REAL qa[4]; - - NxI32 i = 0; - - if (matrix[1*4+1] > matrix[0*4+0]) i = 1; - if (matrix[2*4+2] > matrix[i*4+i]) i = 2; - - NxI32 j = nxt[i]; - NxI32 k = nxt[j]; - - REAL s = sqrt ( ((matrix[i*4+i] - (matrix[j*4+j] + matrix[k*4+k])) + 1.0f) ); - - qa[i] = s * 0.5f; - - if (s != 0.0f ) s = 0.5f / s; - - qa[3] = (matrix[j*4+k] - matrix[k*4+j]) * s; - qa[j] = (matrix[i*4+j] + matrix[j*4+i]) * s; - qa[k] = (matrix[i*4+k] + matrix[k*4+i]) * s; - - quat[0] = qa[0]; - quat[1] = qa[1]; - quat[2] = qa[2]; - quat[3] = qa[3]; - } - - -} - - -REAL fm_sphereVolume(REAL radius) // return's the volume of a sphere of this radius (4/3 PI * R cubed ) -{ - return (4.0f / 3.0f ) * FM_PI * radius * radius * radius; -} - - -REAL fm_cylinderVolume(REAL radius,REAL h) -{ - return FM_PI * radius * radius *h; -} - -REAL fm_capsuleVolume(REAL radius,REAL h) -{ - REAL volume = fm_sphereVolume(radius); // volume of the sphere portion. - REAL ch = h-radius*2; // this is the cylinder length - if ( ch > 0 ) - { - volume+=fm_cylinderVolume(radius,ch); - } - return volume; -} - -void fm_transform(const REAL matrix[16],const REAL v[3],REAL t[3]) // rotate and translate this point -{ - if ( matrix ) - { - REAL tx = (matrix[0*4+0] * v[0]) + (matrix[1*4+0] * v[1]) + (matrix[2*4+0] * v[2]) + matrix[3*4+0]; - REAL ty = (matrix[0*4+1] * v[0]) + (matrix[1*4+1] * v[1]) + (matrix[2*4+1] * v[2]) + matrix[3*4+1]; - REAL tz = (matrix[0*4+2] * v[0]) + (matrix[1*4+2] * v[1]) + (matrix[2*4+2] * v[2]) + matrix[3*4+2]; - t[0] = tx; - t[1] = ty; - t[2] = tz; - } - else - { - t[0] = v[0]; - t[1] = v[1]; - t[2] = v[2]; - } -} - -void fm_rotate(const REAL matrix[16],const REAL v[3],REAL t[3]) // rotate and translate this point -{ - if ( matrix ) - { - REAL tx = (matrix[0*4+0] * v[0]) + (matrix[1*4+0] * v[1]) + (matrix[2*4+0] * v[2]); - REAL ty = (matrix[0*4+1] * v[0]) + (matrix[1*4+1] * v[1]) + (matrix[2*4+1] * v[2]); - REAL tz = (matrix[0*4+2] * v[0]) + (matrix[1*4+2] * v[1]) + (matrix[2*4+2] * v[2]); - t[0] = tx; - t[1] = ty; - t[2] = tz; - } - else - { - t[0] = v[0]; - t[1] = v[1]; - t[2] = v[2]; - } -} - - -REAL fm_distance(const REAL *p1,const REAL *p2) -{ - REAL dx = p1[0] - p2[0]; - REAL dy = p1[1] - p2[1]; - REAL dz = p1[2] - p2[2]; - - return sqrt( dx*dx + dy*dy + dz *dz ); -} - -REAL fm_distanceSquared(const REAL *p1,const REAL *p2) -{ - REAL dx = p1[0] - p2[0]; - REAL dy = p1[1] - p2[1]; - REAL dz = p1[2] - p2[2]; - - return dx*dx + dy*dy + dz *dz; -} - - -REAL fm_distanceSquaredXZ(const REAL *p1,const REAL *p2) -{ - REAL dx = p1[0] - p2[0]; - REAL dz = p1[2] - p2[2]; - - return dx*dx + dz *dz; -} - - -REAL fm_computePlane(const REAL *A,const REAL *B,const REAL *C,REAL *n) // returns D -{ - REAL vx = (B[0] - C[0]); - REAL vy = (B[1] - C[1]); - REAL vz = (B[2] - C[2]); - - REAL wx = (A[0] - B[0]); - REAL wy = (A[1] - B[1]); - REAL wz = (A[2] - B[2]); - - REAL vw_x = vy * wz - vz * wy; - REAL vw_y = vz * wx - vx * wz; - REAL vw_z = vx * wy - vy * wx; - - REAL mag = sqrt((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z)); - - if ( mag < 0.000001f ) - { - mag = 0; - } - else - { - mag = 1.0f/mag; - } - - REAL x = vw_x * mag; - REAL y = vw_y * mag; - REAL z = vw_z * mag; - - - REAL D = 0.0f - ((x*A[0])+(y*A[1])+(z*A[2])); - - n[0] = x; - n[1] = y; - n[2] = z; - - return D; -} - -REAL fm_distToPlane(const REAL *plane,const REAL *p) // computes the distance of this point from the plane. -{ - return p[0]*plane[0]+p[1]*plane[1]+p[2]*plane[2]+plane[3]; -} - -REAL fm_dot(const REAL *p1,const REAL *p2) -{ - return p1[0]*p2[0]+p1[1]*p2[1]+p1[2]*p2[2]; -} - -void fm_cross(REAL *cross,const REAL *a,const REAL *b) -{ - cross[0] = a[1]*b[2] - a[2]*b[1]; - cross[1] = a[2]*b[0] - a[0]*b[2]; - cross[2] = a[0]*b[1] - a[1]*b[0]; -} - -void fm_computeNormalVector(REAL *n,const REAL *p1,const REAL *p2) -{ - n[0] = p2[0] - p1[0]; - n[1] = p2[1] - p1[1]; - n[2] = p2[2] - p1[2]; - fm_normalize(n); -} - -bool fm_computeWindingOrder(const REAL *p1,const REAL *p2,const REAL *p3) // returns true if the triangle is clockwise. -{ - bool ret = false; - - REAL v1[3]; - REAL v2[3]; - - fm_computeNormalVector(v1,p1,p2); // p2-p1 (as vector) and then normalized - fm_computeNormalVector(v2,p1,p3); // p3-p1 (as vector) and then normalized - - REAL cross[3]; - - fm_cross(cross, v1, v2 ); - REAL ref[3] = { 1, 0, 0 }; - - REAL d = fm_dot( cross, ref ); - - - if ( d <= 0 ) - ret = false; - else - ret = true; - - return ret; -} - -REAL fm_normalize(REAL *n) // normalize this vector -{ - REAL dist = (REAL)sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]); - if ( dist > 0.0000001f ) - { - REAL mag = 1.0f / dist; - n[0]*=mag; - n[1]*=mag; - n[2]*=mag; - } - else - { - n[0] = 1; - n[1] = 0; - n[2] = 0; - } - - return dist; -} - - -void fm_matrixMultiply(const REAL *pA,const REAL *pB,REAL *pM) -{ -#if 1 - - REAL a = pA[0*4+0] * pB[0*4+0] + pA[0*4+1] * pB[1*4+0] + pA[0*4+2] * pB[2*4+0] + pA[0*4+3] * pB[3*4+0]; - REAL b = pA[0*4+0] * pB[0*4+1] + pA[0*4+1] * pB[1*4+1] + pA[0*4+2] * pB[2*4+1] + pA[0*4+3] * pB[3*4+1]; - REAL c = pA[0*4+0] * pB[0*4+2] + pA[0*4+1] * pB[1*4+2] + pA[0*4+2] * pB[2*4+2] + pA[0*4+3] * pB[3*4+2]; - REAL d = pA[0*4+0] * pB[0*4+3] + pA[0*4+1] * pB[1*4+3] + pA[0*4+2] * pB[2*4+3] + pA[0*4+3] * pB[3*4+3]; - - REAL e = pA[1*4+0] * pB[0*4+0] + pA[1*4+1] * pB[1*4+0] + pA[1*4+2] * pB[2*4+0] + pA[1*4+3] * pB[3*4+0]; - REAL f = pA[1*4+0] * pB[0*4+1] + pA[1*4+1] * pB[1*4+1] + pA[1*4+2] * pB[2*4+1] + pA[1*4+3] * pB[3*4+1]; - REAL g = pA[1*4+0] * pB[0*4+2] + pA[1*4+1] * pB[1*4+2] + pA[1*4+2] * pB[2*4+2] + pA[1*4+3] * pB[3*4+2]; - REAL h = pA[1*4+0] * pB[0*4+3] + pA[1*4+1] * pB[1*4+3] + pA[1*4+2] * pB[2*4+3] + pA[1*4+3] * pB[3*4+3]; - - REAL i = pA[2*4+0] * pB[0*4+0] + pA[2*4+1] * pB[1*4+0] + pA[2*4+2] * pB[2*4+0] + pA[2*4+3] * pB[3*4+0]; - REAL j = pA[2*4+0] * pB[0*4+1] + pA[2*4+1] * pB[1*4+1] + pA[2*4+2] * pB[2*4+1] + pA[2*4+3] * pB[3*4+1]; - REAL k = pA[2*4+0] * pB[0*4+2] + pA[2*4+1] * pB[1*4+2] + pA[2*4+2] * pB[2*4+2] + pA[2*4+3] * pB[3*4+2]; - REAL l = pA[2*4+0] * pB[0*4+3] + pA[2*4+1] * pB[1*4+3] + pA[2*4+2] * pB[2*4+3] + pA[2*4+3] * pB[3*4+3]; - - REAL m = pA[3*4+0] * pB[0*4+0] + pA[3*4+1] * pB[1*4+0] + pA[3*4+2] * pB[2*4+0] + pA[3*4+3] * pB[3*4+0]; - REAL n = pA[3*4+0] * pB[0*4+1] + pA[3*4+1] * pB[1*4+1] + pA[3*4+2] * pB[2*4+1] + pA[3*4+3] * pB[3*4+1]; - REAL o = pA[3*4+0] * pB[0*4+2] + pA[3*4+1] * pB[1*4+2] + pA[3*4+2] * pB[2*4+2] + pA[3*4+3] * pB[3*4+2]; - REAL p = pA[3*4+0] * pB[0*4+3] + pA[3*4+1] * pB[1*4+3] + pA[3*4+2] * pB[2*4+3] + pA[3*4+3] * pB[3*4+3]; - - pM[0] = a; - pM[1] = b; - pM[2] = c; - pM[3] = d; - - pM[4] = e; - pM[5] = f; - pM[6] = g; - pM[7] = h; - - pM[8] = i; - pM[9] = j; - pM[10] = k; - pM[11] = l; - - pM[12] = m; - pM[13] = n; - pM[14] = o; - pM[15] = p; - - -#else - memset(pM, 0, sizeof(REAL)*16); - for(NxI32 i=0; i<4; i++ ) - for(NxI32 j=0; j<4; j++ ) - for(NxI32 k=0; k<4; k++ ) - pM[4*i+j] += pA[4*i+k] * pB[4*k+j]; -#endif -} - - -void fm_eulerToQuatDX(REAL x,REAL y,REAL z,REAL *quat) // convert euler angles to quaternion using the fucked up DirectX method -{ - REAL matrix[16]; - fm_eulerToMatrix(x,y,z,matrix); - fm_matrixToQuat(matrix,quat); -} - -// implementation copied from: http://blogs.msdn.com/mikepelton/archive/2004/10/29/249501.aspx -void fm_eulerToMatrixDX(REAL x,REAL y,REAL z,REAL *matrix) // convert euler angles to quaternion using the fucked up DirectX method. -{ - fm_identity(matrix); - matrix[0*4+0] = cos(z)*cos(y) + sin(z)*sin(x)*sin(y); - matrix[0*4+1] = sin(z)*cos(x); - matrix[0*4+2] = cos(z)*-sin(y) + sin(z)*sin(x)*cos(y); - - matrix[1*4+0] = -sin(z)*cos(y)+cos(z)*sin(x)*sin(y); - matrix[1*4+1] = cos(z)*cos(x); - matrix[1*4+2] = sin(z)*sin(y) +cos(z)*sin(x)*cos(y); - - matrix[2*4+0] = cos(x)*sin(y); - matrix[2*4+1] = -sin(x); - matrix[2*4+2] = cos(x)*cos(y); -} - - -void fm_scale(REAL x,REAL y,REAL z,REAL *fscale) // apply scale to the matrix. -{ - fscale[0*4+0] = x; - fscale[1*4+1] = y; - fscale[2*4+2] = z; -} - - -void fm_composeTransform(const REAL *position,const REAL *quat,const REAL *scale,REAL *matrix) -{ - fm_identity(matrix); - fm_quatToMatrix(quat,matrix); - - if ( scale && ( scale[0] != 1 || scale[1] != 1 || scale[2] != 1 ) ) - { - REAL work[16]; - memcpy(work,matrix,sizeof(REAL)*16); - REAL mscale[16]; - fm_identity(mscale); - fm_scale(scale[0],scale[1],scale[2],mscale); - fm_matrixMultiply(work,mscale,matrix); - } - - matrix[12] = position[0]; - matrix[13] = position[1]; - matrix[14] = position[2]; -} - - -void fm_setTranslation(const REAL *translation,REAL *matrix) -{ - matrix[12] = translation[0]; - matrix[13] = translation[1]; - matrix[14] = translation[2]; -} - -static REAL enorm0_3d ( REAL x0, REAL y0, REAL z0, REAL x1, REAL y1, REAL z1 ) - -/**********************************************************************/ - -/* -Purpose: - -ENORM0_3D computes the Euclidean norm of (P1-P0) in 3D. - -Modified: - -18 April 1999 - -Author: - -John Burkardt - -Parameters: - -Input, REAL X0, Y0, Z0, X1, Y1, Z1, the coordinates of the points -P0 and P1. - -Output, REAL ENORM0_3D, the Euclidean norm of (P1-P0). -*/ -{ - REAL value; - - value = sqrt ( - ( x1 - x0 ) * ( x1 - x0 ) + - ( y1 - y0 ) * ( y1 - y0 ) + - ( z1 - z0 ) * ( z1 - z0 ) ); - - return value; -} - - -static REAL triangle_area_3d ( REAL x1, REAL y1, REAL z1, REAL x2,REAL y2, REAL z2, REAL x3, REAL y3, REAL z3 ) - - /**********************************************************************/ - - /* - Purpose: - - TRIANGLE_AREA_3D computes the area of a triangle in 3D. - - Modified: - - 22 April 1999 - - Author: - - John Burkardt - - Parameters: - - Input, REAL X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, the (X,Y,Z) - coordinates of the corners of the triangle. - - Output, REAL TRIANGLE_AREA_3D, the area of the triangle. - */ -{ - REAL a; - REAL alpha; - REAL area; - REAL b; - REAL base; - REAL c; - REAL dot; - REAL height; - /* - Find the projection of (P3-P1) onto (P2-P1). - */ - dot = - ( x2 - x1 ) * ( x3 - x1 ) + - ( y2 - y1 ) * ( y3 - y1 ) + - ( z2 - z1 ) * ( z3 - z1 ); - - base = enorm0_3d ( x1, y1, z1, x2, y2, z2 ); - /* - The height of the triangle is the length of (P3-P1) after its - projection onto (P2-P1) has been subtracted. - */ - if ( base == 0.0 ) { - - height = 0.0; - - } - else { - - alpha = dot / ( base * base ); - - a = x3 - x1 - alpha * ( x2 - x1 ); - b = y3 - y1 - alpha * ( y2 - y1 ); - c = z3 - z1 - alpha * ( z2 - z1 ); - - height = sqrt ( a * a + b * b + c * c ); - - } - - area = 0.5f * base * height; - - return area; -} - - -REAL fm_computeArea(const REAL *p1,const REAL *p2,const REAL *p3) -{ - REAL ret = 0; - - ret = triangle_area_3d(p1[0],p1[1],p1[2],p2[0],p2[1],p2[2],p3[0],p3[1],p3[2]); - - return ret; -} - - -void fm_lerp(const REAL *p1,const REAL *p2,REAL *dest,REAL lerpValue) -{ - dest[0] = ((p2[0] - p1[0])*lerpValue) + p1[0]; - dest[1] = ((p2[1] - p1[1])*lerpValue) + p1[1]; - dest[2] = ((p2[2] - p1[2])*lerpValue) + p1[2]; -} - -bool fm_pointTestXZ(const REAL *p,const REAL *i,const REAL *j) -{ - bool ret = false; - - if (((( i[2] <= p[2] ) && ( p[2] < j[2] )) || (( j[2] <= p[2] ) && ( p[2] < i[2] ))) && ( p[0] < (j[0] - i[0]) * (p[2] - i[2]) / (j[2] - i[2]) + i[0])) - ret = true; - - return ret; -}; - - -bool fm_insideTriangleXZ(const REAL *p,const REAL *p1,const REAL *p2,const REAL *p3) -{ - bool ret = false; - - NxI32 c = 0; - if ( fm_pointTestXZ(p,p1,p2) ) c = !c; - if ( fm_pointTestXZ(p,p2,p3) ) c = !c; - if ( fm_pointTestXZ(p,p3,p1) ) c = !c; - if ( c ) ret = true; - - return ret; -} - -bool fm_insideAABB(const REAL *pos,const REAL *bmin,const REAL *bmax) -{ - bool ret = false; - - if ( pos[0] >= bmin[0] && pos[0] <= bmax[0] && - pos[1] >= bmin[1] && pos[1] <= bmax[1] && - pos[2] >= bmin[2] && pos[2] <= bmax[2] ) - ret = true; - - return ret; -} - - -NxU32 fm_clipTestPoint(const REAL *bmin,const REAL *bmax,const REAL *pos) -{ - NxU32 ret = 0; - - if ( pos[0] < bmin[0] ) - ret|=FMCS_XMIN; - else if ( pos[0] > bmax[0] ) - ret|=FMCS_XMAX; - - if ( pos[1] < bmin[1] ) - ret|=FMCS_YMIN; - else if ( pos[1] > bmax[1] ) - ret|=FMCS_YMAX; - - if ( pos[2] < bmin[2] ) - ret|=FMCS_ZMIN; - else if ( pos[2] > bmax[2] ) - ret|=FMCS_ZMAX; - - return ret; -} - -NxU32 fm_clipTestPointXZ(const REAL *bmin,const REAL *bmax,const REAL *pos) // only tests X and Z, not Y -{ - NxU32 ret = 0; - - if ( pos[0] < bmin[0] ) - ret|=FMCS_XMIN; - else if ( pos[0] > bmax[0] ) - ret|=FMCS_XMAX; - - if ( pos[2] < bmin[2] ) - ret|=FMCS_ZMIN; - else if ( pos[2] > bmax[2] ) - ret|=FMCS_ZMAX; - - return ret; -} - -NxU32 fm_clipTestAABB(const REAL *bmin,const REAL *bmax,const REAL *p1,const REAL *p2,const REAL *p3,NxU32 &andCode) -{ - NxU32 orCode = 0; - - andCode = FMCS_XMIN | FMCS_XMAX | FMCS_YMIN | FMCS_YMAX | FMCS_ZMIN | FMCS_ZMAX; - - NxU32 c = fm_clipTestPoint(bmin,bmax,p1); - orCode|=c; - andCode&=c; - - c = fm_clipTestPoint(bmin,bmax,p2); - orCode|=c; - andCode&=c; - - c = fm_clipTestPoint(bmin,bmax,p3); - orCode|=c; - andCode&=c; - - return orCode; -} - -bool intersect(const REAL *si,const REAL *ei,const REAL *bmin,const REAL *bmax,REAL *time) -{ - REAL st,et,fst = 0,fet = 1; - - for (NxI32 i = 0; i < 3; i++) - { - if (*si < *ei) - { - if (*si > *bmax || *ei < *bmin) - return false; - REAL di = *ei - *si; - st = (*si < *bmin)? (*bmin - *si) / di: 0; - et = (*ei > *bmax)? (*bmax - *si) / di: 1; - } - else - { - if (*ei > *bmax || *si < *bmin) - return false; - REAL di = *ei - *si; - st = (*si > *bmax)? (*bmax - *si) / di: 0; - et = (*ei < *bmin)? (*bmin - *si) / di: 1; - } - - if (st > fst) fst = st; - if (et < fet) fet = et; - if (fet < fst) - return false; - bmin++; bmax++; - si++; ei++; - } - - *time = fst; - return true; -} - - - -bool fm_lineTestAABB(const REAL *p1,const REAL *p2,const REAL *bmin,const REAL *bmax,REAL &time) -{ - bool sect = intersect(p1,p2,bmin,bmax,&time); - return sect; -} - - -bool fm_lineTestAABBXZ(const REAL *p1,const REAL *p2,const REAL *bmin,const REAL *bmax,REAL &time) -{ - REAL _bmin[3]; - REAL _bmax[3]; - - _bmin[0] = bmin[0]; - _bmin[1] = -1e9; - _bmin[2] = bmin[2]; - - _bmax[0] = bmax[0]; - _bmax[1] = 1e9; - _bmax[2] = bmax[2]; - - bool sect = intersect(p1,p2,_bmin,_bmax,&time); - - return sect; -} - -void fm_minmax(const REAL *p,REAL *bmin,REAL *bmax) // accmulate to a min-max value -{ - - if ( p[0] < bmin[0] ) bmin[0] = p[0]; - if ( p[1] < bmin[1] ) bmin[1] = p[1]; - if ( p[2] < bmin[2] ) bmin[2] = p[2]; - - if ( p[0] > bmax[0] ) bmax[0] = p[0]; - if ( p[1] > bmax[1] ) bmax[1] = p[1]; - if ( p[2] > bmax[2] ) bmax[2] = p[2]; - -} - -REAL fm_solveX(const REAL *plane,REAL y,REAL z) // solve for X given this plane equation and the other two components. -{ - REAL x = (y*plane[1]+z*plane[2]+plane[3]) / -plane[0]; - return x; -} - -REAL fm_solveY(const REAL *plane,REAL x,REAL z) // solve for Y given this plane equation and the other two components. -{ - REAL y = (x*plane[0]+z*plane[2]+plane[3]) / -plane[1]; - return y; -} - - -REAL fm_solveZ(const REAL *plane,REAL x,REAL y) // solve for Y given this plane equation and the other two components. -{ - REAL z = (x*plane[0]+y*plane[1]+plane[3]) / -plane[2]; - return z; -} - - -void fm_getAABBCenter(const REAL *bmin,const REAL *bmax,REAL *center) -{ - center[0] = (bmax[0]-bmin[0])*0.5f+bmin[0]; - center[1] = (bmax[1]-bmin[1])*0.5f+bmin[1]; - center[2] = (bmax[2]-bmin[2])*0.5f+bmin[2]; -} - -FM_Axis fm_getDominantAxis(const REAL normal[3]) -{ - FM_Axis ret = FM_XAXIS; - - REAL x = fabs(normal[0]); - REAL y = fabs(normal[1]); - REAL z = fabs(normal[2]); - - if ( y > x && y > z ) - ret = FM_YAXIS; - else if ( z > x && z > y ) - ret = FM_ZAXIS; - - return ret; -} - - -bool fm_lineSphereIntersect(const REAL *center,REAL radius,const REAL *p1,const REAL *p2,REAL *intersect) -{ - bool ret = false; - - REAL dir[3]; - - dir[0] = p2[0]-p1[0]; - dir[1] = p2[1]-p1[1]; - dir[2] = p2[2]-p1[2]; - - REAL distance = sqrt( dir[0]*dir[0]+dir[1]*dir[1]+dir[2]*dir[2]); - - if ( distance > 0 ) - { - REAL recip = 1.0f / distance; - dir[0]*=recip; - dir[1]*=recip; - dir[2]*=recip; - ret = fm_raySphereIntersect(center,radius,p1,dir,distance,intersect); - } - else - { - dir[0] = center[0]-p1[0]; - dir[1] = center[1]-p1[1]; - dir[2] = center[2]-p1[2]; - REAL d2 = dir[0]*dir[0]+dir[1]*dir[1]+dir[2]*dir[2]; - REAL r2 = radius*radius; - if ( d2 < r2 ) - { - ret = true; - if ( intersect ) - { - intersect[0] = p1[0]; - intersect[1] = p1[1]; - intersect[2] = p1[2]; - } - } - } - return ret; -} - -#define DOT(p1,p2) (p1[0]*p2[0]+p1[1]*p2[1]+p1[2]*p2[2]) - -bool fm_raySphereIntersect(const REAL *center,REAL radius,const REAL *pos,const REAL *dir,REAL distance,REAL *intersect) -{ - bool ret = false; - - REAL E0[3]; - - E0[0] = center[0] - pos[0]; - E0[1] = center[1] - pos[1]; - E0[2] = center[2] - pos[2]; - - REAL V[3]; - - V[0] = dir[0]; - V[1] = dir[1]; - V[2] = dir[2]; - - - REAL dist2 = E0[0]*E0[0] + E0[1]*E0[1] + E0[2] * E0[2]; - REAL radius2 = radius*radius; // radius squared.. - - // Bug Fix For Gem, if origin is *inside* the sphere, invert the - // direction vector so that we get a valid intersection location. - if ( dist2 < radius2 ) - { - V[0]*=-1; - V[1]*=-1; - V[2]*=-1; - } - - - REAL v = DOT(E0,V); - - REAL disc = radius2 - (dist2 - v*v); - - if (disc > 0.0f) - { - if ( intersect ) - { - REAL d = sqrt(disc); - REAL diff = v-d; - if ( diff < distance ) - { - intersect[0] = pos[0]+V[0]*diff; - intersect[1] = pos[1]+V[1]*diff; - intersect[2] = pos[2]+V[2]*diff; - ret = true; - } - } - } - - return ret; -} - - -void fm_catmullRom(REAL *out_vector,const REAL *p1,const REAL *p2,const REAL *p3,const REAL *p4, const REAL s) -{ - REAL s_squared = s * s; - REAL s_cubed = s_squared * s; - - REAL coefficient_p1 = -s_cubed + 2*s_squared - s; - REAL coefficient_p2 = 3 * s_cubed - 5 * s_squared + 2; - REAL coefficient_p3 = -3 * s_cubed +4 * s_squared + s; - REAL coefficient_p4 = s_cubed - s_squared; - - out_vector[0] = (coefficient_p1 * p1[0] + coefficient_p2 * p2[0] + coefficient_p3 * p3[0] + coefficient_p4 * p4[0])*0.5f; - out_vector[1] = (coefficient_p1 * p1[1] + coefficient_p2 * p2[1] + coefficient_p3 * p3[1] + coefficient_p4 * p4[1])*0.5f; - out_vector[2] = (coefficient_p1 * p1[2] + coefficient_p2 * p2[2] + coefficient_p3 * p3[2] + coefficient_p4 * p4[2])*0.5f; -} - -bool fm_intersectAABB(const REAL *bmin1,const REAL *bmax1,const REAL *bmin2,const REAL *bmax2) -{ - if ((bmin1[0] > bmax2[0]) || (bmin2[0] > bmax1[0])) return false; - if ((bmin1[1] > bmax2[1]) || (bmin2[1] > bmax1[1])) return false; - if ((bmin1[2] > bmax2[2]) || (bmin2[2] > bmax1[2])) return false; - return true; - -} - -bool fm_insideAABB(const REAL *obmin,const REAL *obmax,const REAL *tbmin,const REAL *tbmax) // test if bounding box tbmin/tmbax is fully inside obmin/obmax -{ - bool ret = false; - - if ( tbmax[0] <= obmax[0] && - tbmax[1] <= obmax[1] && - tbmax[2] <= obmax[2] && - tbmin[0] >= obmin[0] && - tbmin[1] >= obmin[1] && - tbmin[2] >= obmin[2] ) ret = true; - - return ret; -} - - -// Reference, from Stan Melax in Game Gems I -// Quaternion q; -// vector3 c = CrossProduct(v0,v1); -// REAL d = DotProduct(v0,v1); -// REAL s = (REAL)sqrt((1+d)*2); -// q.x = c.x / s; -// q.y = c.y / s; -// q.z = c.z / s; -// q.w = s /2.0f; -// return q; -void fm_rotationArc(const REAL *v0,const REAL *v1,REAL *quat) -{ - REAL cross[3]; - - fm_cross(cross,v0,v1); - REAL d = fm_dot(v0,v1); - REAL s = sqrt((1+d)*2); - REAL recip = 1.0f / s; - - quat[0] = cross[0] * recip; - quat[1] = cross[1] * recip; - quat[2] = cross[2] * recip; - quat[3] = s * 0.5f; - -} - - -REAL fm_distancePointLineSegment(const REAL *Point,const REAL *LineStart,const REAL *LineEnd,REAL *intersection,LineSegmentType &type,REAL epsilon) -{ - REAL ret; - - REAL LineMag = fm_distance( LineEnd, LineStart ); - - if ( LineMag > 0 ) - { - REAL U = ( ( ( Point[0] - LineStart[0] ) * ( LineEnd[0] - LineStart[0] ) ) + ( ( Point[1] - LineStart[1] ) * ( LineEnd[1] - LineStart[1] ) ) + ( ( Point[2] - LineStart[2] ) * ( LineEnd[2] - LineStart[2] ) ) ) / ( LineMag * LineMag ); - if( U < 0.0f || U > 1.0f ) - { - REAL d1 = fm_distanceSquared(Point,LineStart); - REAL d2 = fm_distanceSquared(Point,LineEnd); - if ( d1 <= d2 ) - { - ret = sqrt(d1); - intersection[0] = LineStart[0]; - intersection[1] = LineStart[1]; - intersection[2] = LineStart[2]; - type = LS_START; - } - else - { - ret = sqrt(d2); - intersection[0] = LineEnd[0]; - intersection[1] = LineEnd[1]; - intersection[2] = LineEnd[2]; - type = LS_END; - } - } - else - { - intersection[0] = LineStart[0] + U * ( LineEnd[0] - LineStart[0] ); - intersection[1] = LineStart[1] + U * ( LineEnd[1] - LineStart[1] ); - intersection[2] = LineStart[2] + U * ( LineEnd[2] - LineStart[2] ); - - ret = fm_distance(Point,intersection); - - REAL d1 = fm_distanceSquared(intersection,LineStart); - REAL d2 = fm_distanceSquared(intersection,LineEnd); - REAL mag = (epsilon*2)*(epsilon*2); - - if ( d1 < mag ) // if less than 1/100th the total distance, treat is as the 'start' - { - type = LS_START; - } - else if ( d2 < mag ) - { - type = LS_END; - } - else - { - type = LS_MIDDLE; - } - - } - } - else - { - ret = LineMag; - intersection[0] = LineEnd[0]; - intersection[1] = LineEnd[1]; - intersection[2] = LineEnd[2]; - type = LS_END; - } - - return ret; -} - - -#ifndef BEST_FIT_PLANE_H - -#define BEST_FIT_PLANE_H - -template class Eigen -{ -public: - - - void DecrSortEigenStuff(void) - { - Tridiagonal(); //diagonalize the matrix. - QLAlgorithm(); // - DecreasingSort(); - GuaranteeRotation(); - } - - void Tridiagonal(void) - { - Type fM00 = mElement[0][0]; - Type fM01 = mElement[0][1]; - Type fM02 = mElement[0][2]; - Type fM11 = mElement[1][1]; - Type fM12 = mElement[1][2]; - Type fM22 = mElement[2][2]; - - m_afDiag[0] = fM00; - m_afSubd[2] = 0; - if (fM02 != (Type)0.0) - { - Type fLength = sqrt(fM01*fM01+fM02*fM02); - Type fInvLength = ((Type)1.0)/fLength; - fM01 *= fInvLength; - fM02 *= fInvLength; - Type fQ = ((Type)2.0)*fM01*fM12+fM02*(fM22-fM11); - m_afDiag[1] = fM11+fM02*fQ; - m_afDiag[2] = fM22-fM02*fQ; - m_afSubd[0] = fLength; - m_afSubd[1] = fM12-fM01*fQ; - mElement[0][0] = (Type)1.0; - mElement[0][1] = (Type)0.0; - mElement[0][2] = (Type)0.0; - mElement[1][0] = (Type)0.0; - mElement[1][1] = fM01; - mElement[1][2] = fM02; - mElement[2][0] = (Type)0.0; - mElement[2][1] = fM02; - mElement[2][2] = -fM01; - m_bIsRotation = false; - } - else - { - m_afDiag[1] = fM11; - m_afDiag[2] = fM22; - m_afSubd[0] = fM01; - m_afSubd[1] = fM12; - mElement[0][0] = (Type)1.0; - mElement[0][1] = (Type)0.0; - mElement[0][2] = (Type)0.0; - mElement[1][0] = (Type)0.0; - mElement[1][1] = (Type)1.0; - mElement[1][2] = (Type)0.0; - mElement[2][0] = (Type)0.0; - mElement[2][1] = (Type)0.0; - mElement[2][2] = (Type)1.0; - m_bIsRotation = true; - } - } - - bool QLAlgorithm(void) - { - const NxI32 iMaxIter = 32; - - for (NxI32 i0 = 0; i0 <3; i0++) - { - NxI32 i1; - for (i1 = 0; i1 < iMaxIter; i1++) - { - NxI32 i2; - for (i2 = i0; i2 <= (3-2); i2++) - { - Type fTmp = fabs(m_afDiag[i2]) + fabs(m_afDiag[i2+1]); - if ( fabs(m_afSubd[i2]) + fTmp == fTmp ) - break; - } - if (i2 == i0) - { - break; - } - - Type fG = (m_afDiag[i0+1] - m_afDiag[i0])/(((Type)2.0) * m_afSubd[i0]); - Type fR = sqrt(fG*fG+(Type)1.0); - if (fG < (Type)0.0) - { - fG = m_afDiag[i2]-m_afDiag[i0]+m_afSubd[i0]/(fG-fR); - } - else - { - fG = m_afDiag[i2]-m_afDiag[i0]+m_afSubd[i0]/(fG+fR); - } - Type fSin = (Type)1.0, fCos = (Type)1.0, fP = (Type)0.0; - for (NxI32 i3 = i2-1; i3 >= i0; i3--) - { - Type fF = fSin*m_afSubd[i3]; - Type fB = fCos*m_afSubd[i3]; - if (fabs(fF) >= fabs(fG)) - { - fCos = fG/fF; - fR = sqrt(fCos*fCos+(Type)1.0); - m_afSubd[i3+1] = fF*fR; - fSin = ((Type)1.0)/fR; - fCos *= fSin; - } - else - { - fSin = fF/fG; - fR = sqrt(fSin*fSin+(Type)1.0); - m_afSubd[i3+1] = fG*fR; - fCos = ((Type)1.0)/fR; - fSin *= fCos; - } - fG = m_afDiag[i3+1]-fP; - fR = (m_afDiag[i3]-fG)*fSin+((Type)2.0)*fB*fCos; - fP = fSin*fR; - m_afDiag[i3+1] = fG+fP; - fG = fCos*fR-fB; - for (NxI32 i4 = 0; i4 < 3; i4++) - { - fF = mElement[i4][i3+1]; - mElement[i4][i3+1] = fSin*mElement[i4][i3]+fCos*fF; - mElement[i4][i3] = fCos*mElement[i4][i3]-fSin*fF; - } - } - m_afDiag[i0] -= fP; - m_afSubd[i0] = fG; - m_afSubd[i2] = (Type)0.0; - } - if (i1 == iMaxIter) - { - return false; - } - } - return true; - } - - void DecreasingSort(void) - { - //sort eigenvalues in decreasing order, e[0] >= ... >= e[iSize-1] - for (NxI32 i0 = 0, i1; i0 <= 3-2; i0++) - { - // locate maximum eigenvalue - i1 = i0; - Type fMax = m_afDiag[i1]; - NxI32 i2; - for (i2 = i0+1; i2 < 3; i2++) - { - if (m_afDiag[i2] > fMax) - { - i1 = i2; - fMax = m_afDiag[i1]; - } - } - - if (i1 != i0) - { - // swap eigenvalues - m_afDiag[i1] = m_afDiag[i0]; - m_afDiag[i0] = fMax; - // swap eigenvectors - for (i2 = 0; i2 < 3; i2++) - { - Type fTmp = mElement[i2][i0]; - mElement[i2][i0] = mElement[i2][i1]; - mElement[i2][i1] = fTmp; - m_bIsRotation = !m_bIsRotation; - } - } - } - } - - - void GuaranteeRotation(void) - { - if (!m_bIsRotation) - { - // change sign on the first column - for (NxI32 iRow = 0; iRow <3; iRow++) - { - mElement[iRow][0] = -mElement[iRow][0]; - } - } - } - - Type mElement[3][3]; - Type m_afDiag[3]; - Type m_afSubd[3]; - bool m_bIsRotation; -}; - -#endif - -bool fm_computeBestFitPlane(NxU32 vcount, - const REAL *points, - NxU32 vstride, - const REAL *weights, - NxU32 wstride, - REAL *plane) -{ - bool ret = false; - - REAL kOrigin[3] = { 0, 0, 0 }; - - REAL wtotal = 0; - - { - const char *source = (const char *) points; - const char *wsource = (const char *) weights; - - for (NxU32 i=0; i kES; - - kES.mElement[0][0] = fSumXX; - kES.mElement[0][1] = fSumXY; - kES.mElement[0][2] = fSumXZ; - - kES.mElement[1][0] = fSumXY; - kES.mElement[1][1] = fSumYY; - kES.mElement[1][2] = fSumYZ; - - kES.mElement[2][0] = fSumXZ; - kES.mElement[2][1] = fSumYZ; - kES.mElement[2][2] = fSumZZ; - - // compute eigenstuff, smallest eigenvalue is in last position - kES.DecrSortEigenStuff(); - - REAL kNormal[3]; - - kNormal[0] = kES.mElement[0][2]; - kNormal[1] = kES.mElement[1][2]; - kNormal[2] = kES.mElement[2][2]; - - // the minimum energy - plane[0] = kNormal[0]; - plane[1] = kNormal[1]; - plane[2] = kNormal[2]; - - plane[3] = 0 - fm_dot(kNormal,kOrigin); - - ret = true; - - return ret; -} - - -bool fm_colinear(const REAL a1[3],const REAL a2[3],const REAL b1[3],const REAL b2[3],REAL epsilon) // true if these two line segments are co-linear. -{ - bool ret = false; - - REAL dir1[3]; - REAL dir2[3]; - - dir1[0] = (a2[0] - a1[0]); - dir1[1] = (a2[1] - a1[1]); - dir1[2] = (a2[2] - a1[2]); - - dir2[0] = (b2[0]-a1[0]) - (b1[0]-a1[0]); - dir2[1] = (b2[1]-a1[1]) - (b1[1]-a1[1]); - dir2[2] = (b2[2]-a2[2]) - (b1[2]-a2[2]); - - fm_normalize(dir1); - fm_normalize(dir2); - - REAL dot = fm_dot(dir1,dir2); - - if ( dot >= epsilon ) - { - ret = true; - } - - - return ret; -} - -bool fm_colinear(const REAL *p1,const REAL *p2,const REAL *p3,REAL epsilon) -{ - bool ret = false; - - REAL dir1[3]; - REAL dir2[3]; - - dir1[0] = p2[0] - p1[0]; - dir1[1] = p2[1] - p1[1]; - dir1[2] = p2[2] - p1[2]; - - dir2[0] = p3[0] - p2[0]; - dir2[1] = p3[1] - p2[1]; - dir2[2] = p3[2] - p2[2]; - - fm_normalize(dir1); - fm_normalize(dir2); - - REAL dot = fm_dot(dir1,dir2); - - if ( dot >= epsilon ) - { - ret = true; - } - - - return ret; -} - -void fm_initMinMax(const REAL *p,REAL *bmin,REAL *bmax) -{ - bmax[0] = bmin[0] = p[0]; - bmax[1] = bmin[1] = p[1]; - bmax[2] = bmin[2] = p[2]; -} - -IntersectResult fm_intersectLineSegments2d(const REAL *a1,const REAL *a2,const REAL *b1,const REAL *b2,REAL *intersection) -{ - IntersectResult ret; - - REAL denom = ((b2[1] - b1[1])*(a2[0] - a1[0])) - ((b2[0] - b1[0])*(a2[1] - a1[1])); - REAL nume_a = ((b2[0] - b1[0])*(a1[1] - b1[1])) - ((b2[1] - b1[1])*(a1[0] - b1[0])); - REAL nume_b = ((a2[0] - a1[0])*(a1[1] - b1[1])) - ((a2[1] - a1[1])*(a1[0] - b1[0])); - if (denom == 0 ) - { - if(nume_a == 0 && nume_b == 0) - { - ret = IR_COINCIDENT; - } - else - { - ret = IR_PARALLEL; - } - } - else - { - - REAL recip = 1 / denom; - REAL ua = nume_a * recip; - REAL ub = nume_b * recip; - - if(ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1 ) - { - // Get the intersection point. - intersection[0] = a1[0] + ua*(a2[0] - a1[0]); - intersection[1] = a1[1] + ua*(a2[1] - a1[1]); - ret = IR_DO_INTERSECT; - } - else - { - ret = IR_DONT_INTERSECT; - } - } - return ret; -} - -IntersectResult fm_intersectLineSegments2dTime(const REAL *a1,const REAL *a2,const REAL *b1,const REAL *b2,REAL &t1,REAL &t2) -{ - IntersectResult ret; - - REAL denom = ((b2[1] - b1[1])*(a2[0] - a1[0])) - ((b2[0] - b1[0])*(a2[1] - a1[1])); - REAL nume_a = ((b2[0] - b1[0])*(a1[1] - b1[1])) - ((b2[1] - b1[1])*(a1[0] - b1[0])); - REAL nume_b = ((a2[0] - a1[0])*(a1[1] - b1[1])) - ((a2[1] - a1[1])*(a1[0] - b1[0])); - if (denom == 0 ) - { - if(nume_a == 0 && nume_b == 0) - { - ret = IR_COINCIDENT; - } - else - { - ret = IR_PARALLEL; - } - } - else - { - - REAL recip = 1 / denom; - REAL ua = nume_a * recip; - REAL ub = nume_b * recip; - - if(ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1 ) - { - t1 = ua; - t2 = ub; - ret = IR_DO_INTERSECT; - } - else - { - ret = IR_DONT_INTERSECT; - } - } - return ret; -} - -//**** Plane Triangle Intersection - - - - - -// assumes that the points are on opposite sides of the plane! -void fm_intersectPointPlane(const REAL *p1,const REAL *p2,REAL *split,const REAL *plane) -{ - - REAL dp1 = fm_distToPlane(plane,p1); - - REAL dir[3]; - - dir[0] = p2[0] - p1[0]; - dir[1] = p2[1] - p1[1]; - dir[2] = p2[2] - p1[2]; - - REAL dot1 = dir[0]*plane[0] + dir[1]*plane[1] + dir[2]*plane[2]; - REAL dot2 = dp1 - plane[3]; - - REAL t = -(plane[3] + dot2 ) / dot1; - - split[0] = (dir[0]*t)+p1[0]; - split[1] = (dir[1]*t)+p1[1]; - split[2] = (dir[2]*t)+p1[2]; - -} - -PlaneTriResult fm_getSidePlane(const REAL *p,const REAL *plane,REAL epsilon) -{ - PlaneTriResult ret = PTR_ON_PLANE; - - REAL d = fm_distToPlane(plane,p); - - if ( d < -epsilon || d > epsilon ) - { - if ( d > 0 ) - ret = PTR_FRONT; // it is 'in front' within the provided epsilon value. - else - ret = PTR_BACK; - } - - return ret; -} - - - -#ifndef PLANE_TRIANGLE_INTERSECTION_H - -#define PLANE_TRIANGLE_INTERSECTION_H - -#define MAXPTS 256 - -template class point -{ -public: - - void set(const Type *p) - { - x = p[0]; - y = p[1]; - z = p[2]; - } - - Type x; - Type y; - Type z; -}; - -template class plane -{ -public: - plane(const Type *p) - { - normal.x = p[0]; - normal.y = p[1]; - normal.z = p[2]; - D = p[3]; - } - - Type Classify_Point(const point &p) - { - return p.x*normal.x + p.y*normal.y + p.z*normal.z + D; - } - - point normal; - Type D; -}; - -template class polygon -{ -public: - polygon(void) - { - mVcount = 0; - } - - polygon(const Type *p1,const Type *p2,const Type *p3) - { - mVcount = 3; - mVertices[0].set(p1); - mVertices[1].set(p2); - mVertices[2].set(p3); - } - - - NxI32 NumVertices(void) const { return mVcount; }; - - const point& Vertex(NxI32 index) - { - if ( index < 0 ) index+=mVcount; - return mVertices[index]; - }; - - - void set(const point *pts,NxI32 count) - { - for (NxI32 i=0; i *poly,plane *part, polygon &front, polygon &back) - { - NxI32 count = poly->NumVertices (); - NxI32 out_c = 0, in_c = 0; - point ptA, ptB,outpts[MAXPTS],inpts[MAXPTS]; - Type sideA, sideB; - ptA = poly->Vertex (count - 1); - sideA = part->Classify_Point (ptA); - for (NxI32 i = -1; ++i < count;) - { - ptB = poly->Vertex(i); - sideB = part->Classify_Point(ptB); - if (sideB > 0) - { - if (sideA < 0) - { - point v; - fm_intersectPointPlane(&ptB.x, &ptA.x, &v.x, &part->normal.x ); - outpts[out_c++] = inpts[in_c++] = v; - } - outpts[out_c++] = ptB; - } - else if (sideB < 0) - { - if (sideA > 0) - { - point v; - fm_intersectPointPlane(&ptB.x, &ptA.x, &v.x, &part->normal.x ); - outpts[out_c++] = inpts[in_c++] = v; - } - inpts[in_c++] = ptB; - } - else - outpts[out_c++] = inpts[in_c++] = ptB; - ptA = ptB; - sideA = sideB; - } - - front.set(&outpts[0], out_c); - back.set(&inpts[0], in_c); - } - - NxI32 mVcount; - point mVertices[MAXPTS]; -}; - - - -#endif - -static inline void add(const REAL *p,REAL *dest,NxU32 tstride,NxU32 &pcount) -{ - char *d = (char *) dest; - d = d + pcount*tstride; - dest = (REAL *) d; - dest[0] = p[0]; - dest[1] = p[1]; - dest[2] = p[2]; - pcount++; - assert( pcount <= 4 ); -} - - -PlaneTriResult fm_planeTriIntersection(const REAL *_plane, // the plane equation in Ax+By+Cz+D format - const REAL *triangle, // the source triangle. - NxU32 tstride, // stride in bytes of the input and output *vertices* - REAL epsilon, // the co-planar epsilon value. - REAL *front, // the triangle in front of the - NxU32 &fcount, // number of vertices in the 'front' triangle - REAL *back, // the triangle in back of the plane - NxU32 &bcount) // the number of vertices in the 'back' triangle. -{ - - fcount = 0; - bcount = 0; - - const char *tsource = (const char *) triangle; - - // get the three vertices of the triangle. - const REAL *p1 = (const REAL *) (tsource); - const REAL *p2 = (const REAL *) (tsource+tstride); - const REAL *p3 = (const REAL *) (tsource+tstride*2); - - - PlaneTriResult r1 = fm_getSidePlane(p1,_plane,epsilon); // compute the side of the plane each vertex is on - PlaneTriResult r2 = fm_getSidePlane(p2,_plane,epsilon); - PlaneTriResult r3 = fm_getSidePlane(p3,_plane,epsilon); - - // If any of the points lay right *on* the plane.... - if ( r1 == PTR_ON_PLANE || r2 == PTR_ON_PLANE || r3 == PTR_ON_PLANE ) - { - // If the triangle is completely co-planar, then just treat it as 'front' and return! - if ( r1 == PTR_ON_PLANE && r2 == PTR_ON_PLANE && r3 == PTR_ON_PLANE ) - { - add(p1,front,tstride,fcount); - add(p2,front,tstride,fcount); - add(p3,front,tstride,fcount); - return PTR_FRONT; - } - // Decide to place the co-planar points on the same side as the co-planar point. - PlaneTriResult r= PTR_ON_PLANE; - if ( r1 != PTR_ON_PLANE ) - r = r1; - else if ( r2 != PTR_ON_PLANE ) - r = r2; - else if ( r3 != PTR_ON_PLANE ) - r = r3; - - if ( r1 == PTR_ON_PLANE ) r1 = r; - if ( r2 == PTR_ON_PLANE ) r2 = r; - if ( r3 == PTR_ON_PLANE ) r3 = r; - - } - - if ( r1 == r2 && r1 == r3 ) // if all three vertices are on the same side of the plane. - { - if ( r1 == PTR_FRONT ) // if all three are in front of the plane, then copy to the 'front' output triangle. - { - add(p1,front,tstride,fcount); - add(p2,front,tstride,fcount); - add(p3,front,tstride,fcount); - } - else - { - add(p1,back,tstride,bcount); // if all three are in 'back' then copy to the 'back' output triangle. - add(p2,back,tstride,bcount); - add(p3,back,tstride,bcount); - } - return r1; // if all three points are on the same side of the plane return result - } - - - polygon pi(p1,p2,p3); - polygon pfront,pback; - - plane part(_plane); - - pi.Split_Polygon(&pi,&part,pfront,pback); - - for (NxI32 i=0; i bmax[0] ) bmax[0] = t[0]; - if ( t[1] > bmax[1] ) bmax[1] = t[1]; - if ( t[2] > bmax[2] ) bmax[2] = t[2]; - - src+=pstride; - } - - REAL center[3]; - - sides[0] = bmax[0]-bmin[0]; - sides[1] = bmax[1]-bmin[1]; - sides[2] = bmax[2]-bmin[2]; - - center[0] = sides[0]*0.5f+bmin[0]; - center[1] = sides[1]*0.5f+bmin[1]; - center[2] = sides[2]*0.5f+bmin[2]; - - REAL ocenter[3]; - - fm_rotate(matrix,center,ocenter); - - matrix[12]+=ocenter[0]; - matrix[13]+=ocenter[1]; - matrix[14]+=ocenter[2]; - -} - -void fm_computeBestFitOBB(NxU32 vcount,const REAL *points,NxU32 pstride,REAL *sides,REAL *matrix,bool bruteForce) -{ - REAL plane[4]; - fm_computeBestFitPlane(vcount,points,pstride,0,0,plane); - fm_planeToMatrix(plane,matrix); - computeOBB( vcount, points, pstride, sides, matrix ); - - REAL refmatrix[16]; - memcpy(refmatrix,matrix,16*sizeof(REAL)); - - REAL volume = sides[0]*sides[1]*sides[2]; - if ( bruteForce ) - { - for (REAL a=10; a<180; a+=10) - { - REAL quat[4]; - fm_eulerToQuat(0,a*FM_DEG_TO_RAD,0,quat); - REAL temp[16]; - REAL pmatrix[16]; - fm_quatToMatrix(quat,temp); - fm_matrixMultiply(temp,refmatrix,pmatrix); - REAL psides[3]; - computeOBB( vcount, points, pstride, psides, pmatrix ); - REAL v = psides[0]*psides[1]*psides[2]; - if ( v < volume ) - { - volume = v; - memcpy(matrix,pmatrix,sizeof(REAL)*16); - sides[0] = psides[0]; - sides[1] = psides[1]; - sides[2] = psides[2]; - } - } - } -} - -void fm_computeBestFitOBB(NxU32 vcount,const REAL *points,NxU32 pstride,REAL *sides,REAL *pos,REAL *quat,bool bruteForce) -{ - REAL matrix[16]; - fm_computeBestFitOBB(vcount,points,pstride,sides,matrix,bruteForce); - fm_getTranslation(matrix,pos); - fm_matrixToQuat(matrix,quat); -} - -void fm_computeBestFitABB(NxU32 vcount,const REAL *points,NxU32 pstride,REAL *sides,REAL *pos) -{ - REAL bmin[3]; - REAL bmax[3]; - - bmin[0] = points[0]; - bmin[1] = points[1]; - bmin[2] = points[2]; - - bmax[0] = points[0]; - bmax[1] = points[1]; - bmax[2] = points[2]; - - const char *cp = (const char *) points; - for (NxU32 i=0; i bmax[0] ) bmax[0] = p[0]; - if ( p[1] > bmax[1] ) bmax[1] = p[1]; - if ( p[2] > bmax[2] ) bmax[2] = p[2]; - - cp+=pstride; - } - - - sides[0] = bmax[0] - bmin[0]; - sides[1] = bmax[1] - bmin[1]; - sides[2] = bmax[2] - bmin[2]; - - pos[0] = bmin[0]+sides[0]*0.5f; - pos[1] = bmin[1]+sides[1]*0.5f; - pos[2] = bmin[2]+sides[2]*0.5f; - -} - - -void fm_planeToMatrix(const REAL *plane,REAL *matrix) // convert a plane equation to a 4x4 rotation matrix -{ - REAL ref[3] = { 0, 1, 0 }; - REAL quat[4]; - fm_rotationArc(ref,plane,quat); - fm_quatToMatrix(quat,matrix); - REAL origin[3] = { 0, -plane[3], 0 }; - REAL center[3]; - fm_transform(matrix,origin,center); - fm_setTranslation(center,matrix); -} - -void fm_planeToQuat(const REAL *plane,REAL *quat,REAL *pos) // convert a plane equation to a quaternion and translation -{ - REAL ref[3] = { 0, 1, 0 }; - REAL matrix[16]; - fm_rotationArc(ref,plane,quat); - fm_quatToMatrix(quat,matrix); - REAL origin[3] = { 0, plane[3], 0 }; - fm_transform(matrix,origin,pos); -} - -void fm_eulerMatrix(REAL ax,REAL ay,REAL az,REAL *matrix) // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) -{ - REAL quat[4]; - fm_eulerToQuat(ax,ay,az,quat); - fm_quatToMatrix(quat,matrix); -} - - -//********************************************************** -//********************************************************** -//**** Vertex Welding -//********************************************************** -//********************************************************** - -#ifndef VERTEX_INDEX_H - -#define VERTEX_INDEX_H - -namespace VERTEX_INDEX -{ - -class KdTreeNode; - -typedef CONVEX_DECOMPOSITION::Array< KdTreeNode * > KdTreeNodeVector; - -enum Axes -{ - X_AXIS = 0, - Y_AXIS = 1, - Z_AXIS = 2 -}; - -class KdTreeFindNode -{ -public: - KdTreeFindNode(void) - { - mNode = 0; - mDistance = 0; - } - KdTreeNode *mNode; - NxF64 mDistance; -}; - -class KdTreeInterface -{ -public: - virtual const NxF64 * getPositionDouble(NxU32 index) const = 0; - virtual const NxF32 * getPositionFloat(NxU32 index) const = 0; -}; - -class KdTreeNode -{ -public: - KdTreeNode(void) - { - mIndex = 0; - mLeft = 0; - mRight = 0; - } - - KdTreeNode(NxU32 index) - { - mIndex = index; - mLeft = 0; - mRight = 0; - }; - - ~KdTreeNode(void) - { - } - - - void addDouble(KdTreeNode *node,Axes dim,const KdTreeInterface *iface) - { - const NxF64 *nodePosition = iface->getPositionDouble( node->mIndex ); - const NxF64 *position = iface->getPositionDouble( mIndex ); - switch ( dim ) - { - case X_AXIS: - if ( nodePosition[0] <= position[0] ) - { - if ( mLeft ) - mLeft->addDouble(node,Y_AXIS,iface); - else - mLeft = node; - } - else - { - if ( mRight ) - mRight->addDouble(node,Y_AXIS,iface); - else - mRight = node; - } - break; - case Y_AXIS: - if ( nodePosition[1] <= position[1] ) - { - if ( mLeft ) - mLeft->addDouble(node,Z_AXIS,iface); - else - mLeft = node; - } - else - { - if ( mRight ) - mRight->addDouble(node,Z_AXIS,iface); - else - mRight = node; - } - break; - case Z_AXIS: - if ( nodePosition[2] <= position[2] ) - { - if ( mLeft ) - mLeft->addDouble(node,X_AXIS,iface); - else - mLeft = node; - } - else - { - if ( mRight ) - mRight->addDouble(node,X_AXIS,iface); - else - mRight = node; - } - break; - } - - } - - - void addFloat(KdTreeNode *node,Axes dim,const KdTreeInterface *iface) - { - const NxF32 *nodePosition = iface->getPositionFloat( node->mIndex ); - const NxF32 *position = iface->getPositionFloat( mIndex ); - switch ( dim ) - { - case X_AXIS: - if ( nodePosition[0] <= position[0] ) - { - if ( mLeft ) - mLeft->addFloat(node,Y_AXIS,iface); - else - mLeft = node; - } - else - { - if ( mRight ) - mRight->addFloat(node,Y_AXIS,iface); - else - mRight = node; - } - break; - case Y_AXIS: - if ( nodePosition[1] <= position[1] ) - { - if ( mLeft ) - mLeft->addFloat(node,Z_AXIS,iface); - else - mLeft = node; - } - else - { - if ( mRight ) - mRight->addFloat(node,Z_AXIS,iface); - else - mRight = node; - } - break; - case Z_AXIS: - if ( nodePosition[2] <= position[2] ) - { - if ( mLeft ) - mLeft->addFloat(node,X_AXIS,iface); - else - mLeft = node; - } - else - { - if ( mRight ) - mRight->addFloat(node,X_AXIS,iface); - else - mRight = node; - } - break; - } - - } - - - NxU32 getIndex(void) const { return mIndex; }; - - void search(Axes axis,const NxF64 *pos,NxF64 radius,NxU32 &count,NxU32 maxObjects,KdTreeFindNode *found,const KdTreeInterface *iface) - { - - const NxF64 *position = iface->getPositionDouble(mIndex); - - NxF64 dx = pos[0] - position[0]; - NxF64 dy = pos[1] - position[1]; - NxF64 dz = pos[2] - position[2]; - - KdTreeNode *search1 = 0; - KdTreeNode *search2 = 0; - - switch ( axis ) - { - case X_AXIS: - if ( dx <= 0 ) // JWR if we are to the left - { - search1 = mLeft; // JWR then search to the left - if ( -dx < radius ) // JWR if distance to the right is less than our search radius, continue on the right as well. - search2 = mRight; - } - else - { - search1 = mRight; // JWR ok, we go down the left tree - if ( dx < radius ) // JWR if the distance from the right is less than our search radius - search2 = mLeft; - } - axis = Y_AXIS; - break; - case Y_AXIS: - if ( dy <= 0 ) - { - search1 = mLeft; - if ( -dy < radius ) - search2 = mRight; - } - else - { - search1 = mRight; - if ( dy < radius ) - search2 = mLeft; - } - axis = Z_AXIS; - break; - case Z_AXIS: - if ( dz <= 0 ) - { - search1 = mLeft; - if ( -dz < radius ) - search2 = mRight; - } - else - { - search1 = mRight; - if ( dz < radius ) - search2 = mLeft; - } - axis = X_AXIS; - break; - } - - NxF64 r2 = radius*radius; - NxF64 m = dx*dx+dy*dy+dz*dz; - - if ( m < r2 ) - { - switch ( count ) - { - case 0: - found[count].mNode = this; - found[count].mDistance = m; - break; - case 1: - if ( m < found[0].mDistance ) - { - if ( maxObjects == 1 ) - { - found[0].mNode = this; - found[0].mDistance = m; - } - else - { - found[1] = found[0]; - found[0].mNode = this; - found[0].mDistance = m; - } - } - else if ( maxObjects > 1) - { - found[1].mNode = this; - found[1].mDistance = m; - } - break; - default: - { - bool inserted = false; - - for (NxU32 i=0; i= maxObjects ) scan=maxObjects-1; - for (NxU32 j=scan; j>i; j--) - { - found[j] = found[j-1]; - } - found[i].mNode = this; - found[i].mDistance = m; - inserted = true; - break; - } - } - - if ( !inserted && count < maxObjects ) - { - found[count].mNode = this; - found[count].mDistance = m; - } - } - break; - } - count++; - if ( count > maxObjects ) - { - count = maxObjects; - } - } - - - if ( search1 ) - search1->search( axis, pos,radius, count, maxObjects, found, iface); - - if ( search2 ) - search2->search( axis, pos,radius, count, maxObjects, found, iface); - - } - - void search(Axes axis,const NxF32 *pos,NxF32 radius,NxU32 &count,NxU32 maxObjects,KdTreeFindNode *found,const KdTreeInterface *iface) - { - - const NxF32 *position = iface->getPositionFloat(mIndex); - - NxF32 dx = pos[0] - position[0]; - NxF32 dy = pos[1] - position[1]; - NxF32 dz = pos[2] - position[2]; - - KdTreeNode *search1 = 0; - KdTreeNode *search2 = 0; - - switch ( axis ) - { - case X_AXIS: - if ( dx <= 0 ) // JWR if we are to the left - { - search1 = mLeft; // JWR then search to the left - if ( -dx < radius ) // JWR if distance to the right is less than our search radius, continue on the right as well. - search2 = mRight; - } - else - { - search1 = mRight; // JWR ok, we go down the left tree - if ( dx < radius ) // JWR if the distance from the right is less than our search radius - search2 = mLeft; - } - axis = Y_AXIS; - break; - case Y_AXIS: - if ( dy <= 0 ) - { - search1 = mLeft; - if ( -dy < radius ) - search2 = mRight; - } - else - { - search1 = mRight; - if ( dy < radius ) - search2 = mLeft; - } - axis = Z_AXIS; - break; - case Z_AXIS: - if ( dz <= 0 ) - { - search1 = mLeft; - if ( -dz < radius ) - search2 = mRight; - } - else - { - search1 = mRight; - if ( dz < radius ) - search2 = mLeft; - } - axis = X_AXIS; - break; - } - - NxF32 r2 = radius*radius; - NxF32 m = dx*dx+dy*dy+dz*dz; - - if ( m < r2 ) - { - switch ( count ) - { - case 0: - found[count].mNode = this; - found[count].mDistance = m; - break; - case 1: - if ( m < found[0].mDistance ) - { - if ( maxObjects == 1 ) - { - found[0].mNode = this; - found[0].mDistance = m; - } - else - { - found[1] = found[0]; - found[0].mNode = this; - found[0].mDistance = m; - } - } - else if ( maxObjects > 1) - { - found[1].mNode = this; - found[1].mDistance = m; - } - break; - default: - { - bool inserted = false; - - for (NxU32 i=0; i= maxObjects ) scan=maxObjects-1; - for (NxU32 j=scan; j>i; j--) - { - found[j] = found[j-1]; - } - found[i].mNode = this; - found[i].mDistance = m; - inserted = true; - break; - } - } - - if ( !inserted && count < maxObjects ) - { - found[count].mNode = this; - found[count].mDistance = m; - } - } - break; - } - count++; - if ( count > maxObjects ) - { - count = maxObjects; - } - } - - - if ( search1 ) - search1->search( axis, pos,radius, count, maxObjects, found, iface); - - if ( search2 ) - search2->search( axis, pos,radius, count, maxObjects, found, iface); - - } - -private: - - void setLeft(KdTreeNode *left) { mLeft = left; }; - void setRight(KdTreeNode *right) { mRight = right; }; - - KdTreeNode *getLeft(void) { return mLeft; } - KdTreeNode *getRight(void) { return mRight; } - - NxU32 mIndex; - KdTreeNode *mLeft; - KdTreeNode *mRight; -}; - - -#define MAX_BUNDLE_SIZE 1024 // 1024 nodes at a time, to minimize memory allocation and guarentee that pointers are persistent. - -class KdTreeNodeBundle : public Memalloc -{ -public: - - KdTreeNodeBundle(void) - { - mNext = 0; - mIndex = 0; - } - - bool isFull(void) const - { - return (bool)( mIndex == MAX_BUNDLE_SIZE ); - } - - KdTreeNode * getNextNode(void) - { - assert(mIndex DoubleVector; -typedef CONVEX_DECOMPOSITION::Array< NxF32 > FloatVector; - -class KdTree : public KdTreeInterface, public Memalloc -{ -public: - KdTree(void) - { - mRoot = 0; - mBundle = 0; - mVcount = 0; - mUseDouble = false; - } - - virtual ~KdTree(void) - { - reset(); - } - - const NxF64 * getPositionDouble(NxU32 index) const - { - assert( mUseDouble ); - assert ( index < mVcount ); - return &mVerticesDouble[index*3]; - } - - const NxF32 * getPositionFloat(NxU32 index) const - { - assert( !mUseDouble ); - assert ( index < mVcount ); - return &mVerticesFloat[index*3]; - } - - NxU32 search(const NxF64 *pos,NxF64 radius,NxU32 maxObjects,KdTreeFindNode *found) const - { - assert( mUseDouble ); - if ( !mRoot ) return 0; - NxU32 count = 0; - mRoot->search(X_AXIS,pos,radius,count,maxObjects,found,this); - return count; - } - - NxU32 search(const NxF32 *pos,NxF32 radius,NxU32 maxObjects,KdTreeFindNode *found) const - { - assert( !mUseDouble ); - if ( !mRoot ) return 0; - NxU32 count = 0; - mRoot->search(X_AXIS,pos,radius,count,maxObjects,found,this); - return count; - } - - void reset(void) - { - mRoot = 0; - mVerticesDouble.clear(); - mVerticesFloat.clear(); - KdTreeNodeBundle *bundle = mBundle; - while ( bundle ) - { - KdTreeNodeBundle *next = bundle->mNext; - delete bundle; - bundle = next; - } - mBundle = 0; - mVcount = 0; - } - - NxU32 add(NxF64 x,NxF64 y,NxF64 z) - { - assert(mUseDouble); - NxU32 ret = mVcount; - mVerticesDouble.pushBack(x); - mVerticesDouble.pushBack(y); - mVerticesDouble.pushBack(z); - mVcount++; - KdTreeNode *node = getNewNode(ret); - if ( mRoot ) - { - mRoot->addDouble(node,X_AXIS,this); - } - else - { - mRoot = node; - } - return ret; - } - - NxU32 add(NxF32 x,NxF32 y,NxF32 z) - { - assert(!mUseDouble); - NxU32 ret = mVcount; - mVerticesFloat.pushBack(x); - mVerticesFloat.pushBack(y); - mVerticesFloat.pushBack(z); - mVcount++; - KdTreeNode *node = getNewNode(ret); - if ( mRoot ) - { - mRoot->addFloat(node,X_AXIS,this); - } - else - { - mRoot = node; - } - return ret; - } - - KdTreeNode * getNewNode(NxU32 index) - { - if ( mBundle == 0 ) - { - mBundle = MEMALLOC_NEW(KdTreeNodeBundle); - } - if ( mBundle->isFull() ) - { - KdTreeNodeBundle *bundle = MEMALLOC_NEW(KdTreeNodeBundle); - mBundle->mNext = bundle; - mBundle = bundle; - } - KdTreeNode *node = mBundle->getNextNode(); - new ( node ) KdTreeNode(index); - return node; - } - - NxU32 getNearest(const NxF64 *pos,NxF64 radius,bool &_found) const // returns the nearest possible neighbor's index. - { - assert( mUseDouble ); - NxU32 ret = 0; - - _found = false; - KdTreeFindNode found[1]; - NxU32 count = search(pos,radius,1,found); - if ( count ) - { - KdTreeNode *node = found[0].mNode; - ret = node->getIndex(); - _found = true; - } - return ret; - } - - NxU32 getNearest(const NxF32 *pos,NxF32 radius,bool &_found) const // returns the nearest possible neighbor's index. - { - assert( !mUseDouble ); - NxU32 ret = 0; - - _found = false; - KdTreeFindNode found[1]; - NxU32 count = search(pos,radius,1,found); - if ( count ) - { - KdTreeNode *node = found[0].mNode; - ret = node->getIndex(); - _found = true; - } - return ret; - } - - const NxF64 * getVerticesDouble(void) const - { - assert( mUseDouble ); - const NxF64 *ret = 0; - if ( !mVerticesDouble.empty() ) - { - ret = &mVerticesDouble[0]; - } - return ret; - } - - const NxF32 * getVerticesFloat(void) const - { - assert( !mUseDouble ); - const NxF32 * ret = 0; - if ( !mVerticesFloat.empty() ) - { - ret = &mVerticesFloat[0]; - } - return ret; - } - - NxU32 getVcount(void) const { return mVcount; }; - - void setUseDouble(bool useDouble) - { - mUseDouble = useDouble; - } - -private: - bool mUseDouble; - KdTreeNode *mRoot; - KdTreeNodeBundle *mBundle; - NxU32 mVcount; - DoubleVector mVerticesDouble; - FloatVector mVerticesFloat; -}; - -}; // end of namespace VERTEX_INDEX - -class MyVertexIndex : public fm_VertexIndex, public Memalloc -{ -public: - MyVertexIndex(NxF64 granularity,bool snapToGrid) - { - mDoubleGranularity = granularity; - mFloatGranularity = (NxF32)granularity; - mSnapToGrid = snapToGrid; - mUseDouble = true; - mKdTree.setUseDouble(true); - } - - MyVertexIndex(NxF32 granularity,bool snapToGrid) - { - mDoubleGranularity = granularity; - mFloatGranularity = (NxF32)granularity; - mSnapToGrid = snapToGrid; - mUseDouble = false; - mKdTree.setUseDouble(false); - } - - virtual ~MyVertexIndex(void) - { - - } - - - NxF64 snapToGrid(NxF64 p) - { - NxF64 m = fmod(p,mDoubleGranularity); - p-=m; - return p; - } - - NxF32 snapToGrid(NxF32 p) - { - NxF32 m = fmodf(p,mFloatGranularity); - p-=m; - return p; - } - - NxU32 getIndex(const NxF32 *_p,bool &newPos) // get index for a vector NxF32 - { - NxU32 ret; - - if ( mUseDouble ) - { - NxF64 p[3]; - p[0] = _p[0]; - p[1] = _p[1]; - p[2] = _p[2]; - return getIndex(p,newPos); - } - - newPos = false; - - NxF32 p[3]; - - if ( mSnapToGrid ) - { - p[0] = snapToGrid(_p[0]); - p[1] = snapToGrid(_p[1]); - p[2] = snapToGrid(_p[2]); - } - else - { - p[0] = _p[0]; - p[1] = _p[1]; - p[2] = _p[2]; - } - - bool found; - ret = mKdTree.getNearest(p,mFloatGranularity,found); - if ( !found ) - { - newPos = true; - ret = mKdTree.add(p[0],p[1],p[2]); - } - - - return ret; - } - - NxU32 getIndex(const NxF64 *_p,bool &newPos) // get index for a vector NxF64 - { - NxU32 ret; - - if ( !mUseDouble ) - { - NxF32 p[3]; - p[0] = (NxF32)_p[0]; - p[1] = (NxF32)_p[1]; - p[2] = (NxF32)_p[2]; - return getIndex(p,newPos); - } - - newPos = false; - - NxF64 p[3]; - - if ( mSnapToGrid ) - { - p[0] = snapToGrid(_p[0]); - p[1] = snapToGrid(_p[1]); - p[2] = snapToGrid(_p[2]); - } - else - { - p[0] = _p[0]; - p[1] = _p[1]; - p[2] = _p[2]; - } - - bool found; - ret = mKdTree.getNearest(p,mDoubleGranularity,found); - if ( !found ) - { - newPos = true; - ret = mKdTree.add(p[0],p[1],p[2]); - } - - - return ret; - } - - const NxF32 * getVerticesFloat(void) const - { - const NxF32 * ret = 0; - - assert( !mUseDouble ); - - ret = mKdTree.getVerticesFloat(); - - return ret; - } - - const NxF64 * getVerticesDouble(void) const - { - const NxF64 * ret = 0; - - assert( mUseDouble ); - - ret = mKdTree.getVerticesDouble(); - - return ret; - } - - const NxF32 * getVertexFloat(NxU32 index) const - { - const NxF32 * ret = 0; - assert( !mUseDouble ); -#ifdef _DEBUG - NxU32 vcount = mKdTree.getVcount(); - assert( index < vcount ); -#endif - ret = mKdTree.getVerticesFloat(); - ret = &ret[index*3]; - return ret; - } - - const NxF64 * getVertexDouble(NxU32 index) const - { - const NxF64 * ret = 0; - assert( mUseDouble ); -#ifdef _DEBUG - NxU32 vcount = mKdTree.getVcount(); - assert( index < vcount ); -#endif - ret = mKdTree.getVerticesDouble(); - ret = &ret[index*3]; - - return ret; - } - - NxU32 getVcount(void) const - { - return mKdTree.getVcount(); - } - - bool isDouble(void) const - { - return mUseDouble; - } - - - bool saveAsObj(const char *fname,NxU32 tcount,NxU32 *indices) - { - bool ret = false; - - - FILE *fph = fopen(fname,"wb"); - if ( fph ) - { - ret = true; - - NxU32 vcount = getVcount(); - if ( mUseDouble ) - { - const NxF64 *v = getVerticesDouble(); - for (NxU32 i=0; i(ret); -} - -fm_VertexIndex * fm_createVertexIndex(NxF32 granularity,bool snapToGrid) // create an indexed vertext system for floats -{ - MyVertexIndex *ret = MEMALLOC_NEW(MyVertexIndex)(granularity,snapToGrid); - return static_cast< fm_VertexIndex *>(ret); -} - -void fm_releaseVertexIndex(fm_VertexIndex *vindex) -{ - MyVertexIndex *m = static_cast< MyVertexIndex *>(vindex); - delete m; -} - -#endif // END OF VERTEX WELDING CODE - - -//********************************************************** -//********************************************************** -//**** LineSweep Line-Segment Intersection Code -//********************************************************** -//********************************************************** - -//#ifndef LINE_SWEEP_H -#if 0 - -#define LINE_SWEEP_H - -class fm_quickSort -{ -public: - void qsort(void **base,NxI32 num); // perform the qsort. -protected: - // -1 less, 0 equal, +1 greater. - virtual NxI32 compare(void **p1,void **p2) = 0; -private: - void inline swap(char **a,char **b); -}; - - -void fm_quickSort::swap(char **a,char **b) -{ - char *tmp; - - if ( a != b ) - { - tmp = *a; - *a++ = *b; - *b++ = tmp; - } -} - - -void fm_quickSort::qsort(void **b,NxI32 num) -{ - char *lo,*hi; - char *mid; - char *bottom, *top; - NxI32 size; - char *lostk[30], *histk[30]; - NxI32 stkptr; - char **base = (char **)b; - - if (num < 2 ) return; - - stkptr = 0; - - lo = (char *)base; - hi = (char *)base + sizeof(char **) * (num-1); - -nextone: - - size = (NxI32)(hi - lo) / sizeof(char**) + 1; - - mid = lo + (size / 2) * sizeof(char **); - swap((char **)mid,(char **)lo); - bottom = lo; - top = hi + sizeof(char **); - - for (;;) - { - do - { - bottom += sizeof(char **); - } while (bottom <= hi && compare((void **)bottom,(void **)lo) <= 0); - - do - { - top -= sizeof(char **); - } while (top > lo && compare((void **)top,(void **)lo) >= 0); - - if (top < bottom) break; - - swap((char **)bottom,(char **)top); - - } - - swap((char **)lo,(char **)top); - - if ( top - 1 - lo >= hi - bottom ) - { - if (lo + sizeof(char **) < top) - { - lostk[stkptr] = lo; - histk[stkptr] = top - sizeof(char **); - stkptr++; - } - if (bottom < hi) - { - lo = bottom; - goto nextone; - } - } - else - { - if ( bottom < hi ) - { - lostk[stkptr] = bottom; - histk[stkptr] = hi; - stkptr++; - } - if (lo + sizeof(char **) < top) - { - hi = top - sizeof(char **); - goto nextone; /* do small recursion */ - } - } - - stkptr--; - - if (stkptr >= 0) - { - lo = lostk[stkptr]; - hi = histk[stkptr]; - goto nextone; - } - return; -} - - -typedef CONVEX_DECOMPOSITION::Array< fm_LineSegment > LineSegmentVector; - -static inline void setMinMax(NxF64 &vmin,NxF64 &vmax,NxF64 v1,NxF64 v2) -{ - if ( v1 <= v2 ) - { - vmin = v1; - vmax = v2; - } - else - { - vmin = v2; - vmax = v1; - } -} - - -class Intersection -{ -public: - Intersection(void) - { - mIndex = 0; - mTime = 0; - } - Intersection(NxF64 time,const NxF64 *from,const NxF64 *to,fm_VertexIndex *vpool) - { - mTime = time; - NxF64 pos[3]; - pos[0] = (to[0]-from[0])*time+from[0]; - pos[1] = (to[1]-from[1])*time+from[1]; - pos[2] = (to[2]-from[2])*time+from[2]; - bool newPos; - mIndex = vpool->getIndex(pos,newPos); - } - - NxU32 mIndex; - NxF64 mTime; -}; - - -typedef CONVEX_DECOMPOSITION::Array< Intersection > IntersectionList; - -class MyLineSegment : public fm_LineSegment, public Memalloc -{ -public: - - void init(const fm_LineSegment &s,fm_VertexIndex *vpool,NxU32 x) - { - fm_LineSegment *dest = static_cast< fm_LineSegment *>(this); - *dest = s; - - mFlipped = false; - - const NxF64 *p1 = vpool->getVertexDouble(mE1); - const NxF64 *p2 = vpool->getVertexDouble(mE2); - - setMinMax(mMin[0],mMax[0],p1[0],p2[0]); - setMinMax(mMin[1],mMax[1],p1[1],p2[1]); - setMinMax(mMin[2],mMax[2],p1[2],p2[2]); - - if ( p1[x] <= p2[x] ) - { - mFrom[0] = p1[0]; - mFrom[1] = p1[1]; - mFrom[2] = p1[2]; - - mTo[0] = p2[0]; - mTo[1] = p2[1]; - mTo[2] = p2[2]; - } - else - { - mFrom[0] = p2[0]; - mFrom[1] = p2[1]; - mFrom[2] = p2[2]; - - mTo[0] = p1[0]; - mTo[1] = p1[1]; - mTo[2] = p1[2]; - - mFlipped = true; - - swap(mE1,mE2); - } - - } - - // we already know that the x-extent overlaps or we wouldn't be in this routine.. - void intersect(MyLineSegment *segment,NxU32 x,NxU32 y,NxU32 /* z */,fm_VertexIndex *vpool) - { - NxU32 count = 0; - - // if the two segments share any start/end points then they cannot intersect at all! - - if ( segment->mE1 == mE1 || segment->mE1 == mE2 ) count++; - if ( segment->mE2 == mE1 || segment->mE2 == mE2 ) count++; - - if ( count == 0 ) - { - if ( mMax[y] < segment->mMin[y] ) // no intersection... - { - - } - else if ( mMin[y] > segment->mMax[y] ) // no intersection - { - - } - else - { - - NxF64 a1[2]; - NxF64 a2[2]; - NxF64 b1[2]; - NxF64 b2[2]; - - a1[0] = mFrom[x]; - a1[1] = mFrom[y]; - - a2[0] = mTo[x]; - a2[1] = mTo[y]; - - b1[0] = segment->mFrom[x]; - b1[1] = segment->mFrom[y]; - - b2[0] = segment->mTo[x]; - b2[1] = segment->mTo[y]; - - - NxF64 t1,t2; - IntersectResult result = fm_intersectLineSegments2dTime(a1,a2,b1,b2,t1,t2); - - if ( result == IR_DO_INTERSECT ) - { - addIntersect(t1,vpool); - segment->addIntersect(t2,vpool); - } - - - } - } - } - - void addIntersect(NxF64 time,fm_VertexIndex *vpool) - { - Intersection intersect(time,mFrom,mTo,vpool); - - if ( mE1 == intersect.mIndex || mE2 == intersect.mIndex ) - { - //printf("Split too close to the beginning or the end of the line segment.\r\n"); - } - else - { - if ( mIntersections.empty() ) - { - mIntersections.pushBack(intersect); - } - else - { - IntersectionList::Iterator i; - for (i=mIntersections.begin(); i!=mIntersections.end(); ++i) - { - Intersection &it = (*i); - if ( it.mIndex == intersect.mIndex ) - { - //printf("Duplicate Intersection, throwing it away.\r\n"); - break; - } - else - { - if ( it.mTime > time ) - { -//*** TODO TODO TODO mIntersections.insert(i,intersect); - break; - } - } - } - if ( i==mIntersections.end() ) - { - mIntersections.pushBack(intersect); - } - } - } - } - - void getResults(LineSegmentVector &results) - { - if ( mIntersections.empty() ) - { - fm_LineSegment seg(mE1,mE2); - if ( mFlipped ) - { - swap(seg.mE1,seg.mE2); - } - results.pushBack(seg); - } - else - { - NxU32 prev = mE1; - IntersectionList::Iterator i; - for (i=mIntersections.begin(); i!=mIntersections.end(); ++i) - { - Intersection &it = (*i); - fm_LineSegment seg(prev,it.mIndex); - if ( mFlipped ) - { - swap(seg.mE1,seg.mE2); - } - results.pushBack(seg); - prev = it.mIndex; - } - fm_LineSegment seg(prev,mE2); - if ( mFlipped ) - { - swap(seg.mE1,seg.mE2); - } - results.pushBack(seg); - } - } - - void swap(NxU32 &a,NxU32 &b) - { - NxU32 temp = a; - a = b; - b = temp; - } - - bool mFlipped; - NxF64 mFrom[3]; - NxF64 mTo[3]; - NxF64 mMin[3]; - NxF64 mMax[3]; - IntersectionList mIntersections; -}; - -typedef CONVEX_DECOMPOSITION::Array< MyLineSegment > MyLineSegmentVector; - -class MyLineSweep : public fm_LineSweep, public fm_quickSort, public Memalloc -{ -public: - virtual ~MyLineSweep(void) - { - - } - fm_LineSegment * performLineSweep(const fm_LineSegment *segments,NxU32 icount,const NxF64 *planeEquation,fm_VertexIndex *pool,NxU32 &scount) - { - fm_LineSegment *ret = 0; - - FM_Axis axis = fm_getDominantAxis(planeEquation); - switch ( axis ) - { - case FM_XAXIS: - mX = 1; - mY = 2; - mZ = 0; - break; - case FM_YAXIS: - mX = 0; - mY = 2; - mZ = 1; - break; - case FM_ZAXIS: - mX = 0; - mY = 1; - mZ = 2; - break; - } - - - mResults.clear(); - scount = 0; - - MyLineSegment *mls = MEMALLOC_NEW(MyLineSegment)[icount]; - MyLineSegment **mptr = (MyLineSegment **)MEMALLOC_MALLOC(sizeof(MyLineSegment *)*icount); - - for (NxU32 i=0; imTo[mX]; - for (NxU32 j=i+1; jmFrom[mX] >= esegment ) - { - break; - } - else - { - test->intersect(segment,mX,mY,mZ,pool); - } - } - } - - for (NxU32 i=0; igetResults(mResults); - } - - - delete []mls; - MEMALLOC_FREE(mptr); - - if ( !mResults.empty() ) - { - scount = (NxU32)mResults.size(); - ret = &mResults[0]; - } - - return ret; - } - - NxI32 compare(void **p1,void **p2) - { - NxI32 ret = 0; - - MyLineSegment **m1 = (MyLineSegment **) p1; - MyLineSegment **m2 = (MyLineSegment **) p2; - - MyLineSegment *s1 = *m1; - MyLineSegment *s2 = *m2; - - if ( s1->mFrom[mX] < s2->mFrom[mX] ) - ret = -1; - else if ( s1->mFrom[mX] > s2->mFrom[mX] ) - ret = 1; - else if ( s1->mFrom[mY] < s2->mFrom[mY] ) - ret = -1; - else if ( s1->mFrom[mY] > s2->mFrom[mY] ) - ret = 1; - - return ret; - } - - NxU32 mX; // index for the x-axis - NxU32 mY; // index for the y-axis - NxU32 mZ; - fm_VertexIndex *mfm_VertexIndex; - LineSegmentVector mResults; -}; - - -fm_LineSweep * fm_createLineSweep(void) -{ - MyLineSweep *mls = MEMALLOC_NEW(MyLineSweep); - return static_cast< fm_LineSweep *>(mls); -} - -void fm_releaseLineSweep(fm_LineSweep *sweep) -{ - MyLineSweep *mls = static_cast< MyLineSweep *>(sweep); - delete mls; -} - - - -#endif // End of LineSweep code - - - - -REAL fm_computeBestFitAABB(NxU32 vcount,const REAL *points,NxU32 pstride,REAL *bmin,REAL *bmax) // returns the diagonal distance -{ - - const NxU8 *source = (const NxU8 *) points; - - bmin[0] = points[0]; - bmin[1] = points[1]; - bmin[2] = points[2]; - - bmax[0] = points[0]; - bmax[1] = points[1]; - bmax[2] = points[2]; - - - for (NxU32 i=1; i bmax[0] ) bmax[0] = p[0]; - if ( p[1] > bmax[1] ) bmax[1] = p[1]; - if ( p[2] > bmax[2] ) bmax[2] = p[2]; - - } - - REAL dx = bmax[0] - bmin[0]; - REAL dy = bmax[1] - bmin[1]; - REAL dz = bmax[2] - bmin[2]; - - return (REAL) sqrt( dx*dx + dy*dy + dz*dz ); - -} - - - -/* a = b - c */ -#define vector(a,b,c) \ - (a)[0] = (b)[0] - (c)[0]; \ - (a)[1] = (b)[1] - (c)[1]; \ - (a)[2] = (b)[2] - (c)[2]; - - - -#define innerProduct(v,q) \ - ((v)[0] * (q)[0] + \ - (v)[1] * (q)[1] + \ - (v)[2] * (q)[2]) - -#define crossProduct(a,b,c) \ - (a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \ - (a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \ - (a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1]; - - -bool fm_lineIntersectsTriangle(const REAL *rayStart,const REAL *rayEnd,const REAL *p1,const REAL *p2,const REAL *p3,REAL *sect) -{ - REAL dir[3]; - - dir[0] = rayEnd[0] - rayStart[0]; - dir[1] = rayEnd[1] - rayStart[1]; - dir[2] = rayEnd[2] - rayStart[2]; - - REAL d = (REAL)sqrt(dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2]); - REAL r = 1.0f / d; - - dir[0]*=r; - dir[1]*=r; - dir[2]*=r; - - - REAL t; - - bool ret = fm_rayIntersectsTriangle(rayStart, dir, p1, p2, p3, t ); - - if ( ret ) - { - if ( t > d ) - { - sect[0] = rayStart[0] + dir[0]*t; - sect[1] = rayStart[1] + dir[1]*t; - sect[2] = rayStart[2] + dir[2]*t; - } - else - { - ret = false; - } - } - - return ret; -} - - - -bool fm_rayIntersectsTriangle(const REAL *p,const REAL *d,const REAL *v0,const REAL *v1,const REAL *v2,REAL &t) -{ - REAL e1[3],e2[3],h[3],s[3],q[3]; - REAL a,f,u,v; - - vector(e1,v1,v0); - vector(e2,v2,v0); - crossProduct(h,d,e2); - a = innerProduct(e1,h); - - if (a > -0.00001 && a < 0.00001) - return(false); - - f = 1/a; - vector(s,p,v0); - u = f * (innerProduct(s,h)); - - if (u < 0.0 || u > 1.0) - return(false); - - crossProduct(q,s,e1); - v = f * innerProduct(d,q); - if (v < 0.0 || u + v > 1.0) - return(false); - // at this stage we can compute t to find out where - // the intersection point is on the line - t = f * innerProduct(e2,q); - if (t > 0) // ray intersection - return(true); - else // this means that there is a line intersection - // but not a ray intersection - return (false); -} - - -inline REAL det(const REAL *p1,const REAL *p2,const REAL *p3) -{ - return p1[0]*p2[1]*p3[2] + p2[0]*p3[1]*p1[2] + p3[0]*p1[1]*p2[2] -p1[0]*p3[1]*p2[2] - p2[0]*p1[1]*p3[2] - p3[0]*p2[1]*p1[2]; -} - - -REAL fm_computeMeshVolume(const REAL *vertices,NxU32 tcount,const NxU32 *indices) -{ - REAL volume = 0; - - for (NxU32 i=0; i= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); -} - - -REAL fm_areaPolygon2d(NxU32 pcount,const REAL *points,NxU32 pstride) -{ - NxI32 n = (NxI32)pcount; - - REAL A=0.0f; - for(NxI32 p=n-1,q=0; q= y || y2 < y && y1 >= y ) - { - if (x1+(y-y1)/(y2-y1)*(x2-x1)= 3 ) - { - const REAL *prev = fm_getPoint(points,pstride,pcount-1); - const REAL *current = points; - const REAL *next = fm_getPoint(points,pstride,1); - REAL *dest = _dest; - - for (NxU32 i=0; i class Rect3d -{ -public: - Rect3d(void) { }; - - Rect3d(const T *bmin,const T *bmax) - { - - mMin[0] = bmin[0]; - mMin[1] = bmin[1]; - mMin[2] = bmin[2]; - - mMax[0] = bmax[0]; - mMax[1] = bmax[1]; - mMax[2] = bmax[2]; - - } - - void SetMin(const T *bmin) - { - mMin[0] = bmin[0]; - mMin[1] = bmin[1]; - mMin[2] = bmin[2]; - } - - void SetMax(const T *bmax) - { - mMax[0] = bmax[0]; - mMax[1] = bmax[1]; - mMax[2] = bmax[2]; - } - - void SetMin(T x,T y,T z) - { - mMin[0] = x; - mMin[1] = y; - mMin[2] = z; - } - - void SetMax(T x,T y,T z) - { - mMax[0] = x; - mMax[1] = y; - mMax[2] = z; - } - - T mMin[3]; - T mMax[3]; -}; - -#endif - -void splitRect(NxU32 axis, - const Rect3d &source, - Rect3d &b1, - Rect3d &b2, - const REAL *midpoint) -{ - switch ( axis ) - { - case 0: - b1.SetMin(source.mMin); - b1.SetMax( midpoint[0], source.mMax[1], source.mMax[2] ); - - b2.SetMin( midpoint[0], source.mMin[1], source.mMin[2] ); - b2.SetMax(source.mMax); - - break; - case 1: - b1.SetMin(source.mMin); - b1.SetMax( source.mMax[0], midpoint[1], source.mMax[2] ); - - b2.SetMin( source.mMin[0], midpoint[1], source.mMin[2] ); - b2.SetMax(source.mMax); - - break; - case 2: - b1.SetMin(source.mMin); - b1.SetMax( source.mMax[0], source.mMax[1], midpoint[2] ); - - b2.SetMin( source.mMin[0], source.mMin[1], midpoint[2] ); - b2.SetMax(source.mMax); - - break; - } -} - -bool fm_computeSplitPlane(NxU32 vcount, - const REAL *vertices, - NxU32 /* tcount */, - const NxU32 * /* indices */, - REAL *plane) -{ - - REAL sides[3]; - REAL matrix[16]; - - fm_computeBestFitOBB( vcount, vertices, sizeof(REAL)*3, sides, matrix ); - - REAL bmax[3]; - REAL bmin[3]; - - bmax[0] = sides[0]*0.5f; - bmax[1] = sides[1]*0.5f; - bmax[2] = sides[2]*0.5f; - - bmin[0] = -bmax[0]; - bmin[1] = -bmax[1]; - bmin[2] = -bmax[2]; - - - REAL dx = sides[0]; - REAL dy = sides[1]; - REAL dz = sides[2]; - - - REAL laxis = dx; - - NxU32 axis = 0; - - if ( dy > dx ) - { - axis = 1; - laxis = dy; - } - - if ( dz > dx && dz > dy ) - { - axis = 2; - laxis = dz; - } - - REAL p1[3]; - REAL p2[3]; - REAL p3[3]; - - p3[0] = p2[0] = p1[0] = bmin[0] + dx*0.5f; - p3[1] = p2[1] = p1[1] = bmin[1] + dy*0.5f; - p3[2] = p2[2] = p1[2] = bmin[2] + dz*0.5f; - - Rect3d b(bmin,bmax); - - Rect3d b1,b2; - - splitRect(axis,b,b1,b2,p1); - - - switch ( axis ) - { - case 0: - p2[1] = bmin[1]; - p2[2] = bmin[2]; - - if ( dz > dy ) - { - p3[1] = bmax[1]; - p3[2] = bmin[2]; - } - else - { - p3[1] = bmin[1]; - p3[2] = bmax[2]; - } - - break; - case 1: - p2[0] = bmin[0]; - p2[2] = bmin[2]; - - if ( dx > dz ) - { - p3[0] = bmax[0]; - p3[2] = bmin[2]; - } - else - { - p3[0] = bmin[0]; - p3[2] = bmax[2]; - } - - break; - case 2: - p2[0] = bmin[0]; - p2[1] = bmin[1]; - - if ( dx > dy ) - { - p3[0] = bmax[0]; - p3[1] = bmin[1]; - } - else - { - p3[0] = bmin[0]; - p3[1] = bmax[1]; - } - - break; - } - - REAL tp1[3]; - REAL tp2[3]; - REAL tp3[3]; - - fm_transform(matrix,p1,tp1); - fm_transform(matrix,p2,tp2); - fm_transform(matrix,p3,tp3); - - plane[3] = fm_computePlane(tp1,tp2,tp3,plane); - - return true; - -} - -#pragma warning(disable:4100) - -void fm_nearestPointInTriangle(const REAL *nearestPoint,const REAL *p1,const REAL *p2,const REAL *p3,REAL *nearest) -{ - -} - -static REAL Partial(const REAL *a,const REAL *p) -{ - return (a[0]*p[1]) - (p[0]*a[1]); -} - -REAL fm_areaTriangle(const REAL *p0,const REAL *p1,const REAL *p2) -{ - REAL A = Partial(p0,p1); - A+= Partial(p1,p2); - A+= Partial(p2,p0); - return A*0.5f; -} - -void fm_subtract(const REAL *A,const REAL *B,REAL *diff) // compute A-B and store the result in 'diff' -{ - diff[0] = A[0]-B[0]; - diff[1] = A[1]-B[1]; - diff[2] = A[2]-B[2]; -} - - -void fm_multiplyTransform(const REAL *pA,const REAL *pB,REAL *pM) -{ - - REAL a = pA[0*4+0] * pB[0*4+0] + pA[0*4+1] * pB[1*4+0] + pA[0*4+2] * pB[2*4+0] + pA[0*4+3] * pB[3*4+0]; - REAL b = pA[0*4+0] * pB[0*4+1] + pA[0*4+1] * pB[1*4+1] + pA[0*4+2] * pB[2*4+1] + pA[0*4+3] * pB[3*4+1]; - REAL c = pA[0*4+0] * pB[0*4+2] + pA[0*4+1] * pB[1*4+2] + pA[0*4+2] * pB[2*4+2] + pA[0*4+3] * pB[3*4+2]; - REAL d = pA[0*4+0] * pB[0*4+3] + pA[0*4+1] * pB[1*4+3] + pA[0*4+2] * pB[2*4+3] + pA[0*4+3] * pB[3*4+3]; - - REAL e = pA[1*4+0] * pB[0*4+0] + pA[1*4+1] * pB[1*4+0] + pA[1*4+2] * pB[2*4+0] + pA[1*4+3] * pB[3*4+0]; - REAL f = pA[1*4+0] * pB[0*4+1] + pA[1*4+1] * pB[1*4+1] + pA[1*4+2] * pB[2*4+1] + pA[1*4+3] * pB[3*4+1]; - REAL g = pA[1*4+0] * pB[0*4+2] + pA[1*4+1] * pB[1*4+2] + pA[1*4+2] * pB[2*4+2] + pA[1*4+3] * pB[3*4+2]; - REAL h = pA[1*4+0] * pB[0*4+3] + pA[1*4+1] * pB[1*4+3] + pA[1*4+2] * pB[2*4+3] + pA[1*4+3] * pB[3*4+3]; - - REAL i = pA[2*4+0] * pB[0*4+0] + pA[2*4+1] * pB[1*4+0] + pA[2*4+2] * pB[2*4+0] + pA[2*4+3] * pB[3*4+0]; - REAL j = pA[2*4+0] * pB[0*4+1] + pA[2*4+1] * pB[1*4+1] + pA[2*4+2] * pB[2*4+1] + pA[2*4+3] * pB[3*4+1]; - REAL k = pA[2*4+0] * pB[0*4+2] + pA[2*4+1] * pB[1*4+2] + pA[2*4+2] * pB[2*4+2] + pA[2*4+3] * pB[3*4+2]; - REAL l = pA[2*4+0] * pB[0*4+3] + pA[2*4+1] * pB[1*4+3] + pA[2*4+2] * pB[2*4+3] + pA[2*4+3] * pB[3*4+3]; - - REAL m = pA[3*4+0] * pB[0*4+0] + pA[3*4+1] * pB[1*4+0] + pA[3*4+2] * pB[2*4+0] + pA[3*4+3] * pB[3*4+0]; - REAL n = pA[3*4+0] * pB[0*4+1] + pA[3*4+1] * pB[1*4+1] + pA[3*4+2] * pB[2*4+1] + pA[3*4+3] * pB[3*4+1]; - REAL o = pA[3*4+0] * pB[0*4+2] + pA[3*4+1] * pB[1*4+2] + pA[3*4+2] * pB[2*4+2] + pA[3*4+3] * pB[3*4+2]; - REAL p = pA[3*4+0] * pB[0*4+3] + pA[3*4+1] * pB[1*4+3] + pA[3*4+2] * pB[2*4+3] + pA[3*4+3] * pB[3*4+3]; - - pM[0] = a; pM[1] = b; pM[2] = c; pM[3] = d; - - pM[4] = e; pM[5] = f; pM[6] = g; pM[7] = h; - - pM[8] = i; pM[9] = j; pM[10] = k; pM[11] = l; - - pM[12] = m; pM[13] = n; pM[14] = o; pM[15] = p; -} - -void fm_multiply(REAL *A,REAL scaler) -{ - A[0]*=scaler; - A[1]*=scaler; - A[2]*=scaler; -} - -void fm_add(const REAL *A,const REAL *B,REAL *sum) -{ - sum[0] = A[0]+B[0]; - sum[1] = A[1]+B[1]; - sum[2] = A[2]+B[2]; -} - -void fm_copy3(const REAL *source,REAL *dest) -{ - dest[0] = source[0]; - dest[1] = source[1]; - dest[2] = source[2]; -} - - -NxU32 fm_copyUniqueVertices(NxU32 vcount,const REAL *input_vertices,REAL *output_vertices,NxU32 tcount,const NxU32 *input_indices,NxU32 *output_indices) -{ - NxU32 ret = 0; - - REAL *vertices = (REAL *)MEMALLOC_MALLOC(sizeof(REAL)*vcount*3); - memcpy(vertices,input_vertices,sizeof(REAL)*vcount*3); - REAL *dest = output_vertices; - - NxU32 *reindex = (NxU32 *)MEMALLOC_MALLOC(sizeof(NxU32)*vcount); - memset(reindex,0xFF,sizeof(NxU32)*vcount); - - NxU32 icount = tcount*3; - - for (NxU32 i=0; i 0 ) - { - NxU32 i1 = indices[0]; - NxU32 i2 = indices[1]; - NxU32 i3 = indices[2]; - const REAL *p1 = &vertices[i1*3]; - const REAL *p2 = &vertices[i2*3]; - const REAL *p3 = &vertices[i3*3]; - REAL plane[4]; - plane[3] = fm_computePlane(p1,p2,p3,plane); - const NxU32 *scan = &indices[3]; - for (NxU32 i=1; i= dmin && dot <= dmax ) - { - ret = true; // then the plane equation is for practical purposes identical. - } - } - - return ret; -} - - -void fm_initMinMax(REAL bmin[3],REAL bmax[3]) -{ - bmin[0] = FLT_MAX; - bmin[1] = FLT_MAX; - bmin[2] = FLT_MAX; - bmax[0] = FLT_MIN; - bmax[1] = FLT_MIN; - bmax[2] = FLT_MIN; -} - - -#ifndef TESSELATE_H - -#define TESSELATE_H - -typedef CONVEX_DECOMPOSITION::Array< NxU32 > UintVector; - -class Myfm_Tesselate : public fm_Tesselate, public Memalloc -{ -public: - virtual ~Myfm_Tesselate(void) - { - - } - - const NxU32 * tesselate(fm_VertexIndex *vindex,NxU32 tcount,const NxU32 *indices,NxF32 longEdge,NxU32 maxDepth,NxU32 &outcount) - { - const NxU32 *ret = 0; - - mMaxDepth = maxDepth; - mLongEdge = longEdge*longEdge; - mLongEdgeD = mLongEdge; - mVertices = vindex; - - if ( mVertices->isDouble() ) - { - NxU32 vcount = mVertices->getVcount(); - NxF64 *vertices = (NxF64 *)MEMALLOC_MALLOC(sizeof(NxF64)*vcount*3); - memcpy(vertices,mVertices->getVerticesDouble(),sizeof(NxF64)*vcount*3); - - for (NxU32 i=0; igetVcount(); - NxF32 *vertices = (NxF32 *)MEMALLOC_MALLOC(sizeof(NxF32)*vcount*3); - memcpy(vertices,mVertices->getVerticesFloat(),sizeof(NxF32)*vcount*3); - - - for (NxU32 i=0; i mLongEdge || l2 > mLongEdge || l3 > mLongEdge ) - split = true; - - } - - if ( split ) - { - NxU32 edge; - - if ( l1 >= l2 && l1 >= l3 ) - edge = 0; - else if ( l2 >= l1 && l2 >= l3 ) - edge = 1; - else - edge = 2; - - NxF32 split[3]; - - switch ( edge ) - { - case 0: - { - fm_lerp(p1,p2,split,0.5f); - tesselate(p1,split,p3, recurse+1 ); - tesselate(split,p2,p3, recurse+1 ); - } - break; - case 1: - { - fm_lerp(p2,p3,split,0.5f); - tesselate(p1,p2,split, recurse+1 ); - tesselate(p1,split,p3, recurse+1 ); - } - break; - case 2: - { - fm_lerp(p3,p1,split,0.5f); - tesselate(p1,p2,split, recurse+1 ); - tesselate(split,p2,p3, recurse+1 ); - } - break; - } - } - else - { - bool newp; - - NxU32 i1 = mVertices->getIndex(p1,newp); - NxU32 i2 = mVertices->getIndex(p2,newp); - NxU32 i3 = mVertices->getIndex(p3,newp); - - mIndices.pushBack(i1); - mIndices.pushBack(i2); - mIndices.pushBack(i3); - } - - } - - void tesselate(const NxF64 *p1,const NxF64 *p2,const NxF64 *p3,NxU32 recurse) - { - bool split = false; - NxF64 l1,l2,l3; - - l1 = l2 = l3 = 0; - - if ( recurse < mMaxDepth ) - { - l1 = fm_distanceSquared(p1,p2); - l2 = fm_distanceSquared(p2,p3); - l3 = fm_distanceSquared(p3,p1); - - if ( l1 > mLongEdgeD || l2 > mLongEdgeD || l3 > mLongEdgeD ) - split = true; - - } - - if ( split ) - { - NxU32 edge; - - if ( l1 >= l2 && l1 >= l3 ) - edge = 0; - else if ( l2 >= l1 && l2 >= l3 ) - edge = 1; - else - edge = 2; - - NxF64 split[3]; - - switch ( edge ) - { - case 0: - { - fm_lerp(p1,p2,split,0.5); - tesselate(p1,split,p3, recurse+1 ); - tesselate(split,p2,p3, recurse+1 ); - } - break; - case 1: - { - fm_lerp(p2,p3,split,0.5); - tesselate(p1,p2,split, recurse+1 ); - tesselate(p1,split,p3, recurse+1 ); - } - break; - case 2: - { - fm_lerp(p3,p1,split,0.5); - tesselate(p1,p2,split, recurse+1 ); - tesselate(split,p2,p3, recurse+1 ); - } - break; - } - } - else - { - bool newp; - - NxU32 i1 = mVertices->getIndex(p1,newp); - NxU32 i2 = mVertices->getIndex(p2,newp); - NxU32 i3 = mVertices->getIndex(p3,newp); - - mIndices.pushBack(i1); - mIndices.pushBack(i2); - mIndices.pushBack(i3); - } - - } - -private: - NxF32 mLongEdge; - NxF64 mLongEdgeD; - fm_VertexIndex *mVertices; - UintVector mIndices; - NxU32 mMaxDepth; -}; - -fm_Tesselate * fm_createTesselate(void) -{ - Myfm_Tesselate *m = MEMALLOC_NEW(Myfm_Tesselate); - return static_cast< fm_Tesselate * >(m); -} - -void fm_releaseTesselate(fm_Tesselate *t) -{ - Myfm_Tesselate *m = static_cast< Myfm_Tesselate *>(t); - delete m; -} - -#endif - - -#ifndef RAY_ABB_INTERSECT - -#define RAY_ABB_INTERSECT - -//! Integer representation of a floating-point value. -#define IR(x) ((NxU32&)x) - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** -* A method to compute a ray-AABB intersection. -* Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 -* Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) -* Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) -* -* Hence this version is faster as well as more robust than the original one. -* -* Should work provided: -* 1) the integer representation of 0.0f is 0x00000000 -* 2) the sign bit of the NxF32 is the most significant one -* -* Report bugs: p.terdiman@codercorner.com -* -* \param aabb [in] the axis-aligned bounding box -* \param origin [in] ray origin -* \param dir [in] ray direction -* \param coord [out] impact coordinates -* \return true if ray intersects AABB -*/ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#define RAYAABB_EPSILON 0.00001f -bool fm_intersectRayAABB(const NxF32 MinB[3],const NxF32 MaxB[3],const NxF32 origin[3],const NxF32 dir[3],NxF32 coord[3]) -{ - bool Inside = true; - NxF32 MaxT[3]; - MaxT[0]=MaxT[1]=MaxT[2]=-1.0f; - - // Find candidate planes. - for(NxU32 i=0;i<3;i++) - { - if(origin[i] < MinB[i]) - { - coord[i] = MinB[i]; - Inside = false; - - // Calculate T distances to candidate planes - if(IR(dir[i])) MaxT[i] = (MinB[i] - origin[i]) / dir[i]; - } - else if(origin[i] > MaxB[i]) - { - coord[i] = MaxB[i]; - Inside = false; - - // Calculate T distances to candidate planes - if(IR(dir[i])) MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; - } - } - - // Ray origin inside bounding box - if(Inside) - { - coord[0] = origin[0]; - coord[1] = origin[1]; - coord[2] = origin[2]; - return true; - } - - // Get largest of the maxT's for final choice of intersection - NxU32 WhichPlane = 0; - if(MaxT[1] > MaxT[WhichPlane]) WhichPlane = 1; - if(MaxT[2] > MaxT[WhichPlane]) WhichPlane = 2; - - // Check final candidate actually inside box - if(IR(MaxT[WhichPlane])&0x80000000) return false; - - for(NxU32 i=0;i<3;i++) - { - if(i!=WhichPlane) - { - coord[i] = origin[i] + MaxT[WhichPlane] * dir[i]; -#ifdef RAYAABB_EPSILON - if(coord[i] < MinB[i] - RAYAABB_EPSILON || coord[i] > MaxB[i] + RAYAABB_EPSILON) return false; -#else - if(coord[i] < MinB[i] || coord[i] > MaxB[i]) return false; -#endif - } - } - return true; // ray hits box -} - -bool fm_intersectLineSegmentAABB(const NxF32 bmin[3],const NxF32 bmax[3],const NxF32 p1[3],const NxF32 p2[3],NxF32 intersect[3]) -{ - bool ret = false; - - NxF32 dir[3]; - dir[0] = p2[0] - p1[0]; - dir[1] = p2[1] - p1[1]; - dir[2] = p2[2] - p1[2]; - NxF32 dist = fm_normalize(dir); - if ( dist > RAYAABB_EPSILON ) - { - ret = fm_intersectRayAABB(bmin,bmax,p1,dir,intersect); - if ( ret ) - { - NxF32 d = fm_distanceSquared(p1,intersect); - if ( d > (dist*dist) ) - { - ret = false; - } - } - } - return ret; -} - -#endif - -#ifndef OBB_TO_AABB - -#define OBB_TO_AABB - -#pragma warning(disable:4100) -void fm_OBBtoAABB(const NxF32 obmin[3],const NxF32 obmax[3],const NxF32 matrix[16],NxF32 abmin[3],NxF32 abmax[3]) -{ - assert(0); // not yet implemented. -} - - -const REAL * computePos(NxU32 index,const REAL *vertices,NxU32 vstride) -{ - const char *tmp = (const char *)vertices; - tmp+=(index*vstride); - return (const REAL*)tmp; -} - -void computeNormal(NxU32 index,REAL *normals,NxU32 nstride,const REAL *normal) -{ - char *tmp = (char *)normals; - tmp+=(index*nstride); - REAL *dest = (REAL *)tmp; - dest[0]+=normal[0]; - dest[1]+=normal[1]; - dest[2]+=normal[2]; -} - -void fm_computeMeanNormals(NxU32 vcount, // the number of vertices - const REAL *vertices, // the base address of the vertex position data. - NxU32 vstride, // the stride between position data. - REAL *normals, // the base address of the destination for mean vector normals - NxU32 nstride, // the stride between normals - NxU32 tcount, // the number of triangles - const NxU32 *indices) // the triangle indices -{ - - // Step #1 : Zero out the vertex normals - char *dest = (char *)normals; - for (NxU32 i=0; ixmax[0]) - Copy(xmax,caller_p); - if (caller_p[1]ymax[1]) - Copy(ymax,caller_p); - if (caller_p[2]zmax[2]) - Copy(zmax,caller_p); - - scan+=pstride; - } - - /* Set xspan = distance between the 2 points xmin & xmax (squared) */ - REAL dx = xmax[0] - xmin[0]; - REAL dy = xmax[1] - xmin[1]; - REAL dz = xmax[2] - xmin[2]; - REAL xspan = dx*dx + dy*dy + dz*dz; - - /* Same for y & z spans */ - dx = ymax[0] - ymin[0]; - dy = ymax[1] - ymin[1]; - dz = ymax[2] - ymin[2]; - REAL yspan = dx*dx + dy*dy + dz*dz; - - dx = zmax[0] - zmin[0]; - dy = zmax[1] - zmin[1]; - dz = zmax[2] - zmin[2]; - REAL zspan = dx*dx + dy*dy + dz*dz; - - /* Set points dia1 & dia2 to the maximally separated pair */ - Copy(dia1,xmin); - Copy(dia2,xmax); /* assume xspan biggest */ - REAL maxspan = xspan; - - if (yspan>maxspan) - { - maxspan = yspan; - Copy(dia1,ymin); - Copy(dia2,ymax); - } - - if (zspan>maxspan) - { - Copy(dia1,zmin); - Copy(dia2,zmax); - } - - - /* dia1,dia2 is a diameter of initial sphere */ - /* calc initial center */ - center[0] = (dia1[0]+dia2[0])*0.5f; - center[1] = (dia1[1]+dia2[1])*0.5f; - center[2] = (dia1[2]+dia2[2])*0.5f; - - /* calculate initial radius**2 and radius */ - - dx = dia2[0]-center[0]; /* x component of radius vector */ - dy = dia2[1]-center[1]; /* y component of radius vector */ - dz = dia2[2]-center[2]; /* z component of radius vector */ - - radius2 = dx*dx + dy*dy + dz*dz; - radius = REAL(sqrt(radius2)); - - /* SECOND PASS: increment current sphere */ - { - const char *scan = (const char *)points; - for (NxU32 i=0; i radius2) /* do r**2 test first */ - { /* this point is outside of current sphere */ - REAL old_to_p = REAL(sqrt(old_to_p_sq)); - /* calc radius of new sphere */ - radius = (radius + old_to_p) * 0.5f; - radius2 = radius*radius; /* for next r**2 compare */ - REAL old_to_new = old_to_p - radius; - - /* calc center of new sphere */ - - REAL recip = 1.0f /old_to_p; - - REAL cx = (radius*center[0] + old_to_new*caller_p[0]) * recip; - REAL cy = (radius*center[1] + old_to_new*caller_p[1]) * recip; - REAL cz = (radius*center[2] + old_to_new*caller_p[2]) * recip; - - Set(center,cx,cy,cz); - - scan+=pstride; - } - } - } - - return radius; -} - - -void fm_computeBestFitCapsule(NxU32 vcount,const REAL *points,NxU32 pstride,REAL &radius,REAL &height,REAL matrix[16],bool bruteForce) -{ - REAL sides[3]; - REAL omatrix[16]; - fm_computeBestFitOBB(vcount,points,pstride,sides,omatrix,bruteForce); - - NxI32 axis = 0; - if ( sides[0] > sides[1] && sides[0] > sides[2] ) - axis = 0; - else if ( sides[1] > sides[0] && sides[1] > sides[2] ) - axis = 1; - else - axis = 2; - - REAL localTransform[16]; - - REAL maxDist = 0; - REAL maxLen = 0; - - switch ( axis ) - { - case 0: - { - fm_eulerMatrix(0,0,FM_PI/2,localTransform); - fm_matrixMultiply(localTransform,omatrix,matrix); - - const NxU8 *scan = (const NxU8 *)points; - for (NxU32 i=0; i maxDist ) - { - maxDist = dist; - } - REAL l = (REAL) fabs(t[0]); - if ( l > maxLen ) - { - maxLen = l; - } - scan+=pstride; - } - } - height = sides[0]; - break; - case 1: - { - fm_eulerMatrix(0,FM_PI/2,0,localTransform); - fm_matrixMultiply(localTransform,omatrix,matrix); - - const NxU8 *scan = (const NxU8 *)points; - for (NxU32 i=0; i maxDist ) - { - maxDist = dist; - } - REAL l = (REAL) fabs(t[1]); - if ( l > maxLen ) - { - maxLen = l; - } - scan+=pstride; - } - } - height = sides[1]; - break; - case 2: - { - fm_eulerMatrix(FM_PI/2,0,0,localTransform); - fm_matrixMultiply(localTransform,omatrix,matrix); - - const NxU8 *scan = (const NxU8 *)points; - for (NxU32 i=0; i maxDist ) - { - maxDist = dist; - } - REAL l = (REAL) fabs(t[2]); - if ( l > maxLen ) - { - maxLen = l; - } - scan+=pstride; - } - } - height = sides[2]; - break; - } - radius = (REAL)sqrt(maxDist); - height = (maxLen*2)-(radius*2); -} - - -//************* Triangulation - -#ifndef TRIANGULATE_H - -#define TRIANGULATE_H - -typedef NxU32 TU32; - -class TVec -{ -public: - TVec(NxF64 _x,NxF64 _y,NxF64 _z) { x = _x; y = _y; z = _z; }; - TVec(void) { }; - - NxF64 x; - NxF64 y; - NxF64 z; -}; - -typedef CONVEX_DECOMPOSITION::Array< TVec > TVecVector; -typedef CONVEX_DECOMPOSITION::Array< TU32 > TU32Vector; - -class CTriangulator -{ -public: - /// Default constructor - CTriangulator(); - - /// Default destructor - virtual ~CTriangulator(); - - /// Triangulates the contour - void triangulate(TU32Vector &indices); - - /// Returns the given point in the triangulator array - inline TVec get(const TU32 id) { return mPoints[id]; } - - virtual void reset(void) - { - mInputPoints.clear(); - mPoints.clear(); - mIndices.clear(); - } - - virtual void addPoint(NxF64 x,NxF64 y,NxF64 z) - { - TVec v(x,y,z); - // update bounding box... - if ( mInputPoints.empty() ) - { - mMin = v; - mMax = v; - } - else - { - if ( x < mMin.x ) mMin.x = x; - if ( y < mMin.y ) mMin.y = y; - if ( z < mMin.z ) mMin.z = z; - - if ( x > mMax.x ) mMax.x = x; - if ( y > mMax.y ) mMax.y = y; - if ( z > mMax.z ) mMax.z = z; - } - mInputPoints.pushBack(v); - } - - // Triangulation happens in 2d. We could inverse transform the polygon around the normal direction, or we just use the two most signficant axes - // Here we find the two longest axes and use them to triangulate. Inverse transforming them would introduce more doubleing point error and isn't worth it. - virtual NxU32 * triangulate(NxU32 &tcount,NxF64 epsilon) - { - NxU32 *ret = 0; - tcount = 0; - mEpsilon = epsilon; - - if ( !mInputPoints.empty() ) - { - mPoints.clear(); - - NxF64 dx = mMax.x - mMin.x; // locate the first, second and third longest edges and store them in i1, i2, i3 - NxF64 dy = mMax.y - mMin.y; - NxF64 dz = mMax.z - mMin.z; - - NxU32 i1,i2,i3; - - if ( dx > dy && dx > dz ) - { - i1 = 0; - if ( dy > dz ) - { - i2 = 1; - i3 = 2; - } - else - { - i2 = 2; - i3 = 1; - } - } - else if ( dy > dx && dy > dz ) - { - i1 = 1; - if ( dx > dz ) - { - i2 = 0; - i3 = 2; - } - else - { - i2 = 2; - i3 = 0; - } - } - else - { - i1 = 2; - if ( dx > dy ) - { - i2 = 0; - i3 = 1; - } - else - { - i2 = 1; - i3 = 0; - } - } - - NxU32 pcount = (NxU32)mInputPoints.size(); - const NxF64 *points = &mInputPoints[0].x; - for (NxU32 i=0; i 2;) - { - if (0 >= (count--)) - return; - - NxI32 u = v; - if (nv <= u) - u = 0; - v = u + 1; - if (nv <= v) - v = 0; - NxI32 w = v + 1; - if (nv <= w) - w = 0; - - if (_snip(u, v, w, nv, V)) - { - NxI32 a, b, c, s, t; - a = V[u]; - b = V[v]; - c = V[w]; - if ( flipped ) - { - indices.pushBack(a); - indices.pushBack(b); - indices.pushBack(c); - } - else - { - indices.pushBack(c); - indices.pushBack(b); - indices.pushBack(a); - } - m++; - for (s = v, t = v + 1; t < nv; s++, t++) - V[s] = V[t]; - nv--; - count = 2 * nv; - } - } - - MEMALLOC_FREE(V); -} - -/// Returns the area of the contour -NxF64 CTriangulator::_area() -{ - NxI32 n = (NxU32)mPoints.size(); - NxF64 A = 0.0f; - for (NxI32 p = n - 1, q = 0; q < n; p = q++) - { - const TVec &pval = mPoints[p]; - const TVec &qval = mPoints[q]; - A += pval.x * qval.y - qval.x * pval.y; - } - A*=0.5f; - return A; -} - -bool CTriangulator::_snip(NxI32 u, NxI32 v, NxI32 w, NxI32 n, NxI32 *V) -{ - NxI32 p; - - const TVec &A = mPoints[ V[u] ]; - const TVec &B = mPoints[ V[v] ]; - const TVec &C = mPoints[ V[w] ]; - - if (mEpsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))) ) - return false; - - for (p = 0; p < n; p++) - { - if ((p == u) || (p == v) || (p == w)) - continue; - const TVec &P = mPoints[ V[p] ]; - if (_insideTriangle(A, B, C, P)) - return false; - } - return true; -} - -/// Tests if a point is inside the given triangle -bool CTriangulator::_insideTriangle(const TVec& A, const TVec& B, const TVec& C,const TVec& P) -{ - NxF64 ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; - NxF64 cCROSSap, bCROSScp, aCROSSbp; - - ax = C.x - B.x; ay = C.y - B.y; - bx = A.x - C.x; by = A.y - C.y; - cx = B.x - A.x; cy = B.y - A.y; - apx = P.x - A.x; apy = P.y - A.y; - bpx = P.x - B.x; bpy = P.y - B.y; - cpx = P.x - C.x; cpy = P.y - C.y; - - aCROSSbp = ax * bpy - ay * bpx; - cCROSSap = cx * apy - cy * apx; - bCROSScp = bx * cpy - by * cpx; - - return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); -} - -class Triangulate : public fm_Triangulate, public Memalloc -{ -public: - Triangulate(void) - { - mPointsFloat = 0; - mPointsDouble = 0; - } - - virtual ~Triangulate(void) - { - reset(); - } - void reset(void) - { - MEMALLOC_FREE(mPointsFloat); - MEMALLOC_FREE(mPointsDouble); - mPointsFloat = 0; - mPointsDouble = 0; - } - - virtual const NxF64 * triangulate3d(NxU32 pcount, - const NxF64 *_points, - NxU32 vstride, - NxU32 &tcount, - bool consolidate, - NxF64 epsilon) - { - reset(); - - NxF64 *points = (NxF64 *)MEMALLOC_MALLOC(sizeof(NxF64)*pcount*3); - if ( consolidate ) - { - pcount = fm_consolidatePolygon(pcount,_points,vstride,points,1-epsilon); - } - else - { - NxF64 *dest = points; - for (NxU32 i=0; i= 3 ) - { - CTriangulator ct; - for (NxU32 i=0; i(t); -} - -void fm_releaseTriangulate(fm_Triangulate *t) -{ - Triangulate *tt = static_cast< Triangulate *>(t); - delete tt; -} - -#endif - -bool validDistance(const REAL *p1,const REAL *p2,REAL epsilon) -{ - bool ret = true; - - REAL dx = p1[0] - p2[0]; - REAL dy = p1[1] - p2[1]; - REAL dz = p1[2] - p2[2]; - REAL dist = dx*dx+dy*dy+dz*dz; - if ( dist < (epsilon*epsilon) ) - { - ret = false; - } - return ret; -} - -bool fm_isValidTriangle(const REAL *p1,const REAL *p2,const REAL *p3,REAL epsilon) -{ - bool ret = false; - - if ( validDistance(p1,p2,epsilon) && - validDistance(p1,p3,epsilon) && - validDistance(p2,p3,epsilon) ) - { - - REAL area = fm_computeArea(p1,p2,p3); - if ( area > epsilon ) - { - REAL _vertices[3*3],vertices[64*3]; - - _vertices[0] = p1[0]; - _vertices[1] = p1[1]; - _vertices[2] = p1[2]; - - _vertices[3] = p2[0]; - _vertices[4] = p2[1]; - _vertices[5] = p2[2]; - - _vertices[6] = p3[0]; - _vertices[7] = p3[1]; - _vertices[8] = p3[2]; - - NxU32 pcount = fm_consolidatePolygon(3,_vertices,sizeof(REAL)*3,vertices,1-epsilon); - if ( pcount == 3 ) - { - ret = true; - } - } - } - return ret; -} - - -void fm_multiplyQuat(const REAL *left,const REAL *right,REAL *quat) -{ - REAL a,b,c,d; - - a = left[3]*right[3] - left[0]*right[0] - left[1]*right[1] - left[2]*right[2]; - b = left[3]*right[0] + right[3]*left[0] + left[1]*right[2] - right[1]*left[2]; - c = left[3]*right[1] + right[3]*left[1] + left[2]*right[0] - right[2]*left[0]; - d = left[3]*right[2] + right[3]*left[2] + left[0]*right[1] - right[0]*left[1]; - - quat[3] = a; - quat[0] = b; - quat[1] = c; - quat[2] = d; -} - -}; // end of namespace diff --git a/Engine/lib/convexDecomp/NvHashMap.h b/Engine/lib/convexDecomp/NvHashMap.h deleted file mode 100644 index c8dfd7877..000000000 --- a/Engine/lib/convexDecomp/NvHashMap.h +++ /dev/null @@ -1,1905 +0,0 @@ -/* - -NvHashMap.h : A simple hash map and array template class to avoid introducing dependencies on the STL for containers. - -*/ - - -// This code contains NVIDIA Confidential Information and is disclosed -// under the Mutual Non-Disclosure Agreement. -// -// Notice -// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES -// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO -// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT, -// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE. -// -// Information and code furnished is believed to be accurate and reliable. -// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such -// information or for any infringement of patents or other rights of third parties that may -// result from its use. No license is granted by implication or otherwise under any patent -// or patent rights of NVIDIA Corporation. Details are subject to change without notice. -// This code supersedes and replaces all information previously supplied. -// NVIDIA Corporation products are not authorized for use as critical -// components in life support devices or systems without express written approval of -// NVIDIA Corporation. -// -// Copyright � 2009 NVIDIA Corporation. All rights reserved. -// Copyright � 2002-2008 AGEIA Technologies, Inc. All rights reserved. -// Copyright � 2001-2006 NovodeX. All rights reserved. - -#ifndef NV_HASH_MAP_H -#define NV_HASH_MAP_H - -#include "NvUserMemAlloc.h" - -#if (defined(NX_WINDOWS) | defined(NX_X360)) -#include -#endif - -#include -#include -#include -#include -//****************************************************** -//****************************************************** -//****************************************************** - - -#ifndef NV_FOUNDATION_BASIC_TEMPLATES_H -#define NV_FOUNDATION_BASIC_TEMPLATES_H - -#pragma warning(push) -#pragma warning(disable:4512) // suppress the 'assignment operator could not be generated' warning message. - -namespace CONVEX_DECOMPOSITION -{ - template - struct Equal - { - bool operator()(const A& a, const A& b) const { return a==b; } - }; - - template - struct Less - { - bool operator()(const A& a, const A& b) const { return a - struct Greater - { - bool operator()(const A& a, const A& b) const { return a>b; } - }; - - - template - class Pair - { - public: - F first; - S second; - Pair(): first(F()), second(S()) {} - Pair(const F &f, const S &s): first(f), second(s) {} - Pair(const Pair &p): first(p.first), second(p.second) {} - }; - - template struct LogTwo { static const unsigned int value = LogTwo<(A>>1)>::value + 1; }; - template<> struct LogTwo<1>{ static const unsigned int value = 0; }; - - template struct UnConst { typedef T Type; }; - template struct UnConst { typedef T Type; }; -} - -#pragma warning(pop) - -#endif - -#ifndef NV_FOUNDATION_ALLOCATOR -#define NV_FOUNDATION_ALLOCATOR - -#pragma warning(push) -#pragma warning(disable:4100) - -namespace CONVEX_DECOMPOSITION -{ - - -/** -\brief The return value is the greater of the two specified values. -*/ -template -NX_INLINE N NxMax(N a, N b) { return a -NX_INLINE NxF32 NxMax(NxF32 a, NxF32 b) { return a > b ? a : b; } - -/** -\brief The return value is the lesser of the two specified values. -*/ -template -NX_INLINE N NxMin(N a, N b) { return a -NX_INLINE NxF32 NxMin(NxF32 a, NxF32 b) { return a < b ? a : b; } - - - - /** - Allocator used to access the global NxUserAllocator instance without providing additional information. - */ - class Allocator - { - public: - Allocator(const char* dummy = 0) - { - } - void* allocate(size_t size, const char* file, int line) - { - return MEMALLOC_MALLOC(size); - } - void deallocate(void* ptr) - { - MEMALLOC_FREE(ptr); - } - }; - - /** - Allocator used to access the global NxUserAllocator instance using a dynamic name. - */ - class NamedAllocator - { - public: - NamedAllocator(const char* name = 0) - - { - - } - void* allocate(size_t size, const char* filename, int line) - { - return MEMALLOC_MALLOC(size); - } - void deallocate(void* ptr) - { - MEMALLOC_FREE(ptr); - } - private: - }; - - /** - Allocator used to access the global NxUserAllocator instance using a static name derived from T. - */ - template - class ReflectionAllocator - { - static const char* getName() - { -#if defined NX_GNUC - return __PRETTY_FUNCTION__; -#else - return typeid(T).name(); -#endif - } - public: - ReflectionAllocator(const char* dummy=0) - { - } - void* allocate(size_t size, const char* filename, int line) - { - return MEMALLOC_MALLOC(size); - } - void deallocate(void* ptr) - { - MEMALLOC_FREE(ptr); - } - }; - - // if you get a build error here, you are trying to NX_NEW a class - // that is neither plain-old-type nor derived from CONVEX_DECOMPOSITION::UserAllocated - template - union EnableIfPod - { - int i; T t; - typedef X Type; - }; - -} - -// Global placement new for ReflectionAllocator templated by plain-old-type. Allows using NX_NEW for pointers and built-in-types. -// ATTENTION: You need to use NX_DELETE_POD or NX_FREE to deallocate memory, not NX_DELETE. NX_DELETE_POD redirects to NX_FREE. -// Rationale: NX_DELETE uses global operator delete(void*), which we dont' want to overload. -// Any other definition of NX_DELETE couldn't support array syntax 'NX_DELETE([]a);'. -// NX_DELETE_POD was preferred over NX_DELETE_ARRAY because it is used less often and applies to both single instances and arrays. -template -NX_INLINE void* operator new(size_t size, CONVEX_DECOMPOSITION::ReflectionAllocator alloc, const char* fileName, typename CONVEX_DECOMPOSITION::EnableIfPod::Type line) -{ - return alloc.allocate(size, fileName, line); -} - -template -NX_INLINE void* operator new[](size_t size, CONVEX_DECOMPOSITION::ReflectionAllocator alloc, const char* fileName, typename CONVEX_DECOMPOSITION::EnableIfPod::Type line) -{ - return alloc.allocate(size, fileName, line); -} - -// If construction after placement new throws, this placement delete is being called. -template -NX_INLINE void operator delete(void* ptr, CONVEX_DECOMPOSITION::ReflectionAllocator alloc, const char* fileName, typename CONVEX_DECOMPOSITION::EnableIfPod::Type line) -{ - alloc.deallocate(ptr); -} - -// If construction after placement new throws, this placement delete is being called. -template -NX_INLINE void operator delete[](void* ptr, CONVEX_DECOMPOSITION::ReflectionAllocator alloc, const char* fileName, typename CONVEX_DECOMPOSITION::EnableIfPod::Type line) -{ - alloc.deallocate(ptr); -} - -#pragma warning(pop) - -#endif - - -#ifndef NV_FOUNDATION_USERALLOCATED -#define NV_FOUNDATION_USERALLOCATED - -// an expression that should expand to nothing in _DEBUG builds. We currently -// use this only for tagging the purpose of containers for memory use tracking. -#if defined(_DEBUG) -#define NV_DEBUG_EXP(x) (x) -#define NV_DEBUG_EXP_C(x) x, -#else -#define NV_DEBUG_EXP(x) -#define NV_DEBUG_EXP_C(x) -#endif - -#if defined (NX_X360) | defined (NX_WINDOWS) | defined (NX_CELL) | defined (NXLINUX) | defined(NX_WII) -// Stack allocation with alloc fallback for large allocations (>50% of default stack size for platform) -# define NX_ALLOCA(var, type, number) \ - bool alloced_##var = false; \ - if (sizeof(type)*number*2 > (CONVEX_DECOMPOSITION::gSystemServices ? gSystemServices->getAllocaThreshold() : 8192) ) \ - { \ - var = (type *)MEMALLOC_MALLOC(sizeof(type)*number); \ - alloced_##var = true; \ - } else { \ - var = (type *)MEMALLOC_ALLOCA(sizeof(type)*number); \ - } -# define NX_FREEA(var) if (alloced_##var) MEMALLOC_FREE(var); -#else -# define NX_ALLOCA(var, type, number) var = (type *)NxAlloca(sizeof(type)*number); -# define NX_FREEA(var) 0; -#endif - -namespace CONVEX_DECOMPOSITION -{ - /** - Provides new and delete using a UserAllocator. - Guarantees that 'delete x;' uses the UserAllocator too. - */ - class UserAllocated - { - public: - - template - NX_INLINE void* operator new(size_t size, Alloc alloc, const char* fileName, int line) - { - return MEMALLOC_MALLOC(size); - } - template - NX_INLINE void* operator new[](size_t size, Alloc alloc, const char* fileName, int line) - { - return MEMALLOC_MALLOC(size); - } - - NX_INLINE void operator delete(void* ptr) - { - MEMALLOC_FREE(ptr); - } - NX_INLINE void operator delete[](void* ptr) - { - MEMALLOC_FREE(ptr); - } - }; -}; - -#endif - - -#ifndef NV_FOUNDATION_ALIGNEDMALLOC_H -#define NV_FOUNDATION_ALIGNEDMALLOC_H - -/*! -Allocate aligned memory. -Alignment must be a power of 2! --- should be templated by a base allocator -*/ - -namespace CONVEX_DECOMPOSITION -{ - /** - Allocator, which is used to access the global NxUserAllocator instance - (used for dynamic data types template instantiation), which can align memory - */ - - // SCS: AlignedMalloc with 3 params not found, seems not used on PC either - // disabled for now to avoid GCC error - - template - class AlignedAllocator : public BaseAllocator - { - public: - AlignedAllocator(const BaseAllocator& base = BaseAllocator()) - : BaseAllocator(base) {} - - void* allocate(size_t size, const char* file, int line) - { - size_t pad = N - 1 + sizeof(size_t); // store offset for delete. - NxU8* base = (NxU8*)BaseAllocator::allocate(size+pad, file, line); - - NxU8* ptr = (NxU8*)(size_t(base + pad) & ~(N - 1)); // aligned pointer - ((size_t*)ptr)[-1] = ptr - base; // store offset - - return ptr; - } - void deallocate(void* ptr) - { - if(ptr == NULL) - return; - - NxU8* base = ((NxU8*)ptr) - ((size_t*)ptr)[-1]; - BaseAllocator::deallocate(base); - } - }; -} - -#endif - - -#ifndef NV_FOUNDATION_INLINE_ALLOCATOR_H -#define NV_FOUNDATION_INLINE_ALLOCATOR_H - -namespace CONVEX_DECOMPOSITION -{ - // this is used by the array class to allocate some space for a small number - // of objects along with the metadata - template - class InlineAllocator : private BaseAllocator - { - public: - - InlineAllocator(const BaseAllocator& alloc = BaseAllocator()) - : BaseAllocator(alloc) - {} - - void* allocate(size_t size, const char* filename, int line) - { - return size <= N ? mBuffer : BaseAllocator::allocate(size, filename, line); - } - - void deallocate(void* ptr) - { - if(ptr != mBuffer) - BaseAllocator::deallocate(ptr); - } - - private: - NxU8 mBuffer[N]; - }; -} - -#endif - - -#ifndef NV_FOUNDATION_NXSTRIDEDDATA -#define NV_FOUNDATION_NXSTRIDEDDATA -/** \addtogroup foundation - @{ -*/ - -template -class NvStrideIterator -{ - template - struct StripConst - { - typedef X Type; - }; - - template - struct StripConst - { - typedef X Type; - }; - -public: - explicit NX_INLINE NvStrideIterator(T* ptr = NULL, NxU32 stride = sizeof(T)) : - mPtr(ptr), mStride(stride) - { - NX_ASSERT(mStride == 0 || sizeof(T) <= mStride); - } - - NX_INLINE NvStrideIterator(const NvStrideIterator::Type>& strideIterator) : - mPtr(strideIterator.ptr()), mStride(strideIterator.stride()) - { - NX_ASSERT(mStride == 0 || sizeof(T) <= mStride); - } - - NX_INLINE T* ptr() const - { - return mPtr; - } - - NX_INLINE NxU32 stride() const - { - return mStride; - } - - NX_INLINE T& operator*() const - { - return *mPtr; - } - - NX_INLINE T* operator->() const - { - return mPtr; - } - - NX_INLINE T& operator[](int i) const - { - return *byteAdd(mPtr, i * stride()); - } - - // preincrement - NX_INLINE NvStrideIterator& operator++() - { - mPtr = byteAdd(mPtr, stride()); - return *this; - } - - // postincrement - NX_INLINE NvStrideIterator operator++(int) - { - NvStrideIterator tmp = *this; - mPtr = byteAdd(mPtr, stride()); - return tmp; - } - - // predecrement - NX_INLINE NvStrideIterator& operator--() - { - mPtr = byteSub(mPtr, stride()); - return *this; - } - - // postdecrement - NX_INLINE NvStrideIterator operator--(int) - { - NvStrideIterator tmp = *this; - mPtr = byteSub(mPtr, stride()); - return tmp; - } - - NX_INLINE NvStrideIterator& operator+=(int i) - { - mPtr = byteAdd(mPtr, i * stride()); - return *this; - } - - NX_INLINE NvStrideIterator operator+(int i) const - { - return NvStrideIterator(byteAdd(mPtr, i * stride()), stride()); - } - - NX_INLINE NvStrideIterator& operator-=(int i) - { - mPtr = byteSub(mPtr, i * stride()); - return *this; - } - - NX_INLINE NvStrideIterator operator-(int i) const - { - return NvStrideIterator(byteSub(mPtr, i * stride()), stride()); - } - - // iterator difference - NX_INLINE int operator-(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - int byteDiff = static_cast(reinterpret_cast(mPtr) - reinterpret_cast(other.mPtr)); - return byteDiff / static_cast(stride()); - } - - NX_INLINE bool operator==(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - return mPtr == other.mPtr; - } - - NX_INLINE bool operator!=(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - return mPtr != other.mPtr; - } - - NX_INLINE bool operator<(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - return mPtr < other.mPtr; - } - - NX_INLINE bool operator>(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - return mPtr > other.mPtr; - } - - NX_INLINE bool operator<=(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - return mPtr <= other.mPtr; - } - - NX_INLINE bool operator>=(const NvStrideIterator& other) const - { - NX_ASSERT(isCompatible(other)); - return mPtr >= other.mPtr; - } - -private: - NX_INLINE static T* byteAdd(T* ptr, NxU32 bytes) - { - return const_cast(reinterpret_cast(reinterpret_cast(ptr) + bytes)); - } - - NX_INLINE static T* byteSub(T* ptr, NxU32 bytes) - { - return const_cast(reinterpret_cast(reinterpret_cast(ptr) - bytes)); - } - - NX_INLINE bool isCompatible(const NvStrideIterator& other) const - { - int byteDiff = static_cast(reinterpret_cast(mPtr) - reinterpret_cast(other.mPtr)); - return (stride() == other.stride()) && (abs(byteDiff) % stride() == 0); - } - - T* mPtr; - NxU32 mStride; -}; - - -template -NX_INLINE NvStrideIterator operator+(int i, NvStrideIterator it) -{ - it += i; - return it; -} - - /** @} */ -#endif - -#ifndef NV_FOUNDATION_ARRAY -#define NV_FOUNDATION_ARRAY - -namespace CONVEX_DECOMPOSITION -{ - namespace Internal - { - template - struct ArrayMetaData - { - T* mData; - NxU32 mCapacity; - NxU32 mSize; - ArrayMetaData(): mSize(0), mCapacity(0), mData(0) {} - }; - - template - struct AllocatorTraits - { -#if defined _DEBUG - typedef NamedAllocator Type; -#else - typedef ReflectionAllocator Type; -#endif - }; - } - - /*! - An array is a sequential container. - - Implementation note - * entries between 0 and size are valid objects - * we use inheritance to build this because the array is included inline in a lot - of objects and we want the allocator to take no space if it's not stateful, which - aggregation doesn't allow. Also, we want the metadata at the front for the inline - case where the allocator contains some inline storage space - */ - template::Type > - class Array : private Internal::ArrayMetaData, private Alloc - { - typedef Internal::ArrayMetaData MetaData; - - using MetaData::mCapacity; - using MetaData::mData; - using MetaData::mSize; - - public: - - typedef T* Iterator; - typedef const T* ConstIterator; - - /*! - Default array constructor. Initialize an empty array - */ - NX_INLINE Array(const Alloc& alloc = Alloc()) : Alloc(alloc) {} - - /*! - Initialize array with given length - */ - NX_INLINE explicit Array(NxU32 capacity, const Alloc& alloc = Alloc()) - : Alloc(alloc) - { - if(mCapacity>0) - allocate(mCapacity); - } - - /*! - Copy-constructor. Copy all entries from other array - */ - template - NX_INLINE Array(const Array& other, const Alloc& alloc = Alloc()) - { - if(other.mSize > 0) - { - mData = allocate(mSize = mCapacity = other.mSize); - copy(mData, other.mData, mSize); - } - } - - /*! - Default destructor - */ - NX_INLINE ~Array() - { - destroy(0, mSize); - if(mCapacity) - deallocate(mData); - } - - /*! - Assignment operator. Copy content (deep-copy) - */ - template - NX_INLINE const Array& operator= (const Array& t) - { - if(&t == this) - return *this; - - if(mCapacity < t.mSize) - { - destroy(0,mSize); - deallocate(mData); - - mData = allocate(t.mCapacity); - mCapacity = t.mCapacity; - - copy(mData,t.mData,t.mSize); - } - else - { - NxU32 m = NxMin(t.mSize,mSize); - copy(mData,t.mData,m); - for(NxU32 i = m; i < mSize;i++) - mData[i].~T(); - for(NxU32 i = m; i < t.mSize; i++) - new(mData+i)T(t.mData[i]); - } - - mSize = t.mSize; - return *this; - } - - /*! - Array indexing operator. - \param i - The index of the element that will be returned. - \return - The element i in the array. - */ - NX_INLINE const T& operator[] (NxU32 i) const - { - return mData[i]; - } - - /*! - Array indexing operator. - \param i - The index of the element that will be returned. - \return - The element i in the array. - */ - NX_INLINE T& operator[] (NxU32 i) - { - return mData[i]; - } - - /*! - Returns a pointer to the initial element of the array. - \return - a pointer to the initial element of the array. - */ - NX_INLINE ConstIterator begin() const - { - return mData; - } - - NX_INLINE Iterator begin() - { - return mData; - } - - /*! - Returns an iterator beyond the last element of the array. Do not dereference. - \return - a pointer to the element beyond the last element of the array. - */ - - NX_INLINE ConstIterator end() const - { - return mData+mSize; - } - - NX_INLINE Iterator end() - { - return mData+mSize; - } - - /*! - Returns a reference to the first element of the array. Undefined if the array is empty. - \return a reference to the first element of the array - */ - - NX_INLINE const T& front() const - { - NX_ASSERT(mSize); - return mData[0]; - } - - NX_INLINE T& front() - { - NX_ASSERT(mSize); - return mData[0]; - } - - /*! - Returns a reference to the last element of the array. Undefined if the array is empty - \return a reference to the last element of the array - */ - - NX_INLINE const T& back() const - { - NX_ASSERT(mSize); - return mData[mSize-1]; - } - - NX_INLINE T& back() - { - NX_ASSERT(mSize); - return mData[mSize-1]; - } - - - /*! - Returns the number of entries in the array. This can, and probably will, - differ from the array capacity. - \return - The number of of entries in the array. - */ - NX_INLINE NxU32 size() const - { - return mSize; - } - - /*! - Clears the array. - */ - NX_INLINE void clear() - { - destroy(0,mSize); - mSize = 0; - } - - /*! - Returns whether the array is empty (i.e. whether its size is 0). - \return - true if the array is empty - */ - NX_INLINE bool empty() const - { - return mSize==0; - } - - /*! - Finds the first occurrence of an element in the array. - \param a - The element that will be removed. - */ - - - NX_INLINE Iterator find(const T&a) - { - NxU32 index; - for(index=0;index(i-mData)); - } - - ///////////////////////////////////////////////////////////////////////// - /*! - Replaces the first occurrence of the element a with the last element - Operation is O(n) - \param i - The position of the element that will be subtracted from this array. - \return Returns true if the element has been removed. - */ - ///////////////////////////////////////////////////////////////////////// - - NX_INLINE bool findAndReplaceWithLast(const T& a) - { - NxU32 index; - for(index=0;index= mSize) - return false; - replaceWithLast(index); - return true; - } - - ///////////////////////////////////////////////////////////////////////// - /*! - Subtracts the element on position i from the array. Shift the entire - array one step. - Operation is O(n) - \param i - The position of the element that will be subtracted from this array. - \return - The element that was removed. - */ - ///////////////////////////////////////////////////////////////////////// - NX_INLINE void remove(NxU32 i) - { - NX_ASSERT(i mCapacity) - { - grow(size); - } - else if (compaction && (size != mCapacity)) - { - recreate(size, NxMin(mSize, size)); - } - - for(NxU32 i = mSize; i < size; i++) - ::new(mData+i)T(a); - - if (!compaction) // With compaction, these elements have been deleted already - { - for(NxU32 i = size; i < mSize; i++) - mData[i].~T(); - } - - mSize = size; - } - - - ////////////////////////////////////////////////////////////////////////// - /*! - Resize array such that only as much memory is allocated to hold the - existing elements - */ - ////////////////////////////////////////////////////////////////////////// - NX_INLINE void shrink() - { - resize(mSize, true); - } - - - ////////////////////////////////////////////////////////////////////////// - /*! - Deletes all array elements and frees memory. - */ - ////////////////////////////////////////////////////////////////////////// - NX_INLINE void reset() - { - resize(0, true); - } - - - ////////////////////////////////////////////////////////////////////////// - /*! - Ensure that the array has at least size capacity. - */ - ////////////////////////////////////////////////////////////////////////// - NX_INLINE void reserve(const NxU32 size) - { - if(size > mCapacity) - grow(size); - } - - ////////////////////////////////////////////////////////////////////////// - /*! - Query the capacity(allocated mem) for the array. - */ - ////////////////////////////////////////////////////////////////////////// - NX_INLINE NxU32 capacity() const - { - return mCapacity; - } - - - private: - - NX_INLINE T* allocate(size_t capacity) - { - return (T*)Alloc::allocate(sizeof(T) * capacity, __FILE__, __LINE__); - } - - NX_INLINE void deallocate(void *mem) - { - Alloc::deallocate(mem); - } - - NX_INLINE void copy(T* dst, const T* src, size_t count) - { - for(size_t i=0;i= copyCount); - NX_ASSERT(mSize >= copyCount); - T* newData = allocate(capacity); - NX_ASSERT( ((newData != NULL) && (capacity > 0)) || - ((newData == NULL) && (capacity == 0)) ); - - if(mCapacity) - { - copy(newData,mData,copyCount); - destroy(0,mSize); - deallocate(mData); - } - - mData = newData; - mCapacity = capacity; - } - - /*! - Resizes the available memory for the array. - - \param capacity - The number of entries that the set should be able to hold. - */ - NX_INLINE void grow(NxU32 capacity) - { - NX_ASSERT(mCapacity < capacity); - recreate(capacity, mSize); - } - }; - - // array that pre-allocates for N elements - template ::Type> - class InlineArray : public Array > - { - typedef InlineAllocator Allocator; - public: - NX_INLINE InlineArray(const Alloc& alloc = Alloc()) - : Array(alloc) - {} - }; -} - -template -NX_INLINE NvStrideIterator getStrideIterator(CONVEX_DECOMPOSITION::Array& array) -{ - return NvStrideIterator(array.begin(), sizeof(T)); -} - -template -NX_INLINE NvStrideIterator getConstStrideIterator(CONVEX_DECOMPOSITION::Array& array) -{ - return NvStrideIterator(array.begin(), sizeof(T)); -} - - -#endif - -#ifndef NV_FOUNDATION_BITUTILS_H -#define NV_FOUNDATION_BITUTILS_H - -namespace CONVEX_DECOMPOSITION -{ - NX_INLINE NxU32 bitCount32(NxU32 v) - { - // from http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel - NxU32 const w = v - ((v >> 1) & 0x55555555); - NxU32 const x = (w & 0x33333333) + ((w >> 2) & 0x33333333); - return ((x + (x >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; - } - - /*! - Return the index of the highest set bit. Or 0 if no bits are set. - */ - NX_INLINE NxU32 highestSetBit32(NxU32 v) - { - for(NxU32 j = 32; j-- > 0;) - { - if(v&(1<> 1); - x |= (x >> 2); - x |= (x >> 4); - x |= (x >> 8); - x |= (x >> 16); - return x+1; - } - - // Helper function to approximate log2 of an integer value (assumes that the input is actually power of two) - NX_INLINE NxU32 ilog2(NxU32 num) - { - for (NxU32 i=0; i<32; i++) - { - num >>= 1; - if (num == 0) return i; - } - - NX_ASSERT(0); - return (NxU32)-1; - } - - NX_INLINE int intChop(const NxF32& f) - { - NxI32 a = *reinterpret_cast(&f); // take bit pattern of float into a register - NxI32 sign = (a>>31); // sign = 0xFFFFFFFF if original value is negative, 0 if positive - NxI32 mantissa = (a&((1<<23)-1))|(1<<23); // extract mantissa and add the hidden bit - NxI32 exponent = ((a&0x7fffffff)>>23)-127; // extract the exponent - NxI32 r = ((NxU32)(mantissa)<<8)>>(31-exponent); // ((1<>24 -- (we know that mantissa > (1<<24)) - return ((r ^ (sign)) - sign ) &~ (exponent>>31); // add original sign. If exponent was negative, make return value 0. - } - - NX_INLINE int intFloor(const NxF32& f) - { - NxI32 a = *reinterpret_cast(&f); // take bit pattern of float into a register - NxI32 sign = (a>>31); // sign = 0xFFFFFFFF if original value is negative, 0 if positive - a&=0x7fffffff; // we don't need the sign any more - NxI32 exponent = (a>>23)-127; // extract the exponent - NxI32 expsign = ~(exponent>>31); // 0xFFFFFFFF if exponent is positive, 0 otherwise - NxI32 imask = ( (1<<(31-(exponent))))-1; // mask for true integer values - NxI32 mantissa = (a&((1<<23)-1)); // extract mantissa (without the hidden bit) - NxI32 r = ((NxU32)(mantissa|(1<<23))<<8)>>(31-exponent); // ((1<>24 -- (we know that mantissa > (1<<24)) - r = ((r & expsign) ^ (sign)) + ((!((mantissa<<8)&imask)&(expsign^((a-1)>>31)))&sign); // if (fabs(value)<1.0) value = 0; copy sign; if (value < 0 && value==(int)(value)) value++; - return r; - } - - NX_INLINE int intCeil(const NxF32& f) - { - NxI32 a = *reinterpret_cast(&f) ^ 0x80000000; // take bit pattern of float into a register - NxI32 sign = (a>>31); // sign = 0xFFFFFFFF if original value is negative, 0 if positive - a&=0x7fffffff; // we don't need the sign any more - NxI32 exponent = (a>>23)-127; // extract the exponent - NxI32 expsign = ~(exponent>>31); // 0xFFFFFFFF if exponent is positive, 0 otherwise - NxI32 imask = ( (1<<(31-(exponent))))-1; // mask for true integer values - NxI32 mantissa = (a&((1<<23)-1)); // extract mantissa (without the hidden bit) - NxI32 r = ((NxU32)(mantissa|(1<<23))<<8)>>(31-exponent); // ((1<>24 -- (we know that mantissa > (1<<24)) - r = ((r & expsign) ^ (sign)) + ((!((mantissa<<8)&imask)&(expsign^((a-1)>>31)))&sign); // if (fabs(value)<1.0) value = 0; copy sign; if (value < 0 && value==(int)(value)) value++; - return -r; - } - -} - -#endif - -#ifndef NV_FOUNDATION_HASHFUNCTION_H -#define NV_FOUNDATION_HASHFUNCTION_H - -/*! -Central definition of hash functions -*/ - -namespace CONVEX_DECOMPOSITION -{ - // Hash functions - template - NxU32 hash(const T& key) - { - return (NxU32)key; - } - - // Thomas Wang's 32 bit mix - // http://www.cris.com/~Ttwang/tech/inthash.htm - template<> - NX_INLINE NxU32 hash(const NxU32& key) - { - NxU32 k = key; - k += ~(k << 15); - k ^= (k >> 10); - k += (k << 3); - k ^= (k >> 6); - k += ~(k << 11); - k ^= (k >> 16); - return (NxU32)k; - } - - template<> - NX_INLINE NxU32 hash(const NxI32& key) - { - return hash((NxU32)key); - } - - // Thomas Wang's 64 bit mix - // http://www.cris.com/~Ttwang/tech/inthash.htm - template<> - NX_INLINE NxU32 hash(const NxU64& key) - { - NxU64 k = key; - k += ~(k << 32); - k ^= (k >> 22); - k += ~(k << 13); - k ^= (k >> 8); - k += (k << 3); - k ^= (k >> 15); - k += ~(k << 27); - k ^= (k >> 31); - return (NxU32)k; - } - - // Helper for pointer hashing - template - NxU32 PointerHash(const void* ptr); - - template<> - NX_INLINE NxU32 PointerHash<4>(const void* ptr) - { - return hash(static_cast(reinterpret_cast(ptr))); - } - - - template<> - NX_INLINE NxU32 PointerHash<8>(const void* ptr) - { - return hash(reinterpret_cast(ptr)); - } - - // Hash function for pointers - template - NX_INLINE NxU32 hash(T* key) - { - return PointerHash(key); - } - - // Hash function object for pointers - template - struct PointerHashFunctor - { - NxU32 operator()(const T* t) const - { - return PointerHash(t); - } - bool operator()(const T* t0, const T* t1) const - { - return t0 == t1; - } - }; - - /* - -------------------------------------------------------------------- - lookup2.c, by Bob Jenkins, December 1996, Public Domain. - -------------------------------------------------------------------- - -------------------------------------------------------------------- - mix -- mix 3 32-bit values reversibly. - For every delta with one or two bit set, and the deltas of all three - high bits or all three low bits, whether the original value of a,b,c - is almost all zero or is uniformly distributed, - * If mix() is run forward or backward, at least 32 bits in a,b,c - have at least 1/4 probability of changing. - * If mix() is run forward, every bit of c will change between 1/3 and - 2/3 of the time. (Well, 22/100 and 78/100 for some 2-bit deltas.) - mix() was built out of 36 single-cycle latency instructions in a - structure that could supported 2x parallelism, like so: - a -= b; - a -= c; x = (c>>13); - b -= c; a ^= x; - b -= a; x = (a<<8); - c -= a; b ^= x; - c -= b; x = (b>>13); - ... - Unfortunately, superscalar Pentiums and Sparcs can't take advantage - of that parallelism. They've also turned some of those single-cycle - latency instructions into multi-cycle latency instructions. Still, - this is the fastest good hash I could find. There were about 2^^68 - to choose from. I only looked at a billion or so. - -------------------------------------------------------------------- - */ - NX_INLINE NxU32 hashMix(NxU32 &a, NxU32 &b, NxU32 &c) - { - a -= b; a -= c; a ^= (c>>13); - b -= c; b -= a; b ^= (a<<8); - c -= a; c -= b; c ^= (b>>13); - a -= b; a -= c; a ^= (c>>12); - b -= c; b -= a; b ^= (a<<16); - c -= a; c -= b; c ^= (b>>5); - a -= b; a -= c; a ^= (c>>3); - b -= c; b -= a; b ^= (a<<10); - c -= a; c -= b; c ^= (b>>15); - } - - NX_INLINE NxU32 hash(const NxU32 *k, NxU32 length) - { - NxU32 a,b,c,len; - - /* Set up the internal state */ - len = length; - a = b = 0x9e3779b9; /* the golden ratio; an arbitrary value */ - c = 0; /* the previous hash value */ - - /*---------------------------------------- handle most of the key */ - while (len >= 3) - { - a += k[0]; - b += k[1]; - c += k[2]; - hashMix(a,b,c); - k += 3; - len -= 3; - } - - /*-------------------------------------- handle the last 2 ub4's */ - c += length; - switch(len) /* all the case statements fall through */ - { - /* c is reserved for the length */ - case 2 : b+=k[1]; - case 1 : a+=k[0]; - /* case 0: nothing left to add */ - } - hashMix(a,b,c); - /*-------------------------------------------- report the result */ - return c; - } - - template - class Hash - { - public: - NxU32 operator()(const Key &k) const { return hash(k); } - bool operator()(const Key &k0, const Key &k1) const { return k0 == k1; } - }; - - class NvStringHash - { - public: - NxU32 operator()(const char *string) const - { - // "DJB" string hash - NxU32 h = 5381; - for(const char *ptr = string; *ptr; ptr++) - h = ((h<<5)+h)^*ptr; - return h; - } - bool operator()(const char* string0, const char* string1) const - { - return !strcmp(string0, string1); - } - }; -} - -#endif - - -#ifndef NV_FOUNDATION_HASHINTERNALS -#define NV_FOUNDATION_HASHINTERNALS - - -#pragma warning(push) -#pragma warning(disable:4127 4512) // disable the 'conditoinal expression is constant' warning message - -namespace CONVEX_DECOMPOSITION -{ - namespace Internal - { - template - class HashBase - { - public: - typedef Entry EntryType; - - HashBase(NxU32 initialTableSize = 64, float loadFactor = 0.75f): - mLoadFactor(loadFactor), - mFreeList((NxU32)EOL), - mTimestamp(0), - mSize(0), - mEntries(Allocator(NV_DEBUG_EXP("hashBaseEntries"))), - mNext(Allocator(NV_DEBUG_EXP("hashBaseNext"))), - mHash(Allocator(NV_DEBUG_EXP("hashBaseHash"))) - { - if(initialTableSize) - reserveInternal(initialTableSize); - } - - ~HashBase() - { - for(NxU32 i = 0;imHash.size()) - reserveInternal(size); - } - - NX_INLINE const Entry *getEntries() const - { - return &mEntries[0]; - } - - private: - - // free list management - if we're coalescing, then we use mFreeList to hold - // the top of the free list and it should always be equal to size(). Otherwise, - // we build a free list in the next() pointers. - - NX_INLINE void freeListAdd(NxU32 index) - { - if(compacting) - { - mFreeList--; - NX_ASSERT(mFreeList == mSize); - } - else - { - mNext[index] = mFreeList; - mFreeList = index; - } - } - - NX_INLINE void freeListAdd(NxU32 start, NxU32 end) - { - if(!compacting) - { - for(NxU32 i = start; i mEntries; - Array mNext; - Array mHash; - float mLoadFactor; - NxU32 mFreeList; - NxU32 mTimestamp; - NxU32 mSize; - - friend class Iter; - - public: - class Iter - { - public: - NX_INLINE Iter(HashBase &b): mBase(b), mTimestamp(b.mTimestamp), mBucket(0), mEntry((NxU32)b.EOL) - { - if(mBase.mEntries.size()>0) - { - mEntry = mBase.mHash[0]; - skip(); - } - } - - NX_INLINE void check() { NX_ASSERT(mTimestamp == mBase.mTimestamp); } - NX_INLINE Entry operator*() { check(); return mBase.mEntries[mEntry]; } - NX_INLINE Entry *operator->() { check(); return &mBase.mEntries[mEntry]; } - NX_INLINE Iter operator++() { check(); advance(); return *this; } - NX_INLINE Iter operator++(int) { check(); Iter i = *this; advance(); return i; } - NX_INLINE bool done() { check(); return mEntry == mBase.EOL; } - - private: - NX_INLINE void advance() { mEntry = mBase.mNext[mEntry]; skip(); } - NX_INLINE void skip() - { - while(mEntry==mBase.EOL) - { - if(++mBucket == mBase.mHash.size()) - break; - mEntry = mBase.mHash[mBucket]; - } - } - - NxU32 mBucket; - NxU32 mEntry; - NxU32 mTimestamp; - HashBase &mBase; - }; - }; - - template - class HashSetBase - { - public: - struct GetKey { NX_INLINE const Key &operator()(const Key &e) { return e; } }; - - typedef HashBase BaseMap; - typedef typename BaseMap::Iter Iterator; - - HashSetBase(NxU32 initialTableSize = 64, - float loadFactor = 0.75f): mBase(initialTableSize,loadFactor) {} - - bool insert(const Key &k) - { - bool exists; - Key *e = mBase.create(k,exists); - if(!exists) - new(e)Key(k); - return !exists; - } - - NX_INLINE bool contains(const Key &k) const { return mBase.find(k)!=0; } - NX_INLINE bool erase(const Key &k) { return mBase.erase(k); } - NX_INLINE NxU32 size() const { return mBase.size(); } - NX_INLINE void reserve(NxU32 size) { mBase.reserve(size); } - NX_INLINE void clear() { mBase.clear(); } - protected: - BaseMap mBase; - - }; - - template - - class HashMapBase - { - public: - typedef Pair Entry; - struct GetKey { NX_INLINE const Key &operator()(const Entry &e) { return e.first; } }; - typedef HashBase, Key, HashFn, GetKey, Allocator, true> BaseMap; - typedef typename BaseMap::Iter Iterator; - - HashMapBase(NxU32 initialTableSize = 64, float loadFactor = 0.75f): mBase(initialTableSize,loadFactor) {} - - bool insert(const Key &k, const Value &v) - { - bool exists; - Entry *e = mBase.create(k,exists); - if(!exists) - new(e)Entry(k,v); - return !exists; - } - - Value &operator [](const Key &k) - { - bool exists; - Entry *e = mBase.create(k, exists); - if(!exists) - new(e)Entry(k,Value()); - - return e->second; - } - - NX_INLINE const Entry * find(const Key &k) const { return mBase.find(k); } - NX_INLINE bool erase(const Key &k) { return mBase.erase(k); } - NX_INLINE NxU32 size() const { return mBase.size(); } - NX_INLINE Iterator getIterator() { return Iterator(mBase); } - NX_INLINE void reserve(NxU32 size) { mBase.reserve(size); } - NX_INLINE void clear() { mBase.clear(); } - - protected: - BaseMap mBase; - }; - - } -} - -#pragma warning(pop) - -#endif - -#ifndef NV_FOUNDATION_HASHMAP -#define NV_FOUNDATION_HASHMAP - - -// TODO: make this doxy-format -// -// This header defines two hash maps. Hash maps -// * support custom initial table sizes (rounded up internally to power-of-2) -// * support custom static allocator objects -// * auto-resize, based on a load factor (i.e. a 64-entry .75 load factor hash will resize -// when the 49th element is inserted) -// * are based on open hashing -// * have O(1) contains, erase -// -// Maps have STL-like copying semantics, and properly initialize and destruct copies of objects -// -// There are two forms of map: coalesced and uncoalesced. Coalesced maps keep the entries in the -// initial segment of an array, so are fast to iterate over; however deletion is approximately -// twice as expensive. -// -// HashMap: -// bool insert(const Key &k, const Value &v) O(1) amortized (exponential resize policy) -// Value & operator[](const Key &k) O(1) for existing objects, else O(1) amortized -// const Entry * find(const Key &k); O(1) -// bool erase(const T &k); O(1) -// NxU32 size(); constant -// void reserve(NxU32 size); O(MAX(currentOccupancy,size)) -// void clear(); O(currentOccupancy) (with zero constant for objects without destructors) -// Iterator getIterator(); -// -// operator[] creates an entry if one does not exist, initializing with the default constructor. -// CoalescedHashMap does not support getInterator, but instead supports -// const Key *getEntries(); -// -// Use of iterators: -// -// for(HashMap::Iterator iter = test.getIterator(); !iter.done(); ++iter) -// myFunction(iter->first, iter->second); - -namespace CONVEX_DECOMPOSITION -{ - template , - class Allocator = Allocator > - class HashMap: public Internal::HashMapBase - { - public: - - typedef Internal::HashMapBase HashMapBase; - typedef typename HashMapBase::Iterator Iterator; - - HashMap(NxU32 initialTableSize = 64, float loadFactor = 0.75f): HashMapBase(initialTableSize,loadFactor) {} - Iterator getIterator() { return Iterator(HashMapBase::mBase); } - }; - - template , - class Allocator = Allocator > - class CoalescedHashMap: public Internal::HashMapBase - { - typedef Internal::HashMapBase HashMapBase; - - CoalescedHashMap(NxU32 initialTableSize = 64, float loadFactor = 0.75f): HashMapBase(initialTableSize,loadFactor){} - const Key *getEntries() const { return HashMapBase::mBase.getEntries(); } - }; - -} -#endif - -#endif diff --git a/Engine/lib/convexDecomp/NvMeshIslandGeneration.cpp b/Engine/lib/convexDecomp/NvMeshIslandGeneration.cpp deleted file mode 100644 index 35157200b..000000000 --- a/Engine/lib/convexDecomp/NvMeshIslandGeneration.cpp +++ /dev/null @@ -1,783 +0,0 @@ -/* - -NvMeshIslandGeneration.cpp : This code snippet walks the toplogy of a triangle mesh and detects the set of unique connected 'mesh islands' - -*/ - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include -#include -#include -#include - -#pragma warning(disable:4100 4288) -#include "NvMeshIslandGeneration.h" -#include "NvFloatMath.h" -#include "NvHashMap.h" - -namespace CONVEX_DECOMPOSITION -{ - -typedef CONVEX_DECOMPOSITION::Array< NxU32 > NxU32Vector; - -class Edge; -class Island; - -class AABB -{ -public: - NxF32 mMin[3]; - NxF32 mMax[3]; -}; - -class Triangle -{ -public: - Triangle(void) - { - mConsumed = false; - mIsland = 0; - mHandle = 0; - mId = 0; - } - - void minmax(const NxF32 *p,AABB &box) - { - if ( p[0] < box.mMin[0] ) box.mMin[0] = p[0]; - if ( p[1] < box.mMin[1] ) box.mMin[1] = p[1]; - if ( p[2] < box.mMin[2] ) box.mMin[2] = p[2]; - - if ( p[0] > box.mMax[0] ) box.mMax[0] = p[0]; - if ( p[1] > box.mMax[1] ) box.mMax[1] = p[1]; - if ( p[2] > box.mMax[2] ) box.mMax[2] = p[2]; - } - - void minmax(const NxF64 *p,AABB &box) - { - if ( (NxF32)p[0] < box.mMin[0] ) box.mMin[0] = (NxF32)p[0]; - if ( (NxF32)p[1] < box.mMin[1] ) box.mMin[1] = (NxF32)p[1]; - if ( (NxF32)p[2] < box.mMin[2] ) box.mMin[2] = (NxF32)p[2]; - if ( (NxF32)p[0] > box.mMax[0] ) box.mMax[0] = (NxF32)p[0]; - if ( (NxF32)p[1] > box.mMax[1] ) box.mMax[1] = (NxF32)p[1]; - if ( (NxF32)p[2] > box.mMax[2] ) box.mMax[2] = (NxF32)p[2]; - } - - void buildBox(const NxF32 *vertices_f,const NxF64 *vertices_d,NxU32 id); - - void render(NxU32 color) - { -// gRenderDebug->DebugBound(&mBox.mMin[0],&mBox.mMax[0],color,60.0f); - } - - void getTriangle(NxF32 *tri,const NxF32 *vertices_f,const NxF64 *vertices_d); - - NxU32 mHandle; - bool mConsumed; - Edge *mEdges[3]; - Island *mIsland; // identifies which island it is a member of - unsigned short mId; - AABB mBox; -}; - - -class Edge -{ -public: - Edge(void) - { - mI1 = 0; - mI2 = 0; - mHash = 0; - mNext = 0; - mPrevious = 0; - mParent = 0; - mNextTriangleEdge = 0; - } - - void init(NxU32 i1,NxU32 i2,Triangle *parent) - { - assert( i1 < 65536 ); - assert( i2 < 65536 ); - - mI1 = i1; - mI2 = i2; - mHash = (i2<<16)|i1; - mReverseHash = (i1<<16)|i2; - mNext = 0; - mPrevious = 0; - mParent = parent; - } - - NxU32 mI1; - NxU32 mI2; - NxU32 mHash; - NxU32 mReverseHash; - - Edge *mNext; - Edge *mPrevious; - Edge *mNextTriangleEdge; - Triangle *mParent; -}; - -typedef CONVEX_DECOMPOSITION::HashMap< NxU32, Edge * > EdgeHashMap; -typedef CONVEX_DECOMPOSITION::Array< Triangle * > TriangleVector; - -class EdgeCheck -{ -public: - EdgeCheck(Triangle *t,Edge *e) - { - mTriangle = t; - mEdge = e; - } - - Triangle *mTriangle; - Edge *mEdge; -}; - -typedef CONVEX_DECOMPOSITION::Array< EdgeCheck > EdgeCheckQueue; - -class Island -{ -public: - Island(Triangle *t,Triangle *root) - { - mVerticesFloat = 0; - mVerticesDouble = 0; - t->mIsland = this; - mTriangles.pushBack(t); - mCoplanar = false; - fm_initMinMax(mMin,mMax); - } - - void add(Triangle *t,Triangle *root) - { - t->mIsland = this; - mTriangles.pushBack(t); - } - - void merge(Island &isl) - { - TriangleVector::Iterator i; - for (i=isl.mTriangles.begin(); i!=isl.mTriangles.end(); ++i) - { - Triangle *t = (*i); - mTriangles.pushBack(t); - } - isl.mTriangles.clear(); - } - - bool isTouching(Island *isl,const NxF32 *vertices_f,const NxF64 *vertices_d) - { - bool ret = false; - - mVerticesFloat = vertices_f; - mVerticesDouble = vertices_d; - - if ( fm_intersectAABB(mMin,mMax,isl->mMin,isl->mMax) ) // if the two islands has an intersecting AABB - { - // todo.. - } - - - return ret; - } - - - void SAP_DeletePair(const void* object0, const void* object1, void* user_data, void* pair_user_data) - { - } - - void render(NxU32 color) - { -// gRenderDebug->DebugBound(mMin,mMax,color,60.0f); - TriangleVector::Iterator i; - for (i=mTriangles.begin(); i!=mTriangles.end(); ++i) - { - Triangle *t = (*i); - t->render(color); - } - } - - - const NxF64 *mVerticesDouble; - const NxF32 *mVerticesFloat; - - NxF32 mMin[3]; - NxF32 mMax[3]; - bool mCoplanar; // marked as co-planar.. - TriangleVector mTriangles; -}; - - -void Triangle::getTriangle(NxF32 *tri,const NxF32 *vertices_f,const NxF64 *vertices_d) -{ - NxU32 i1 = mEdges[0]->mI1; - NxU32 i2 = mEdges[1]->mI1; - NxU32 i3 = mEdges[2]->mI1; - if ( vertices_f ) - { - const NxF32 *p1 = &vertices_f[i1*3]; - const NxF32 *p2 = &vertices_f[i2*3]; - const NxF32 *p3 = &vertices_f[i3*3]; - fm_copy3(p1,tri); - fm_copy3(p2,tri+3); - fm_copy3(p3,tri+6); - } - else - { - const NxF64 *p1 = &vertices_d[i1*3]; - const NxF64 *p2 = &vertices_d[i2*3]; - const NxF64 *p3 = &vertices_d[i3*3]; - fm_doubleToFloat3(p1,tri); - fm_doubleToFloat3(p2,tri+3); - fm_doubleToFloat3(p3,tri+6); - } -} - -void Triangle::buildBox(const NxF32 *vertices_f,const NxF64 *vertices_d,NxU32 id) -{ - mId = (unsigned short)id; - NxU32 i1 = mEdges[0]->mI1; - NxU32 i2 = mEdges[1]->mI1; - NxU32 i3 = mEdges[2]->mI1; - - if ( vertices_f ) - { - const NxF32 *p1 = &vertices_f[i1*3]; - const NxF32 *p2 = &vertices_f[i2*3]; - const NxF32 *p3 = &vertices_f[i3*3]; - mBox.mMin[0] = p1[0]; - mBox.mMin[1] = p1[1]; - mBox.mMin[2] = p1[2]; - mBox.mMax[0] = p1[0]; - mBox.mMax[1] = p1[1]; - mBox.mMax[2] = p1[2]; - minmax(p2,mBox); - minmax(p3,mBox); - } - else - { - const NxF64 *p1 = &vertices_d[i1*3]; - const NxF64 *p2 = &vertices_d[i2*3]; - const NxF64 *p3 = &vertices_d[i3*3]; - mBox.mMin[0] = (NxF32)p1[0]; - mBox.mMin[1] = (NxF32)p1[1]; - mBox.mMin[2] = (NxF32)p1[2]; - mBox.mMax[0] = (NxF32)p1[0]; - mBox.mMax[1] = (NxF32)p1[1]; - mBox.mMax[2] = (NxF32)p1[2]; - minmax(p2,mBox); - minmax(p3,mBox); - } - - assert(mIsland); - if ( mIsland ) - { - if ( mBox.mMin[0] < mIsland->mMin[0] ) mIsland->mMin[0] = mBox.mMin[0]; - if ( mBox.mMin[1] < mIsland->mMin[1] ) mIsland->mMin[1] = mBox.mMin[1]; - if ( mBox.mMin[2] < mIsland->mMin[2] ) mIsland->mMin[2] = mBox.mMin[2]; - - if ( mBox.mMax[0] > mIsland->mMax[0] ) mIsland->mMax[0] = mBox.mMax[0]; - if ( mBox.mMax[1] > mIsland->mMax[1] ) mIsland->mMax[1] = mBox.mMax[1]; - if ( mBox.mMax[2] > mIsland->mMax[2] ) mIsland->mMax[2] = mBox.mMax[2]; - } - -} - - -typedef CONVEX_DECOMPOSITION::Array< Island * > IslandVector; - -class MyMeshIslandGeneration : public MeshIslandGeneration -{ -public: - MyMeshIslandGeneration(void) - { - mTriangles = 0; - mEdges = 0; - mVerticesDouble = 0; - mVerticesFloat = 0; - } - - ~MyMeshIslandGeneration(void) - { - reset(); - } - - void reset(void) - { - delete []mTriangles; - delete []mEdges; - mTriangles = 0; - mEdges = 0; - mTriangleEdges.clear(); - IslandVector::Iterator i; - for (i=mIslands.begin(); i!=mIslands.end(); ++i) - { - Island *_i = (*i); - delete _i; - } - mIslands.clear(); - } - - NxU32 islandGenerate(NxU32 tcount,const NxU32 *indices,const NxF64 *vertices) - { - mVerticesDouble = vertices; - mVerticesFloat = 0; - return islandGenerate(tcount,indices); - } - - NxU32 islandGenerate(NxU32 tcount,const NxU32 *indices,const NxF32 *vertices) - { - mVerticesDouble = 0; - mVerticesFloat = vertices; - return islandGenerate(tcount,indices); - } - - NxU32 islandGenerate(NxU32 tcount,const NxU32 *indices) - { - NxU32 ret = 0; - - reset(); - - mTcount = tcount; - mTriangles = new Triangle[tcount]; - mEdges = new Edge[tcount*3]; - Edge *e = mEdges; - - for (NxU32 i=0; isecond->mParent; - - Island *i = new Island(t,mTriangles); // the initial triangle... - removeTriangle(t); // remove this triangle from the triangle-edges hashmap - - mIslands.pushBack(i); - - // now keep adding to this island until we can no longer walk any shared edges.. - addEdgeCheck(t,t->mEdges[0]); - addEdgeCheck(t,t->mEdges[1]); - addEdgeCheck(t,t->mEdges[2]); - - while ( !mEdgeCheckQueue.empty() ) - { - - EdgeCheck e = mEdgeCheckQueue.popBack(); - - // Process all triangles which share this edge - Edge *edge = locateSharedEdge(e.mEdge); - - while ( edge ) - { - Triangle *t = edge->mParent; - assert(!t->mConsumed); - i->add(t,mTriangles); - removeTriangle(t); // remove this triangle from the triangle-edges hashmap - - // now keep adding to this island until we can no longer walk any shared edges.. - - if ( edge != t->mEdges[0] ) - { - addEdgeCheck(t,t->mEdges[0]); - } - - if ( edge != t->mEdges[1] ) - { - addEdgeCheck(t,t->mEdges[1]); - } - - if ( edge != t->mEdges[2] ) - { - addEdgeCheck(t,t->mEdges[2]); - } - - edge = locateSharedEdge(e.mEdge); // keep going until all shared edges have been processed! - } - - } - } - - ret = (NxU32)mIslands.size(); - - return ret; - } - - NxU32 * getIsland(NxU32 index,NxU32 &otcount) - { - NxU32 *ret = 0; - - mIndices.clear(); - if ( index < mIslands.size() ) - { - Island *i = mIslands[index]; - otcount = (NxU32)i->mTriangles.size(); - TriangleVector::Iterator j; - for (j=i->mTriangles.begin(); j!=i->mTriangles.end(); ++j) - { - Triangle *t = (*j); - mIndices.pushBack(t->mEdges[0]->mI1); - mIndices.pushBack(t->mEdges[1]->mI1); - mIndices.pushBack(t->mEdges[2]->mI1); - } - ret = &mIndices[0]; - } - - return ret; - } - -private: - - void removeTriangle(Triangle *t) - { - t->mConsumed = true; - - removeEdge(t->mEdges[0]); - removeEdge(t->mEdges[1]); - removeEdge(t->mEdges[2]); - - } - - - Edge * locateSharedEdge(Edge *e) - { - Edge *ret = 0; - - const EdgeHashMap::Entry *found = mTriangleEdges.find( e->mReverseHash ); - if ( found != NULL ) - { - ret = (*found).second; - assert( ret->mHash == e->mReverseHash ); - } - return ret; - } - - void removeEdge(Edge *e) - { - const EdgeHashMap::Entry *found = mTriangleEdges.find( e->mHash ); - - if ( found != NULL ) - { - Edge *prev = 0; - Edge *scan = (*found).second; - while ( scan && scan != e ) - { - prev = scan; - scan = scan->mNextTriangleEdge; - } - - if ( scan ) - { - if ( prev == 0 ) - { - if ( scan->mNextTriangleEdge ) - { - mTriangleEdges.erase(e->mHash); - mTriangleEdges[e->mHash] = scan->mNextTriangleEdge; - } - else - { - mTriangleEdges.erase(e->mHash); // no more polygons have an edge here - } - } - else - { - prev->mNextTriangleEdge = scan->mNextTriangleEdge; - } - } - else - { - assert(0); - } - } - else - { - assert(0); // impossible! - } - } - - - Edge * addEdge(Edge *e,Triangle *t,NxU32 i1,NxU32 i2) - { - - e->init(i1,i2,t); - - const EdgeHashMap::Entry *found = mTriangleEdges.find(e->mHash); - if ( found == NULL ) - { - mTriangleEdges[ e->mHash ] = e; - } - else - { - Edge *pn = (*found).second; - e->mNextTriangleEdge = pn; - mTriangleEdges.erase(e->mHash); - mTriangleEdges[e->mHash] = e; - } - - e++; - - return e; - } - - void addEdgeCheck(Triangle *t,Edge *e) - { - EdgeCheck ec(t,e); - mEdgeCheckQueue.pushBack(ec); - } - - NxU32 mergeCoplanarIslands(const NxF32 *vertices) - { - mVerticesFloat = vertices; - mVerticesDouble = 0; - return mergeCoplanarIslands(); - } - - NxU32 mergeCoplanarIslands(const NxF64 *vertices) - { - mVerticesDouble = vertices; - mVerticesFloat = 0; - return mergeCoplanarIslands(); - } - - // this island needs to be merged - void mergeTouching(Island *isl) - { - Island *touching = 0; - - IslandVector::Iterator i; - for (i=mIslands.begin(); i!=mIslands.end(); ++i) - { - Island *_i = (*i); - if ( !_i->mCoplanar ) // can't merge with coplanar islands! - { - if ( _i->isTouching(isl,mVerticesFloat,mVerticesDouble) ) - { - touching = _i; - } - } - } - } - - NxU32 mergeCoplanarIslands(void) - { - NxU32 ret = 0; - - if ( !mIslands.empty() ) - { - - - NxU32 cp_count = 0; - NxU32 npc_count = 0; - - NxU32 count = (NxU32)mIslands.size(); - - for (NxU32 i=0; imCoplanar = true; - cp_count++; - } - else - { - npc_count++; - } - } - else - { - assert(0); - } - } - - if ( cp_count ) - { - if ( npc_count == 0 ) // all islands are co-planar! - { - IslandVector temp = mIslands; - mIslands.clear(); - Island *isl = mIslands[0]; - mIslands.pushBack(isl); - for (NxU32 i=1; imerge(*_i); - delete _i; - } - } - else - { - - - Triangle *t = mTriangles; - for (NxU32 i=0; ibuildBox(mVerticesFloat,mVerticesDouble,i); - t++; - } - - IslandVector::Iterator i; - for (i=mIslands.begin(); i!=mIslands.end(); ++i) - { - Island *isl = (*i); - - NxU32 color = 0x00FF00; - - if ( isl->mCoplanar ) - { - color = 0xFFFF00; - } - - mergeTouching(isl); - - } - - IslandVector temp = mIslands; - mIslands.clear(); - for (i=temp.begin(); i!=temp.end(); i++) - { - Island *isl = (*i); - if ( isl->mCoplanar ) - { - delete isl; // kill it - } - else - { - mIslands.pushBack(isl); - } - } - ret = (NxU32)mIslands.size(); - } - } - else - { - ret = npc_count; - } - } - - - return ret; - } - - NxU32 mergeTouchingIslands(const NxF32 *vertices) - { - NxU32 ret = 0; - - return ret; - } - - NxU32 mergeTouchingIslands(const NxF64 *vertices) - { - NxU32 ret = 0; - - return ret; - } - - NxU32 mTcount; - Triangle *mTriangles; - Edge *mEdges; - EdgeHashMap mTriangleEdges; - IslandVector mIslands; - EdgeCheckQueue mEdgeCheckQueue; - const NxF64 *mVerticesDouble; - const NxF32 *mVerticesFloat; - NxU32Vector mIndices; -}; - - -MeshIslandGeneration * createMeshIslandGeneration(void) -{ - MyMeshIslandGeneration *mig = new MyMeshIslandGeneration; - return static_cast< MeshIslandGeneration *>(mig); -} - -void releaseMeshIslandGeneration(MeshIslandGeneration *cm) -{ - MyMeshIslandGeneration *mig = static_cast< MyMeshIslandGeneration *>(cm); - delete mig; -} - -}; // end of namespace - diff --git a/Engine/lib/convexDecomp/NvMeshIslandGeneration.h b/Engine/lib/convexDecomp/NvMeshIslandGeneration.h deleted file mode 100644 index 5c9347613..000000000 --- a/Engine/lib/convexDecomp/NvMeshIslandGeneration.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef MESH_ISLAND_GENERATION_H - -#define MESH_ISLAND_GENERATION_H - -/* - -NvMeshIslandGeneration.h : This code snippet walks the toplogy of a triangle mesh and detects the set of unique connected 'mesh islands' - -*/ - - -#include "NvUserMemAlloc.h" - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -namespace CONVEX_DECOMPOSITION -{ - -class MeshIslandGeneration -{ -public: - - virtual NxU32 islandGenerate(NxU32 tcount,const NxU32 *indices,const NxF32 *vertices) = 0; - virtual NxU32 islandGenerate(NxU32 tcount,const NxU32 *indices,const NxF64 *vertices) = 0; - - // sometimes island generation can produce co-planar islands. Slivers if you will. If you are passing these islands into a geometric system - // that wants to turn them into physical objects, they may not be acceptable. In this case it may be preferable to merge the co-planar islands with - // other islands that it 'touches'. - virtual NxU32 mergeCoplanarIslands(const NxF32 *vertices) = 0; - virtual NxU32 mergeCoplanarIslands(const NxF64 *vertices) = 0; - - virtual NxU32 mergeTouchingIslands(const NxF32 *vertices) = 0; - virtual NxU32 mergeTouchingIslands(const NxF64 *vertices) = 0; - - virtual NxU32 * getIsland(NxU32 index,NxU32 &tcount) = 0; - - -}; - -MeshIslandGeneration * createMeshIslandGeneration(void); -void releaseMeshIslandGeneration(MeshIslandGeneration *cm); - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvRayCast.cpp b/Engine/lib/convexDecomp/NvRayCast.cpp deleted file mode 100644 index 6a9f02fe7..000000000 --- a/Engine/lib/convexDecomp/NvRayCast.cpp +++ /dev/null @@ -1,153 +0,0 @@ -/* - -NvRayCast.cpp : A code snippet to cast a ray against a triangle mesh. This implementation does not use any acceleration data structures. That is a 'to do' item. - -*/ -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include "NvRayCast.h" -#include "NvUserMemAlloc.h" -#include "NvFloatMath.h" - -#pragma warning(disable:4100) - -namespace CONVEX_DECOMPOSITION -{ - -class RayCast : public iRayCast, public Memalloc -{ -public: - RayCast(const NxF32 *vertices,NxU32 tcount,const NxU32 *indices) - { - mVertices = vertices; - mTcount = tcount; - mIndices = indices; - } - - ~RayCast(void) - { - } - - virtual bool castRay(const NxF32 *orig,const NxF32 *dir,NxF32 *dest,NxF32 *hitNormal) - { - bool ret = false; - - NxF32 p2[3]; - - const NxF32 RAY_DIST=50; - - dest[0] = p2[0] = orig[0]+ dir[0]*RAY_DIST; - dest[1] = p2[1] = orig[1]+ dir[1]*RAY_DIST; - dest[2] = p2[2] = orig[2]+ dir[2]*RAY_DIST; - - NxF32 nearest = 1e9; - NxU32 near_face=0; - - - for (NxU32 i=0; i(rc); -} - -void releaseRayCast(iRayCast *rc) -{ - RayCast *r = static_cast< RayCast *>(rc); - delete r; -} - -}; - diff --git a/Engine/lib/convexDecomp/NvRayCast.h b/Engine/lib/convexDecomp/NvRayCast.h deleted file mode 100644 index 4f4cf1fc5..000000000 --- a/Engine/lib/convexDecomp/NvRayCast.h +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef NV_RAYCAST_H - -#define NV_RAYCAST_H - -/* - -NvRayCast.h : A code snippet to cast a ray against a triangle mesh. This implementation does not use any acceleration data structures. That is a 'to do' item. - -*/ - - -#include "NvSimpleTypes.h" - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -namespace CONVEX_DECOMPOSITION -{ - -class iRayCast -{ -public: - virtual bool castRay(const NxF32 *orig,const NxF32 *dir,NxF32 *hitPoint,NxF32 *hitNormal) = 0; -protected: - virtual ~iRayCast(void) { }; -}; - - -iRayCast *createRayCast(const NxF32 *vertices,NxU32 tcount,const NxU32 *indices); -void releaseRayCast(iRayCast *rc); - -}; - -#endif diff --git a/Engine/lib/convexDecomp/NvRemoveTjunctions.cpp b/Engine/lib/convexDecomp/NvRemoveTjunctions.cpp deleted file mode 100644 index 3d0aefafd..000000000 --- a/Engine/lib/convexDecomp/NvRemoveTjunctions.cpp +++ /dev/null @@ -1,713 +0,0 @@ -/* - -NvRemoveTjunctions.cpp : A code snippet to remove tjunctions from a triangle mesh. This version is currently disabled as it appears to have a bug. - -*/ - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include -#include -#include -#include -#pragma warning(disable:4702) -#pragma warning(disable:4127) //conditional expression is constant (because _HAS_EXCEPTIONS=0) -#include -#if defined( __APPLE__ ) || defined( __FreeBSD__) - #include -#elif LINUX - #include -#elif _MSC_VER < 1500 - #include -#elif _MSC_VER > 1800 - #include -#endif -#include "NvUserMemAlloc.h" -#include "NvHashMap.h" -#include "NvRemoveTjunctions.h" -#include "NvFloatMath.h" -#ifdef LINUX - #include -#endif - -#pragma warning(disable:4189) - -using namespace CONVEX_DECOMPOSITION; - -namespace CONVEX_DECOMPOSITION -{ - -class AABB -{ -public: - NxF32 mMin[3]; - NxF32 mMax[3]; -}; - -bool gDebug=false; -NxU32 gCount=0; - -typedef CONVEX_DECOMPOSITION::Array< NxU32 > NxU32Vector; - -class Triangle -{ -public: - Triangle(void) - { - mPending = false; - mSplit = false; - mI1 = mI2 = mI3 = 0xFFFFFFFF; - mId = 0; - } - - Triangle(NxU32 i1,NxU32 i2,NxU32 i3,const float *vertices,NxU32 id) - { - mPending = false; - init(i1,i2,i3,vertices,id); - mSplit = false; - } - - void init(NxU32 i1,NxU32 i2,NxU32 i3,const float *vertices,NxU32 id) - { - mSplit = false; - mI1 = i1; - mI2 = i2; - mI3 = i3; - mId = id; - - const float *p1 = &vertices[mI1*3]; - const float *p2 = &vertices[mI2*3]; - const float *p3 = &vertices[mI3*3]; - - initMinMax(p1,p2,p3); - } - - void initMinMax(const float *p1,const float *p2,const float *p3) - { - fm_copy3(p1,mBmin); - fm_copy3(p1,mBmax); - fm_minmax(p2,mBmin,mBmax); - fm_minmax(p3,mBmin,mBmax); - } - - void init(const NxU32 *idx,const float *vertices,NxU32 id) - { - mSplit = false; - mI1 = idx[0]; - mI2 = idx[1]; - mI3 = idx[2]; - mId = id; - - const float *p1 = &vertices[mI1*3]; - const float *p2 = &vertices[mI2*3]; - const float *p3 = &vertices[mI3*3]; - - initMinMax(p1,p2,p3); - - } - - bool intersects(const float *pos,const float *p1,const float *p2,float epsilon) const - { - bool ret = false; - - float sect[3]; - LineSegmentType type; - - float dist = fm_distancePointLineSegment(pos,p1,p2,sect,type,epsilon); - - if ( type == LS_MIDDLE && dist < epsilon ) - { - ret = true; - } - - return ret; - } - - bool intersects(NxU32 i,const float *vertices,NxU32 &edge,float epsilon) const - { - bool ret = true; - - const float *pos = &vertices[i*3]; - const float *p1 = &vertices[mI1*3]; - const float *p2 = &vertices[mI2*3]; - const float *p3 = &vertices[mI3*3]; - if ( intersects(pos,p1,p2,epsilon) ) - { - edge = 0; - } - else if ( intersects(pos,p2,p3,epsilon) ) - { - edge = 1; - } - else if ( intersects(pos,p3,p1,epsilon) ) - { - edge = 2; - } - else - { - ret = false; - } - return ret; - } - - bool intersects(const Triangle *t,const float *vertices,NxU32 &intersection_index,NxU32 &edge,float epsilon) - { - bool ret = false; - - if ( fm_intersectAABB(mBmin,mBmax,t->mBmin,t->mBmax) ) // only if the AABB's of the two triangles intersect... - { - - if ( t->intersects(mI1,vertices,edge,epsilon) ) - { - intersection_index = mI1; - ret = true; - } - - if ( t->intersects(mI2,vertices,edge,epsilon) ) - { - intersection_index = mI2; - ret = true; - } - - if ( t->intersects(mI3,vertices,edge,epsilon) ) - { - intersection_index = mI3; - ret = true; - } - - } - - return ret; - } - - bool mSplit:1; - bool mPending:1; - NxU32 mI1; - NxU32 mI2; - NxU32 mI3; - NxU32 mId; - float mBmin[3]; - float mBmax[3]; -}; - -class RtEdge -{ -public: - RtEdge(void) - { - mNextEdge = 0; - mTriangle = 0; - mHash = 0; - } - - NxU32 init(Triangle *t,NxU32 i1,NxU32 i2) - { - mTriangle = t; - mNextEdge = 0; - NX_ASSERT( i1 < 65536 ); - NX_ASSERT( i2 < 65536 ); - if ( i1 < i2 ) - { - mHash = (i1<<16)|i2; - } - else - { - mHash = (i2<<16)|i1; - } - return mHash; - } - RtEdge *mNextEdge; - Triangle *mTriangle; - NxU32 mHash; -}; - - -typedef CONVEX_DECOMPOSITION::Array< Triangle * > TriangleVector; -typedef CONVEX_DECOMPOSITION::HashMap< NxU32, RtEdge * > EdgeMap; - -class MyRemoveTjunctions : public RemoveTjunctions -{ -public: - MyRemoveTjunctions(void) - { - mInputTriangles = 0; - mEdges = 0; - mVcount = 0; - mVertices = 0; - mEdgeCount = 0; - } - ~MyRemoveTjunctions(void) - { - release(); - } - - virtual NxU32 removeTjunctions(RemoveTjunctionsDesc &desc) - { - NxU32 ret = 0; - - mEpsilon = desc.mEpsilon; - - size_t TcountOut; - - desc.mIndicesOut = removeTjunctions(desc.mVcount, desc.mVertices, desc.mTcount, desc.mIndices, TcountOut, desc.mIds); - -#ifdef WIN32 -# pragma warning(push) -# pragma warning(disable:4267) -#endif - - NX_ASSERT( TcountOut < UINT_MAX ); - desc.mTcountOut = TcountOut; - -#ifdef WIN32 -# pragma warning(pop) -#endif - - if ( !mIds.empty() ) - { - desc.mIdsOut = &mIds[0]; - } - - ret = desc.mTcountOut; - - bool check = ret != desc.mTcount; -#if 0 - while ( check ) - { - NxU32 tcount = ret; - NxU32 *indices = new NxU32[tcount*3]; - NxU32 *ids = new NxU32[tcount]; - memcpy(indices,desc.mIndicesOut,sizeof(NxU32)*ret*3); - memcpy(ids,desc.mIdsOut,sizeof(NxU32)*ret); - desc.mIndicesOut = removeTjunctions(desc.mVcount, desc.mVertices, tcount, indices, desc.mTcountOut, ids ); - if ( !mIds.empty() ) - { - desc.mIdsOut = &mIds[0]; - } - ret = desc.mTcountOut; - delete []indices; - delete []ids; - check = ret != tcount; - } -#endif - return ret; - } - - RtEdge * addEdge(Triangle *t,RtEdge *e,NxU32 i1,NxU32 i2) - { - NxU32 hash = e->init(t,i1,i2); - const EdgeMap::Entry *found = mEdgeMap.find(hash); - if ( found == NULL ) - { - mEdgeMap[hash] = e; - } - else - { - RtEdge *old_edge = (*found).second; - e->mNextEdge = old_edge; - mEdgeMap.erase(hash); - mEdgeMap[hash] = e; - } - e++; - mEdgeCount++; - return e; - } - - RtEdge * init(Triangle *t,const NxU32 *indices,const float *vertices,RtEdge *e,NxU32 id) - { - t->init(indices,vertices,id); - e = addEdge(t,e,t->mI1,t->mI2); - e = addEdge(t,e,t->mI2,t->mI3); - e = addEdge(t,e,t->mI3,t->mI1); - return e; - } - - void release(void) - { - mIds.clear(); - mEdgeMap.clear(); - mIndices.clear(); - mSplit.clear(); - delete []mInputTriangles; - delete []mEdges; - mInputTriangles = 0; - mEdges = 0; - mVcount = 0; - mVertices = 0; - mEdgeCount = 0; - - } - - virtual NxU32 * removeTjunctions(NxU32 vcount, - const float *vertices, - size_t tcount, - const NxU32 *indices, - size_t &tcount_out, - const NxU32 * ids) - { - NxU32 *ret = 0; - - release(); - - mVcount = vcount; - mVertices = vertices; - mTcount = (NxU32)tcount; - tcount_out = 0; - - mTcount = (NxU32)tcount; - mMaxTcount = (NxU32)tcount*2; - mInputTriangles = new Triangle[mMaxTcount]; - Triangle *t = mInputTriangles; - - mEdges = new RtEdge[mMaxTcount*3]; - mEdgeCount = 0; - - NxU32 id = 0; - - RtEdge *e = mEdges; - for (NxU32 i=0; imNextEdge == 0 ) // open edge! - { - Triangle *t = e->mTriangle; - if ( !t->mPending ) - { - test.pushBack(t); - t->mPending = true; - } - } - } - - if ( !test.empty() ) - { - TriangleVector::Iterator i; - for (i=test.begin(); i!=test.end(); ++i) - { - Triangle *t = (*i); - locateIntersection(t); - } - } - - } - - while ( !mSplit.empty() ) - { - TriangleVector scan = mSplit; - mSplit.clear(); - TriangleVector::Iterator i; - for (i=scan.begin(); i!=scan.end(); ++i) - { - Triangle *t = (*i); - locateIntersection(t); - } - } - - - mIndices.clear(); - mIds.clear(); - - t = mInputTriangles; - for (NxU32 i=0; imI1); - mIndices.pushBack(t->mI2); - mIndices.pushBack(t->mI3); - mIds.pushBack(t->mId); - t++; - } - - - mEdgeMap.clear(); - - delete []mEdges; - mEdges = 0; - delete []mInputTriangles; - mInputTriangles = 0; - tcount_out = mIndices.size()/3; - ret = tcount_out ? &mIndices[0] : 0; -#ifdef _DEBUG - if ( ret ) - { - const NxU32 *scan = ret; - for (NxU32 i=0; imI1 < mVcount ); - NX_ASSERT( scan->mI2 < mVcount ); - NX_ASSERT( scan->mI3 < mVcount ); - - NX_ASSERT( t->mI1 < mVcount ); - NX_ASSERT( t->mI2 < mVcount ); - NX_ASSERT( t->mI3 < mVcount ); - - - NxU32 intersection_index; - NxU32 edge; - - if ( scan != t && scan->intersects(t,mVertices,intersection_index,edge,mEpsilon) ) - { - - if ( t->mI1 == intersection_index || t->mI2 == intersection_index || t->mI3 == intersection_index ) - { - } - else - { - // here is where it intersects! - NxU32 i1,i2,i3; - NxU32 j1,j2,j3; - NxU32 id = t->mId; - - switch ( edge ) - { - case 0: - i1 = t->mI1; - i2 = intersection_index; - i3 = t->mI3; - j1 = intersection_index; - j2 = t->mI2; - j3 = t->mI3; - break; - case 1: - i1 = t->mI2; - i2 = intersection_index; - i3 = t->mI1; - j1 = intersection_index; - j2 = t->mI3; - j3 = t->mI1; - break; - case 2: - i1 = t->mI3; - i2 = intersection_index; - i3 = t->mI2; - j1 = intersection_index; - j2 = t->mI1; - j3 = t->mI2; - break; - default: - NX_ASSERT(0); - i1 = i2 = i3 = 0; - j1 = j2 = j3 = 0; - break; - } - - if ( mTcount < mMaxTcount ) - { - t->init(i1,i2,i3,mVertices,id); - Triangle *newt = &mInputTriangles[mTcount]; - newt->init(j1,j2,j3,mVertices,id); - mTcount++; - t->mSplit = true; - newt->mSplit = true; - - mSplit.pushBack(t); - mSplit.pushBack(newt); - ret = scan; - } - } - } - return ret; - } - - Triangle * testIntersection(Triangle *scan,Triangle *t) - { - Triangle *ret = 0; - - NxU32 t1 = (NxU32)(scan-mInputTriangles); - NxU32 t2 = (NxU32)(t-mInputTriangles); - - NX_ASSERT( t1 < mTcount ); - NX_ASSERT( t2 < mTcount ); - - NX_ASSERT( scan->mI1 < mVcount ); - NX_ASSERT( scan->mI2 < mVcount ); - NX_ASSERT( scan->mI3 < mVcount ); - - NX_ASSERT( t->mI1 < mVcount ); - NX_ASSERT( t->mI2 < mVcount ); - NX_ASSERT( t->mI3 < mVcount ); - - - NxU32 intersection_index; - NxU32 edge; - - assert( scan != t ); - - if ( scan->intersects(t,mVertices,intersection_index,edge,mEpsilon) ) - { - // here is where it intersects! - NxU32 i1,i2,i3; - NxU32 j1,j2,j3; - NxU32 id = t->mId; - - switch ( edge ) - { - case 0: - i1 = t->mI1; - i2 = intersection_index; - i3 = t->mI3; - j1 = intersection_index; - j2 = t->mI2; - j3 = t->mI3; - break; - case 1: - i1 = t->mI2; - i2 = intersection_index; - i3 = t->mI1; - j1 = intersection_index; - j2 = t->mI3; - j3 = t->mI1; - break; - case 2: - i1 = t->mI3; - i2 = intersection_index; - i3 = t->mI2; - j1 = intersection_index; - j2 = t->mI1; - j3 = t->mI2; - break; - default: - NX_ASSERT(0); - i1 = i2 = i3 = 0; - j1 = j2 = j3 = 0; - break; - } - - if ( mTcount < mMaxTcount ) - { - t->init(i1,i2,i3,mVertices,id); - Triangle *newt = &mInputTriangles[mTcount]; - newt->init(j1,j2,j3,mVertices,id); - mTcount++; - t->mSplit = true; - newt->mSplit = true; - - mSplit.pushBack(t); - mSplit.pushBack(newt); - ret = scan; - } - } - return ret; - } - - Triangle * locateIntersection(Triangle *t) - { - Triangle *ret = 0; - - Triangle *scan = mInputTriangles; - - for (NxU32 i=0; i(m); -} - -void releaseRemoveTjunctions(RemoveTjunctions *tj) -{ - MyRemoveTjunctions *m = static_cast< MyRemoveTjunctions *>(tj); - delete m; -} - - -}; // end of namespace - diff --git a/Engine/lib/convexDecomp/NvRemoveTjunctions.h b/Engine/lib/convexDecomp/NvRemoveTjunctions.h deleted file mode 100644 index b6a674722..000000000 --- a/Engine/lib/convexDecomp/NvRemoveTjunctions.h +++ /dev/null @@ -1,110 +0,0 @@ -#ifndef REMOVE_TJUNCTIONS_H - -#define REMOVE_TJUNCTIONS_H - -/* - -NvRemoveTjunctions.h : A code snippet to remove tjunctions from a triangle mesh. This version is currently disabled as it appears to have a bug. - -*/ - - -#include "NvUserMemAlloc.h" - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -namespace CONVEX_DECOMPOSITION -{ - -class RemoveTjunctionsDesc -{ -public: - RemoveTjunctionsDesc(void) - { - mVcount = 0; - mVertices = 0; - mTcount = 0; - mIndices = 0; - mIds = 0; - mTcountOut = 0; - mIndicesOut = 0; - mIdsOut = 0; - mEpsilon = 0.00000001f; - } - -// input - NxF32 mEpsilon; - NxF32 mDistanceEpsilon; - NxU32 mVcount; // input vertice count. - const NxF32 *mVertices; // input vertices as NxF32s or... - NxU32 mTcount; // number of input triangles. - const NxU32 *mIndices; // triangle indices. - const NxU32 *mIds; // optional triangle Id numbers. -// output.. - NxU32 mTcountOut; // number of output triangles. - const NxU32 *mIndicesOut; // output triangle indices - const NxU32 *mIdsOut; // output retained id numbers. -}; - -// Removes t-junctions from an input mesh. Does not generate any new data points, but may possible produce additional triangles and new indices. -class RemoveTjunctions -{ -public: - - virtual NxU32 removeTjunctions(RemoveTjunctionsDesc &desc) =0; // returns number of triangles output and the descriptor is filled with the appropriate results. - - -}; - -RemoveTjunctions * createRemoveTjunctions(void); -void releaseRemoveTjunctions(RemoveTjunctions *tj); - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvSimpleTypes.h b/Engine/lib/convexDecomp/NvSimpleTypes.h deleted file mode 100644 index 3dddaa2fc..000000000 --- a/Engine/lib/convexDecomp/NvSimpleTypes.h +++ /dev/null @@ -1,189 +0,0 @@ -#ifndef NV_SIMPLE_TYPES_H - -#define NV_SIMPLE_TYPES_H - -/* - -NvSimpleTypes.h : Defines basic data types for integers and floats. - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - - -#if defined(__APPLE__) - #include -#else -#if defined( __FreeBSD__) - #include -#else - #include -#endif -#endif -#include - -#if defined(__APPLE__) || defined(__CELLOS_LV2__) || defined(LINUX) - -#ifndef stricmp -#define stricmp(a, b) strcasecmp((a), (b)) -#define _stricmp(a, b) strcasecmp((a), (b)) -#endif - -#endif - -#if defined(WIN32) - typedef __int64 NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned __int64 NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#elif defined(LINUX) - typedef long long NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned long long NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#elif defined(__APPLE__) - typedef long long NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned long long NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#elif defined(__FreeBSD__) - typedef long long NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned long long NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#elif defined(__CELLOS_LV2__) - typedef long long NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned long long NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#elif defined(_XBOX) - typedef __int64 NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned __int64 NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#elif defined(__PPCGEKKO__) - typedef long long NxI64; - typedef signed int NxI32; - typedef signed short NxI16; - typedef signed char NxI8; - - typedef unsigned long long NxU64; - typedef unsigned int NxU32; - typedef unsigned short NxU16; - typedef unsigned char NxU8; - - typedef float NxF32; - typedef double NxF64; - -#else - #error Unknown platform! -#endif - -#ifndef NX_INLINE -#define NX_INLINE inline -#define NX_ASSERT assert -#endif - - -#endif diff --git a/Engine/lib/convexDecomp/NvSplitMesh.cpp b/Engine/lib/convexDecomp/NvSplitMesh.cpp deleted file mode 100644 index 49905f91e..000000000 --- a/Engine/lib/convexDecomp/NvSplitMesh.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/* - -NvSplitMesh.cpp : A code snippet to split a mesh into two seperate meshes based on a slicing plane. - -*/ - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#define SHOW_DEBUG 0 -#if SHOW_DEBUG -#include "RenderDebug.h" -#endif - -#include "NvSplitMesh.h" -#include "NvFloatMath.h" -#include "NvHashMap.h" - -#pragma warning(disable:4100) - -namespace CONVEX_DECOMPOSITION -{ - - -typedef Array< NxU32 > NxU32Array; - -class SplitMesh : public iSplitMesh, public Memalloc -{ -public: - SplitMesh(void) - { - mLeftVertices = 0; - mRightVertices = 0; - } - - ~SplitMesh(void) - { - reset(); - } - - void reset(void) - { - if ( mLeftVertices ) - { - fm_releaseVertexIndex(mLeftVertices); - mLeftVertices = 0; - } - if ( mRightVertices ) - { - fm_releaseVertexIndex(mRightVertices); - mRightVertices = 0; - } - mLeftIndices.clear(); - mRightIndices.clear(); - } - - - virtual void splitMesh(const NvSplitMesh &source,NvSplitMesh &leftMesh,NvSplitMesh &rightMesh,const NxF32 *planeEquation,NxF32 precision) - { - reset(); - - mLeftVertices = fm_createVertexIndex(precision,false); - mRightVertices = fm_createVertexIndex(precision,false); - - for (NxU32 i=0; igetIndex( &front_tri[0],newPos ); - i2 = mLeftVertices->getIndex( &front_tri[3],newPos ); - i3 = mLeftVertices->getIndex( &front_tri[6],newPos ); - mLeftIndices.pushBack(i1); - mLeftIndices.pushBack(i2); - mLeftIndices.pushBack(i3); - #if SHOW_DEBUG - NVSHARE::gRenderDebug->setCurrentColor(0xFFFFFF); - NVSHARE::gRenderDebug->DebugTri(&front_tri[0],&front_tri[3],&front_tri[6]); - #endif - if ( fcount == 4 ) - { - i4 = mLeftVertices->getIndex( &front_tri[9],newPos ); - mLeftIndices.pushBack(i1); - mLeftIndices.pushBack(i3); - mLeftIndices.pushBack(i4); - #if SHOW_DEBUG - NVSHARE::gRenderDebug->setCurrentColor(0xFFFF00); - NVSHARE::gRenderDebug->DebugTri(&front_tri[0],&front_tri[6],&front_tri[9]); - #endif - } - } - if ( bcount ) - { - NxU32 i1,i2,i3,i4; - i1 = mRightVertices->getIndex( &back_tri[0],newPos ); - i2 = mRightVertices->getIndex( &back_tri[3],newPos ); - i3 = mRightVertices->getIndex( &back_tri[6],newPos ); - mRightIndices.pushBack(i1); - mRightIndices.pushBack(i2); - mRightIndices.pushBack(i3); - #if SHOW_DEBUG - NVSHARE::gRenderDebug->setCurrentColor(0xFF8080); - NVSHARE::gRenderDebug->DebugTri(&back_tri[0],&back_tri[3],&back_tri[6]); - #endif - if ( bcount == 4 ) - { - i4 = mRightVertices->getIndex( &back_tri[9],newPos ); - mRightIndices.pushBack(i1); - mRightIndices.pushBack(i3); - mRightIndices.pushBack(i4); - #if SHOW_DEBUG - NVSHARE::gRenderDebug->setCurrentColor(0x00FF00); - NVSHARE::gRenderDebug->DebugTri(&back_tri[0],&back_tri[6],&back_tri[9]); - #endif - } - } - } - - leftMesh.mVcount = mLeftVertices->getVcount(); - leftMesh.mVertices = mLeftVertices->getVerticesFloat(); - leftMesh.mTcount = mLeftIndices.size()/3; - leftMesh.mIndices = &mLeftIndices[0]; - - rightMesh.mVcount = mRightVertices->getVcount(); - rightMesh.mVertices = mRightVertices->getVerticesFloat(); - rightMesh.mTcount = mRightIndices.size()/3; - rightMesh.mIndices = &mRightIndices[0]; - - } - - - fm_VertexIndex *mLeftVertices; - fm_VertexIndex *mRightVertices; - NxU32Array mLeftIndices; - NxU32Array mRightIndices; -}; - -iSplitMesh *createSplitMesh(void) -{ - SplitMesh *sm = MEMALLOC_NEW(SplitMesh); - return static_cast< iSplitMesh *>(sm); -} - -void releaseSplitMesh(iSplitMesh *splitMesh) -{ - SplitMesh *sm = static_cast< SplitMesh *>(splitMesh); - delete sm; -} - -}; // end of namespace diff --git a/Engine/lib/convexDecomp/NvSplitMesh.h b/Engine/lib/convexDecomp/NvSplitMesh.h deleted file mode 100644 index d9d623b54..000000000 --- a/Engine/lib/convexDecomp/NvSplitMesh.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef NV_SPLIT_MESH_H - -#define NV_SPLIT_MESH_H - -/* - -NvSplitMesh.h : A code snippet to split a mesh into two seperate meshes based on a slicing plane. - -*/ - - -#include "NvUserMemAlloc.h" - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -namespace CONVEX_DECOMPOSITION -{ - -struct NvSplitMesh -{ - NxU32 mVcount; - const NxF32 *mVertices; - NxU32 mTcount; - const NxU32 *mIndices; -}; - - -class iSplitMesh -{ -public: - virtual void splitMesh(const NvSplitMesh &source,NvSplitMesh &leftMesh,NvSplitMesh &rightMesh,const NxF32 *planeEquation,NxF32 precision) = 0; -protected: - virtual ~iSplitMesh(void) { }; -}; - -iSplitMesh *createSplitMesh(void); -void releaseSplitMesh(iSplitMesh *splitMesh); - - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvStanHull.cpp b/Engine/lib/convexDecomp/NvStanHull.cpp deleted file mode 100644 index 09448aaca..000000000 --- a/Engine/lib/convexDecomp/NvStanHull.cpp +++ /dev/null @@ -1,3464 +0,0 @@ - -/* - -NvStanHull.cpp : A convex hull generator written by Stan Melax - -*/ -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include -#include -#include -#include -#include -#include - - -#include -#include - -#include "NvStanHull.h" - -namespace CONVEX_DECOMPOSITION -{ - -//***************************************************** -//*** DARRAY.H -//***************************************************** - -template class ArrayRet; -template class Array -{ - public: - Array(NxI32 s=0); - Array(Array &array); - Array(ArrayRet &array); - ~Array(); - void allocate(NxI32 s); - void SetSize(NxI32 s); - void Pack(); - Type& Add(Type); - void AddUnique(Type); - NxI32 Contains(Type); - void Insert(Type,NxI32); - NxI32 IndexOf(Type); - void Remove(Type); - void DelIndex(NxI32 i); - Type * element; - NxI32 count; - NxI32 array_size; - const Type &operator[](NxI32 i) const { assert(i>=0 && i=0 && i &operator=(Array &array); - Array &operator=(ArrayRet &array); - // operator ArrayRet &() { return *(ArrayRet *)this;} // this worked but i suspect could be dangerous -}; - -template class ArrayRet:public Array -{ -}; - -template Array::Array(NxI32 s) -{ - count=0; - array_size = 0; - element = NULL; - if(s) - { - allocate(s); - } -} - - -template Array::Array(Array &array) -{ - count=0; - array_size = 0; - element = NULL; - for(NxI32 i=0;i Array::Array(ArrayRet &array) -{ - *this = array; -} -template Array &Array::operator=(ArrayRet &array) -{ - count=array.count; - array_size = array.array_size; - element = array.element; - array.element=NULL; - array.count=0; - array.array_size=0; - return *this; -} - - -template Array &Array::operator=(Array &array) -{ - count=0; - for(NxI32 i=0;i Array::~Array() -{ - if (element != NULL) - { - MEMALLOC_FREE(element); - } - count=0;array_size=0;element=NULL; -} - -template void Array::allocate(NxI32 s) -{ - assert(s>0); - assert(s>=count); - Type *old = element; - array_size =s; - element = (Type *) MEMALLOC_MALLOC( sizeof(Type)*array_size ); - assert(element); - for(NxI32 i=0;i void Array::SetSize(NxI32 s) -{ - if(s==0) - { - if(element) - { - MEMALLOC_FREE(element); - element = NULL; - } - array_size = s; - } - else - { - allocate(s); - } - count=s; -} - -template void Array::Pack() -{ - allocate(count); -} - -template Type& Array::Add(Type t) -{ - assert(count<=array_size); - if(count==array_size) - { - allocate((array_size)?array_size *2:16); - } - element[count++] = t; - return element[count-1]; -} - -template NxI32 Array::Contains(Type t) -{ - NxI32 i; - NxI32 found=0; - for(i=0;i void Array::AddUnique(Type t) -{ - if(!Contains(t)) Add(t); -} - - -template void Array::DelIndex(NxI32 i) -{ - assert(i void Array::Remove(Type t) -{ - NxI32 i; - for(i=0;i void Array::Insert(Type t,NxI32 k) -{ - NxI32 i=count; - Add(t); // to allocate space - while(i>k) - { - element[i]=element[i-1]; - i--; - } - assert(i==k); - element[k]=t; -} - - -template NxI32 Array::IndexOf(Type t) -{ - NxI32 i; - for(i=0;i Member )))- ((char*)NULL)) - - - -NxI32 argmin(NxF32 a[],NxI32 n); -NxF32 sqr(NxF32 a); -NxF32 clampf(NxF32 a) ; -NxF32 Round(NxF32 a,NxF32 precision); -NxF32 Interpolate(const NxF32 &f0,const NxF32 &f1,NxF32 alpha) ; - -template -void Swap(T &a,T &b) -{ - T tmp = a; - a=b; - b=tmp; -} - - - -template -T Max(const T &a,const T &b) -{ - return (a>b)?a:b; -} - -template -T Min(const T &a,const T &b) -{ - return (a=0&&i<2);return ((NxF32*)this)[i];} - const NxF32& operator[](NxI32 i) const {assert(i>=0&&i<2);return ((NxF32*)this)[i];} -}; -inline float2 operator-( const float2& a, const float2& b ){return float2(a.x-b.x,a.y-b.y);} -inline float2 operator+( const float2& a, const float2& b ){return float2(a.x+b.x,a.y+b.y);} - -//--------- 3D --------- - -class float3 : public Memalloc // 3D -{ - public: - NxF32 x,y,z; - float3(){x=0;y=0;z=0;}; - float3(NxF32 _x,NxF32 _y,NxF32 _z){x=_x;y=_y;z=_z;}; - //operator NxF32 *() { return &x;}; - NxF32& operator[](NxI32 i) {assert(i>=0&&i<3);return ((NxF32*)this)[i];} - const NxF32& operator[](NxI32 i) const {assert(i>=0&&i<3);return ((NxF32*)this)[i];} -}; - - -float3& operator+=( float3 &a, const float3& b ); -float3& operator-=( float3 &a ,const float3& b ); -float3& operator*=( float3 &v ,const NxF32 s ); -float3& operator/=( float3 &v, const NxF32 s ); - -NxF32 magnitude( const float3& v ); -float3 normalize( const float3& v ); -float3 safenormalize(const float3 &v); -float3 vabs(const float3 &v); -float3 operator+( const float3& a, const float3& b ); -float3 operator-( const float3& a, const float3& b ); -float3 operator-( const float3& v ); -float3 operator*( const float3& v, const NxF32 s ); -float3 operator*( const NxF32 s, const float3& v ); -float3 operator/( const float3& v, const NxF32 s ); -inline NxI32 operator==( const float3 &a, const float3 &b ) { return (a.x==b.x && a.y==b.y && a.z==b.z); } -inline NxI32 operator!=( const float3 &a, const float3 &b ) { return (a.x!=b.x || a.y!=b.y || a.z!=b.z); } -// due to ambiguity and inconsistent standards ther are no overloaded operators for mult such as va*vb. -NxF32 dot( const float3& a, const float3& b ); -float3 cmul( const float3 &a, const float3 &b); -float3 cross( const float3& a, const float3& b ); -float3 Interpolate(const float3 &v0,const float3 &v1,NxF32 alpha); -float3 Round(const float3& a,NxF32 precision); -float3 VectorMax(const float3 &a, const float3 &b); -float3 VectorMin(const float3 &a, const float3 &b); - - - -class float3x3 : public Memalloc -{ - public: - float3 x,y,z; // the 3 rows of the Matrix - float3x3(){} - float3x3(NxF32 xx,NxF32 xy,NxF32 xz,NxF32 yx,NxF32 yy,NxF32 yz,NxF32 zx,NxF32 zy,NxF32 zz):x(xx,xy,xz),y(yx,yy,yz),z(zx,zy,zz){} - float3x3(float3 _x,float3 _y,float3 _z):x(_x),y(_y),z(_z){} - float3& operator[](NxI32 i) {assert(i>=0&&i<3);return (&x)[i];} - const float3& operator[](NxI32 i) const {assert(i>=0&&i<3);return (&x)[i];} - NxF32& operator()(NxI32 r, NxI32 c) {assert(r>=0&&r<3&&c>=0&&c<3);return ((&x)[r])[c];} - const NxF32& operator()(NxI32 r, NxI32 c) const {assert(r>=0&&r<3&&c>=0&&c<3);return ((&x)[r])[c];} -}; -float3x3 Transpose( const float3x3& m ); -float3 operator*( const float3& v , const float3x3& m ); -float3 operator*( const float3x3& m , const float3& v ); -float3x3 operator*( const float3x3& m , const NxF32& s ); -float3x3 operator*( const float3x3& ma, const float3x3& mb ); -float3x3 operator/( const float3x3& a, const NxF32& s ) ; -float3x3 operator+( const float3x3& a, const float3x3& b ); -float3x3 operator-( const float3x3& a, const float3x3& b ); -float3x3 &operator+=( float3x3& a, const float3x3& b ); -float3x3 &operator-=( float3x3& a, const float3x3& b ); -float3x3 &operator*=( float3x3& a, const NxF32& s ); -NxF32 Determinant(const float3x3& m ); -float3x3 Inverse(const float3x3& a); // its just 3x3 so we simply do that cofactor method - - -//-------- 4D Math -------- - -class float4 : public Memalloc -{ -public: - NxF32 x,y,z,w; - float4(){x=0;y=0;z=0;w=0;}; - float4(NxF32 _x,NxF32 _y,NxF32 _z,NxF32 _w){x=_x;y=_y;z=_z;w=_w;} - float4(const float3 &v,NxF32 _w){x=v.x;y=v.y;z=v.z;w=_w;} - //operator NxF32 *() { return &x;}; - NxF32& operator[](NxI32 i) {assert(i>=0&&i<4);return ((NxF32*)this)[i];} - const NxF32& operator[](NxI32 i) const {assert(i>=0&&i<4);return ((NxF32*)this)[i];} - const float3& xyz() const { return *((float3*)this);} - float3& xyz() { return *((float3*)this);} -}; - - -struct D3DXMATRIX; - -class float4x4 : public Memalloc -{ - public: - float4 x,y,z,w; // the 4 rows - float4x4(){} - float4x4(const float4 &_x, const float4 &_y, const float4 &_z, const float4 &_w):x(_x),y(_y),z(_z),w(_w){} - float4x4(NxF32 m00, NxF32 m01, NxF32 m02, NxF32 m03, - NxF32 m10, NxF32 m11, NxF32 m12, NxF32 m13, - NxF32 m20, NxF32 m21, NxF32 m22, NxF32 m23, - NxF32 m30, NxF32 m31, NxF32 m32, NxF32 m33 ) - :x(m00,m01,m02,m03),y(m10,m11,m12,m13),z(m20,m21,m22,m23),w(m30,m31,m32,m33){} - NxF32& operator()(NxI32 r, NxI32 c) {assert(r>=0&&r<4&&c>=0&&c<4);return ((&x)[r])[c];} - const NxF32& operator()(NxI32 r, NxI32 c) const {assert(r>=0&&r<4&&c>=0&&c<4);return ((&x)[r])[c];} - operator NxF32* () {return &x.x;} - operator const NxF32* () const {return &x.x;} - operator struct D3DXMATRIX* () { return (struct D3DXMATRIX*) this;} - operator const struct D3DXMATRIX* () const { return (struct D3DXMATRIX*) this;} -}; - - -NxI32 operator==( const float4 &a, const float4 &b ); -float4 Homogenize(const float3 &v3,const NxF32 &w=1.0f); // Turns a 3D float3 4D vector4 by appending w -float4 cmul( const float4 &a, const float4 &b); -float4 operator*( const float4 &v, NxF32 s); -float4 operator*( NxF32 s, const float4 &v); -float4 operator+( const float4 &a, const float4 &b); -float4 operator-( const float4 &a, const float4 &b); -float4x4 operator*( const float4x4& a, const float4x4& b ); -float4 operator*( const float4& v, const float4x4& m ); -float4x4 Inverse(const float4x4 &m); -float4x4 MatrixRigidInverse(const float4x4 &m); -float4x4 MatrixTranspose(const float4x4 &m); -float4x4 MatrixPerspectiveFov(NxF32 fovy, NxF32 Aspect, NxF32 zn, NxF32 zf ); -float4x4 MatrixTranslation(const float3 &t); -float4x4 MatrixRotationZ(const NxF32 angle_radians); -float4x4 MatrixLookAt(const float3& eye, const float3& at, const float3& up); -NxI32 operator==( const float4x4 &a, const float4x4 &b ); - - -//-------- Quaternion ------------ - -class Quaternion :public float4 -{ - public: - Quaternion() { x = y = z = 0.0f; w = 1.0f; } - Quaternion( float3 v, NxF32 t ) { v = normalize(v); w = cosf(t/2.0f); v = v*sinf(t/2.0f); x = v.x; y = v.y; z = v.z; } - Quaternion(NxF32 _x, NxF32 _y, NxF32 _z, NxF32 _w){x=_x;y=_y;z=_z;w=_w;} - NxF32 angle() const { return acosf(w)*2.0f; } - float3 axis() const { float3 a(x,y,z); if(fabsf(angle())<0.0000001f) return float3(1,0,0); return a*(1/sinf(angle()/2.0f)); } - float3 xdir() const { return float3( 1-2*(y*y+z*z), 2*(x*y+w*z), 2*(x*z-w*y) ); } - float3 ydir() const { return float3( 2*(x*y-w*z),1-2*(x*x+z*z), 2*(y*z+w*x) ); } - float3 zdir() const { return float3( 2*(x*z+w*y), 2*(y*z-w*x),1-2*(x*x+y*y) ); } - float3x3 getmatrix() const { return float3x3( xdir(), ydir(), zdir() ); } - operator float3x3() { return getmatrix(); } - void Normalize(); -}; - -Quaternion& operator*=(Quaternion& a, NxF32 s ); -Quaternion operator*( const Quaternion& a, NxF32 s ); -Quaternion operator*( const Quaternion& a, const Quaternion& b); -Quaternion operator+( const Quaternion& a, const Quaternion& b ); -Quaternion normalize( Quaternion a ); -NxF32 dot( const Quaternion &a, const Quaternion &b ); -float3 operator*( const Quaternion& q, const float3& v ); -float3 operator*( const float3& v, const Quaternion& q ); -Quaternion slerp( Quaternion a, const Quaternion& b, NxF32 interp ); -Quaternion Interpolate(const Quaternion &q0,const Quaternion &q1,NxF32 alpha); -Quaternion RotationArc(float3 v0, float3 v1 ); // returns quat q where q*v0=v1 -Quaternion Inverse(const Quaternion &q); -float4x4 MatrixFromQuatVec(const Quaternion &q, const float3 &v); - - -//------ Euler Angle ----- - -Quaternion YawPitchRoll( NxF32 yaw, NxF32 pitch, NxF32 roll ); -NxF32 Yaw( const Quaternion& q ); -NxF32 Pitch( const Quaternion& q ); -NxF32 Roll( Quaternion q ); -NxF32 Yaw( const float3& v ); -NxF32 Pitch( const float3& v ); - - -//------- Plane ---------- - -class Plane -{ - public: - float3 normal; - NxF32 dist; // distance below origin - the D from plane equasion Ax+By+Cz+D=0 - Plane(const float3 &n,NxF32 d):normal(n),dist(d){} - Plane():normal(),dist(0){} - void Transform(const float3 &position, const Quaternion &orientation); -}; - -inline Plane PlaneFlip(const Plane &plane){return Plane(-plane.normal,-plane.dist);} -inline NxI32 operator==( const Plane &a, const Plane &b ) { return (a.normal==b.normal && a.dist==b.dist); } -inline NxI32 coplanar( const Plane &a, const Plane &b ) { return (a==b || a==PlaneFlip(b)); } - - -//--------- Utility Functions ------ - -float3 PlaneLineIntersection(const Plane &plane, const float3 &p0, const float3 &p1); -float3 PlaneProject(const Plane &plane, const float3 &point); -float3 LineProject(const float3 &p0, const float3 &p1, const float3 &a); // projects a onto infinite line p0p1 -NxF32 LineProjectTime(const float3 &p0, const float3 &p1, const float3 &a); -float3 ThreePlaneIntersection(const Plane &p0,const Plane &p1, const Plane &p2); -NxI32 PolyHit(const float3 *vert,const NxI32 n,const float3 &v0, const float3 &v1, float3 *impact=NULL, float3 *normal=NULL); -NxI32 BoxInside(const float3 &p,const float3 &bmin, const float3 &bmax) ; -NxI32 BoxIntersect(const float3 &v0, const float3 &v1, const float3 &bmin, const float3 &bmax, float3 *impact); -NxF32 DistanceBetweenLines(const float3 &ustart, const float3 &udir, const float3 &vstart, const float3 &vdir, float3 *upoint=NULL, float3 *vpoint=NULL); -float3 TriNormal(const float3 &v0, const float3 &v1, const float3 &v2); -float3 NormalOf(const float3 *vert, const NxI32 n); -Quaternion VirtualTrackBall(const float3 &cop, const float3 &cor, const float3 &dir0, const float3 &dir1); - - - - -//***************************************************** -// ** VECMATH.CPP -//***************************************************** - - -NxF32 sqr(NxF32 a) {return a*a;} -NxF32 clampf(NxF32 a) {return Min(1.0f,Max(0.0f,a));} - - -NxF32 Round(NxF32 a,NxF32 precision) -{ - return floorf(0.5f+a/precision)*precision; -} - - -NxF32 Interpolate(const NxF32 &f0,const NxF32 &f1,NxF32 alpha) -{ - return f0*(1-alpha) + f1*alpha; -} - - -NxI32 argmin(NxF32 a[],NxI32 n) -{ - NxI32 r=0; - for(NxI32 i=1;i=1.0) { - return a; - } - NxF32 theta = acosf(d); - if(theta==0.0f) { return(a);} - return a*(sinf(theta-interp*theta)/sinf(theta)) + b*(sinf(interp*theta)/sinf(theta)); -} - - -Quaternion Interpolate(const Quaternion &q0,const Quaternion &q1,NxF32 alpha) { - return slerp(q0,q1,alpha); -} - - -Quaternion YawPitchRoll( NxF32 yaw, NxF32 pitch, NxF32 roll ) -{ - roll *= DEG2RAD; - yaw *= DEG2RAD; - pitch *= DEG2RAD; - return Quaternion(float3(0.0f,0.0f,1.0f),yaw)*Quaternion(float3(1.0f,0.0f,0.0f),pitch)*Quaternion(float3(0.0f,1.0f,0.0f),roll); -} - -NxF32 Yaw( const Quaternion& q ) -{ - static float3 v; - v=q.ydir(); - return (v.y==0.0&&v.x==0.0) ? 0.0f: atan2f(-v.x,v.y)*RAD2DEG; -} - -NxF32 Pitch( const Quaternion& q ) -{ - static float3 v; - v=q.ydir(); - return atan2f(v.z,sqrtf(sqr(v.x)+sqr(v.y)))*RAD2DEG; -} - -NxF32 Roll( Quaternion q ) -{ - q = Quaternion(float3(0.0f,0.0f,1.0f),-Yaw(q)*DEG2RAD) *q; - q = Quaternion(float3(1.0f,0.0f,0.0f),-Pitch(q)*DEG2RAD) *q; - return atan2f(-q.xdir().z,q.xdir().x)*RAD2DEG; -} - -NxF32 Yaw( const float3& v ) -{ - return (v.y==0.0&&v.x==0.0) ? 0.0f: atan2f(-v.x,v.y)*RAD2DEG; -} - -NxF32 Pitch( const float3& v ) -{ - return atan2f(v.z,sqrtf(sqr(v.x)+sqr(v.y)))*RAD2DEG; -} - - -//------------- Plane -------------- - - -void Plane::Transform(const float3 &position, const Quaternion &orientation) { - // Transforms the plane to the space defined by the - // given position/orientation. - static float3 newnormal; - static float3 origin; - - newnormal = Inverse(orientation)*normal; - origin = Inverse(orientation)*(-normal*dist - position); - - normal = newnormal; - dist = -dot(newnormal, origin); -} - - - - -//--------- utility functions ------------- - -// RotationArc() -// Given two vectors v0 and v1 this function -// returns quaternion q where q*v0==v1. -// Routine taken from game programming gems. -Quaternion RotationArc(float3 v0,float3 v1){ - static Quaternion q; - v0 = normalize(v0); // Comment these two lines out if you know its not needed. - v1 = normalize(v1); // If vector is already unit length then why do it again? - float3 c = cross(v0,v1); - NxF32 d = dot(v0,v1); - if(d<=-1.0f) { return Quaternion(1,0,0,0);} // 180 about x axis - NxF32 s = sqrtf((1+d)*2); - q.x = c.x / s; - q.y = c.y / s; - q.z = c.z / s; - q.w = s /2.0f; - return q; -} - - -float4x4 MatrixFromQuatVec(const Quaternion &q, const float3 &v) -{ - // builds a 4x4 transformation matrix based on orientation q and translation v - NxF32 qx2 = q.x*q.x; - NxF32 qy2 = q.y*q.y; - NxF32 qz2 = q.z*q.z; - - NxF32 qxqy = q.x*q.y; - NxF32 qxqz = q.x*q.z; - NxF32 qxqw = q.x*q.w; - NxF32 qyqz = q.y*q.z; - NxF32 qyqw = q.y*q.w; - NxF32 qzqw = q.z*q.w; - - return float4x4( - 1-2*(qy2+qz2), - 2*(qxqy+qzqw), - 2*(qxqz-qyqw), - 0 , - 2*(qxqy-qzqw), - 1-2*(qx2+qz2), - 2*(qyqz+qxqw), - 0 , - 2*(qxqz+qyqw), - 2*(qyqz-qxqw), - 1-2*(qx2+qy2), - 0 , - v.x , - v.y , - v.z , - 1.0f ); -} - - -float3 PlaneLineIntersection(const Plane &plane, const float3 &p0, const float3 &p1) -{ - // returns the point where the line p0-p1 intersects the plane n&d - static float3 dif; - dif = p1-p0; - NxF32 dn= dot(plane.normal,dif); - NxF32 t = -(plane.dist+dot(plane.normal,p0) )/dn; - return p0 + (dif*t); -} - -float3 PlaneProject(const Plane &plane, const float3 &point) -{ - return point - plane.normal * (dot(point,plane.normal)+plane.dist); -} - -float3 LineProject(const float3 &p0, const float3 &p1, const float3 &a) -{ - float3 w; - w = p1-p0; - NxF32 t= dot(w,(a-p0)) / (sqr(w.x)+sqr(w.y)+sqr(w.z)); - return p0+ w*t; -} - - -NxF32 LineProjectTime(const float3 &p0, const float3 &p1, const float3 &a) -{ - float3 w; - w = p1-p0; - NxF32 t= dot(w,(a-p0)) / (sqr(w.x)+sqr(w.y)+sqr(w.z)); - return t; -} - - - -float3 TriNormal(const float3 &v0, const float3 &v1, const float3 &v2) -{ - // return the normal of the triangle - // inscribed by v0, v1, and v2 - float3 cp=cross(v1-v0,v2-v1); - NxF32 m=magnitude(cp); - if(m==0) return float3(1,0,0); - return cp*(1.0f/m); -} - - - -NxI32 BoxInside(const float3 &p, const float3 &bmin, const float3 &bmax) -{ - return (p.x >= bmin.x && p.x <=bmax.x && - p.y >= bmin.y && p.y <=bmax.y && - p.z >= bmin.z && p.z <=bmax.z ); -} - - -NxI32 BoxIntersect(const float3 &v0, const float3 &v1, const float3 &bmin, const float3 &bmax,float3 *impact) -{ - if(BoxInside(v0,bmin,bmax)) - { - *impact=v0; - return 1; - } - if(v0.x<=bmin.x && v1.x>=bmin.x) - { - NxF32 a = (bmin.x-v0.x)/(v1.x-v0.x); - //v.x = bmin.x; - NxF32 vy = (1-a) *v0.y + a*v1.y; - NxF32 vz = (1-a) *v0.z + a*v1.z; - if(vy>=bmin.y && vy<=bmax.y && vz>=bmin.z && vz<=bmax.z) - { - impact->x = bmin.x; - impact->y = vy; - impact->z = vz; - return 1; - } - } - else if(v0.x >= bmax.x && v1.x <= bmax.x) - { - NxF32 a = (bmax.x-v0.x)/(v1.x-v0.x); - //v.x = bmax.x; - NxF32 vy = (1-a) *v0.y + a*v1.y; - NxF32 vz = (1-a) *v0.z + a*v1.z; - if(vy>=bmin.y && vy<=bmax.y && vz>=bmin.z && vz<=bmax.z) - { - impact->x = bmax.x; - impact->y = vy; - impact->z = vz; - return 1; - } - } - if(v0.y<=bmin.y && v1.y>=bmin.y) - { - NxF32 a = (bmin.y-v0.y)/(v1.y-v0.y); - NxF32 vx = (1-a) *v0.x + a*v1.x; - //v.y = bmin.y; - NxF32 vz = (1-a) *v0.z + a*v1.z; - if(vx>=bmin.x && vx<=bmax.x && vz>=bmin.z && vz<=bmax.z) - { - impact->x = vx; - impact->y = bmin.y; - impact->z = vz; - return 1; - } - } - else if(v0.y >= bmax.y && v1.y <= bmax.y) - { - NxF32 a = (bmax.y-v0.y)/(v1.y-v0.y); - NxF32 vx = (1-a) *v0.x + a*v1.x; - // vy = bmax.y; - NxF32 vz = (1-a) *v0.z + a*v1.z; - if(vx>=bmin.x && vx<=bmax.x && vz>=bmin.z && vz<=bmax.z) - { - impact->x = vx; - impact->y = bmax.y; - impact->z = vz; - return 1; - } - } - if(v0.z<=bmin.z && v1.z>=bmin.z) - { - NxF32 a = (bmin.z-v0.z)/(v1.z-v0.z); - NxF32 vx = (1-a) *v0.x + a*v1.x; - NxF32 vy = (1-a) *v0.y + a*v1.y; - // v.z = bmin.z; - if(vy>=bmin.y && vy<=bmax.y && vx>=bmin.x && vx<=bmax.x) - { - impact->x = vx; - impact->y = vy; - impact->z = bmin.z; - return 1; - } - } - else if(v0.z >= bmax.z && v1.z <= bmax.z) - { - NxF32 a = (bmax.z-v0.z)/(v1.z-v0.z); - NxF32 vx = (1-a) *v0.x + a*v1.x; - NxF32 vy = (1-a) *v0.y + a*v1.y; - // v.z = bmax.z; - if(vy>=bmin.y && vy<=bmax.y && vx>=bmin.x && vx<=bmax.x) - { - impact->x = vx; - impact->y = vy; - impact->z = bmax.z; - return 1; - } - } - return 0; -} - - -NxF32 DistanceBetweenLines(const float3 &ustart, const float3 &udir, const float3 &vstart, const float3 &vdir, float3 *upoint, float3 *vpoint) -{ - static float3 cp; - cp = normalize(cross(udir,vdir)); - - NxF32 distu = -dot(cp,ustart); - NxF32 distv = -dot(cp,vstart); - NxF32 dist = (NxF32)fabs(distu-distv); - if(upoint) - { - Plane plane; - plane.normal = normalize(cross(vdir,cp)); - plane.dist = -dot(plane.normal,vstart); - *upoint = PlaneLineIntersection(plane,ustart,ustart+udir); - } - if(vpoint) - { - Plane plane; - plane.normal = normalize(cross(udir,cp)); - plane.dist = -dot(plane.normal,ustart); - *vpoint = PlaneLineIntersection(plane,vstart,vstart+vdir); - } - return dist; -} - - -Quaternion VirtualTrackBall(const float3 &cop, const float3 &cor, const float3 &dir1, const float3 &dir2) -{ - // routine taken from game programming gems. - // Implement track ball functionality to spin stuf on the screen - // cop center of projection - // cor center of rotation - // dir1 old mouse direction - // dir2 new mouse direction - // pretend there is a sphere around cor. Then find the points - // where dir1 and dir2 intersect that sphere. Find the - // rotation that takes the first point to the second. - NxF32 m; - // compute plane - float3 nrml = cor - cop; - NxF32 fudgefactor = 1.0f/(magnitude(nrml) * 0.25f); // since trackball proportional to distance from cop - nrml = normalize(nrml); - NxF32 dist = -dot(nrml,cor); - float3 u= PlaneLineIntersection(Plane(nrml,dist),cop,cop+dir1); - u=u-cor; - u=u*fudgefactor; - m= magnitude(u); - if(m>1) - { - u/=m; - } - else - { - u=u - (nrml * sqrtf(1-m*m)); - } - float3 v= PlaneLineIntersection(Plane(nrml,dist),cop,cop+dir2); - v=v-cor; - v=v*fudgefactor; - m= magnitude(v); - if(m>1) - { - v/=m; - } - else - { - v=v - (nrml * sqrtf(1-m*m)); - } - return RotationArc(u,v); -} - - -NxI32 countpolyhit=0; -NxI32 PolyHit(const float3 *vert, const NxI32 n, const float3 &v0, const float3 &v1, float3 *impact, float3 *normal) -{ - countpolyhit++; - NxI32 i; - float3 nrml(0,0,0); - for(i=0;i0) - { - return 0; - } - - static float3 the_point; - // By using the cached plane distances d0 and d1 - // we can optimize the following: - // the_point = planelineintersection(nrml,dist,v0,v1); - NxF32 a = d0/(d0-d1); - the_point = v0*(1-a) + v1*a; - - - NxI32 inside=1; - for(NxI32 j=0;inside && j= 0.0); - } - if(inside) - { - if(normal){*normal=nrml;} - if(impact){*impact=the_point;} - } - return inside; -} - -//**************************************************** -// HULL.H source code goes here -//**************************************************** -class PHullResult -{ -public: - - PHullResult(void) - { - mVcount = 0; - mIndexCount = 0; - mFaceCount = 0; - mVertices = 0; - mIndices = 0; - } - - NxU32 mVcount; - NxU32 mIndexCount; - NxU32 mFaceCount; - NxF32 *mVertices; - NxU32 *mIndices; -}; - -bool ComputeHull(NxU32 vcount,const NxF32 *vertices,PHullResult &result,NxU32 maxverts,NxF32 inflate); -void ReleaseHull(PHullResult &result); - -//***************************************************** -// HULL.cpp source code goes here -//***************************************************** - - -#define REAL3 float3 -#define REAL NxF32 - -#define COPLANAR (0) -#define UNDER (1) -#define OVER (2) -#define SPLIT (OVER|UNDER) -#define PAPERWIDTH (0.001f) -#define VOLUME_EPSILON (1e-20f) - -NxF32 planetestepsilon = PAPERWIDTH; - -class ConvexH : public Memalloc -{ - public: - class HalfEdge - { - public: - short ea; // the other half of the edge (index into edges list) - NxU8 v; // the vertex at the start of this edge (index into vertices list) - NxU8 p; // the facet on which this edge lies (index into facets list) - HalfEdge(){} - HalfEdge(short _ea,NxU8 _v, NxU8 _p):ea(_ea),v(_v),p(_p){} - }; - Array vertices; - Array edges; - Array facets; - ConvexH(NxI32 vertices_size,NxI32 edges_size,NxI32 facets_size); -}; - -typedef ConvexH::HalfEdge HalfEdge; - -ConvexH::ConvexH(NxI32 vertices_size,NxI32 edges_size,NxI32 facets_size) - :vertices(vertices_size) - ,edges(edges_size) - ,facets(facets_size) -{ - vertices.count=vertices_size; - edges.count = edges_size; - facets.count = facets_size; -} - -ConvexH *ConvexHDup(ConvexH *src) -{ - ConvexH *dst = MEMALLOC_NEW(ConvexH)(src->vertices.count,src->edges.count,src->facets.count); - - memcpy(dst->vertices.element,src->vertices.element,sizeof(float3)*src->vertices.count); - memcpy(dst->edges.element,src->edges.element,sizeof(HalfEdge)*src->edges.count); - memcpy(dst->facets.element,src->facets.element,sizeof(Plane)*src->facets.count); - return dst; -} - - -NxI32 PlaneTest(const Plane &p, const REAL3 &v) { - REAL a = dot(v,p.normal)+p.dist; - NxI32 flag = (a>planetestepsilon)?OVER:((a<-planetestepsilon)?UNDER:COPLANAR); - return flag; -} - -NxI32 SplitTest(ConvexH &convex,const Plane &plane) { - NxI32 flag=0; - for(NxI32 i=0;i= convex.edges.count || convex.edges[inext].p != convex.edges[i].p) { - inext = estart; - } - assert(convex.edges[inext].p == convex.edges[i].p); - NxI32 nb = convex.edges[i].ea; - assert(nb!=255); - if(nb==255 || nb==-1) return 0; - assert(nb!=-1); - assert(i== convex.edges[nb].ea); - } - for(i=0;i= convex.edges.count || convex.edges[i1].p != convex.edges[i].p) { - i1 = estart; - } - NxI32 i2 = i1+1; - if(i2>= convex.edges.count || convex.edges[i2].p != convex.edges[i].p) { - i2 = estart; - } - if(i==i2) continue; // i sliced tangent to an edge and created 2 meaningless edges - REAL3 localnormal = TriNormal(convex.vertices[convex.edges[i ].v], - convex.vertices[convex.edges[i1].v], - convex.vertices[convex.edges[i2].v]); - //assert(dot(localnormal,convex.facets[convex.edges[i].p].normal)>0);//Commented out on Stan Melax' advice - if(dot(localnormal,convex.facets[convex.edges[i].p].normal)<=0)return 0; - } - return 1; -} - -ConvexH *ConvexHCrop(ConvexH &convex,const Plane &slice) -{ - NxI32 i; - NxI32 vertcountunder=0; - NxI32 vertcountover =0; - static Array vertscoplanar; // existing vertex members of convex that are coplanar - vertscoplanar.count=0; - static Array edgesplit; // existing edges that members of convex that cross the splitplane - edgesplit.count=0; - - assert(convex.edges.count<480); - - EdgeFlag edgeflag[512]; - VertFlag vertflag[256]; - PlaneFlag planeflag[128]; - HalfEdge tmpunderedges[512]; - Plane tmpunderplanes[128]; - Coplanar coplanaredges[512]; - NxI32 coplanaredges_num=0; - - Array createdverts; - // do the side-of-plane tests - for(i=0;i= convex.edges.count || convex.edges[e1].p!=currentplane) { - enextface = e1; - e1=estart; - } - HalfEdge &edge0 = convex.edges[e0]; - HalfEdge &edge1 = convex.edges[e1]; - HalfEdge &edgea = convex.edges[edge0.ea]; - - - planeside |= vertflag[edge0.v].planetest; - //if((vertflag[edge0.v].planetest & vertflag[edge1.v].planetest) == COPLANAR) { - // assert(ecop==-1); - // ecop=e; - //} - - - if(vertflag[edge0.v].planetest == OVER && vertflag[edge1.v].planetest == OVER){ - // both endpoints over plane - edgeflag[e0].undermap = -1; - } - else if((vertflag[edge0.v].planetest | vertflag[edge1.v].planetest) == UNDER) { - // at least one endpoint under, the other coplanar or under - - edgeflag[e0].undermap = (short)under_edge_count; - tmpunderedges[under_edge_count].v = (NxU8)vertflag[edge0.v].undermap; - tmpunderedges[under_edge_count].p = (NxU8)underplanescount; - if(edge0.ea < e0) { - // connect the neighbors - assert(edgeflag[edge0.ea].undermap !=-1); - tmpunderedges[under_edge_count].ea = edgeflag[edge0.ea].undermap; - tmpunderedges[edgeflag[edge0.ea].undermap].ea = (short)under_edge_count; - } - under_edge_count++; - } - else if((vertflag[edge0.v].planetest | vertflag[edge1.v].planetest) == COPLANAR) { - // both endpoints coplanar - // must check a 3rd point to see if UNDER - NxI32 e2 = e1+1; - if(e2>=convex.edges.count || convex.edges[e2].p!=currentplane) { - e2 = estart; - } - assert(convex.edges[e2].p==currentplane); - HalfEdge &edge2 = convex.edges[e2]; - if(vertflag[edge2.v].planetest==UNDER) { - - edgeflag[e0].undermap = (short)under_edge_count; - tmpunderedges[under_edge_count].v = (NxU8)vertflag[edge0.v].undermap; - tmpunderedges[under_edge_count].p = (NxU8)underplanescount; - tmpunderedges[under_edge_count].ea = -1; - // make sure this edge is added to the "coplanar" list - coplanaredge = under_edge_count; - vout = vertflag[edge0.v].undermap; - vin = vertflag[edge1.v].undermap; - under_edge_count++; - } - else { - edgeflag[e0].undermap = -1; - } - } - else if(vertflag[edge0.v].planetest == UNDER && vertflag[edge1.v].planetest == OVER) { - // first is under 2nd is over - - edgeflag[e0].undermap = (short) under_edge_count; - tmpunderedges[under_edge_count].v = (NxU8)vertflag[edge0.v].undermap; - tmpunderedges[under_edge_count].p = (NxU8)underplanescount; - if(edge0.ea < e0) { - assert(edgeflag[edge0.ea].undermap !=-1); - // connect the neighbors - tmpunderedges[under_edge_count].ea = edgeflag[edge0.ea].undermap; - tmpunderedges[edgeflag[edge0.ea].undermap].ea = (short)under_edge_count; - vout = tmpunderedges[edgeflag[edge0.ea].undermap].v; - } - else { - Plane &p0 = convex.facets[edge0.p]; - Plane &pa = convex.facets[edgea.p]; - createdverts.Add(ThreePlaneIntersection(p0,pa,slice)); - //createdverts.Add(PlaneProject(slice,PlaneLineIntersection(slice,convex.vertices[edge0.v],convex.vertices[edgea.v]))); - //createdverts.Add(PlaneLineIntersection(slice,convex.vertices[edge0.v],convex.vertices[edgea.v])); - vout = vertcountunder++; - } - under_edge_count++; - /// hmmm something to think about: i might be able to output this edge regarless of - // wheter or not we know v-in yet. ok i;ll try this now: - tmpunderedges[under_edge_count].v = (NxU8)vout; - tmpunderedges[under_edge_count].p = (NxU8)underplanescount; - tmpunderedges[under_edge_count].ea = -1; - coplanaredge = under_edge_count; - under_edge_count++; - - if(vin!=-1) { - // we previously processed an edge where we came under - // now we know about vout as well - - // ADD THIS EDGE TO THE LIST OF EDGES THAT NEED NEIGHBOR ON PARTITION PLANE!! - } - - } - else if(vertflag[edge0.v].planetest == COPLANAR && vertflag[edge1.v].planetest == OVER) { - // first is coplanar 2nd is over - - edgeflag[e0].undermap = -1; - vout = vertflag[edge0.v].undermap; - // I hate this but i have to make sure part of this face is UNDER before ouputting this vert - NxI32 k=estart; - assert(edge0.p == currentplane); - while(!(planeside&UNDER) && kedge0.ea) { - assert(edgeflag[edge0.ea].undermap !=-1); - // connect the neighbors - tmpunderedges[under_edge_count].ea = edgeflag[edge0.ea].undermap; - tmpunderedges[edgeflag[edge0.ea].undermap].ea = (short)under_edge_count; - } - assert(edgeflag[e0].undermap == under_edge_count); - under_edge_count++; - } - else if(vertflag[edge0.v].planetest == OVER && vertflag[edge1.v].planetest == COPLANAR) { - // first is over next is coplanar - - edgeflag[e0].undermap = -1; - vin = vertflag[edge1.v].undermap; - if (vin==-1) return NULL; - if(vout!=-1) { - // we previously processed an edge where we came under - // now we know both endpoints - // ADD THIS EDGE TO THE LIST OF EDGES THAT NEED NEIGHBOR ON PARTITION PLANE!! - } - - } - else { - assert(0); - } - - - e0=e1; - e1++; // do the modulo at the beginning of the loop - - } while(e0!=estart) ; - e0 = enextface; - if(planeside&UNDER) { - planeflag[currentplane].undermap = (NxU8)underplanescount; - tmpunderplanes[underplanescount] = convex.facets[currentplane]; - underplanescount++; - } - else { - planeflag[currentplane].undermap = 0; - } - if(vout>=0 && (planeside&UNDER)) { - assert(vin>=0); - assert(coplanaredge>=0); - assert(coplanaredge!=511); - coplanaredges[coplanaredges_num].ea = (short)coplanaredge; - coplanaredges[coplanaredges_num].v0 = (NxU8)vin; - coplanaredges[coplanaredges_num].v1 = (NxU8)vout; - coplanaredges_num++; - } - } - - // add the new plane to the mix: - if(coplanaredges_num>0) { - tmpunderplanes[underplanescount++]=slice; - } - for(i=0;i=coplanaredges_num) - { - // assert(jvertices.count;j++) - { - dmax = Max(dmax,dot(convex->vertices[j],planes[i].normal)+planes[i].dist); - dmin = Min(dmin,dot(convex->vertices[j],planes[i].normal)+planes[i].dist); - } - NxF32 dr = dmax-dmin; - if(drfacets.count;j++) - { - if(planes[i]==convex->facets[j]) - { - d=0;continue; - } - if(dot(planes[i].normal,convex->facets[j].normal)>maxdot_minang) - { - for(NxI32 k=0;kedges.count;k++) - { - if(convex->edges[k].p!=j) continue; - if(dot(convex->vertices[convex->edges[k].v],planes[i].normal)+planes[i].dist<0) - { - d=0; // so this plane wont get selected. - break; - } - } - } - } - if(d>md) - { - p=i; - md=d; - } - } - return (md>epsilon)?p:-1; -} - - - -template -inline NxI32 maxdir(const T *p,NxI32 count,const T &dir) -{ - assert(count); - NxI32 m=0; - for(NxI32 i=1;idot(p[m],dir)) m=i; - } - return m; -} - - -template -NxI32 maxdirfiltered(const T *p,NxI32 count,const T &dir,Array &allow) -{ - assert(count); - NxI32 m=-1; - for(NxI32 i=0;idot(p[m],dir)) m=i; - } - assert(m!=-1); - return m; -} - -float3 orth(const float3 &v) -{ - float3 a=cross(v,float3(0,0,1)); - float3 b=cross(v,float3(0,1,0)); - return normalize((magnitude(a)>magnitude(b))?a:b); -} - - -template -NxI32 maxdirsterid(const T *p,NxI32 count,const T &dir,Array &allow) -{ - NxI32 m=-1; - while(m==-1) - { - m = maxdirfiltered(p,count,dir,allow); - if(allow[m]==3) return m; - T u = orth(dir); - T v = cross(u,dir); - NxI32 ma=-1; - for(NxF32 x = 0.0f ; x<= 360.0f ; x+= 45.0f) - { - NxF32 s = sinf(DEG2RAD*(x)); - NxF32 c = cosf(DEG2RAD*(x)); - NxI32 mb = maxdirfiltered(p,count,dir+(u*s+v*c)*0.025f,allow); - if(ma==m && mb==m) - { - allow[m]=3; - return m; - } - if(ma!=-1 && ma!=mb) // Yuck - this is really ugly - { - NxI32 mc = ma; - for(NxF32 xx = x-40.0f ; xx <= x ; xx+= 5.0f) - { - NxF32 s = sinf(DEG2RAD*(xx)); - NxF32 c = cosf(DEG2RAD*(xx)); - NxI32 md = maxdirfiltered(p,count,dir+(u*s+v*c)*0.025f,allow); - if(mc==m && md==m) - { - allow[m]=3; - return m; - } - mc=md; - } - } - ma=mb; - } - allow[m]=0; - m=-1; - } - assert(0); - return m; -} - - - - -NxI32 operator ==(const int3 &a,const int3 &b) -{ - for(NxI32 i=0;i<3;i++) - { - if(a[i]!=b[i]) return 0; - } - return 1; -} - -int3 roll3(int3 a) -{ - NxI32 tmp=a[0]; - a[0]=a[1]; - a[1]=a[2]; - a[2]=tmp; - return a; -} -NxI32 isa(const int3 &a,const int3 &b) -{ - return ( a==b || roll3(a)==b || a==roll3(b) ); -} -NxI32 b2b(const int3 &a,const int3 &b) -{ - return isa(a,int3(b[2],b[1],b[0])); -} -NxI32 above(float3* vertices,const int3& t, const float3 &p, NxF32 epsilon) -{ - float3 n=TriNormal(vertices[t[0]],vertices[t[1]],vertices[t[2]]); - return (dot(n,p-vertices[t[0]]) > epsilon); // EPSILON??? -} -NxI32 hasedge(const int3 &t, NxI32 a,NxI32 b) -{ - for(NxI32 i=0;i<3;i++) - { - NxI32 i1= (i+1)%3; - if(t[i]==a && t[i1]==b) return 1; - } - return 0; -} -NxI32 hasvert(const int3 &t, NxI32 v) -{ - return (t[0]==v || t[1]==v || t[2]==v) ; -} -NxI32 shareedge(const int3 &a,const int3 &b) -{ - NxI32 i; - for(i=0;i<3;i++) - { - NxI32 i1= (i+1)%3; - if(hasedge(a,b[i1],b[i])) return 1; - } - return 0; -} - -class Tri; - -static Array tris; // djs: For heaven's sake!!!! - -class Tri : public int3 -{ -public: - int3 n; - NxI32 id; - NxI32 vmax; - NxF32 rise; - Tri(NxI32 a,NxI32 b,NxI32 c):int3(a,b,c),n(-1,-1,-1) - { - id = tris.count; - tris.Add(this); - vmax=-1; - rise = 0.0f; - } - ~Tri() - { - assert(tris[id]==this); - tris[id]=NULL; - } - NxI32 &neib(NxI32 a,NxI32 b); -}; - - -NxI32 &Tri::neib(NxI32 a,NxI32 b) -{ - static NxI32 er=-1; - NxI32 i; - for(i=0;i<3;i++) - { - NxI32 i1=(i+1)%3; - NxI32 i2=(i+2)%3; - if((*this)[i]==a && (*this)[i1]==b) return n[i2]; - if((*this)[i]==b && (*this)[i1]==a) return n[i2]; - } - assert(0); - return er; -} -void b2bfix(Tri* s,Tri*t) -{ - NxI32 i; - for(i=0;i<3;i++) - { - NxI32 i1=(i+1)%3; - NxI32 i2=(i+2)%3; - NxI32 a = (*s)[i1]; - NxI32 b = (*s)[i2]; - assert(tris[s->neib(a,b)]->neib(b,a) == s->id); - assert(tris[t->neib(a,b)]->neib(b,a) == t->id); - tris[s->neib(a,b)]->neib(b,a) = t->neib(b,a); - tris[t->neib(b,a)]->neib(a,b) = s->neib(a,b); - } -} - -void removeb2b(Tri* s,Tri*t) -{ - b2bfix(s,t); - delete s; - delete t; -} - -void extrude(Tri *t0,NxI32 v) -{ - int3 t= *t0; - NxI32 n = tris.count; - Tri* ta = MEMALLOC_NEW(Tri)(v,t[1],t[2]); - ta->n = int3(t0->n[0],n+1,n+2); - tris[t0->n[0]]->neib(t[1],t[2]) = n+0; - Tri* tb = MEMALLOC_NEW(Tri)(v,t[2],t[0]); - tb->n = int3(t0->n[1],n+2,n+0); - tris[t0->n[1]]->neib(t[2],t[0]) = n+1; - Tri* tc = MEMALLOC_NEW(Tri)(v,t[0],t[1]); - tc->n = int3(t0->n[2],n+0,n+1); - tris[t0->n[2]]->neib(t[0],t[1]) = n+2; - if(hasvert(*tris[ta->n[0]],v)) removeb2b(ta,tris[ta->n[0]]); - if(hasvert(*tris[tb->n[0]],v)) removeb2b(tb,tris[tb->n[0]]); - if(hasvert(*tris[tc->n[0]],v)) removeb2b(tc,tris[tc->n[0]]); - delete t0; - -} - -Tri *extrudable(NxF32 epsilon) -{ - NxI32 i; - Tri *t=NULL; - for(i=0;iriserise)) - { - t = tris[i]; - } - } - return (t->rise >epsilon)?t:NULL ; -} - -class int4 -{ -public: - NxI32 x,y,z,w; - int4(){}; - int4(NxI32 _x,NxI32 _y, NxI32 _z,NxI32 _w){x=_x;y=_y;z=_z;w=_w;} - const NxI32& operator[](NxI32 i) const {return (&x)[i];} - NxI32& operator[](NxI32 i) {return (&x)[i];} -}; - - - -bool hasVolume(float3 *verts, NxI32 p0, NxI32 p1, NxI32 p2, NxI32 p3) -{ - float3 result3 = cross(verts[p1]-verts[p0], verts[p2]-verts[p0]); - if (magnitude(result3) < VOLUME_EPSILON && magnitude(result3) > -VOLUME_EPSILON) // Almost collinear or otherwise very close to each other - return false; - NxF32 result = dot(normalize(result3), verts[p3]-verts[p0]); - return (result > VOLUME_EPSILON || result < -VOLUME_EPSILON); // Returns true iff volume is significantly non-zero -} - -int4 FindSimplex(float3 *verts,NxI32 verts_count,Array &allow) -{ - float3 basis[3]; - basis[0] = float3( 0.01f, 0.02f, 1.0f ); - NxI32 p0 = maxdirsterid(verts,verts_count, basis[0],allow); - NxI32 p1 = maxdirsterid(verts,verts_count,-basis[0],allow); - basis[0] = verts[p0]-verts[p1]; - if(p0==p1 || basis[0]==float3(0,0,0)) - return int4(-1,-1,-1,-1); - basis[1] = cross(float3( 1, 0.02f, 0),basis[0]); - basis[2] = cross(float3(-0.02f, 1, 0),basis[0]); - basis[1] = normalize( (magnitude(basis[1])>magnitude(basis[2])) ? basis[1]:basis[2]); - NxI32 p2 = maxdirsterid(verts,verts_count,basis[1],allow); - if(p2 == p0 || p2 == p1) - { - p2 = maxdirsterid(verts,verts_count,-basis[1],allow); - } - if(p2 == p0 || p2 == p1) - return int4(-1,-1,-1,-1); - basis[1] = verts[p2] - verts[p0]; - basis[2] = normalize(cross(basis[1],basis[0])); - NxI32 p3 = maxdirsterid(verts,verts_count,basis[2],allow); - if(p3==p0||p3==p1||p3==p2||!hasVolume(verts, p0, p1, p2, p3)) p3 = maxdirsterid(verts,verts_count,-basis[2],allow); - if(p3==p0||p3==p1||p3==p2) - return int4(-1,-1,-1,-1); - assert(!(p0==p1||p0==p2||p0==p3||p1==p2||p1==p3||p2==p3)); - if(dot(verts[p3]-verts[p0],cross(verts[p1]-verts[p0],verts[p2]-verts[p0])) <0) {Swap(p2,p3);} - return int4(p0,p1,p2,p3); -} -#pragma warning(push) -#pragma warning(disable:4706) -NxI32 calchullgen(float3 *verts,NxI32 verts_count, NxI32 vlimit) -{ - if(verts_count <4) return 0; - if(vlimit==0) vlimit=1000000000; - NxI32 j; - float3 bmin(*verts),bmax(*verts); - Array isextreme(verts_count); - Array allow(verts_count); - for(j=0;jn=int3(2,3,1); - Tri *t1 = MEMALLOC_NEW(Tri)(p[3],p[2],p[0]); t1->n=int3(3,2,0); - Tri *t2 = MEMALLOC_NEW(Tri)(p[0],p[1],p[3]); t2->n=int3(0,1,3); - Tri *t3 = MEMALLOC_NEW(Tri)(p[1],p[0],p[2]); t3->n=int3(1,0,2); - isextreme[p[0]]=isextreme[p[1]]=isextreme[p[2]]=isextreme[p[3]]=1; - - for(j=0;jvmax<0); - float3 n=TriNormal(verts[(*t)[0]],verts[(*t)[1]],verts[(*t)[2]]); - t->vmax = maxdirsterid(verts,verts_count,n,allow); - t->rise = dot(n,verts[t->vmax]-verts[(*t)[0]]); - } - Tri *te; - vlimit-=4; - while(vlimit >0 && (te=extrudable(epsilon))) - { - int3 ti=*te; - NxI32 v=te->vmax; - assert(!isextreme[v]); // wtf we've already done this vertex - isextreme[v]=1; - //if(v==p0 || v==p1 || v==p2 || v==p3) continue; // done these already - j=tris.count; - while(j--) { - if(!tris[j]) continue; - int3 t=*tris[j]; - if(above(verts,t,verts[v],0.01f*epsilon)) - { - extrude(tris[j],v); - } - } - // now check for those degenerate cases where we have a flipped triangle or a really skinny triangle - j=tris.count; - while(j--) - { - if(!tris[j]) continue; - if(!hasvert(*tris[j],v)) break; - int3 nt=*tris[j]; - if(above(verts,nt,center,0.01f*epsilon) || magnitude(cross(verts[nt[1]]-verts[nt[0]],verts[nt[2]]-verts[nt[1]]))< epsilon*epsilon*0.1f ) - { - Tri *nb = tris[tris[j]->n[0]]; - assert(nb);assert(!hasvert(*nb,v));assert(nb->idvmax>=0) break; - float3 n=TriNormal(verts[(*t)[0]],verts[(*t)[1]],verts[(*t)[2]]); - t->vmax = maxdirsterid(verts,verts_count,n,allow); - if(isextreme[t->vmax]) - { - t->vmax=-1; // already done that vertex - algorithm needs to be able to terminate. - } - else - { - t->rise = dot(n,verts[t->vmax]-verts[(*t)[0]]); - } - } - vlimit --; - } - return 1; -} -#pragma warning(pop) - -NxI32 calchull(float3 *verts,NxI32 verts_count, NxI32 *&tris_out, NxI32 &tris_count,NxI32 vlimit) -{ - NxI32 rc=calchullgen(verts,verts_count, vlimit) ; - if(!rc) return 0; - Array ts; - for(NxI32 i=0;i &planes,NxF32 bevangle) -{ - NxI32 i,j; - Array bplanes; - planes.count=0; - NxI32 rc = calchullgen(verts,verts_count,vlimit); - if(!rc) return 0; - extern NxF32 minadjangle; // default is 3.0f; // in degrees - result wont have two adjacent facets within this angle of each other. - NxF32 maxdot_minang = cosf(DEG2RAD*minadjangle); - for(i=0;in[j]id) continue; - Tri *s = tris[t->n[j]]; - REAL3 snormal = TriNormal(verts[(*s)[0]],verts[(*s)[1]],verts[(*s)[2]]); - if(dot(snormal,p.normal)>=cos(bevangle*DEG2RAD)) continue; - REAL3 e = verts[(*t)[(j+2)%3]] - verts[(*t)[(j+1)%3]]; - REAL3 n = (e!=REAL3(0,0,0))? cross(snormal,e)+cross(e,p.normal) : snormal+p.normal; - assert(n!=REAL3(0,0,0)); - if(n==REAL3(0,0,0)) return 0; - n=normalize(n); - bplanes.Add(Plane(n,-dot(n,verts[maxdir(verts,verts_count,n)]))); - } - } - for(i=0;imaxdot_minang) - { - // somebody has to die, keep the biggest triangle - if( area2(verts[(*ti)[0]],verts[(*ti)[1]],verts[(*ti)[2]]) < area2(verts[(*tj)[0]],verts[(*tj)[1]],verts[(*tj)[2]])) - { - delete tris[i]; - } - else - { - delete tris[j]; - } - } - } - for(i=0;imaxdot_minang) break; - } - if(j==planes.count) - { - planes.Add(bplanes[i]); - } - } - for(i=0;ivertices[0] = REAL3(0,0,0); - convex->vertices[1] = REAL3(0,0,1); - convex->vertices[2] = REAL3(0,1,0); - convex->vertices[3] = REAL3(0,1,1); - convex->vertices[4] = REAL3(1,0,0); - convex->vertices[5] = REAL3(1,0,1); - convex->vertices[6] = REAL3(1,1,0); - convex->vertices[7] = REAL3(1,1,1); - - convex->facets[0] = Plane(REAL3(-1,0,0),0); - convex->facets[1] = Plane(REAL3(1,0,0),-1); - convex->facets[2] = Plane(REAL3(0,-1,0),0); - convex->facets[3] = Plane(REAL3(0,1,0),-1); - convex->facets[4] = Plane(REAL3(0,0,-1),0); - convex->facets[5] = Plane(REAL3(0,0,1),-1); - - convex->edges[0 ] = HalfEdge(11,0,0); - convex->edges[1 ] = HalfEdge(23,1,0); - convex->edges[2 ] = HalfEdge(15,3,0); - convex->edges[3 ] = HalfEdge(16,2,0); - - convex->edges[4 ] = HalfEdge(13,6,1); - convex->edges[5 ] = HalfEdge(21,7,1); - convex->edges[6 ] = HalfEdge( 9,5,1); - convex->edges[7 ] = HalfEdge(18,4,1); - - convex->edges[8 ] = HalfEdge(19,0,2); - convex->edges[9 ] = HalfEdge( 6,4,2); - convex->edges[10] = HalfEdge(20,5,2); - convex->edges[11] = HalfEdge( 0,1,2); - - convex->edges[12] = HalfEdge(22,3,3); - convex->edges[13] = HalfEdge( 4,7,3); - convex->edges[14] = HalfEdge(17,6,3); - convex->edges[15] = HalfEdge( 2,2,3); - - convex->edges[16] = HalfEdge( 3,0,4); - convex->edges[17] = HalfEdge(14,2,4); - convex->edges[18] = HalfEdge( 7,6,4); - convex->edges[19] = HalfEdge( 8,4,4); - - convex->edges[20] = HalfEdge(10,1,5); - convex->edges[21] = HalfEdge( 5,5,5); - convex->edges[22] = HalfEdge(12,7,5); - convex->edges[23] = HalfEdge( 1,3,5); - - - return convex; -} - -ConvexH *ConvexHMakeCube(const REAL3 &bmin, const REAL3 &bmax) -{ - ConvexH *convex = test_cube(); - convex->vertices[0] = REAL3(bmin.x,bmin.y,bmin.z); - convex->vertices[1] = REAL3(bmin.x,bmin.y,bmax.z); - convex->vertices[2] = REAL3(bmin.x,bmax.y,bmin.z); - convex->vertices[3] = REAL3(bmin.x,bmax.y,bmax.z); - convex->vertices[4] = REAL3(bmax.x,bmin.y,bmin.z); - convex->vertices[5] = REAL3(bmax.x,bmin.y,bmax.z); - convex->vertices[6] = REAL3(bmax.x,bmax.y,bmin.z); - convex->vertices[7] = REAL3(bmax.x,bmax.y,bmax.z); - - convex->facets[0] = Plane(REAL3(-1,0,0), bmin.x); - convex->facets[1] = Plane(REAL3(1,0,0), -bmax.x); - convex->facets[2] = Plane(REAL3(0,-1,0), bmin.y); - convex->facets[3] = Plane(REAL3(0,1,0), -bmax.y); - convex->facets[4] = Plane(REAL3(0,0,-1), bmin.z); - convex->facets[5] = Plane(REAL3(0,0,1), -bmax.z); - return convex; -} - - -static NxI32 overhull(Plane *planes,NxI32 planes_count,float3 *verts, NxI32 verts_count,NxI32 maxplanes, - float3 *&verts_out, NxI32 &verts_count_out, NxI32 *&faces_out, NxI32 &faces_count_out ,NxF32 inflate) -{ - NxI32 i,j; - if (verts_count < 4) return 0; - maxplanes = Min(maxplanes,planes_count); - float3 bmin(verts[0]),bmax(verts[0]); - for(i=0;i maxdot_minang) - { - (*((j%2)?&bmax:&bmin)) += n * (diameter*0.5f); - break; - } - } - } - ConvexH *c = ConvexHMakeCube(REAL3(bmin),REAL3(bmax)); - NxI32 k; - while(maxplanes-- && (k=candidateplane(planes,planes_count,c,epsilon))>=0) - { - ConvexH *tmp = c; - c = ConvexHCrop(*tmp,planes[k]); - if(c==NULL) {c=tmp; break;} // might want to debug this case better!!! - if(!AssertIntact(*c)) {c=tmp; break;} // might want to debug this case better too!!! - delete tmp; - } - - assert(AssertIntact(*c)); - //return c; - faces_out = (NxI32*)MEMALLOC_MALLOC(sizeof(NxI32)*(1+c->facets.count+c->edges.count)); // new NxI32[1+c->facets.count+c->edges.count]; - faces_count_out=0; - i=0; - faces_out[faces_count_out++]=-1; - k=0; - while(iedges.count) - { - j=1; - while(j+iedges.count && c->edges[i].p==c->edges[i+j].p) { j++; } - faces_out[faces_count_out++]=j; - while(j--) - { - faces_out[faces_count_out++] = c->edges[i].v; - i++; - } - k++; - } - faces_out[0]=k; // number of faces. - assert(k==c->facets.count); - assert(faces_count_out == 1+c->facets.count+c->edges.count); - verts_out = c->vertices.element; // new float3[c->vertices.count]; - verts_count_out = c->vertices.count; - for(i=0;ivertices.count;i++) - { - verts_out[i] = float3(c->vertices[i]); - } - c->vertices.count=c->vertices.array_size=0; c->vertices.element=NULL; - delete c; - return 1; -} - -static NxI32 overhullv(float3 *verts, NxI32 verts_count,NxI32 maxplanes, - float3 *&verts_out, NxI32 &verts_count_out, NxI32 *&faces_out, NxI32 &faces_count_out ,NxF32 inflate,NxF32 bevangle,NxI32 vlimit) -{ - if(!verts_count) return 0; - extern NxI32 calchullpbev(float3 *verts,NxI32 verts_count,NxI32 vlimit, Array &planes,NxF32 bevangle) ; - Array planes; - NxI32 rc=calchullpbev(verts,verts_count,vlimit,planes,bevangle) ; - if(!rc) return 0; - return overhull(planes.element,planes.count,verts,verts_count,maxplanes,verts_out,verts_count_out,faces_out,faces_count_out,inflate); -} - - -//***************************************************** -//***************************************************** - - -bool ComputeHull(NxU32 vcount,const NxF32 *vertices,PHullResult &result,NxU32 vlimit,NxF32 inflate) -{ - - NxI32 index_count; - NxI32 *faces; - float3 *verts_out; - NxI32 verts_count_out; - - if(inflate==0.0f) - { - NxI32 *tris_out; - NxI32 tris_count; - NxI32 ret = calchull( (float3 *) vertices, (NxI32) vcount, tris_out, tris_count, vlimit ); - if(!ret) return false; - result.mIndexCount = (NxU32) (tris_count*3); - result.mFaceCount = (NxU32) tris_count; - result.mVertices = (NxF32*) vertices; - result.mVcount = (NxU32) vcount; - result.mIndices = (NxU32 *) tris_out; - return true; - } - - NxI32 ret = overhullv((float3*)vertices,vcount,35,verts_out,verts_count_out,faces,index_count,inflate,120.0f,vlimit); - if(!ret) { - tris.SetSize(0); //have to set the size to 0 in order to protect from a "pure virtual function call" problem - return false; - } - - Array tris; - NxI32 n=faces[0]; - NxI32 k=1; - for(NxI32 i=0;i bmax[j] ) bmax[j] = p[j]; - } - } - } - - NxF32 dx = bmax[0] - bmin[0]; - NxF32 dy = bmax[1] - bmin[1]; - NxF32 dz = bmax[2] - bmin[2]; - - NxF32 center[3]; - - center[0] = dx*0.5f + bmin[0]; - center[1] = dy*0.5f + bmin[1]; - center[2] = dz*0.5f + bmin[2]; - - if ( dx < EPSILON || dy < EPSILON || dz < EPSILON || svcount < 3 ) - { - - NxF32 len = FLT_MAX; - - if ( dx > EPSILON && dx < len ) len = dx; - if ( dy > EPSILON && dy < len ) len = dy; - if ( dz > EPSILON && dz < len ) len = dz; - - if ( len == FLT_MAX ) - { - dx = dy = dz = 0.01f; // one centimeter - } - else - { - if ( dx < EPSILON ) dx = len * 0.05f; // 1/5th the shortest non-zero edge. - if ( dy < EPSILON ) dy = len * 0.05f; - if ( dz < EPSILON ) dz = len * 0.05f; - } - - NxF32 x1 = center[0] - dx; - NxF32 x2 = center[0] + dx; - - NxF32 y1 = center[1] - dy; - NxF32 y2 = center[1] + dy; - - NxF32 z1 = center[2] - dz; - NxF32 z2 = center[2] + dz; - - AddPoint(vcount,vertices,x1,y1,z1); - AddPoint(vcount,vertices,x2,y1,z1); - AddPoint(vcount,vertices,x2,y2,z1); - AddPoint(vcount,vertices,x1,y2,z1); - AddPoint(vcount,vertices,x1,y1,z2); - AddPoint(vcount,vertices,x2,y1,z2); - AddPoint(vcount,vertices,x2,y2,z2); - AddPoint(vcount,vertices,x1,y2,z2); - - return true; // return cube - - - } - else - { - if ( scale ) - { - scale[0] = dx; - scale[1] = dy; - scale[2] = dz; - - recip[0] = 1 / dx; - recip[1] = 1 / dy; - recip[2] = 1 / dz; - - center[0]*=recip[0]; - center[1]*=recip[1]; - center[2]*=recip[2]; - - } - - } - - - - vtx = (const char *) svertices; - - for (NxU32 i=0; i dist2 ) - { - v[0] = px; - v[1] = py; - v[2] = pz; - } - - break; - } - } - - if ( j == vcount ) - { - NxF32 *dest = &vertices[vcount*3]; - dest[0] = px; - dest[1] = py; - dest[2] = pz; - vcount++; - } - } - } - - // ok..now make sure we didn't prune so many vertices it is now invalid. - { - NxF32 bmin[3] = { FLT_MAX, FLT_MAX, FLT_MAX }; - NxF32 bmax[3] = { -FLT_MAX, -FLT_MAX, -FLT_MAX }; - - for (NxU32 i=0; i bmax[j] ) bmax[j] = p[j]; - } - } - - NxF32 dx = bmax[0] - bmin[0]; - NxF32 dy = bmax[1] - bmin[1]; - NxF32 dz = bmax[2] - bmin[2]; - - if ( dx < EPSILON || dy < EPSILON || dz < EPSILON || vcount < 3) - { - NxF32 cx = dx*0.5f + bmin[0]; - NxF32 cy = dy*0.5f + bmin[1]; - NxF32 cz = dz*0.5f + bmin[2]; - - NxF32 len = FLT_MAX; - - if ( dx >= EPSILON && dx < len ) len = dx; - if ( dy >= EPSILON && dy < len ) len = dy; - if ( dz >= EPSILON && dz < len ) len = dz; - - if ( len == FLT_MAX ) - { - dx = dy = dz = 0.01f; // one centimeter - } - else - { - if ( dx < EPSILON ) dx = len * 0.05f; // 1/5th the shortest non-zero edge. - if ( dy < EPSILON ) dy = len * 0.05f; - if ( dz < EPSILON ) dz = len * 0.05f; - } - - NxF32 x1 = cx - dx; - NxF32 x2 = cx + dx; - - NxF32 y1 = cy - dy; - NxF32 y2 = cy + dy; - - NxF32 z1 = cz - dz; - NxF32 z2 = cz + dz; - - vcount = 0; // add box - - AddPoint(vcount,vertices,x1,y1,z1); - AddPoint(vcount,vertices,x2,y1,z1); - AddPoint(vcount,vertices,x2,y2,z1); - AddPoint(vcount,vertices,x1,y2,z1); - AddPoint(vcount,vertices,x1,y1,z2); - AddPoint(vcount,vertices,x2,y1,z2); - AddPoint(vcount,vertices,x2,y2,z2); - AddPoint(vcount,vertices,x1,y2,z2); - - return true; - } - } - - return true; -} - -void HullLibrary::BringOutYourDead(const NxF32 *verts,NxU32 vcount, NxF32 *overts,NxU32 &ocount,NxU32 *indices,NxU32 indexcount) -{ - NxU32 *used = (NxU32 *)MEMALLOC_MALLOC(sizeof(NxU32)*vcount); - memset(used,0,sizeof(NxU32)*vcount); - - ocount = 0; - - for (NxU32 i=0; iConvexHullTriangle(v3,v2,v1); -} - -//================================================================================== -NxF32 HullLibrary::ComputeNormal(NxF32 *n,const NxF32 *A,const NxF32 *B,const NxF32 *C) -{ - NxF32 vx,vy,vz,wx,wy,wz,vw_x,vw_y,vw_z,mag; - - vx = (B[0] - C[0]); - vy = (B[1] - C[1]); - vz = (B[2] - C[2]); - - wx = (A[0] - B[0]); - wy = (A[1] - B[1]); - wz = (A[2] - B[2]); - - vw_x = vy * wz - vz * wy; - vw_y = vz * wx - vx * wz; - vw_z = vx * wy - vy * wx; - - mag = sqrtf((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z)); - - if ( mag < 0.000001f ) - { - mag = 0; - } - else - { - mag = 1.0f/mag; - } - - n[0] = vw_x * mag; - n[1] = vw_y * mag; - n[2] = vw_z * mag; - - return mag; -} - -}; // End of namespace diff --git a/Engine/lib/convexDecomp/NvStanHull.h b/Engine/lib/convexDecomp/NvStanHull.h deleted file mode 100644 index 7890d05bf..000000000 --- a/Engine/lib/convexDecomp/NvStanHull.h +++ /dev/null @@ -1,201 +0,0 @@ -#ifndef NV_STAN_HULL_H - -#define NV_STAN_HULL_H - -/* - -NvStanHull.h : A convex hull generator written by Stan Melax - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "NvUserMemAlloc.h" - -namespace CONVEX_DECOMPOSITION -{ - -class HullResult -{ -public: - HullResult(void) - { - mPolygons = true; - mNumOutputVertices = 0; - mOutputVertices = 0; - mNumFaces = 0; - mNumIndices = 0; - mIndices = 0; - } - bool mPolygons; // true if indices represents polygons, false indices are triangles - NxU32 mNumOutputVertices; // number of vertices in the output hull - NxF32 *mOutputVertices; // array of vertices, 3 floats each x,y,z - NxU32 mNumFaces; // the number of faces produced - NxU32 mNumIndices; // the total number of indices - NxU32 *mIndices; // pointer to indices. - -// If triangles, then indices are array indexes into the vertex list. -// If polygons, indices are in the form (number of points in face) (p1, p2, p3, ..) etc.. -}; - -enum HullFlag -{ - QF_TRIANGLES = (1<<0), // report results as triangles, not polygons. - QF_REVERSE_ORDER = (1<<1), // reverse order of the triangle indices. - QF_SKIN_WIDTH = (1<<2), // extrude hull based on this skin width - QF_DEFAULT = (QF_TRIANGLES | QF_SKIN_WIDTH) -}; - - -class HullDesc -{ -public: - HullDesc(void) - { - mFlags = QF_DEFAULT; - mVcount = 0; - mVertices = 0; - mVertexStride = 0; - mNormalEpsilon = 0.001f; - mMaxVertices = 4096; // maximum number of points to be considered for a convex hull. - mSkinWidth = 0.01f; // default is one centimeter - }; - - HullDesc(HullFlag flag, - NxU32 vcount, - const NxF32 *vertices, - NxU32 stride) - { - mFlags = flag; - mVcount = vcount; - mVertices = vertices; - mVertexStride = stride; - mNormalEpsilon = 0.001f; - mMaxVertices = 4096; - mSkinWidth = 0.01f; // default is one centimeter - } - - bool HasHullFlag(HullFlag flag) const - { - if ( mFlags & flag ) return true; - return false; - } - - void SetHullFlag(HullFlag flag) - { - mFlags|=flag; - } - - void ClearHullFlag(HullFlag flag) - { - mFlags&=~flag; - } - - NxU32 mFlags; // flags to use when generating the convex hull. - NxU32 mVcount; // number of vertices in the input point cloud - const NxF32 *mVertices; // the array of vertices. - NxU32 mVertexStride; // the stride of each vertex, in bytes. - NxF32 mNormalEpsilon; // the epsilon for removing duplicates. This is a normalized value, if normalized bit is on. - NxF32 mSkinWidth; - NxU32 mMaxVertices; // maximum number of vertices to be considered for the hull! -}; - -enum HullError -{ - QE_OK, // success! - QE_FAIL, // failed. - QE_NOT_READY, -}; - -// This class is used when converting a convex hull into a triangle mesh. -class ConvexHullVertex -{ -public: - NxF32 mPos[3]; - NxF32 mNormal[3]; - NxF32 mTexel[2]; -}; - -// A virtual interface to receive the triangles from the convex hull. -class ConvexHullTriangleInterface -{ -public: - virtual void ConvexHullTriangle(const ConvexHullVertex &v1,const ConvexHullVertex &v2,const ConvexHullVertex &v3) = 0; -}; - - -class HullLibrary -{ -public: - - HullError CreateConvexHull(const HullDesc &desc, // describes the input request - HullResult &result); // contains the resulst - - HullError ReleaseResult(HullResult &result); // release memory allocated for this result, we are done with it. - - HullError CreateTriangleMesh(HullResult &answer,ConvexHullTriangleInterface *iface); -private: - NxF32 ComputeNormal(NxF32 *n,const NxF32 *A,const NxF32 *B,const NxF32 *C); - void AddConvexTriangle(ConvexHullTriangleInterface *callback,const NxF32 *p1,const NxF32 *p2,const NxF32 *p3); - - void BringOutYourDead(const NxF32 *verts,NxU32 vcount, NxF32 *overts,NxU32 &ocount,NxU32 *indices,NxU32 indexcount); - - bool CleanupVertices(NxU32 svcount, - const NxF32 *svertices, - NxU32 stride, - NxU32 &vcount, // output number of vertices - NxF32 *vertices, // location to store the results. - NxF32 normalepsilon, - NxF32 *scale); -}; - -}; // end of namespace - -#endif diff --git a/Engine/lib/convexDecomp/NvThreadConfig.cpp b/Engine/lib/convexDecomp/NvThreadConfig.cpp deleted file mode 100644 index 5ae9907ca..000000000 --- a/Engine/lib/convexDecomp/NvThreadConfig.cpp +++ /dev/null @@ -1,511 +0,0 @@ -/* - -NvThreadConfig.cpp : A simple wrapper class to define threading and mutex locks. - -*/ -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#include -#include "NvThreadConfig.h" - -#if defined(WIN32) - -#define _WIN32_WINNT 0x400 -#include - -#pragma comment(lib,"winmm.lib") - -// #ifndef _WIN32_WINNT - -// #endif -// #include -//#include -#endif - -#if defined(_XBOX) - #include -#endif - -#if defined(__linux__) || defined( __APPLE__ ) || defined( __FreeBSD__) - //#include - #include - #include - #include - #define __stdcall -#endif - -#if defined( __APPLE__ ) || defined( __FreeBSD__) - - #include -#endif - -#if defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - #include -#endif - -#if defined( __APPLE__ ) || defined( __FreeBSD__) - - #define PTHREAD_MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE -#endif - - -#ifdef NDEBUG -#define VERIFY( x ) (x) -#else -#define VERIFY( x ) assert((x)) -#endif - -namespace CONVEX_DECOMPOSITION -{ - -NxU32 tc_timeGetTime(void) -{ - #if defined(__linux__) - struct timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; - #elif defined( __APPLE__ ) || defined( __FreeBSD__) - - struct timeval tp; - gettimeofday(&tp, (struct timezone *)0); - return tp.tv_sec * 1000 + tp.tv_usec / 1000; - #elif defined( _XBOX ) - return GetTickCount(); - #else - return timeGetTime(); - #endif -} - -void tc_sleep(NxU32 ms) -{ - #if defined(__linux__) || defined( __APPLE__ ) || defined( __FreeBSD__) - usleep(ms * 1000); - #else - Sleep(ms); - #endif -} - -void tc_spinloop() -{ - - #if defined( _XBOX ) - // Pause would do nothing on the Xbox. Threads are not scheduled. - #elif defined( _WIN64 ) - YieldProcessor( ); - #elif defined( __APPLE__ ) - pthread_yield_np(); - #elif defined(__linux__) || defined(__FreeBSD__) - #if defined(_POSIX_PRIORITY_SCHEDULING) - sched_yield(); - #else - asm("pause"); - #endif - #elif - __asm { pause }; - #endif -} - -void tc_interlockedExchange(void *dest, const int64_t exchange) -{ - #if defined( __linux__ ) || defined( __APPLE__ ) || defined( __FreeBSD__) - - // not working - assert(false); - //__sync_lock_test_and_set((int64_t*)dest, exchange); -#elif defined( _XBOX ) || defined( _WIN64 ) - InterlockedExchange((volatile LONG *)dest, exchange); - #else - __asm - { - mov ebx, dword ptr [exchange] - mov ecx, dword ptr [exchange + 4] - mov edi, dest - mov eax, dword ptr [edi] - mov edx, dword ptr [edi + 4] - jmp start - retry: - pause - start: - lock cmpxchg8b [edi] - jnz retry - }; - #endif -} - -NxI32 tc_interlockedCompareExchange(void *dest, NxI32 exchange, NxI32 compare) -{ - #if defined( __linux__ ) || defined( __APPLE__ ) || defined( __FreeBSD__) - - // not working - assert(false); - return 0; - //return __sync_val_compare_and_swap((uintptr_t*)dest, exchange, compare); - //return __sync_bool_compare_and_swap((uintptr_t*)dest, exchange, compare); - #elif defined( _XBOX ) || defined( _WIN64 ) - return InterlockedCompareExchange((volatile LONG *)dest, exchange, compare); - #else - char _ret; - // - __asm - { - mov edx, [dest] - mov eax, [compare] - mov ecx, [exchange] - - lock cmpxchg [edx], ecx - - setz al - mov byte ptr [_ret], al - } - // - return _ret; - #endif -} - -NxI32 tc_interlockedCompareExchange(void *dest, const NxI32 exchange1, const NxI32 exchange2, const NxI32 compare1, const NxI32 compare2) -{ - #if defined( __linux__ ) || defined( __APPLE__ ) || defined( __FreeBSD__) - // not working - assert(false); - return 0; - //uint64_t exchange = ((uint64_t)exchange1 << 32) | (uint64_t)exchange2; - //uint64_t compare = ((uint64_t)compare1 << 32) | (uint64_t)compare2; - //return __sync_bool_compare_and_swap((int64_t*)dest, exchange, compare); - #elif defined( _XBOX ) || defined( _WIN64 ) - assert(false); - return 0; - #else - char _ret; - // - __asm - { - mov ebx, [exchange1] - mov ecx, [exchange2] - mov edi, [dest] - mov eax, [compare1] - mov edx, [compare2] - lock cmpxchg8b [edi] - setz al - mov byte ptr [_ret], al - } - // - return _ret; - #endif -} - -class MyThreadMutex : public ThreadMutex -{ -public: - MyThreadMutex(void) - { - #if defined(WIN32) || defined(_XBOX) - InitializeCriticalSection(&m_Mutex); - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - pthread_mutexattr_t mutexAttr; // Mutex Attribute - VERIFY( pthread_mutexattr_init(&mutexAttr) == 0 ); - VERIFY( pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_RECURSIVE_NP) == 0 ); - VERIFY( pthread_mutex_init(&m_Mutex, &mutexAttr) == 0 ); - VERIFY( pthread_mutexattr_destroy(&mutexAttr) == 0 ); - #endif - } - - ~MyThreadMutex(void) - { - #if defined(WIN32) || defined(_XBOX) - DeleteCriticalSection(&m_Mutex); - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_mutex_destroy(&m_Mutex) == 0 ); - #endif - } - - void lock(void) - { - #if defined(WIN32) || defined(_XBOX) - EnterCriticalSection(&m_Mutex); - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_mutex_lock(&m_Mutex) == 0 ); - #endif - } - - bool tryLock(void) - { - #if defined(WIN32) || defined(_XBOX) - bool bRet = false; - //assert(("TryEnterCriticalSection seems to not work on XP???", 0)); - bRet = TryEnterCriticalSection(&m_Mutex) ? true : false; - return bRet; - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - NxI32 result = pthread_mutex_trylock(&m_Mutex); - return (result == 0); - #endif - } - - void unlock(void) - { - #if defined(WIN32) || defined(_XBOX) - LeaveCriticalSection(&m_Mutex); - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_mutex_unlock(&m_Mutex) == 0 ); - #endif - } - -private: - #if defined(WIN32) || defined(_XBOX) - CRITICAL_SECTION m_Mutex; - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - pthread_mutex_t m_Mutex; - #endif -}; - -ThreadMutex * tc_createThreadMutex(void) -{ - MyThreadMutex *m = new MyThreadMutex; - return static_cast< ThreadMutex *>(m); -} - -void tc_releaseThreadMutex(ThreadMutex *tm) -{ - MyThreadMutex *m = static_cast< MyThreadMutex *>(tm); - delete m; -} - -#if defined(WIN32) || defined(_XBOX) -static unsigned long __stdcall _ThreadWorkerFunc(LPVOID arg); -#elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - -static void* _ThreadWorkerFunc(void* arg); -#endif - -class MyThread : public Thread -{ -public: - MyThread(ThreadInterface *iface) - { - mInterface = iface; - #if defined(WIN32) || defined(_XBOX) - mThread = CreateThread(0, 0, _ThreadWorkerFunc, this, 0, 0); - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_create(&mThread, NULL, _ThreadWorkerFunc, this) == 0 ); - #endif - } - - ~MyThread(void) - { - #if defined(WIN32) || defined(_XBOX) - if ( mThread ) - { - CloseHandle(mThread); - mThread = 0; - } - #endif - } - - void onJobExecute(void) - { - mInterface->threadMain(); - } - -private: - ThreadInterface *mInterface; - #if defined(WIN32) || defined(_XBOX) - HANDLE mThread; - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - pthread_t mThread; - #endif -}; - - -Thread * tc_createThread(ThreadInterface *tinterface) -{ - MyThread *m = new MyThread(tinterface); - return static_cast< Thread *>(m); -} - -void tc_releaseThread(Thread *t) -{ - MyThread *m = static_cast(t); - delete m; -} - -#if defined(WIN32) || defined(_XBOX) -static unsigned long __stdcall _ThreadWorkerFunc(LPVOID arg) -#elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - -static void* _ThreadWorkerFunc(void* arg) -#endif -{ - MyThread *worker = (MyThread *) arg; - worker->onJobExecute(); - return 0; -} - - -class MyThreadEvent : public ThreadEvent -{ -public: - MyThreadEvent(void) - { - #if defined(WIN32) || defined(_XBOX) - mEvent = ::CreateEventA(NULL,TRUE,TRUE,"ThreadEvent"); - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - pthread_mutexattr_t mutexAttr; // Mutex Attribute - VERIFY( pthread_mutexattr_init(&mutexAttr) == 0 ); - VERIFY( pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_RECURSIVE_NP) == 0 ); - VERIFY( pthread_mutex_init(&mEventMutex, &mutexAttr) == 0 ); - VERIFY( pthread_mutexattr_destroy(&mutexAttr) == 0 ); - VERIFY( pthread_cond_init(&mEvent, NULL) == 0 ); - #endif - } - - ~MyThreadEvent(void) - { - #if defined(WIN32) || defined(_XBOX) - if ( mEvent ) - { - ::CloseHandle(mEvent); - } - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_cond_destroy(&mEvent) == 0 ); - VERIFY( pthread_mutex_destroy(&mEventMutex) == 0 ); - #endif - } - - virtual void setEvent(void) // signal the event - { - #if defined(WIN32) || defined(_XBOX) - if ( mEvent ) - { - ::SetEvent(mEvent); - } - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_mutex_lock(&mEventMutex) == 0 ); - VERIFY( pthread_cond_signal(&mEvent) == 0 ); - VERIFY( pthread_mutex_unlock(&mEventMutex) == 0 ); - #endif - } - - void resetEvent(void) - { - #if defined(WIN32) || defined(_XBOX) - if ( mEvent ) - { - ::ResetEvent(mEvent); - } - #endif - } - - virtual void waitForSingleObject(NxU32 ms) - { - #if defined(WIN32) || defined(_XBOX) - if ( mEvent ) - { - ::WaitForSingleObject(mEvent,ms); - } - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - VERIFY( pthread_mutex_lock(&mEventMutex) == 0 ); - if (ms == 0xffffffff) - { - VERIFY( pthread_cond_wait(&mEvent, &mEventMutex) == 0 ); - } - else - { - struct timespec ts; - #ifdef __APPLE__ - struct timeval tp; - gettimeofday(&tp, (struct timezone *)0); - ts.tv_nsec = tp.tv_usec * 1000; - ts.tv_sec = tp.tv_sec; - #else - clock_gettime(CLOCK_REALTIME, &ts); - #endif - ts.tv_nsec += ms * 1000000; - ts.tv_sec += ts.tv_nsec / 1000000000; - ts.tv_nsec %= 1000000000; - NxI32 result = pthread_cond_timedwait(&mEvent, &mEventMutex, &ts); - assert(result == 0 || result == ETIMEDOUT); - } - VERIFY( pthread_mutex_unlock(&mEventMutex) == 0 ); - #endif - } - -private: - #if defined(WIN32) || defined(_XBOX) - HANDLE mEvent; - #elif defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__) - - pthread_mutex_t mEventMutex; - pthread_cond_t mEvent; - #endif -}; - -ThreadEvent * tc_createThreadEvent(void) -{ - MyThreadEvent *m = new MyThreadEvent; - return static_cast(m); -} - -void tc_releaseThreadEvent(ThreadEvent *t) -{ - MyThreadEvent *m = static_cast< MyThreadEvent *>(t); - delete m; -} - -}; // end of namespace diff --git a/Engine/lib/convexDecomp/NvThreadConfig.h b/Engine/lib/convexDecomp/NvThreadConfig.h deleted file mode 100644 index 1074c045c..000000000 --- a/Engine/lib/convexDecomp/NvThreadConfig.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef NV_THREAD_CONFIG_H - -#define NV_THREAD_CONFIG_H - -#include "NvUserMemAlloc.h" - -/* - -NvThreadConfig.h : A simple wrapper class to define threading and mutex locks. - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifdef _MSC_VER -typedef __int64 int64_t; -#else -#include -#endif - -namespace CONVEX_DECOMPOSITION -{ - -NxU32 tc_timeGetTime(void); -void tc_sleep(NxU32 ms); - -void tc_spinloop(); -void tc_interlockedExchange(void *dest, const int64_t exchange); -NxI32 tc_interlockedCompareExchange(void *dest, NxI32 exchange, NxI32 compare); -NxI32 tc_interlockedCompareExchange(void *dest, const NxI32 exchange1, const NxI32 exchange2, const NxI32 compare1, const NxI32 compare2); - -class ThreadMutex -{ -public: - virtual void lock(void) = 0; - virtual void unlock(void) = 0; - virtual bool tryLock(void) = 0; -}; - - -ThreadMutex * tc_createThreadMutex(void); -void tc_releaseThreadMutex(ThreadMutex *tm); - -class ThreadInterface -{ -public: - virtual void threadMain(void) = 0; -}; - -class Thread -{ -public: -}; - -Thread * tc_createThread(ThreadInterface *tinterface); -void tc_releaseThread(Thread *t); - -class ThreadEvent -{ -public: - virtual void setEvent(void) = 0; // signal the event - virtual void resetEvent(void) = 0; - virtual void waitForSingleObject(NxU32 ms) = 0; -}; - -ThreadEvent * tc_createThreadEvent(void); -void tc_releaseThreadEvent(ThreadEvent *t); - -}; // end of namespace - - -#endif diff --git a/Engine/lib/convexDecomp/NvUserMemAlloc.h b/Engine/lib/convexDecomp/NvUserMemAlloc.h deleted file mode 100644 index 14ae899d9..000000000 --- a/Engine/lib/convexDecomp/NvUserMemAlloc.h +++ /dev/null @@ -1,81 +0,0 @@ -#ifndef NV_USER_MEMALLOC_H - -#define NV_USER_MEMALLOC_H - -#include "NvSimpleTypes.h" - -/* - -NvUserMemAlloc.h : Modify these macros to change the default memory allocation behavior of the convex decomposition code. - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#ifndef MEMALLOC_NEW -#define MEMALLOC_NEW(x) new x -#define MEMALLOC_MALLOC(x) ::malloc(x) -#define MEMALLOC_FREE(x) ::free(x) -#define MEMALLOC_REALLOC(x,y) ::realloc(x,y) -#endif - -namespace CONVEX_DECOMPOSITION -{ - -class Memalloc -{ -public: -}; - -}; // end of namespace - - - -#endif diff --git a/Engine/lib/convexDecomp/readme.txt b/Engine/lib/convexDecomp/readme.txt deleted file mode 100644 index 679396aa3..000000000 --- a/Engine/lib/convexDecomp/readme.txt +++ /dev/null @@ -1,38 +0,0 @@ -The ConvexDecomposition library was written by John W. Ratcliff mailto:jratcliffscarab@gmail.com - -What is Convex Decomposition? - -Convex Decomposition is when you take an arbitrarily complex triangle mesh and sub-divide it into -a collection of discrete compound pieces (each represented as a convex hull) to approximate -the original shape of the objet. - -This is required since few physics engines can treat aribtrary triangle mesh objects as dynamic -objects. Even those engines which can handle this use case incurr a huge performance and memory -penalty to do so. - -By breaking a complex triangle mesh up into a discrete number of convex components you can greatly -improve performance for dynamic simulations. - --------------------------------------------------------------------------------- - -This code is released under the MIT license. - -The code is functional but could use the following improvements: - -(1) The convex hull generator, originally written by Stan Melax, could use some major code cleanup. - -(2) The code to remove T-junctions appears to have a bug in it. This code was working fine before, - but I haven't had time to debug why it stopped working. - -(3) Island generation once the mesh has been split is currently disabled due to the fact that the - Remove Tjunctions functionality has a bug in it. - -(4) The code to perform a raycast against a triangle mesh does not currently use any acceleration - data structures. - -(5) When a split is performed, the surface that got split is not 'capped'. This causes a problem - if you use a high recursion depth on your convex decomposition. It will cause the object to - be modelled as if it had a hollow interior. A lot of work was done to solve this problem, but - it hasn't been integrated into this code drop yet. - - diff --git a/Engine/lib/convexDecomp/wavefront.cpp b/Engine/lib/convexDecomp/wavefront.cpp deleted file mode 100644 index 0d4b979b1..000000000 --- a/Engine/lib/convexDecomp/wavefront.cpp +++ /dev/null @@ -1,852 +0,0 @@ -/* - -wavefront.cpp : A very small code snippet to read a Wavefront OBJ file into memory. - -*/ - - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ -#ifndef __PPCGEKKO__ - -#include -#include -#include -#include -#include - -#include "wavefront.h" - -#include - -typedef std::vector< NxI32 > IntVector; -typedef std::vector< NxF32 > FloatVector; - -#pragma warning(disable:4996) - -namespace WAVEFRONT -{ - - -/*******************************************************************/ -/******************** InParser.h ********************************/ -/*******************************************************************/ -class InPlaceParserInterface -{ -public: - virtual NxI32 ParseLine(NxI32 lineno,NxI32 argc,const char **argv) =0; // return TRUE to continue parsing, return FALSE to abort parsing process -}; - -enum SeparatorType -{ - ST_DATA, // is data - ST_HARD, // is a hard separator - ST_SOFT, // is a soft separator - ST_EOS // is a comment symbol, and everything past this character should be ignored -}; - -class InPlaceParser -{ -public: - InPlaceParser(void) - { - Init(); - } - - InPlaceParser(char *data,NxI32 len) - { - Init(); - SetSourceData(data,len); - } - - InPlaceParser(const char *fname) - { - Init(); - SetFile(fname); - } - - ~InPlaceParser(void); - - void Init(void) - { - mQuoteChar = 34; - mData = 0; - mLen = 0; - mMyAlloc = false; - for (NxI32 i=0; i<256; i++) - { - mHard[i] = ST_DATA; - mHardString[i*2] = i; - mHardString[i*2+1] = 0; - } - mHard[0] = ST_EOS; - mHard[32] = ST_SOFT; - mHard[9] = ST_SOFT; - mHard[13] = ST_SOFT; - mHard[10] = ST_SOFT; - } - - void SetFile(const char *fname); // use this file as source data to parse. - - void SetSourceData(char *data,NxI32 len) - { - mData = data; - mLen = len; - mMyAlloc = false; - }; - - NxI32 Parse(InPlaceParserInterface *callback); // returns true if entire file was parsed, false if it aborted for some reason - - NxI32 ProcessLine(NxI32 lineno,char *line,InPlaceParserInterface *callback); - - const char ** GetArglist(char *source,NxI32 &count); // convert source string into an arg list, this is a destructive parse. - - void SetHardSeparator(char c) // add a hard separator - { - mHard[c] = ST_HARD; - } - - void SetHard(char c) // add a hard separator - { - mHard[c] = ST_HARD; - } - - - void SetCommentSymbol(char c) // comment character, treated as 'end of string' - { - mHard[c] = ST_EOS; - } - - void ClearHardSeparator(char c) - { - mHard[c] = ST_DATA; - } - - - void DefaultSymbols(void); // set up default symbols for hard seperator and comment symbol of the '#' character. - - bool EOS(char c) - { - if ( mHard[c] == ST_EOS ) - { - return true; - } - return false; - } - - void SetQuoteChar(char c) - { - mQuoteChar = c; - } - -private: - - - inline char * AddHard(NxI32 &argc,const char **argv,char *foo); - inline bool IsHard(char c); - inline char * SkipSpaces(char *foo); - inline bool IsWhiteSpace(char c); - inline bool IsNonSeparator(char c); // non seperator,neither hard nor soft - - bool mMyAlloc; // whether or not *I* allocated the buffer and am responsible for deleting it. - char *mData; // ascii data to parse. - NxI32 mLen; // length of data - SeparatorType mHard[256]; - char mHardString[256*2]; - char mQuoteChar; -}; - -/*******************************************************************/ -/******************** InParser.cpp ********************************/ -/*******************************************************************/ -void InPlaceParser::SetFile(const char *fname) -{ - if ( mMyAlloc ) - { - free(mData); - } - mData = 0; - mLen = 0; - mMyAlloc = false; - - FILE *fph = fopen(fname,"rb"); - if ( fph ) - { - fseek(fph,0L,SEEK_END); - mLen = ftell(fph); - fseek(fph,0L,SEEK_SET); - if ( mLen ) - { - mData = (char *) malloc(sizeof(char)*(mLen+1)); - size_t ok = fread(mData, mLen, 1, fph); - if ( !ok ) - { - free(mData); - mData = 0; - } - else - { - mData[mLen] = 0; // zero byte terminate end of file marker. - mMyAlloc = true; - } - } - fclose(fph); - } - -} - -InPlaceParser::~InPlaceParser(void) -{ - if ( mMyAlloc ) - { - free(mData); - } -} - -#define MAXARGS 512 - -bool InPlaceParser::IsHard(char c) -{ - return mHard[c] == ST_HARD; -} - -char * InPlaceParser::AddHard(NxI32 &argc,const char **argv,char *foo) -{ - while ( IsHard(*foo) ) - { - const char *hard = &mHardString[*foo*2]; - if ( argc < MAXARGS ) - { - argv[argc++] = hard; - } - foo++; - } - return foo; -} - -bool InPlaceParser::IsWhiteSpace(char c) -{ - return mHard[c] == ST_SOFT; -} - -char * InPlaceParser::SkipSpaces(char *foo) -{ - while ( !EOS(*foo) && IsWhiteSpace(*foo) ) foo++; - return foo; -} - -bool InPlaceParser::IsNonSeparator(char c) -{ - if ( !IsHard(c) && !IsWhiteSpace(c) && c != 0 ) return true; - return false; -} - - -NxI32 InPlaceParser::ProcessLine(NxI32 lineno,char *line,InPlaceParserInterface *callback) -{ - NxI32 ret = 0; - - const char *argv[MAXARGS]; - NxI32 argc = 0; - - char *foo = line; - - while ( !EOS(*foo) && argc < MAXARGS ) - { - - foo = SkipSpaces(foo); // skip any leading spaces - - if ( EOS(*foo) ) break; - - if ( *foo == mQuoteChar ) // if it is an open quote - { - foo++; - if ( argc < MAXARGS ) - { - argv[argc++] = foo; - } - while ( !EOS(*foo) && *foo != mQuoteChar ) foo++; - if ( !EOS(*foo) ) - { - *foo = 0; // replace close quote with zero byte EOS - foo++; - } - } - else - { - - foo = AddHard(argc,argv,foo); // add any hard separators, skip any spaces - - if ( IsNonSeparator(*foo) ) // add non-hard argument. - { - bool quote = false; - if ( *foo == mQuoteChar ) - { - foo++; - quote = true; - } - - if ( argc < MAXARGS ) - { - argv[argc++] = foo; - } - - if ( quote ) - { - while (*foo && *foo != mQuoteChar ) foo++; - if ( *foo ) *foo = 32; - } - - // continue..until we hit an eos .. - while ( !EOS(*foo) ) // until we hit EOS - { - if ( IsWhiteSpace(*foo) ) // if we hit a space, stomp a zero byte, and exit - { - *foo = 0; - foo++; - break; - } - else if ( IsHard(*foo) ) // if we hit a hard separator, stomp a zero byte and store the hard separator argument - { - const char *hard = &mHardString[*foo*2]; - *foo = 0; - if ( argc < MAXARGS ) - { - argv[argc++] = hard; - } - foo++; - break; - } - foo++; - } // end of while loop... - } - } - } - - if ( argc ) - { - ret = callback->ParseLine(lineno, argc, argv ); - } - - return ret; -} - -NxI32 InPlaceParser::Parse(InPlaceParserInterface *callback) // returns true if entire file was parsed, false if it aborted for some reason -{ - assert( callback ); - if ( !mData ) return 0; - - NxI32 ret = 0; - - NxI32 lineno = 0; - - char *foo = mData; - char *begin = foo; - - - while ( *foo ) - { - if ( *foo == 10 || *foo == 13 ) - { - lineno++; - *foo = 0; - - if ( *begin ) // if there is any data to parse at all... - { - NxI32 v = ProcessLine(lineno,begin,callback); - if ( v ) ret = v; - } - - foo++; - if ( *foo == 10 ) foo++; // skip line feed, if it is in the carraige-return line-feed format... - begin = foo; - } - else - { - foo++; - } - } - - lineno++; // lasst line. - - NxI32 v = ProcessLine(lineno,begin,callback); - if ( v ) ret = v; - return ret; -} - - -void InPlaceParser::DefaultSymbols(void) -{ - SetHardSeparator(','); - SetHardSeparator('('); - SetHardSeparator(')'); - SetHardSeparator('='); - SetHardSeparator('['); - SetHardSeparator(']'); - SetHardSeparator('{'); - SetHardSeparator('}'); - SetCommentSymbol('#'); -} - - -const char ** InPlaceParser::GetArglist(char *line,NxI32 &count) // convert source string into an arg list, this is a destructive parse. -{ - const char **ret = 0; - - static const char *argv[MAXARGS]; - NxI32 argc = 0; - - char *foo = line; - - while ( !EOS(*foo) && argc < MAXARGS ) - { - - foo = SkipSpaces(foo); // skip any leading spaces - - if ( EOS(*foo) ) break; - - if ( *foo == mQuoteChar ) // if it is an open quote - { - foo++; - if ( argc < MAXARGS ) - { - argv[argc++] = foo; - } - while ( !EOS(*foo) && *foo != mQuoteChar ) foo++; - if ( !EOS(*foo) ) - { - *foo = 0; // replace close quote with zero byte EOS - foo++; - } - } - else - { - - foo = AddHard(argc,argv,foo); // add any hard separators, skip any spaces - - if ( IsNonSeparator(*foo) ) // add non-hard argument. - { - bool quote = false; - if ( *foo == mQuoteChar ) - { - foo++; - quote = true; - } - - if ( argc < MAXARGS ) - { - argv[argc++] = foo; - } - - if ( quote ) - { - while (*foo && *foo != mQuoteChar ) foo++; - if ( *foo ) *foo = 32; - } - - // continue..until we hit an eos .. - while ( !EOS(*foo) ) // until we hit EOS - { - if ( IsWhiteSpace(*foo) ) // if we hit a space, stomp a zero byte, and exit - { - *foo = 0; - foo++; - break; - } - else if ( IsHard(*foo) ) // if we hit a hard separator, stomp a zero byte and store the hard separator argument - { - const char *hard = &mHardString[*foo*2]; - *foo = 0; - if ( argc < MAXARGS ) - { - argv[argc++] = hard; - } - foo++; - break; - } - foo++; - } // end of while loop... - } - } - } - - count = argc; - if ( argc ) - { - ret = argv; - } - - return ret; -} - -/*******************************************************************/ -/******************** Geometry.h ********************************/ -/*******************************************************************/ - -class GeometryVertex -{ -public: - NxF32 mPos[3]; - NxF32 mNormal[3]; - NxF32 mTexel[2]; -}; - - -class GeometryInterface -{ -public: - - virtual void NodeTriangle(const GeometryVertex *v1,const GeometryVertex *v2,const GeometryVertex *v3, bool textured) - { - } - -}; - - -/*******************************************************************/ -/******************** Obj.h ********************************/ -/*******************************************************************/ - - -class OBJ : public InPlaceParserInterface -{ -public: - NxI32 LoadMesh(const char *fname,GeometryInterface *callback, bool textured); - NxI32 ParseLine(NxI32 lineno,NxI32 argc,const char **argv); // return TRUE to continue parsing, return FALSE to abort parsing process -private: - - void GetVertex(GeometryVertex &v,const char *face) const; - - FloatVector mVerts; - FloatVector mTexels; - FloatVector mNormals; - - bool mTextured; - - GeometryInterface *mCallback; -}; - - -/*******************************************************************/ -/******************** Obj.cpp ********************************/ -/*******************************************************************/ - -NxI32 OBJ::LoadMesh(const char *fname,GeometryInterface *iface, bool textured) -{ - mTextured = textured; - NxI32 ret = 0; - - mVerts.clear(); - mTexels.clear(); - mNormals.clear(); - - mCallback = iface; - - InPlaceParser ipp(fname); - - ipp.Parse(this); - -return ret; -} - -static const char * GetArg(const char **argv,NxI32 i,NxI32 argc) -{ - const char * ret = 0; - if ( i < argc ) ret = argv[i]; - return ret; -} - -void OBJ::GetVertex(GeometryVertex &v,const char *face) const -{ - v.mPos[0] = 0; - v.mPos[1] = 0; - v.mPos[2] = 0; - - v.mTexel[0] = 0; - v.mTexel[1] = 0; - - v.mNormal[0] = 0; - v.mNormal[1] = 1; - v.mNormal[2] = 0; - - NxI32 index = atoi( face )-1; - - const char *texel = strstr(face,"/"); - - if ( texel ) - { - NxI32 tindex = atoi( texel+1) - 1; - - if ( tindex >=0 && tindex < (NxI32)(mTexels.size()/2) ) - { - const NxF32 *t = &mTexels[tindex*2]; - - v.mTexel[0] = t[0]; - v.mTexel[1] = t[1]; - - } - - const char *normal = strstr(texel+1,"/"); - if ( normal ) - { - NxI32 nindex = atoi( normal+1 ) - 1; - - if (nindex >= 0 && nindex < (NxI32)(mNormals.size()/3) ) - { - const NxF32 *n = &mNormals[nindex*3]; - - v.mNormal[0] = n[0]; - v.mNormal[1] = n[1]; - v.mNormal[2] = n[2]; - } - } - } - - if ( index >= 0 && index < (NxI32)(mVerts.size()/3) ) - { - - const NxF32 *p = &mVerts[index*3]; - - v.mPos[0] = p[0]; - v.mPos[1] = p[1]; - v.mPos[2] = p[2]; - } - -} - -NxI32 OBJ::ParseLine(NxI32 lineno,NxI32 argc,const char **argv) // return TRUE to continue parsing, return FALSE to abort parsing process -{ - NxI32 ret = 0; - - if ( argc >= 1 ) - { - const char *foo = argv[0]; - if ( *foo != '#' ) - { - if ( _stricmp(argv[0],"v") == 0 && argc == 4 ) - { - NxF32 vx = (NxF32) atof( argv[1] ); - NxF32 vy = (NxF32) atof( argv[2] ); - NxF32 vz = (NxF32) atof( argv[3] ); - mVerts.push_back(vx); - mVerts.push_back(vy); - mVerts.push_back(vz); - } - else if ( _stricmp(argv[0],"vt") == 0 && (argc == 3 || argc == 4)) - { - // ignore 4rd component if present - NxF32 tx = (NxF32) atof( argv[1] ); - NxF32 ty = (NxF32) atof( argv[2] ); - mTexels.push_back(tx); - mTexels.push_back(ty); - } - else if ( _stricmp(argv[0],"vn") == 0 && argc == 4 ) - { - NxF32 normalx = (NxF32) atof(argv[1]); - NxF32 normaly = (NxF32) atof(argv[2]); - NxF32 normalz = (NxF32) atof(argv[3]); - mNormals.push_back(normalx); - mNormals.push_back(normaly); - mNormals.push_back(normalz); - } - else if ( _stricmp(argv[0],"f") == 0 && argc >= 4 ) - { - GeometryVertex v[32]; - - NxI32 vcount = argc-1; - - for (NxI32 i=1; iNodeTriangle(&v[0],&v[1],&v[2], mTextured); - - if ( vcount >=3 ) // do the fan - { - for (NxI32 i=2; i<(vcount-1); i++) - { - mCallback->NodeTriangle(&v[0],&v[i],&v[i+1], mTextured); - } - } - - } - } - } - - return ret; -} - - - - -class BuildMesh : public GeometryInterface -{ -public: - - NxI32 GetIndex(const NxF32 *p, const NxF32 *texCoord) - { - - NxI32 vcount = (NxI32)mVertices.size()/3; - - if(vcount>0) - { - //New MS STL library checks indices in debug build, so zero causes an assert if it is empty. - const NxF32 *v = &mVertices[0]; - const NxF32 *t = texCoord != NULL ? &mTexCoords[0] : NULL; - - for (NxI32 i=0; imPos, textured ? v1->mTexel : NULL) ); - mIndices.push_back( GetIndex(v2->mPos, textured ? v2->mTexel : NULL) ); - mIndices.push_back( GetIndex(v3->mPos, textured ? v3->mTexel : NULL) ); - } - - const FloatVector& GetVertices(void) const { return mVertices; }; - const FloatVector& GetTexCoords(void) const { return mTexCoords; }; - const IntVector& GetIndices(void) const { return mIndices; }; - -private: - FloatVector mVertices; - FloatVector mTexCoords; - IntVector mIndices; -}; - -}; - -using namespace WAVEFRONT; - -WavefrontObj::WavefrontObj(void) -{ - mVertexCount = 0; - mTriCount = 0; - mIndices = 0; - mVertices = NULL; - mTexCoords = NULL; -} - -WavefrontObj::~WavefrontObj(void) -{ - delete mIndices; - delete mVertices; -} - -NxU32 WavefrontObj::loadObj(const char *fname, bool textured) // load a wavefront obj returns number of triangles that were loaded. Data is persists until the class is destructed. -{ - - NxU32 ret = 0; - - delete mVertices; - mVertices = 0; - delete mIndices; - mIndices = 0; - mVertexCount = 0; - mTriCount = 0; - - - BuildMesh bm; - - OBJ obj; - - obj.LoadMesh(fname,&bm, textured); - - - const FloatVector &vlist = bm.GetVertices(); - const IntVector &indices = bm.GetIndices(); - if ( vlist.size() ) - { - mVertexCount = (NxI32)vlist.size()/3; - mVertices = new NxF32[mVertexCount*3]; - memcpy( mVertices, &vlist[0], sizeof(NxF32)*mVertexCount*3 ); - - if (textured) - { - mTexCoords = new NxF32[mVertexCount * 2]; - const FloatVector& tList = bm.GetTexCoords(); - memcpy( mTexCoords, &tList[0], sizeof(NxF32) * mVertexCount * 2); - } - - mTriCount = (NxI32)indices.size()/3; - mIndices = new NxU32[mTriCount*3*sizeof(NxU32)]; - memcpy(mIndices, &indices[0], sizeof(NxU32)*mTriCount*3); - ret = mTriCount; - } - - - return ret; -} - -#endif diff --git a/Engine/lib/convexDecomp/wavefront.h b/Engine/lib/convexDecomp/wavefront.h deleted file mode 100644 index 601d28885..000000000 --- a/Engine/lib/convexDecomp/wavefront.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef WAVEFRONT_OBJ_H - -#define WAVEFRONT_OBJ_H - -/* - -wavefront.h : A very small code snippet to read a Wavefront OBJ file into memory. - -*/ - -/*! -** -** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com -** -** Portions of this source has been released with the PhysXViewer application, as well as -** Rocket, CreateDynamics, ODF, and as a number of sample code snippets. -** -** If you find this code useful or you are feeling particularily generous I would -** ask that you please go to http://www.amillionpixels.us and make a donation -** to Troy DeMolay. -** -** DeMolay is a youth group for young men between the ages of 12 and 21. -** It teaches strong moral principles, as well as leadership skills and -** public speaking. The donations page uses the 'pay for pixels' paradigm -** where, in this case, a pixel is only a single penny. Donations can be -** made for as small as $4 or as high as a $100 block. Each person who donates -** will get a link to their own site as well as acknowledgement on the -** donations blog located here http://www.amillionpixels.blogspot.com/ -** -** If you wish to contact me you can use the following methods: -** -** Skype ID: jratcliff63367 -** Yahoo: jratcliff63367 -** AOL: jratcliff1961 -** email: jratcliffscarab@gmail.com -** -** -** The MIT license: -** -** Permission is hereby granted, free of charge, to any person obtaining a copy -** of this software and associated documentation files (the "Software"), to deal -** in the Software without restriction, including without limitation the rights -** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -** copies of the Software, and to permit persons to whom the Software is furnished -** to do so, subject to the following conditions: -** -** The above copyright notice and this permission notice shall be included in all -** copies or substantial portions of the Software. - -** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -*/ - -#include "NvUserMemAlloc.h" - -class WavefrontObj -{ -public: - - WavefrontObj(void); - ~WavefrontObj(void); - - NxU32 loadObj(const char *fname, bool textured); // load a wavefront obj returns number of triangles that were loaded. Data is persists until the class is destructed. - - NxU32 mVertexCount; - NxU32 mTriCount; - NxU32 *mIndices; - NxF32 *mVertices; - NxF32 *mTexCoords; -}; - -#endif diff --git a/Engine/lib/convexMath/CMakeLists.txt b/Engine/lib/convexMath/CMakeLists.txt new file mode 100644 index 000000000..1cd502166 --- /dev/null +++ b/Engine/lib/convexMath/CMakeLists.txt @@ -0,0 +1,3 @@ +file(GLOB CONVEX_DECOMP_SOURCES "*.cpp") +add_library(convexMath STATIC ${CONVEX_DECOMP_SOURCES}) +target_include_directories(convexMath PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/Engine/lib/convexMath/FloatMath.cpp b/Engine/lib/convexMath/FloatMath.cpp new file mode 100644 index 000000000..481e87510 --- /dev/null +++ b/Engine/lib/convexMath/FloatMath.cpp @@ -0,0 +1,17 @@ +#include +#include +#include +#include +#include +#include +#include "FloatMath.h" +#include + +#define REAL float + +#include "FloatMath.inl" + +#undef REAL +#define REAL double + +#include "FloatMath.inl" diff --git a/Engine/lib/convexMath/FloatMath.h b/Engine/lib/convexMath/FloatMath.h new file mode 100644 index 000000000..71b6043ce --- /dev/null +++ b/Engine/lib/convexMath/FloatMath.h @@ -0,0 +1,525 @@ +#ifndef FLOAT_MATH_LIB_H + +#define FLOAT_MATH_LIB_H + + +#include +#include + +namespace FLOAT_MATH +{ + +enum FM_ClipState +{ + FMCS_XMIN = (1<<0), + FMCS_XMAX = (1<<1), + FMCS_YMIN = (1<<2), + FMCS_YMAX = (1<<3), + FMCS_ZMIN = (1<<4), + FMCS_ZMAX = (1<<5), +}; + +enum FM_Axis +{ + FM_XAXIS = (1<<0), + FM_YAXIS = (1<<1), + FM_ZAXIS = (1<<2) +}; + +enum LineSegmentType +{ + LS_START, + LS_MIDDLE, + LS_END +}; + + +const float FM_PI = 3.1415926535897932384626433832795028841971693993751f; +const float FM_DEG_TO_RAD = ((2.0f * FM_PI) / 360.0f); +const float FM_RAD_TO_DEG = (360.0f / (2.0f * FM_PI)); + +//***************** Float versions +//*** +//*** vectors are assumed to be 3 floats or 3 doubles representing X, Y, Z +//*** quaternions are assumed to be 4 floats or 4 doubles representing X,Y,Z,W +//*** matrices are assumed to be 16 floats or 16 doubles representing a standard D3D or OpenGL style 4x4 matrix +//*** bounding volumes are expressed as two sets of 3 floats/double representing bmin(x,y,z) and bmax(x,y,z) +//*** Plane equations are assumed to be 4 floats or 4 doubles representing Ax,By,Cz,D + +FM_Axis fm_getDominantAxis(const float normal[3]); +FM_Axis fm_getDominantAxis(const double normal[3]); + +void fm_decomposeTransform(const float local_transform[16],float trans[3],float rot[4],float scale[3]); +void fm_decomposeTransform(const double local_transform[16],double trans[3],double rot[4],double scale[3]); + +void fm_multiplyTransform(const float *pA,const float *pB,float *pM); +void fm_multiplyTransform(const double *pA,const double *pB,double *pM); + +void fm_inverseTransform(const float matrix[16],float inverse_matrix[16]); +void fm_inverseTransform(const double matrix[16],double inverse_matrix[16]); + +void fm_identity(float matrix[16]); // set 4x4 matrix to identity. +void fm_identity(double matrix[16]); // set 4x4 matrix to identity. + +void fm_inverseRT(const float matrix[16], const float pos[3], float t[3]); // inverse rotate translate the point. +void fm_inverseRT(const double matrix[16],const double pos[3],double t[3]); // inverse rotate translate the point. + +void fm_transform(const float matrix[16], const float pos[3], float t[3]); // rotate and translate this point. +void fm_transform(const double matrix[16],const double pos[3],double t[3]); // rotate and translate this point. + +float fm_getDeterminant(const float matrix[16]); +double fm_getDeterminant(const double matrix[16]); + +void fm_getSubMatrix(int32_t ki,int32_t kj,float pDst[16],const float matrix[16]); +void fm_getSubMatrix(int32_t ki,int32_t kj,double pDst[16],const float matrix[16]); + +void fm_rotate(const float matrix[16],const float pos[3],float t[3]); // only rotate the point by a 4x4 matrix, don't translate. +void fm_rotate(const double matrix[16],const double pos[3],double t[3]); // only rotate the point by a 4x4 matrix, don't translate. + +void fm_eulerToMatrix(float ax,float ay,float az,float matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) +void fm_eulerToMatrix(double ax,double ay,double az,double matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) + +void fm_getAABB(uint32_t vcount,const float *points,uint32_t pstride,float bmin[3],float bmax[3]); +void fm_getAABB(uint32_t vcount,const double *points,uint32_t pstride,double bmin[3],double bmax[3]); + +void fm_getAABBCenter(const float bmin[3],const float bmax[3],float center[3]); +void fm_getAABBCenter(const double bmin[3],const double bmax[3],double center[3]); + +void fm_transformAABB(const float bmin[3],const float bmax[3],const float matrix[16],float tbmin[3],float tbmax[3]); +void fm_transformAABB(const double bmin[3],const double bmax[3],const double matrix[16],double tbmin[3],double tbmax[3]); + +void fm_eulerToQuat(float x,float y,float z,float quat[4]); // convert euler angles to quaternion. +void fm_eulerToQuat(double x,double y,double z,double quat[4]); // convert euler angles to quaternion. + +void fm_quatToEuler(const float quat[4],float &ax,float &ay,float &az); +void fm_quatToEuler(const double quat[4],double &ax,double &ay,double &az); + +void fm_eulerToQuat(const float euler[3],float quat[4]); // convert euler angles to quaternion. Angles must be radians not degrees! +void fm_eulerToQuat(const double euler[3],double quat[4]); // convert euler angles to quaternion. + +void fm_scale(float x,float y,float z,float matrix[16]); // apply scale to the matrix. +void fm_scale(double x,double y,double z,double matrix[16]); // apply scale to the matrix. + +void fm_eulerToQuatDX(float x,float y,float z,float quat[4]); // convert euler angles to quaternion using the fucked up DirectX method +void fm_eulerToQuatDX(double x,double y,double z,double quat[4]); // convert euler angles to quaternion using the fucked up DirectX method + +void fm_eulerToMatrixDX(float x,float y,float z,float matrix[16]); // convert euler angles to quaternion using the fucked up DirectX method. +void fm_eulerToMatrixDX(double x,double y,double z,double matrix[16]); // convert euler angles to quaternion using the fucked up DirectX method. + +void fm_quatToMatrix(const float quat[4],float matrix[16]); // convert quaternion rotation to matrix, translation set to zero. +void fm_quatToMatrix(const double quat[4],double matrix[16]); // convert quaternion rotation to matrix, translation set to zero. + +void fm_quatRotate(const float quat[4],const float v[3],float r[3]); // rotate a vector directly by a quaternion. +void fm_quatRotate(const double quat[4],const double v[3],double r[3]); // rotate a vector directly by a quaternion. + +void fm_getTranslation(const float matrix[16],float t[3]); +void fm_getTranslation(const double matrix[16],double t[3]); + +void fm_setTranslation(const float *translation,float matrix[16]); +void fm_setTranslation(const double *translation,double matrix[16]); + +void fm_multiplyQuat(const float *qa,const float *qb,float *quat); +void fm_multiplyQuat(const double *qa,const double *qb,double *quat); + +void fm_matrixToQuat(const float matrix[16],float quat[4]); // convert the 3x3 portion of a 4x4 matrix into a quaternion as x,y,z,w +void fm_matrixToQuat(const double matrix[16],double quat[4]); // convert the 3x3 portion of a 4x4 matrix into a quaternion as x,y,z,w + +float fm_sphereVolume(float radius); // return's the volume of a sphere of this radius (4/3 PI * R cubed ) +double fm_sphereVolume(double radius); // return's the volume of a sphere of this radius (4/3 PI * R cubed ) + +float fm_cylinderVolume(float radius,float h); +double fm_cylinderVolume(double radius,double h); + +float fm_capsuleVolume(float radius,float h); +double fm_capsuleVolume(double radius,double h); + +float fm_distance(const float p1[3],const float p2[3]); +double fm_distance(const double p1[3],const double p2[3]); + +float fm_distanceSquared(const float p1[3],const float p2[3]); +double fm_distanceSquared(const double p1[3],const double p2[3]); + +float fm_distanceSquaredXZ(const float p1[3],const float p2[3]); +double fm_distanceSquaredXZ(const double p1[3],const double p2[3]); + +float fm_computePlane(const float p1[3],const float p2[3],const float p3[3],float *n); // return D +double fm_computePlane(const double p1[3],const double p2[3],const double p3[3],double *n); // return D + +float fm_distToPlane(const float plane[4],const float pos[3]); // computes the distance of this point from the plane. +double fm_distToPlane(const double plane[4],const double pos[3]); // computes the distance of this point from the plane. + +float fm_dot(const float p1[3],const float p2[3]); +double fm_dot(const double p1[3],const double p2[3]); + +void fm_cross(float cross[3],const float a[3],const float b[3]); +void fm_cross(double cross[3],const double a[3],const double b[3]); + +float fm_computeNormalVector(float n[3],const float p1[3],const float p2[3]); // as P2-P1 normalized. +double fm_computeNormalVector(double n[3],const double p1[3],const double p2[3]); // as P2-P1 normalized. + +bool fm_computeWindingOrder(const float p1[3],const float p2[3],const float p3[3]); // returns true if the triangle is clockwise. +bool fm_computeWindingOrder(const double p1[3],const double p2[3],const double p3[3]); // returns true if the triangle is clockwise. + +float fm_normalize(float n[3]); // normalize this vector and return the distance +double fm_normalize(double n[3]); // normalize this vector and return the distance + +float fm_normalizeQuat(float n[4]); // normalize this quat +double fm_normalizeQuat(double n[4]); // normalize this quat + +void fm_matrixMultiply(const float A[16],const float B[16],float dest[16]); +void fm_matrixMultiply(const double A[16],const double B[16],double dest[16]); + +void fm_composeTransform(const float position[3],const float quat[4],const float scale[3],float matrix[16]); +void fm_composeTransform(const double position[3],const double quat[4],const double scale[3],double matrix[16]); + +float fm_computeArea(const float p1[3],const float p2[3],const float p3[3]); +double fm_computeArea(const double p1[3],const double p2[3],const double p3[3]); + +void fm_lerp(const float p1[3],const float p2[3],float dest[3],float lerpValue); +void fm_lerp(const double p1[3],const double p2[3],double dest[3],double lerpValue); + +bool fm_insideTriangleXZ(const float test[3],const float p1[3],const float p2[3],const float p3[3]); +bool fm_insideTriangleXZ(const double test[3],const double p1[3],const double p2[3],const double p3[3]); + +bool fm_insideAABB(const float pos[3],const float bmin[3],const float bmax[3]); +bool fm_insideAABB(const double pos[3],const double bmin[3],const double bmax[3]); + +bool fm_insideAABB(const float obmin[3],const float obmax[3],const float tbmin[3],const float tbmax[3]); // test if bounding box tbmin/tmbax is fully inside obmin/obmax +bool fm_insideAABB(const double obmin[3],const double obmax[3],const double tbmin[3],const double tbmax[3]); // test if bounding box tbmin/tmbax is fully inside obmin/obmax + +uint32_t fm_clipTestPoint(const float bmin[3],const float bmax[3],const float pos[3]); +uint32_t fm_clipTestPoint(const double bmin[3],const double bmax[3],const double pos[3]); + +uint32_t fm_clipTestPointXZ(const float bmin[3],const float bmax[3],const float pos[3]); // only tests X and Z, not Y +uint32_t fm_clipTestPointXZ(const double bmin[3],const double bmax[3],const double pos[3]); // only tests X and Z, not Y + + +uint32_t fm_clipTestAABB(const float bmin[3],const float bmax[3],const float p1[3],const float p2[3],const float p3[3],uint32_t &andCode); +uint32_t fm_clipTestAABB(const double bmin[3],const double bmax[3],const double p1[3],const double p2[3],const double p3[3],uint32_t &andCode); + + +bool fm_lineTestAABBXZ(const float p1[3],const float p2[3],const float bmin[3],const float bmax[3],float &time); +bool fm_lineTestAABBXZ(const double p1[3],const double p2[3],const double bmin[3],const double bmax[3],double &time); + +bool fm_lineTestAABB(const float p1[3],const float p2[3],const float bmin[3],const float bmax[3],float &time); +bool fm_lineTestAABB(const double p1[3],const double p2[3],const double bmin[3],const double bmax[3],double &time); + + +void fm_initMinMax(const float p[3],float bmin[3],float bmax[3]); +void fm_initMinMax(const double p[3],double bmin[3],double bmax[3]); + +void fm_initMinMax(float bmin[3],float bmax[3]); +void fm_initMinMax(double bmin[3],double bmax[3]); + +void fm_minmax(const float p[3],float bmin[3],float bmax[3]); // accumulate to a min-max value +void fm_minmax(const double p[3],double bmin[3],double bmax[3]); // accumulate to a min-max value + +// Computes the diagonal length of the bounding box and then inflates the bounding box on all sides +// by the ratio provided. +void fm_inflateMinMax(float bmin[3], float bmax[3], float ratio); +void fm_inflateMinMax(double bmin[3], double bmax[3], double ratio); + +float fm_solveX(const float plane[4],float y,float z); // solve for X given this plane equation and the other two components. +double fm_solveX(const double plane[4],double y,double z); // solve for X given this plane equation and the other two components. + +float fm_solveY(const float plane[4],float x,float z); // solve for Y given this plane equation and the other two components. +double fm_solveY(const double plane[4],double x,double z); // solve for Y given this plane equation and the other two components. + +float fm_solveZ(const float plane[4],float x,float y); // solve for Z given this plane equation and the other two components. +double fm_solveZ(const double plane[4],double x,double y); // solve for Z given this plane equation and the other two components. + +bool fm_computeBestFitPlane(uint32_t vcount, // number of input data points + const float *points, // starting address of points array. + uint32_t vstride, // stride between input points. + const float *weights, // *optional point weighting values. + uint32_t wstride, // weight stride for each vertex. + float plane[4], // Best fit plane equation + float center[3]); // Best fit weighted center of input points + +bool fm_computeBestFitPlane(uint32_t vcount, // number of input data points + const double *points, // starting address of points array. + uint32_t vstride, // stride between input points. + const double *weights, // *optional point weighting values. + uint32_t wstride, // weight stride for each vertex. + double plane[4], + double center[3]); + +// Computes the average center of a set of data points +bool fm_computeCentroid(uint32_t vcount, // number of input data points + const float *points, // starting address of points array. + float *center); + +bool fm_computeCentroid(uint32_t vcount, // number of input data points + const double *points, // starting address of points array. + double *center); + +// Compute centroid of a triangle mesh; takes area of each triangle into account +// weighted average +bool fm_computeCentroid(uint32_t vcount, // number of input data points + const float *points, // starting address of points array. + uint32_t triangleCount, + const uint32_t *indices, + float *center); + +// Compute centroid of a triangle mesh; takes area of each triangle into account +// weighted average +bool fm_computeCentroid(uint32_t vcount, // number of input data points + const double *points, // starting address of points array. + uint32_t triangleCount, + const uint32_t *indices, + double *center); + + +float fm_computeBestFitAABB(uint32_t vcount,const float *points,uint32_t pstride,float bmin[3],float bmax[3]); // returns the diagonal distance +double fm_computeBestFitAABB(uint32_t vcount,const double *points,uint32_t pstride,double bmin[3],double bmax[3]); // returns the diagonal distance + +float fm_computeBestFitSphere(uint32_t vcount,const float *points,uint32_t pstride,float center[3]); +double fm_computeBestFitSphere(uint32_t vcount,const double *points,uint32_t pstride,double center[3]); + +bool fm_lineSphereIntersect(const float center[3],float radius,const float p1[3],const float p2[3],float intersect[3]); +bool fm_lineSphereIntersect(const double center[3],double radius,const double p1[3],const double p2[3],double intersect[3]); + +bool fm_intersectRayAABB(const float bmin[3],const float bmax[3],const float pos[3],const float dir[3],float intersect[3]); +bool fm_intersectLineSegmentAABB(const float bmin[3],const float bmax[3],const float p1[3],const float p2[3],float intersect[3]); + +bool fm_lineIntersectsTriangle(const float rayStart[3],const float rayEnd[3],const float p1[3],const float p2[3],const float p3[3],float sect[3]); +bool fm_lineIntersectsTriangle(const double rayStart[3],const double rayEnd[3],const double p1[3],const double p2[3],const double p3[3],double sect[3]); + +bool fm_rayIntersectsTriangle(const float origin[3],const float dir[3],const float v0[3],const float v1[3],const float v2[3],float &t); +bool fm_rayIntersectsTriangle(const double origin[3],const double dir[3],const double v0[3],const double v1[3],const double v2[3],double &t); + +bool fm_raySphereIntersect(const float center[3],float radius,const float pos[3],const float dir[3],float distance,float intersect[3]); +bool fm_raySphereIntersect(const double center[3],double radius,const double pos[3],const double dir[3],double distance,double intersect[3]); + +void fm_catmullRom(float out_vector[3],const float p1[3],const float p2[3],const float p3[3],const float *p4, const float s); +void fm_catmullRom(double out_vector[3],const double p1[3],const double p2[3],const double p3[3],const double *p4, const double s); + +bool fm_intersectAABB(const float bmin1[3],const float bmax1[3],const float bmin2[3],const float bmax2[3]); +bool fm_intersectAABB(const double bmin1[3],const double bmax1[3],const double bmin2[3],const double bmax2[3]); + + +// computes the rotation quaternion to go from unit-vector v0 to unit-vector v1 +void fm_rotationArc(const float v0[3],const float v1[3],float quat[4]); +void fm_rotationArc(const double v0[3],const double v1[3],double quat[4]); + +float fm_distancePointLineSegment(const float Point[3],const float LineStart[3],const float LineEnd[3],float intersection[3],LineSegmentType &type,float epsilon); +double fm_distancePointLineSegment(const double Point[3],const double LineStart[3],const double LineEnd[3],double intersection[3],LineSegmentType &type,double epsilon); + + +bool fm_colinear(const double p1[3],const double p2[3],const double p3[3],double epsilon=0.999); // true if these three points in a row are co-linear +bool fm_colinear(const float p1[3],const float p2[3],const float p3[3],float epsilon=0.999f); + +bool fm_colinear(const float a1[3],const float a2[3],const float b1[3],const float b2[3],float epsilon=0.999f); // true if these two line segments are co-linear. +bool fm_colinear(const double a1[3],const double a2[3],const double b1[3],const double b2[3],double epsilon=0.999); // true if these two line segments are co-linear. + +enum IntersectResult +{ + IR_DONT_INTERSECT, + IR_DO_INTERSECT, + IR_COINCIDENT, + IR_PARALLEL, +}; + +IntersectResult fm_intersectLineSegments2d(const float a1[3], const float a2[3], const float b1[3], const float b2[3], float intersectionPoint[3]); +IntersectResult fm_intersectLineSegments2d(const double a1[3],const double a2[3],const double b1[3],const double b2[3],double intersectionPoint[3]); + +IntersectResult fm_intersectLineSegments2dTime(const float a1[3], const float a2[3], const float b1[3], const float b2[3],float &t1,float &t2); +IntersectResult fm_intersectLineSegments2dTime(const double a1[3],const double a2[3],const double b1[3],const double b2[3],double &t1,double &t2); + +// Plane-Triangle splitting + +enum PlaneTriResult +{ + PTR_ON_PLANE, + PTR_FRONT, + PTR_BACK, + PTR_SPLIT, +}; + +PlaneTriResult fm_planeTriIntersection(const float plane[4], // the plane equation in Ax+By+Cz+D format + const float *triangle, // the source triangle. + uint32_t tstride, // stride in bytes of the input and output *vertices* + float epsilon, // the co-planer epsilon value. + float *front, // the triangle in front of the + uint32_t &fcount, // number of vertices in the 'front' triangle + float *back, // the triangle in back of the plane + uint32_t &bcount); // the number of vertices in the 'back' triangle. + + +PlaneTriResult fm_planeTriIntersection(const double plane[4], // the plane equation in Ax+By+Cz+D format + const double *triangle, // the source triangle. + uint32_t tstride, // stride in bytes of the input and output *vertices* + double epsilon, // the co-planer epsilon value. + double *front, // the triangle in front of the + uint32_t &fcount, // number of vertices in the 'front' triangle + double *back, // the triangle in back of the plane + uint32_t &bcount); // the number of vertices in the 'back' triangle. + + +bool fm_intersectPointPlane(const float p1[3],const float p2[3],float *split,const float plane[4]); +bool fm_intersectPointPlane(const double p1[3],const double p2[3],double *split,const double plane[4]); + +PlaneTriResult fm_getSidePlane(const float p[3],const float plane[4],float epsilon); +PlaneTriResult fm_getSidePlane(const double p[3],const double plane[4],double epsilon); + + +void fm_computeBestFitOBB(uint32_t vcount,const float *points,uint32_t pstride,float *sides,float matrix[16],bool bruteForce=true); +void fm_computeBestFitOBB(uint32_t vcount,const double *points,uint32_t pstride,double *sides,double matrix[16],bool bruteForce=true); + +void fm_computeBestFitOBB(uint32_t vcount,const float *points,uint32_t pstride,float *sides,float pos[3],float quat[4],bool bruteForce=true); +void fm_computeBestFitOBB(uint32_t vcount,const double *points,uint32_t pstride,double *sides,double pos[3],double quat[4],bool bruteForce=true); + +void fm_computeBestFitABB(uint32_t vcount,const float *points,uint32_t pstride,float *sides,float pos[3]); +void fm_computeBestFitABB(uint32_t vcount,const double *points,uint32_t pstride,double *sides,double pos[3]); + + +//** Note, if the returned capsule height is less than zero, then you must represent it is a sphere of size radius. +void fm_computeBestFitCapsule(uint32_t vcount,const float *points,uint32_t pstride,float &radius,float &height,float matrix[16],bool bruteForce=true); +void fm_computeBestFitCapsule(uint32_t vcount,const double *points,uint32_t pstride,float &radius,float &height,double matrix[16],bool bruteForce=true); + + +void fm_planeToMatrix(const float plane[4],float matrix[16]); // convert a plane equation to a 4x4 rotation matrix. Reference vector is 0,1,0 +void fm_planeToQuat(const float plane[4],float quat[4],float pos[3]); // convert a plane equation to a quaternion and translation + +void fm_planeToMatrix(const double plane[4],double matrix[16]); // convert a plane equation to a 4x4 rotation matrix +void fm_planeToQuat(const double plane[4],double quat[4],double pos[3]); // convert a plane equation to a quaternion and translation + +inline void fm_doubleToFloat3(const double p[3],float t[3]) { t[0] = (float) p[0]; t[1] = (float)p[1]; t[2] = (float)p[2]; }; +inline void fm_floatToDouble3(const float p[3],double t[3]) { t[0] = (double)p[0]; t[1] = (double)p[1]; t[2] = (double)p[2]; }; + + +void fm_eulerMatrix(float ax,float ay,float az,float matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) +void fm_eulerMatrix(double ax,double ay,double az,double matrix[16]); // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) + + +float fm_computeMeshVolume(const float *vertices,uint32_t tcount,const uint32_t *indices); +double fm_computeMeshVolume(const double *vertices,uint32_t tcount,const uint32_t *indices); + + +#define FM_DEFAULT_GRANULARITY 0.001f // 1 millimeter is the default granularity + +class fm_VertexIndex +{ +public: + virtual uint32_t getIndex(const float pos[3],bool &newPos) = 0; // get welded index for this float vector[3] + virtual uint32_t getIndex(const double pos[3],bool &newPos) = 0; // get welded index for this double vector[3] + virtual const float * getVerticesFloat(void) const = 0; + virtual const double * getVerticesDouble(void) const = 0; + virtual const float * getVertexFloat(uint32_t index) const = 0; + virtual const double * getVertexDouble(uint32_t index) const = 0; + virtual uint32_t getVcount(void) const = 0; + virtual bool isDouble(void) const = 0; + virtual bool saveAsObj(const char *fname,uint32_t tcount,uint32_t *indices) = 0; +}; + +fm_VertexIndex * fm_createVertexIndex(double granularity,bool snapToGrid); // create an indexed vertex system for doubles +fm_VertexIndex * fm_createVertexIndex(float granularity,bool snapToGrid); // create an indexed vertex system for floats +void fm_releaseVertexIndex(fm_VertexIndex *vindex); + + +class fm_Triangulate +{ +public: + virtual const double * triangulate3d(uint32_t pcount, + const double *points, + uint32_t vstride, + uint32_t &tcount, + bool consolidate, + double epsilon) = 0; + + virtual const float * triangulate3d(uint32_t pcount, + const float *points, + uint32_t vstride, + uint32_t &tcount, + bool consolidate, + float epsilon) = 0; +}; + +fm_Triangulate * fm_createTriangulate(void); +void fm_releaseTriangulate(fm_Triangulate *t); + + +const float * fm_getPoint(const float *points,uint32_t pstride,uint32_t index); +const double * fm_getPoint(const double *points,uint32_t pstride,uint32_t index); + +bool fm_insideTriangle(float Ax, float Ay,float Bx, float By,float Cx, float Cy,float Px, float Py); +bool fm_insideTriangle(double Ax, double Ay,double Bx, double By,double Cx, double Cy,double Px, double Py); +float fm_areaPolygon2d(uint32_t pcount,const float *points,uint32_t pstride); +double fm_areaPolygon2d(uint32_t pcount,const double *points,uint32_t pstride); + +bool fm_pointInsidePolygon2d(uint32_t pcount,const float *points,uint32_t pstride,const float *point,uint32_t xindex=0,uint32_t yindex=1); +bool fm_pointInsidePolygon2d(uint32_t pcount,const double *points,uint32_t pstride,const double *point,uint32_t xindex=0,uint32_t yindex=1); + +uint32_t fm_consolidatePolygon(uint32_t pcount,const float *points,uint32_t pstride,float *dest,float epsilon=0.999999f); // collapses co-linear edges. +uint32_t fm_consolidatePolygon(uint32_t pcount,const double *points,uint32_t pstride,double *dest,double epsilon=0.999999); // collapses co-linear edges. + + +bool fm_computeSplitPlane(uint32_t vcount,const double *vertices,uint32_t tcount,const uint32_t *indices,double *plane); +bool fm_computeSplitPlane(uint32_t vcount,const float *vertices,uint32_t tcount,const uint32_t *indices,float *plane); + +void fm_nearestPointInTriangle(const float *pos,const float *p1,const float *p2,const float *p3,float *nearest); +void fm_nearestPointInTriangle(const double *pos,const double *p1,const double *p2,const double *p3,double *nearest); + +float fm_areaTriangle(const float *p1,const float *p2,const float *p3); +double fm_areaTriangle(const double *p1,const double *p2,const double *p3); + +void fm_subtract(const float *A,const float *B,float *diff); // compute A-B and store the result in 'diff' +void fm_subtract(const double *A,const double *B,double *diff); // compute A-B and store the result in 'diff' + +void fm_multiply(float *A,float scalar); +void fm_multiply(double *A,double scalar); + +void fm_add(const float *A,const float *B,float *sum); +void fm_add(const double *A,const double *B,double *sum); + +void fm_copy3(const float *source,float *dest); +void fm_copy3(const double *source,double *dest); + +// re-indexes an indexed triangle mesh but drops unused vertices. The output_indices can be the same pointer as the input indices. +// the output_vertices can point to the input vertices if you desire. The output_vertices buffer should be at least the same size +// is the input buffer. The routine returns the new vertex count after re-indexing. +uint32_t fm_copyUniqueVertices(uint32_t vcount,const float *input_vertices,float *output_vertices,uint32_t tcount,const uint32_t *input_indices,uint32_t *output_indices); +uint32_t fm_copyUniqueVertices(uint32_t vcount,const double *input_vertices,double *output_vertices,uint32_t tcount,const uint32_t *input_indices,uint32_t *output_indices); + +bool fm_isMeshCoplanar(uint32_t tcount,const uint32_t *indices,const float *vertices,bool doubleSided); // returns true if this collection of indexed triangles are co-planar! +bool fm_isMeshCoplanar(uint32_t tcount,const uint32_t *indices,const double *vertices,bool doubleSided); // returns true if this collection of indexed triangles are co-planar! + +bool fm_samePlane(const float p1[4],const float p2[4],float normalEpsilon=0.01f,float dEpsilon=0.001f,bool doubleSided=false); // returns true if these two plane equations are identical within an epsilon +bool fm_samePlane(const double p1[4],const double p2[4],double normalEpsilon=0.01,double dEpsilon=0.001,bool doubleSided=false); + +void fm_OBBtoAABB(const float obmin[3],const float obmax[3],const float matrix[16],float abmin[3],float abmax[3]); + +// a utility class that will tessellate a mesh. +class fm_Tesselate +{ +public: + virtual const uint32_t * tesselate(fm_VertexIndex *vindex,uint32_t tcount,const uint32_t *indices,float longEdge,uint32_t maxDepth,uint32_t &outcount) = 0; +}; + +fm_Tesselate * fm_createTesselate(void); +void fm_releaseTesselate(fm_Tesselate *t); + +void fm_computeMeanNormals(uint32_t vcount, // the number of vertices + const float *vertices, // the base address of the vertex position data. + uint32_t vstride, // the stride between position data. + float *normals, // the base address of the destination for mean vector normals + uint32_t nstride, // the stride between normals + uint32_t tcount, // the number of triangles + const uint32_t *indices); // the triangle indices + +void fm_computeMeanNormals(uint32_t vcount, // the number of vertices + const double *vertices, // the base address of the vertex position data. + uint32_t vstride, // the stride between position data. + double *normals, // the base address of the destination for mean vector normals + uint32_t nstride, // the stride between normals + uint32_t tcount, // the number of triangles + const uint32_t *indices); // the triangle indices + + +bool fm_isValidTriangle(const float *p1,const float *p2,const float *p3,float epsilon=0.00001f); +bool fm_isValidTriangle(const double *p1,const double *p2,const double *p3,double epsilon=0.00001f); + + +}; // end of namespace + +#endif diff --git a/Engine/lib/convexMath/FloatMath.inl b/Engine/lib/convexMath/FloatMath.inl new file mode 100644 index 000000000..fde892c5f --- /dev/null +++ b/Engine/lib/convexMath/FloatMath.inl @@ -0,0 +1,5280 @@ +// a set of routines that let you do common 3d math +// operations without any vector, matrix, or quaternion +// classes or templates. +// +// a vector (or point) is a 'float *' to 3 floating point numbers. +// a matrix is a 'float *' to an array of 16 floating point numbers representing a 4x4 transformation matrix compatible with D3D or OGL +// a quaternion is a 'float *' to 4 floats representing a quaternion x,y,z,w +// + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + +namespace FLOAT_MATH +{ + +void fm_inverseRT(const REAL matrix[16],const REAL pos[3],REAL t[3]) // inverse rotate translate the point. +{ + + REAL _x = pos[0] - matrix[3*4+0]; + REAL _y = pos[1] - matrix[3*4+1]; + REAL _z = pos[2] - matrix[3*4+2]; + + // Multiply inverse-translated source vector by inverted rotation transform + + t[0] = (matrix[0*4+0] * _x) + (matrix[0*4+1] * _y) + (matrix[0*4+2] * _z); + t[1] = (matrix[1*4+0] * _x) + (matrix[1*4+1] * _y) + (matrix[1*4+2] * _z); + t[2] = (matrix[2*4+0] * _x) + (matrix[2*4+1] * _y) + (matrix[2*4+2] * _z); + +} + +REAL fm_getDeterminant(const REAL matrix[16]) +{ + REAL tempv[3]; + REAL p0[3]; + REAL p1[3]; + REAL p2[3]; + + + p0[0] = matrix[0*4+0]; + p0[1] = matrix[0*4+1]; + p0[2] = matrix[0*4+2]; + + p1[0] = matrix[1*4+0]; + p1[1] = matrix[1*4+1]; + p1[2] = matrix[1*4+2]; + + p2[0] = matrix[2*4+0]; + p2[1] = matrix[2*4+1]; + p2[2] = matrix[2*4+2]; + + fm_cross(tempv,p1,p2); + + return fm_dot(p0,tempv); + +} + +REAL fm_squared(REAL x) { return x*x; }; + +void fm_decomposeTransform(const REAL local_transform[16],REAL trans[3],REAL rot[4],REAL scale[3]) +{ + + trans[0] = local_transform[12]; + trans[1] = local_transform[13]; + trans[2] = local_transform[14]; + + scale[0] = (REAL)sqrt(fm_squared(local_transform[0*4+0]) + fm_squared(local_transform[0*4+1]) + fm_squared(local_transform[0*4+2])); + scale[1] = (REAL)sqrt(fm_squared(local_transform[1*4+0]) + fm_squared(local_transform[1*4+1]) + fm_squared(local_transform[1*4+2])); + scale[2] = (REAL)sqrt(fm_squared(local_transform[2*4+0]) + fm_squared(local_transform[2*4+1]) + fm_squared(local_transform[2*4+2])); + + REAL m[16]; + memcpy(m,local_transform,sizeof(REAL)*16); + + REAL sx = 1.0f / scale[0]; + REAL sy = 1.0f / scale[1]; + REAL sz = 1.0f / scale[2]; + + m[0*4+0]*=sx; + m[0*4+1]*=sx; + m[0*4+2]*=sx; + + m[1*4+0]*=sy; + m[1*4+1]*=sy; + m[1*4+2]*=sy; + + m[2*4+0]*=sz; + m[2*4+1]*=sz; + m[2*4+2]*=sz; + + fm_matrixToQuat(m,rot); + +} + +void fm_getSubMatrix(int32_t ki,int32_t kj,REAL pDst[16],const REAL matrix[16]) +{ + int32_t row, col; + int32_t dstCol = 0, dstRow = 0; + + for ( col = 0; col < 4; col++ ) + { + if ( col == kj ) + { + continue; + } + for ( dstRow = 0, row = 0; row < 4; row++ ) + { + if ( row == ki ) + { + continue; + } + pDst[dstCol*4+dstRow] = matrix[col*4+row]; + dstRow++; + } + dstCol++; + } +} + +void fm_inverseTransform(const REAL matrix[16],REAL inverse_matrix[16]) +{ + REAL determinant = fm_getDeterminant(matrix); + determinant = 1.0f / determinant; + for (int32_t i = 0; i < 4; i++ ) + { + for (int32_t j = 0; j < 4; j++ ) + { + int32_t sign = 1 - ( ( i + j ) % 2 ) * 2; + REAL subMat[16]; + fm_identity(subMat); + fm_getSubMatrix( i, j, subMat, matrix ); + REAL subDeterminant = fm_getDeterminant(subMat); + inverse_matrix[i*4+j] = ( subDeterminant * sign ) * determinant; + } + } +} + +void fm_identity(REAL matrix[16]) // set 4x4 matrix to identity. +{ + matrix[0*4+0] = 1; + matrix[1*4+1] = 1; + matrix[2*4+2] = 1; + matrix[3*4+3] = 1; + + matrix[1*4+0] = 0; + matrix[2*4+0] = 0; + matrix[3*4+0] = 0; + + matrix[0*4+1] = 0; + matrix[2*4+1] = 0; + matrix[3*4+1] = 0; + + matrix[0*4+2] = 0; + matrix[1*4+2] = 0; + matrix[3*4+2] = 0; + + matrix[0*4+3] = 0; + matrix[1*4+3] = 0; + matrix[2*4+3] = 0; + +} + +void fm_quatToEuler(const REAL quat[4],REAL &ax,REAL &ay,REAL &az) +{ + REAL x = quat[0]; + REAL y = quat[1]; + REAL z = quat[2]; + REAL w = quat[3]; + + REAL sint = (2.0f * w * y) - (2.0f * x * z); + REAL cost_temp = 1.0f - (sint * sint); + REAL cost = 0; + + if ( (REAL)fabs(cost_temp) > 0.001f ) + { + cost = (REAL)sqrt( cost_temp ); + } + + REAL sinv, cosv, sinf, cosf; + if ( (REAL)fabs(cost) > 0.001f ) + { + cost = 1.0f / cost; + sinv = ((2.0f * y * z) + (2.0f * w * x)) * cost; + cosv = (1.0f - (2.0f * x * x) - (2.0f * y * y)) * cost; + sinf = ((2.0f * x * y) + (2.0f * w * z)) * cost; + cosf = (1.0f - (2.0f * y * y) - (2.0f * z * z)) * cost; + } + else + { + sinv = (2.0f * w * x) - (2.0f * y * z); + cosv = 1.0f - (2.0f * x * x) - (2.0f * z * z); + sinf = 0; + cosf = 1.0f; + } + + // compute output rotations + ax = (REAL)atan2( sinv, cosv ); + ay = (REAL)atan2( sint, cost ); + az = (REAL)atan2( sinf, cosf ); + +} + +void fm_eulerToMatrix(REAL ax,REAL ay,REAL az,REAL *matrix) // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) +{ + REAL quat[4]; + fm_eulerToQuat(ax,ay,az,quat); + fm_quatToMatrix(quat,matrix); +} + +void fm_getAABB(uint32_t vcount,const REAL *points,uint32_t pstride,REAL *bmin,REAL *bmax) +{ + + const uint8_t *source = (const uint8_t *) points; + + bmin[0] = points[0]; + bmin[1] = points[1]; + bmin[2] = points[2]; + + bmax[0] = points[0]; + bmax[1] = points[1]; + bmax[2] = points[2]; + + + for (uint32_t i=1; i bmax[0] ) bmax[0] = p[0]; + if ( p[1] > bmax[1] ) bmax[1] = p[1]; + if ( p[2] > bmax[2] ) bmax[2] = p[2]; + + } +} + +void fm_eulerToQuat(const REAL *euler,REAL *quat) // convert euler angles to quaternion. +{ + fm_eulerToQuat(euler[0],euler[1],euler[2],quat); +} + +void fm_eulerToQuat(REAL roll,REAL pitch,REAL yaw,REAL *quat) // convert euler angles to quaternion. +{ + roll *= 0.5f; + pitch *= 0.5f; + yaw *= 0.5f; + + REAL cr = (REAL)cos(roll); + REAL cp = (REAL)cos(pitch); + REAL cy = (REAL)cos(yaw); + + REAL sr = (REAL)sin(roll); + REAL sp = (REAL)sin(pitch); + REAL sy = (REAL)sin(yaw); + + REAL cpcy = cp * cy; + REAL spsy = sp * sy; + REAL spcy = sp * cy; + REAL cpsy = cp * sy; + + quat[0] = ( sr * cpcy - cr * spsy); + quat[1] = ( cr * spcy + sr * cpsy); + quat[2] = ( cr * cpsy - sr * spcy); + quat[3] = cr * cpcy + sr * spsy; +} + +void fm_quatToMatrix(const REAL *quat,REAL *matrix) // convert quaternion rotation to matrix, zeros out the translation component. +{ + + REAL xx = quat[0]*quat[0]; + REAL yy = quat[1]*quat[1]; + REAL zz = quat[2]*quat[2]; + REAL xy = quat[0]*quat[1]; + REAL xz = quat[0]*quat[2]; + REAL yz = quat[1]*quat[2]; + REAL wx = quat[3]*quat[0]; + REAL wy = quat[3]*quat[1]; + REAL wz = quat[3]*quat[2]; + + matrix[0*4+0] = 1 - 2 * ( yy + zz ); + matrix[1*4+0] = 2 * ( xy - wz ); + matrix[2*4+0] = 2 * ( xz + wy ); + + matrix[0*4+1] = 2 * ( xy + wz ); + matrix[1*4+1] = 1 - 2 * ( xx + zz ); + matrix[2*4+1] = 2 * ( yz - wx ); + + matrix[0*4+2] = 2 * ( xz - wy ); + matrix[1*4+2] = 2 * ( yz + wx ); + matrix[2*4+2] = 1 - 2 * ( xx + yy ); + + matrix[3*4+0] = matrix[3*4+1] = matrix[3*4+2] = (REAL) 0.0f; + matrix[0*4+3] = matrix[1*4+3] = matrix[2*4+3] = (REAL) 0.0f; + matrix[3*4+3] =(REAL) 1.0f; + +} + + +void fm_quatRotate(const REAL *quat,const REAL *v,REAL *r) // rotate a vector directly by a quaternion. +{ + REAL left[4]; + + left[0] = quat[3]*v[0] + quat[1]*v[2] - v[1]*quat[2]; + left[1] = quat[3]*v[1] + quat[2]*v[0] - v[2]*quat[0]; + left[2] = quat[3]*v[2] + quat[0]*v[1] - v[0]*quat[1]; + left[3] = - quat[0]*v[0] - quat[1]*v[1] - quat[2]*v[2]; + + r[0] = (left[3]*-quat[0]) + (quat[3]*left[0]) + (left[1]*-quat[2]) - (-quat[1]*left[2]); + r[1] = (left[3]*-quat[1]) + (quat[3]*left[1]) + (left[2]*-quat[0]) - (-quat[2]*left[0]); + r[2] = (left[3]*-quat[2]) + (quat[3]*left[2]) + (left[0]*-quat[1]) - (-quat[0]*left[1]); + +} + + +void fm_getTranslation(const REAL *matrix,REAL *t) +{ + t[0] = matrix[3*4+0]; + t[1] = matrix[3*4+1]; + t[2] = matrix[3*4+2]; +} + +void fm_matrixToQuat(const REAL *matrix,REAL *quat) // convert the 3x3 portion of a 4x4 matrix into a quaternion as x,y,z,w +{ + + REAL tr = matrix[0*4+0] + matrix[1*4+1] + matrix[2*4+2]; + + // check the diagonal + + if (tr > 0.0f ) + { + REAL s = (REAL) sqrt ( (double) (tr + 1.0f) ); + quat[3] = s * 0.5f; + s = 0.5f / s; + quat[0] = (matrix[1*4+2] - matrix[2*4+1]) * s; + quat[1] = (matrix[2*4+0] - matrix[0*4+2]) * s; + quat[2] = (matrix[0*4+1] - matrix[1*4+0]) * s; + + } + else + { + // diagonal is negative + int32_t nxt[3] = {1, 2, 0}; + REAL qa[4]; + + int32_t i = 0; + + if (matrix[1*4+1] > matrix[0*4+0]) i = 1; + if (matrix[2*4+2] > matrix[i*4+i]) i = 2; + + int32_t j = nxt[i]; + int32_t k = nxt[j]; + + REAL s = (REAL)sqrt ( ((matrix[i*4+i] - (matrix[j*4+j] + matrix[k*4+k])) + 1.0f) ); + + qa[i] = s * 0.5f; + + if (s != 0.0f ) s = 0.5f / s; + + qa[3] = (matrix[j*4+k] - matrix[k*4+j]) * s; + qa[j] = (matrix[i*4+j] + matrix[j*4+i]) * s; + qa[k] = (matrix[i*4+k] + matrix[k*4+i]) * s; + + quat[0] = qa[0]; + quat[1] = qa[1]; + quat[2] = qa[2]; + quat[3] = qa[3]; + } +// fm_normalizeQuat(quat); +} + + +REAL fm_sphereVolume(REAL radius) // return's the volume of a sphere of this radius (4/3 PI * R cubed ) +{ + return (4.0f / 3.0f ) * FM_PI * radius * radius * radius; +} + + +REAL fm_cylinderVolume(REAL radius,REAL h) +{ + return FM_PI * radius * radius *h; +} + +REAL fm_capsuleVolume(REAL radius,REAL h) +{ + REAL volume = fm_sphereVolume(radius); // volume of the sphere portion. + REAL ch = h-radius*2; // this is the cylinder length + if ( ch > 0 ) + { + volume+=fm_cylinderVolume(radius,ch); + } + return volume; +} + +void fm_transform(const REAL matrix[16],const REAL v[3],REAL t[3]) // rotate and translate this point +{ + if ( matrix ) + { + REAL tx = (matrix[0*4+0] * v[0]) + (matrix[1*4+0] * v[1]) + (matrix[2*4+0] * v[2]) + matrix[3*4+0]; + REAL ty = (matrix[0*4+1] * v[0]) + (matrix[1*4+1] * v[1]) + (matrix[2*4+1] * v[2]) + matrix[3*4+1]; + REAL tz = (matrix[0*4+2] * v[0]) + (matrix[1*4+2] * v[1]) + (matrix[2*4+2] * v[2]) + matrix[3*4+2]; + t[0] = tx; + t[1] = ty; + t[2] = tz; + } + else + { + t[0] = v[0]; + t[1] = v[1]; + t[2] = v[2]; + } +} + +void fm_rotate(const REAL matrix[16],const REAL v[3],REAL t[3]) // rotate and translate this point +{ + if ( matrix ) + { + REAL tx = (matrix[0*4+0] * v[0]) + (matrix[1*4+0] * v[1]) + (matrix[2*4+0] * v[2]); + REAL ty = (matrix[0*4+1] * v[0]) + (matrix[1*4+1] * v[1]) + (matrix[2*4+1] * v[2]); + REAL tz = (matrix[0*4+2] * v[0]) + (matrix[1*4+2] * v[1]) + (matrix[2*4+2] * v[2]); + t[0] = tx; + t[1] = ty; + t[2] = tz; + } + else + { + t[0] = v[0]; + t[1] = v[1]; + t[2] = v[2]; + } +} + + +REAL fm_distance(const REAL *p1,const REAL *p2) +{ + REAL dx = p1[0] - p2[0]; + REAL dy = p1[1] - p2[1]; + REAL dz = p1[2] - p2[2]; + + return (REAL)sqrt( dx*dx + dy*dy + dz *dz ); +} + +REAL fm_distanceSquared(const REAL *p1,const REAL *p2) +{ + REAL dx = p1[0] - p2[0]; + REAL dy = p1[1] - p2[1]; + REAL dz = p1[2] - p2[2]; + + return dx*dx + dy*dy + dz *dz; +} + + +REAL fm_distanceSquaredXZ(const REAL *p1,const REAL *p2) +{ + REAL dx = p1[0] - p2[0]; + REAL dz = p1[2] - p2[2]; + + return dx*dx + dz *dz; +} + + +REAL fm_computePlane(const REAL *A,const REAL *B,const REAL *C,REAL *n) // returns D +{ + REAL vx = (B[0] - C[0]); + REAL vy = (B[1] - C[1]); + REAL vz = (B[2] - C[2]); + + REAL wx = (A[0] - B[0]); + REAL wy = (A[1] - B[1]); + REAL wz = (A[2] - B[2]); + + REAL vw_x = vy * wz - vz * wy; + REAL vw_y = vz * wx - vx * wz; + REAL vw_z = vx * wy - vy * wx; + + REAL mag = (REAL)sqrt((vw_x * vw_x) + (vw_y * vw_y) + (vw_z * vw_z)); + + if ( mag < 0.000001f ) + { + mag = 0; + } + else + { + mag = 1.0f/mag; + } + + REAL x = vw_x * mag; + REAL y = vw_y * mag; + REAL z = vw_z * mag; + + + REAL D = 0.0f - ((x*A[0])+(y*A[1])+(z*A[2])); + + n[0] = x; + n[1] = y; + n[2] = z; + + return D; +} + +REAL fm_distToPlane(const REAL *plane,const REAL *p) // computes the distance of this point from the plane. +{ + return p[0]*plane[0]+p[1]*plane[1]+p[2]*plane[2]+plane[3]; +} + +REAL fm_dot(const REAL *p1,const REAL *p2) +{ + return p1[0]*p2[0]+p1[1]*p2[1]+p1[2]*p2[2]; +} + +void fm_cross(REAL *cross,const REAL *a,const REAL *b) +{ + cross[0] = a[1]*b[2] - a[2]*b[1]; + cross[1] = a[2]*b[0] - a[0]*b[2]; + cross[2] = a[0]*b[1] - a[1]*b[0]; +} + +REAL fm_computeNormalVector(REAL *n,const REAL *p1,const REAL *p2) +{ + n[0] = p2[0] - p1[0]; + n[1] = p2[1] - p1[1]; + n[2] = p2[2] - p1[2]; + return fm_normalize(n); +} + +bool fm_computeWindingOrder(const REAL *p1,const REAL *p2,const REAL *p3) // returns true if the triangle is clockwise. +{ + bool ret = false; + + REAL v1[3]; + REAL v2[3]; + + fm_computeNormalVector(v1,p1,p2); // p2-p1 (as vector) and then normalized + fm_computeNormalVector(v2,p1,p3); // p3-p1 (as vector) and then normalized + + REAL cross[3]; + + fm_cross(cross, v1, v2 ); + REAL ref[3] = { 1, 0, 0 }; + + REAL d = fm_dot( cross, ref ); + + + if ( d <= 0 ) + ret = false; + else + ret = true; + + return ret; +} + +REAL fm_normalize(REAL *n) // normalize this vector +{ + REAL dist = (REAL)sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]); + if ( dist > 0.0000001f ) + { + REAL mag = 1.0f / dist; + n[0]*=mag; + n[1]*=mag; + n[2]*=mag; + } + else + { + n[0] = 1; + n[1] = 0; + n[2] = 0; + } + + return dist; +} + + +void fm_matrixMultiply(const REAL *pA,const REAL *pB,REAL *pM) +{ +#if 1 + + REAL a = pA[0*4+0] * pB[0*4+0] + pA[0*4+1] * pB[1*4+0] + pA[0*4+2] * pB[2*4+0] + pA[0*4+3] * pB[3*4+0]; + REAL b = pA[0*4+0] * pB[0*4+1] + pA[0*4+1] * pB[1*4+1] + pA[0*4+2] * pB[2*4+1] + pA[0*4+3] * pB[3*4+1]; + REAL c = pA[0*4+0] * pB[0*4+2] + pA[0*4+1] * pB[1*4+2] + pA[0*4+2] * pB[2*4+2] + pA[0*4+3] * pB[3*4+2]; + REAL d = pA[0*4+0] * pB[0*4+3] + pA[0*4+1] * pB[1*4+3] + pA[0*4+2] * pB[2*4+3] + pA[0*4+3] * pB[3*4+3]; + + REAL e = pA[1*4+0] * pB[0*4+0] + pA[1*4+1] * pB[1*4+0] + pA[1*4+2] * pB[2*4+0] + pA[1*4+3] * pB[3*4+0]; + REAL f = pA[1*4+0] * pB[0*4+1] + pA[1*4+1] * pB[1*4+1] + pA[1*4+2] * pB[2*4+1] + pA[1*4+3] * pB[3*4+1]; + REAL g = pA[1*4+0] * pB[0*4+2] + pA[1*4+1] * pB[1*4+2] + pA[1*4+2] * pB[2*4+2] + pA[1*4+3] * pB[3*4+2]; + REAL h = pA[1*4+0] * pB[0*4+3] + pA[1*4+1] * pB[1*4+3] + pA[1*4+2] * pB[2*4+3] + pA[1*4+3] * pB[3*4+3]; + + REAL i = pA[2*4+0] * pB[0*4+0] + pA[2*4+1] * pB[1*4+0] + pA[2*4+2] * pB[2*4+0] + pA[2*4+3] * pB[3*4+0]; + REAL j = pA[2*4+0] * pB[0*4+1] + pA[2*4+1] * pB[1*4+1] + pA[2*4+2] * pB[2*4+1] + pA[2*4+3] * pB[3*4+1]; + REAL k = pA[2*4+0] * pB[0*4+2] + pA[2*4+1] * pB[1*4+2] + pA[2*4+2] * pB[2*4+2] + pA[2*4+3] * pB[3*4+2]; + REAL l = pA[2*4+0] * pB[0*4+3] + pA[2*4+1] * pB[1*4+3] + pA[2*4+2] * pB[2*4+3] + pA[2*4+3] * pB[3*4+3]; + + REAL m = pA[3*4+0] * pB[0*4+0] + pA[3*4+1] * pB[1*4+0] + pA[3*4+2] * pB[2*4+0] + pA[3*4+3] * pB[3*4+0]; + REAL n = pA[3*4+0] * pB[0*4+1] + pA[3*4+1] * pB[1*4+1] + pA[3*4+2] * pB[2*4+1] + pA[3*4+3] * pB[3*4+1]; + REAL o = pA[3*4+0] * pB[0*4+2] + pA[3*4+1] * pB[1*4+2] + pA[3*4+2] * pB[2*4+2] + pA[3*4+3] * pB[3*4+2]; + REAL p = pA[3*4+0] * pB[0*4+3] + pA[3*4+1] * pB[1*4+3] + pA[3*4+2] * pB[2*4+3] + pA[3*4+3] * pB[3*4+3]; + + pM[0] = a; + pM[1] = b; + pM[2] = c; + pM[3] = d; + + pM[4] = e; + pM[5] = f; + pM[6] = g; + pM[7] = h; + + pM[8] = i; + pM[9] = j; + pM[10] = k; + pM[11] = l; + + pM[12] = m; + pM[13] = n; + pM[14] = o; + pM[15] = p; + + +#else + memset(pM, 0, sizeof(REAL)*16); + for(int32_t i=0; i<4; i++ ) + for(int32_t j=0; j<4; j++ ) + for(int32_t k=0; k<4; k++ ) + pM[4*i+j] += pA[4*i+k] * pB[4*k+j]; +#endif +} + + +void fm_eulerToQuatDX(REAL x,REAL y,REAL z,REAL *quat) // convert euler angles to quaternion using the fucked up DirectX method +{ + REAL matrix[16]; + fm_eulerToMatrix(x,y,z,matrix); + fm_matrixToQuat(matrix,quat); +} + +// implementation copied from: http://blogs.msdn.com/mikepelton/archive/2004/10/29/249501.aspx +void fm_eulerToMatrixDX(REAL x,REAL y,REAL z,REAL *matrix) // convert euler angles to quaternion using the fucked up DirectX method. +{ + fm_identity(matrix); + matrix[0*4+0] = (REAL)(cos(z)*cos(y) + sin(z)*sin(x)*sin(y)); + matrix[0*4+1] = (REAL)(sin(z)*cos(x)); + matrix[0*4+2] = (REAL)(cos(z)*-sin(y) + sin(z)*sin(x)*cos(y)); + + matrix[1*4+0] = (REAL)(-sin(z)*cos(y)+cos(z)*sin(x)*sin(y)); + matrix[1*4+1] = (REAL)(cos(z)*cos(x)); + matrix[1*4+2] = (REAL)(sin(z)*sin(y) +cos(z)*sin(x)*cos(y)); + + matrix[2*4+0] = (REAL)(cos(x)*sin(y)); + matrix[2*4+1] = (REAL)(-sin(x)); + matrix[2*4+2] = (REAL)(cos(x)*cos(y)); +} + + +void fm_scale(REAL x,REAL y,REAL z,REAL *fscale) // apply scale to the matrix. +{ + fscale[0*4+0] = x; + fscale[1*4+1] = y; + fscale[2*4+2] = z; +} + + +void fm_composeTransform(const REAL *position,const REAL *quat,const REAL *scale,REAL *matrix) +{ + fm_identity(matrix); + fm_quatToMatrix(quat,matrix); + + if ( scale && ( scale[0] != 1 || scale[1] != 1 || scale[2] != 1 ) ) + { + REAL work[16]; + memcpy(work,matrix,sizeof(REAL)*16); + REAL mscale[16]; + fm_identity(mscale); + fm_scale(scale[0],scale[1],scale[2],mscale); + fm_matrixMultiply(work,mscale,matrix); + } + + matrix[12] = position[0]; + matrix[13] = position[1]; + matrix[14] = position[2]; +} + + +void fm_setTranslation(const REAL *translation,REAL *matrix) +{ + matrix[12] = translation[0]; + matrix[13] = translation[1]; + matrix[14] = translation[2]; +} + +static REAL enorm0_3d ( REAL x0, REAL y0, REAL z0, REAL x1, REAL y1, REAL z1 ) + +/**********************************************************************/ + +/* +Purpose: + +ENORM0_3D computes the Euclidean norm of (P1-P0) in 3D. + +Modified: + +18 April 1999 + +Author: + +John Burkardt + +Parameters: + +Input, REAL X0, Y0, Z0, X1, Y1, Z1, the coordinates of the points +P0 and P1. + +Output, REAL ENORM0_3D, the Euclidean norm of (P1-P0). +*/ +{ + REAL value; + + value = (REAL)sqrt ( + ( x1 - x0 ) * ( x1 - x0 ) + + ( y1 - y0 ) * ( y1 - y0 ) + + ( z1 - z0 ) * ( z1 - z0 ) ); + + return value; +} + + +static REAL triangle_area_3d ( REAL x1, REAL y1, REAL z1, REAL x2,REAL y2, REAL z2, REAL x3, REAL y3, REAL z3 ) + + /**********************************************************************/ + + /* + Purpose: + + TRIANGLE_AREA_3D computes the area of a triangle in 3D. + + Modified: + + 22 April 1999 + + Author: + + John Burkardt + + Parameters: + + Input, REAL X1, Y1, Z1, X2, Y2, Z2, X3, Y3, Z3, the (X,Y,Z) + coordinates of the corners of the triangle. + + Output, REAL TRIANGLE_AREA_3D, the area of the triangle. + */ +{ + REAL a; + REAL alpha; + REAL area; + REAL b; + REAL base; + REAL c; + REAL dot; + REAL height; + /* + Find the projection of (P3-P1) onto (P2-P1). + */ + dot = + ( x2 - x1 ) * ( x3 - x1 ) + + ( y2 - y1 ) * ( y3 - y1 ) + + ( z2 - z1 ) * ( z3 - z1 ); + + base = enorm0_3d ( x1, y1, z1, x2, y2, z2 ); + /* + The height of the triangle is the length of (P3-P1) after its + projection onto (P2-P1) has been subtracted. + */ + if ( base == 0.0 ) { + + height = 0.0; + + } + else { + + alpha = dot / ( base * base ); + + a = x3 - x1 - alpha * ( x2 - x1 ); + b = y3 - y1 - alpha * ( y2 - y1 ); + c = z3 - z1 - alpha * ( z2 - z1 ); + + height = (REAL)sqrt ( a * a + b * b + c * c ); + + } + + area = 0.5f * base * height; + + return area; +} + + +REAL fm_computeArea(const REAL *p1,const REAL *p2,const REAL *p3) +{ + REAL ret = 0; + + ret = triangle_area_3d(p1[0],p1[1],p1[2],p2[0],p2[1],p2[2],p3[0],p3[1],p3[2]); + + return ret; +} + + +void fm_lerp(const REAL *p1,const REAL *p2,REAL *dest,REAL lerpValue) +{ + dest[0] = ((p2[0] - p1[0])*lerpValue) + p1[0]; + dest[1] = ((p2[1] - p1[1])*lerpValue) + p1[1]; + dest[2] = ((p2[2] - p1[2])*lerpValue) + p1[2]; +} + +bool fm_pointTestXZ(const REAL *p,const REAL *i,const REAL *j) +{ + bool ret = false; + + if (((( i[2] <= p[2] ) && ( p[2] < j[2] )) || (( j[2] <= p[2] ) && ( p[2] < i[2] ))) && ( p[0] < (j[0] - i[0]) * (p[2] - i[2]) / (j[2] - i[2]) + i[0])) + ret = true; + + return ret; +}; + + +bool fm_insideTriangleXZ(const REAL *p,const REAL *p1,const REAL *p2,const REAL *p3) +{ + bool ret = false; + + int32_t c = 0; + if ( fm_pointTestXZ(p,p1,p2) ) c = !c; + if ( fm_pointTestXZ(p,p2,p3) ) c = !c; + if ( fm_pointTestXZ(p,p3,p1) ) c = !c; + if ( c ) ret = true; + + return ret; +} + +bool fm_insideAABB(const REAL *pos,const REAL *bmin,const REAL *bmax) +{ + bool ret = false; + + if ( pos[0] >= bmin[0] && pos[0] <= bmax[0] && + pos[1] >= bmin[1] && pos[1] <= bmax[1] && + pos[2] >= bmin[2] && pos[2] <= bmax[2] ) + ret = true; + + return ret; +} + + +uint32_t fm_clipTestPoint(const REAL *bmin,const REAL *bmax,const REAL *pos) +{ + uint32_t ret = 0; + + if ( pos[0] < bmin[0] ) + ret|=FMCS_XMIN; + else if ( pos[0] > bmax[0] ) + ret|=FMCS_XMAX; + + if ( pos[1] < bmin[1] ) + ret|=FMCS_YMIN; + else if ( pos[1] > bmax[1] ) + ret|=FMCS_YMAX; + + if ( pos[2] < bmin[2] ) + ret|=FMCS_ZMIN; + else if ( pos[2] > bmax[2] ) + ret|=FMCS_ZMAX; + + return ret; +} + +uint32_t fm_clipTestPointXZ(const REAL *bmin,const REAL *bmax,const REAL *pos) // only tests X and Z, not Y +{ + uint32_t ret = 0; + + if ( pos[0] < bmin[0] ) + ret|=FMCS_XMIN; + else if ( pos[0] > bmax[0] ) + ret|=FMCS_XMAX; + + if ( pos[2] < bmin[2] ) + ret|=FMCS_ZMIN; + else if ( pos[2] > bmax[2] ) + ret|=FMCS_ZMAX; + + return ret; +} + +uint32_t fm_clipTestAABB(const REAL *bmin,const REAL *bmax,const REAL *p1,const REAL *p2,const REAL *p3,uint32_t &andCode) +{ + uint32_t orCode = 0; + + andCode = FMCS_XMIN | FMCS_XMAX | FMCS_YMIN | FMCS_YMAX | FMCS_ZMIN | FMCS_ZMAX; + + uint32_t c = fm_clipTestPoint(bmin,bmax,p1); + orCode|=c; + andCode&=c; + + c = fm_clipTestPoint(bmin,bmax,p2); + orCode|=c; + andCode&=c; + + c = fm_clipTestPoint(bmin,bmax,p3); + orCode|=c; + andCode&=c; + + return orCode; +} + +bool intersect(const REAL *si,const REAL *ei,const REAL *bmin,const REAL *bmax,REAL *time) +{ + REAL st,et,fst = 0,fet = 1; + + for (int32_t i = 0; i < 3; i++) + { + if (*si < *ei) + { + if (*si > *bmax || *ei < *bmin) + return false; + REAL di = *ei - *si; + st = (*si < *bmin)? (*bmin - *si) / di: 0; + et = (*ei > *bmax)? (*bmax - *si) / di: 1; + } + else + { + if (*ei > *bmax || *si < *bmin) + return false; + REAL di = *ei - *si; + st = (*si > *bmax)? (*bmax - *si) / di: 0; + et = (*ei < *bmin)? (*bmin - *si) / di: 1; + } + + if (st > fst) fst = st; + if (et < fet) fet = et; + if (fet < fst) + return false; + bmin++; bmax++; + si++; ei++; + } + + *time = fst; + return true; +} + + + +bool fm_lineTestAABB(const REAL *p1,const REAL *p2,const REAL *bmin,const REAL *bmax,REAL &time) +{ + bool sect = intersect(p1,p2,bmin,bmax,&time); + return sect; +} + + +bool fm_lineTestAABBXZ(const REAL *p1,const REAL *p2,const REAL *bmin,const REAL *bmax,REAL &time) +{ + REAL _bmin[3]; + REAL _bmax[3]; + + _bmin[0] = bmin[0]; + _bmin[1] = -1e9; + _bmin[2] = bmin[2]; + + _bmax[0] = bmax[0]; + _bmax[1] = 1e9; + _bmax[2] = bmax[2]; + + bool sect = intersect(p1,p2,_bmin,_bmax,&time); + + return sect; +} + +void fm_minmax(const REAL *p,REAL *bmin,REAL *bmax) // accumulate to a min-max value +{ + + if ( p[0] < bmin[0] ) bmin[0] = p[0]; + if ( p[1] < bmin[1] ) bmin[1] = p[1]; + if ( p[2] < bmin[2] ) bmin[2] = p[2]; + + if ( p[0] > bmax[0] ) bmax[0] = p[0]; + if ( p[1] > bmax[1] ) bmax[1] = p[1]; + if ( p[2] > bmax[2] ) bmax[2] = p[2]; + +} + +REAL fm_solveX(const REAL *plane,REAL y,REAL z) // solve for X given this plane equation and the other two components. +{ + REAL x = (y*plane[1]+z*plane[2]+plane[3]) / -plane[0]; + return x; +} + +REAL fm_solveY(const REAL *plane,REAL x,REAL z) // solve for Y given this plane equation and the other two components. +{ + REAL y = (x*plane[0]+z*plane[2]+plane[3]) / -plane[1]; + return y; +} + + +REAL fm_solveZ(const REAL *plane,REAL x,REAL y) // solve for Y given this plane equation and the other two components. +{ + REAL z = (x*plane[0]+y*plane[1]+plane[3]) / -plane[2]; + return z; +} + + +void fm_getAABBCenter(const REAL *bmin,const REAL *bmax,REAL *center) +{ + center[0] = (bmax[0]-bmin[0])*0.5f+bmin[0]; + center[1] = (bmax[1]-bmin[1])*0.5f+bmin[1]; + center[2] = (bmax[2]-bmin[2])*0.5f+bmin[2]; +} + +FM_Axis fm_getDominantAxis(const REAL normal[3]) +{ + FM_Axis ret = FM_XAXIS; + + REAL x = (REAL)fabs(normal[0]); + REAL y = (REAL)fabs(normal[1]); + REAL z = (REAL)fabs(normal[2]); + + if ( y > x && y > z ) + ret = FM_YAXIS; + else if ( z > x && z > y ) + ret = FM_ZAXIS; + + return ret; +} + + +bool fm_lineSphereIntersect(const REAL *center,REAL radius,const REAL *p1,const REAL *p2,REAL *intersect) +{ + bool ret = false; + + REAL dir[3]; + + dir[0] = p2[0]-p1[0]; + dir[1] = p2[1]-p1[1]; + dir[2] = p2[2]-p1[2]; + + REAL distance = (REAL)sqrt( dir[0]*dir[0]+dir[1]*dir[1]+dir[2]*dir[2]); + + if ( distance > 0 ) + { + REAL recip = 1.0f / distance; + dir[0]*=recip; + dir[1]*=recip; + dir[2]*=recip; + ret = fm_raySphereIntersect(center,radius,p1,dir,distance,intersect); + } + else + { + dir[0] = center[0]-p1[0]; + dir[1] = center[1]-p1[1]; + dir[2] = center[2]-p1[2]; + REAL d2 = dir[0]*dir[0]+dir[1]*dir[1]+dir[2]*dir[2]; + REAL r2 = radius*radius; + if ( d2 < r2 ) + { + ret = true; + if ( intersect ) + { + intersect[0] = p1[0]; + intersect[1] = p1[1]; + intersect[2] = p1[2]; + } + } + } + return ret; +} + +#define DOT(p1,p2) (p1[0]*p2[0]+p1[1]*p2[1]+p1[2]*p2[2]) + +bool fm_raySphereIntersect(const REAL *center,REAL radius,const REAL *pos,const REAL *dir,REAL distance,REAL *intersect) +{ + bool ret = false; + + REAL E0[3]; + + E0[0] = center[0] - pos[0]; + E0[1] = center[1] - pos[1]; + E0[2] = center[2] - pos[2]; + + REAL V[3]; + + V[0] = dir[0]; + V[1] = dir[1]; + V[2] = dir[2]; + + + REAL dist2 = E0[0]*E0[0] + E0[1]*E0[1] + E0[2] * E0[2]; + REAL radius2 = radius*radius; // radius squared.. + + // Bug Fix For Gem, if origin is *inside* the sphere, invert the + // direction vector so that we get a valid intersection location. + if ( dist2 < radius2 ) + { + V[0]*=-1; + V[1]*=-1; + V[2]*=-1; + } + + + REAL v = DOT(E0,V); + + REAL disc = radius2 - (dist2 - v*v); + + if (disc > 0.0f) + { + if ( intersect ) + { + REAL d = (REAL)sqrt(disc); + REAL diff = v-d; + if ( diff < distance ) + { + intersect[0] = pos[0]+V[0]*diff; + intersect[1] = pos[1]+V[1]*diff; + intersect[2] = pos[2]+V[2]*diff; + ret = true; + } + } + } + + return ret; +} + + +void fm_catmullRom(REAL *out_vector,const REAL *p1,const REAL *p2,const REAL *p3,const REAL *p4, const REAL s) +{ + REAL s_squared = s * s; + REAL s_cubed = s_squared * s; + + REAL coefficient_p1 = -s_cubed + 2*s_squared - s; + REAL coefficient_p2 = 3 * s_cubed - 5 * s_squared + 2; + REAL coefficient_p3 = -3 * s_cubed +4 * s_squared + s; + REAL coefficient_p4 = s_cubed - s_squared; + + out_vector[0] = (coefficient_p1 * p1[0] + coefficient_p2 * p2[0] + coefficient_p3 * p3[0] + coefficient_p4 * p4[0])*0.5f; + out_vector[1] = (coefficient_p1 * p1[1] + coefficient_p2 * p2[1] + coefficient_p3 * p3[1] + coefficient_p4 * p4[1])*0.5f; + out_vector[2] = (coefficient_p1 * p1[2] + coefficient_p2 * p2[2] + coefficient_p3 * p3[2] + coefficient_p4 * p4[2])*0.5f; +} + +bool fm_intersectAABB(const REAL *bmin1,const REAL *bmax1,const REAL *bmin2,const REAL *bmax2) +{ + if ((bmin1[0] > bmax2[0]) || (bmin2[0] > bmax1[0])) return false; + if ((bmin1[1] > bmax2[1]) || (bmin2[1] > bmax1[1])) return false; + if ((bmin1[2] > bmax2[2]) || (bmin2[2] > bmax1[2])) return false; + return true; + +} + +bool fm_insideAABB(const REAL *obmin,const REAL *obmax,const REAL *tbmin,const REAL *tbmax) // test if bounding box tbmin/tmbax is fully inside obmin/obmax +{ + bool ret = false; + + if ( tbmax[0] <= obmax[0] && + tbmax[1] <= obmax[1] && + tbmax[2] <= obmax[2] && + tbmin[0] >= obmin[0] && + tbmin[1] >= obmin[1] && + tbmin[2] >= obmin[2] ) ret = true; + + return ret; +} + + +// Reference, from Stan Melax in Game Gems I +// Quaternion q; +// vector3 c = CrossProduct(v0,v1); +// REAL d = DotProduct(v0,v1); +// REAL s = (REAL)sqrt((1+d)*2); +// q.x = c.x / s; +// q.y = c.y / s; +// q.z = c.z / s; +// q.w = s /2.0f; +// return q; +void fm_rotationArc(const REAL *v0,const REAL *v1,REAL *quat) +{ + REAL cross[3]; + + fm_cross(cross,v0,v1); + REAL d = fm_dot(v0,v1); + + if( d<= -0.99999f ) // 180 about x axis + { + if ( fabsf((float)v0[0]) < 0.1f ) + { + quat[0] = 0; + quat[1] = v0[2]; + quat[2] = -v0[1]; + quat[3] = 0; + } + else + { + quat[0] = v0[1]; + quat[1] = -v0[0]; + quat[2] = 0; + quat[3] = 0; + } + REAL magnitudeSquared = quat[0]*quat[0] + quat[1]*quat[1] + quat[2]*quat[2] + quat[3]*quat[3]; + REAL magnitude = sqrtf((float)magnitudeSquared); + REAL recip = 1.0f / magnitude; + quat[0]*=recip; + quat[1]*=recip; + quat[2]*=recip; + quat[3]*=recip; + } + else + { + REAL s = (REAL)sqrt((1+d)*2); + REAL recip = 1.0f / s; + + quat[0] = cross[0] * recip; + quat[1] = cross[1] * recip; + quat[2] = cross[2] * recip; + quat[3] = s * 0.5f; + } +} + + +REAL fm_distancePointLineSegment(const REAL *Point,const REAL *LineStart,const REAL *LineEnd,REAL *intersection,LineSegmentType &type,REAL epsilon) +{ + REAL ret; + + REAL LineMag = fm_distance( LineEnd, LineStart ); + + if ( LineMag > 0 ) + { + REAL U = ( ( ( Point[0] - LineStart[0] ) * ( LineEnd[0] - LineStart[0] ) ) + ( ( Point[1] - LineStart[1] ) * ( LineEnd[1] - LineStart[1] ) ) + ( ( Point[2] - LineStart[2] ) * ( LineEnd[2] - LineStart[2] ) ) ) / ( LineMag * LineMag ); + if( U < 0.0f || U > 1.0f ) + { + REAL d1 = fm_distanceSquared(Point,LineStart); + REAL d2 = fm_distanceSquared(Point,LineEnd); + if ( d1 <= d2 ) + { + ret = (REAL)sqrt(d1); + intersection[0] = LineStart[0]; + intersection[1] = LineStart[1]; + intersection[2] = LineStart[2]; + type = LS_START; + } + else + { + ret = (REAL)sqrt(d2); + intersection[0] = LineEnd[0]; + intersection[1] = LineEnd[1]; + intersection[2] = LineEnd[2]; + type = LS_END; + } + } + else + { + intersection[0] = LineStart[0] + U * ( LineEnd[0] - LineStart[0] ); + intersection[1] = LineStart[1] + U * ( LineEnd[1] - LineStart[1] ); + intersection[2] = LineStart[2] + U * ( LineEnd[2] - LineStart[2] ); + + ret = fm_distance(Point,intersection); + + REAL d1 = fm_distanceSquared(intersection,LineStart); + REAL d2 = fm_distanceSquared(intersection,LineEnd); + REAL mag = (epsilon*2)*(epsilon*2); + + if ( d1 < mag ) // if less than 1/100th the total distance, treat is as the 'start' + { + type = LS_START; + } + else if ( d2 < mag ) + { + type = LS_END; + } + else + { + type = LS_MIDDLE; + } + + } + } + else + { + ret = LineMag; + intersection[0] = LineEnd[0]; + intersection[1] = LineEnd[1]; + intersection[2] = LineEnd[2]; + type = LS_END; + } + + return ret; +} + + +#ifndef BEST_FIT_PLANE_H + +#define BEST_FIT_PLANE_H + +template class Eigen +{ +public: + + + void DecrSortEigenStuff(void) + { + Tridiagonal(); //diagonalize the matrix. + QLAlgorithm(); // + DecreasingSort(); + GuaranteeRotation(); + } + + void Tridiagonal(void) + { + Type fM00 = mElement[0][0]; + Type fM01 = mElement[0][1]; + Type fM02 = mElement[0][2]; + Type fM11 = mElement[1][1]; + Type fM12 = mElement[1][2]; + Type fM22 = mElement[2][2]; + + m_afDiag[0] = fM00; + m_afSubd[2] = 0; + if (fM02 != (Type)0.0) + { + Type fLength = (REAL)sqrt(fM01*fM01+fM02*fM02); + Type fInvLength = ((Type)1.0)/fLength; + fM01 *= fInvLength; + fM02 *= fInvLength; + Type fQ = ((Type)2.0)*fM01*fM12+fM02*(fM22-fM11); + m_afDiag[1] = fM11+fM02*fQ; + m_afDiag[2] = fM22-fM02*fQ; + m_afSubd[0] = fLength; + m_afSubd[1] = fM12-fM01*fQ; + mElement[0][0] = (Type)1.0; + mElement[0][1] = (Type)0.0; + mElement[0][2] = (Type)0.0; + mElement[1][0] = (Type)0.0; + mElement[1][1] = fM01; + mElement[1][2] = fM02; + mElement[2][0] = (Type)0.0; + mElement[2][1] = fM02; + mElement[2][2] = -fM01; + m_bIsRotation = false; + } + else + { + m_afDiag[1] = fM11; + m_afDiag[2] = fM22; + m_afSubd[0] = fM01; + m_afSubd[1] = fM12; + mElement[0][0] = (Type)1.0; + mElement[0][1] = (Type)0.0; + mElement[0][2] = (Type)0.0; + mElement[1][0] = (Type)0.0; + mElement[1][1] = (Type)1.0; + mElement[1][2] = (Type)0.0; + mElement[2][0] = (Type)0.0; + mElement[2][1] = (Type)0.0; + mElement[2][2] = (Type)1.0; + m_bIsRotation = true; + } + } + + bool QLAlgorithm(void) + { + const int32_t iMaxIter = 32; + + for (int32_t i0 = 0; i0 <3; i0++) + { + int32_t i1; + for (i1 = 0; i1 < iMaxIter; i1++) + { + int32_t i2; + for (i2 = i0; i2 <= (3-2); i2++) + { + Type fTmp = Type(fabs(m_afDiag[i2]) + fabs(m_afDiag[i2+1])); + if ( fabs(m_afSubd[i2]) + fTmp == fTmp ) + break; + } + if (i2 == i0) + { + break; + } + + Type fG = (m_afDiag[i0+1] - m_afDiag[i0])/(((Type)2.0) * m_afSubd[i0]); + Type fR = (REAL)sqrt(fG*fG+(Type)1.0); + if (fG < (Type)0.0) + { + fG = m_afDiag[i2]-m_afDiag[i0]+m_afSubd[i0]/(fG-fR); + } + else + { + fG = m_afDiag[i2]-m_afDiag[i0]+m_afSubd[i0]/(fG+fR); + } + Type fSin = (Type)1.0, fCos = (Type)1.0, fP = (Type)0.0; + for (int32_t i3 = i2-1; i3 >= i0; i3--) + { + Type fF = fSin*m_afSubd[i3]; + Type fB = fCos*m_afSubd[i3]; + if (fabs(fF) >= fabs(fG)) + { + fCos = fG/fF; + fR = (REAL)sqrt(fCos*fCos+(Type)1.0); + m_afSubd[i3+1] = fF*fR; + fSin = ((Type)1.0)/fR; + fCos *= fSin; + } + else + { + fSin = fF/fG; + fR = (REAL)sqrt(fSin*fSin+(Type)1.0); + m_afSubd[i3+1] = fG*fR; + fCos = ((Type)1.0)/fR; + fSin *= fCos; + } + fG = m_afDiag[i3+1]-fP; + fR = (m_afDiag[i3]-fG)*fSin+((Type)2.0)*fB*fCos; + fP = fSin*fR; + m_afDiag[i3+1] = fG+fP; + fG = fCos*fR-fB; + for (int32_t i4 = 0; i4 < 3; i4++) + { + fF = mElement[i4][i3+1]; + mElement[i4][i3+1] = fSin*mElement[i4][i3]+fCos*fF; + mElement[i4][i3] = fCos*mElement[i4][i3]-fSin*fF; + } + } + m_afDiag[i0] -= fP; + m_afSubd[i0] = fG; + m_afSubd[i2] = (Type)0.0; + } + if (i1 == iMaxIter) + { + return false; + } + } + return true; + } + + void DecreasingSort(void) + { + //sort eigenvalues in decreasing order, e[0] >= ... >= e[iSize-1] + for (int32_t i0 = 0, i1; i0 <= 3-2; i0++) + { + // locate maximum eigenvalue + i1 = i0; + Type fMax = m_afDiag[i1]; + int32_t i2; + for (i2 = i0+1; i2 < 3; i2++) + { + if (m_afDiag[i2] > fMax) + { + i1 = i2; + fMax = m_afDiag[i1]; + } + } + + if (i1 != i0) + { + // swap eigenvalues + m_afDiag[i1] = m_afDiag[i0]; + m_afDiag[i0] = fMax; + // swap eigenvectors + for (i2 = 0; i2 < 3; i2++) + { + Type fTmp = mElement[i2][i0]; + mElement[i2][i0] = mElement[i2][i1]; + mElement[i2][i1] = fTmp; + m_bIsRotation = !m_bIsRotation; + } + } + } + } + + + void GuaranteeRotation(void) + { + if (!m_bIsRotation) + { + // change sign on the first column + for (int32_t iRow = 0; iRow <3; iRow++) + { + mElement[iRow][0] = -mElement[iRow][0]; + } + } + } + + Type mElement[3][3]; + Type m_afDiag[3]; + Type m_afSubd[3]; + bool m_bIsRotation; +}; + +#endif + +bool fm_computeBestFitPlane(uint32_t vcount, + const REAL *points, + uint32_t vstride, + const REAL *weights, + uint32_t wstride, + REAL *plane, + REAL *center) +{ + bool ret = false; + + REAL kOrigin[3] = { 0, 0, 0 }; + + REAL wtotal = 0; + + { + const char *source = (const char *) points; + const char *wsource = (const char *) weights; + + for (uint32_t i=0; i kES; + + kES.mElement[0][0] = fSumXX; + kES.mElement[0][1] = fSumXY; + kES.mElement[0][2] = fSumXZ; + + kES.mElement[1][0] = fSumXY; + kES.mElement[1][1] = fSumYY; + kES.mElement[1][2] = fSumYZ; + + kES.mElement[2][0] = fSumXZ; + kES.mElement[2][1] = fSumYZ; + kES.mElement[2][2] = fSumZZ; + + // compute eigenstuff, smallest eigenvalue is in last position + kES.DecrSortEigenStuff(); + + REAL kNormal[3]; + + kNormal[0] = kES.mElement[0][2]; + kNormal[1] = kES.mElement[1][2]; + kNormal[2] = kES.mElement[2][2]; + + // the minimum energy + plane[0] = kNormal[0]; + plane[1] = kNormal[1]; + plane[2] = kNormal[2]; + + plane[3] = 0 - fm_dot(kNormal,kOrigin); + + ret = true; + + return ret; +} + + +bool fm_colinear(const REAL a1[3],const REAL a2[3],const REAL b1[3],const REAL b2[3],REAL epsilon) // true if these two line segments are co-linear. +{ + bool ret = false; + + REAL dir1[3]; + REAL dir2[3]; + + dir1[0] = (a2[0] - a1[0]); + dir1[1] = (a2[1] - a1[1]); + dir1[2] = (a2[2] - a1[2]); + + dir2[0] = (b2[0]-a1[0]) - (b1[0]-a1[0]); + dir2[1] = (b2[1]-a1[1]) - (b1[1]-a1[1]); + dir2[2] = (b2[2]-a2[2]) - (b1[2]-a2[2]); + + fm_normalize(dir1); + fm_normalize(dir2); + + REAL dot = fm_dot(dir1,dir2); + + if ( dot >= epsilon ) + { + ret = true; + } + + + return ret; +} + +bool fm_colinear(const REAL *p1,const REAL *p2,const REAL *p3,REAL epsilon) +{ + bool ret = false; + + REAL dir1[3]; + REAL dir2[3]; + + dir1[0] = p2[0] - p1[0]; + dir1[1] = p2[1] - p1[1]; + dir1[2] = p2[2] - p1[2]; + + dir2[0] = p3[0] - p2[0]; + dir2[1] = p3[1] - p2[1]; + dir2[2] = p3[2] - p2[2]; + + fm_normalize(dir1); + fm_normalize(dir2); + + REAL dot = fm_dot(dir1,dir2); + + if ( dot >= epsilon ) + { + ret = true; + } + + + return ret; +} + +void fm_initMinMax(const REAL *p,REAL *bmin,REAL *bmax) +{ + bmax[0] = bmin[0] = p[0]; + bmax[1] = bmin[1] = p[1]; + bmax[2] = bmin[2] = p[2]; +} + +IntersectResult fm_intersectLineSegments2d(const REAL *a1,const REAL *a2,const REAL *b1,const REAL *b2,REAL *intersection) +{ + IntersectResult ret; + + REAL denom = ((b2[1] - b1[1])*(a2[0] - a1[0])) - ((b2[0] - b1[0])*(a2[1] - a1[1])); + REAL nume_a = ((b2[0] - b1[0])*(a1[1] - b1[1])) - ((b2[1] - b1[1])*(a1[0] - b1[0])); + REAL nume_b = ((a2[0] - a1[0])*(a1[1] - b1[1])) - ((a2[1] - a1[1])*(a1[0] - b1[0])); + if (denom == 0 ) + { + if(nume_a == 0 && nume_b == 0) + { + ret = IR_COINCIDENT; + } + else + { + ret = IR_PARALLEL; + } + } + else + { + + REAL recip = 1 / denom; + REAL ua = nume_a * recip; + REAL ub = nume_b * recip; + + if(ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1 ) + { + // Get the intersection point. + intersection[0] = a1[0] + ua*(a2[0] - a1[0]); + intersection[1] = a1[1] + ua*(a2[1] - a1[1]); + ret = IR_DO_INTERSECT; + } + else + { + ret = IR_DONT_INTERSECT; + } + } + return ret; +} + +IntersectResult fm_intersectLineSegments2dTime(const REAL *a1,const REAL *a2,const REAL *b1,const REAL *b2,REAL &t1,REAL &t2) +{ + IntersectResult ret; + + REAL denom = ((b2[1] - b1[1])*(a2[0] - a1[0])) - ((b2[0] - b1[0])*(a2[1] - a1[1])); + REAL nume_a = ((b2[0] - b1[0])*(a1[1] - b1[1])) - ((b2[1] - b1[1])*(a1[0] - b1[0])); + REAL nume_b = ((a2[0] - a1[0])*(a1[1] - b1[1])) - ((a2[1] - a1[1])*(a1[0] - b1[0])); + if (denom == 0 ) + { + if(nume_a == 0 && nume_b == 0) + { + ret = IR_COINCIDENT; + } + else + { + ret = IR_PARALLEL; + } + } + else + { + + REAL recip = 1 / denom; + REAL ua = nume_a * recip; + REAL ub = nume_b * recip; + + if(ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1 ) + { + t1 = ua; + t2 = ub; + ret = IR_DO_INTERSECT; + } + else + { + ret = IR_DONT_INTERSECT; + } + } + return ret; +} + +//**** Plane Triangle Intersection + + + + + +// assumes that the points are on opposite sides of the plane! +bool fm_intersectPointPlane(const REAL *p1,const REAL *p2,REAL *split,const REAL *plane) +{ + + REAL dp1 = fm_distToPlane(plane,p1); + REAL dp2 = fm_distToPlane(plane, p2); + if (dp1 <= 0 && dp2 <= 0) + { + return false; + } + if (dp1 >= 0 && dp2 >= 0) + { + return false; + } + + REAL dir[3]; + + dir[0] = p2[0] - p1[0]; + dir[1] = p2[1] - p1[1]; + dir[2] = p2[2] - p1[2]; + + REAL dot1 = dir[0]*plane[0] + dir[1]*plane[1] + dir[2]*plane[2]; + REAL dot2 = dp1 - plane[3]; + + REAL t = -(plane[3] + dot2 ) / dot1; + + split[0] = (dir[0]*t)+p1[0]; + split[1] = (dir[1]*t)+p1[1]; + split[2] = (dir[2]*t)+p1[2]; + + return true; +} + +PlaneTriResult fm_getSidePlane(const REAL *p,const REAL *plane,REAL epsilon) +{ + PlaneTriResult ret = PTR_ON_PLANE; + + REAL d = fm_distToPlane(plane,p); + + if ( d < -epsilon || d > epsilon ) + { + if ( d > 0 ) + ret = PTR_FRONT; // it is 'in front' within the provided epsilon value. + else + ret = PTR_BACK; + } + + return ret; +} + + + +#ifndef PLANE_TRIANGLE_INTERSECTION_H + +#define PLANE_TRIANGLE_INTERSECTION_H + +#define MAXPTS 256 + +template class point +{ +public: + + void set(const Type *p) + { + x = p[0]; + y = p[1]; + z = p[2]; + } + + Type x; + Type y; + Type z; +}; + +template class plane +{ +public: + plane(const Type *p) + { + normal.x = p[0]; + normal.y = p[1]; + normal.z = p[2]; + D = p[3]; + } + + Type Classify_Point(const point &p) + { + return p.x*normal.x + p.y*normal.y + p.z*normal.z + D; + } + + point normal; + Type D; +}; + +template class polygon +{ +public: + polygon(void) + { + mVcount = 0; + } + + polygon(const Type *p1,const Type *p2,const Type *p3) + { + mVcount = 3; + mVertices[0].set(p1); + mVertices[1].set(p2); + mVertices[2].set(p3); + } + + + int32_t NumVertices(void) const { return mVcount; }; + + const point& Vertex(int32_t index) + { + if ( index < 0 ) index+=mVcount; + return mVertices[index]; + }; + + + void set(const point *pts,int32_t count) + { + for (int32_t i=0; i *poly,plane *part, polygon &front, polygon &back) + { + int32_t count = poly->NumVertices (); + int32_t out_c = 0, in_c = 0; + point ptA, ptB,outpts[MAXPTS],inpts[MAXPTS]; + Type sideA, sideB; + ptA = poly->Vertex (count - 1); + sideA = part->Classify_Point (ptA); + for (int32_t i = -1; ++i < count;) + { + ptB = poly->Vertex(i); + sideB = part->Classify_Point(ptB); + if (sideB > 0) + { + if (sideA < 0) + { + point v; + fm_intersectPointPlane(&ptB.x, &ptA.x, &v.x, &part->normal.x ); + outpts[out_c++] = inpts[in_c++] = v; + } + outpts[out_c++] = ptB; + } + else if (sideB < 0) + { + if (sideA > 0) + { + point v; + fm_intersectPointPlane(&ptB.x, &ptA.x, &v.x, &part->normal.x ); + outpts[out_c++] = inpts[in_c++] = v; + } + inpts[in_c++] = ptB; + } + else + outpts[out_c++] = inpts[in_c++] = ptB; + ptA = ptB; + sideA = sideB; + } + + front.set(&outpts[0], out_c); + back.set(&inpts[0], in_c); + } + + int32_t mVcount; + point mVertices[MAXPTS]; +}; + + + +#endif + +static inline void add(const REAL *p,REAL *dest,uint32_t tstride,uint32_t &pcount) +{ + char *d = (char *) dest; + d = d + pcount*tstride; + dest = (REAL *) d; + dest[0] = p[0]; + dest[1] = p[1]; + dest[2] = p[2]; + pcount++; + assert( pcount <= 4 ); +} + + +PlaneTriResult fm_planeTriIntersection(const REAL *_plane, // the plane equation in Ax+By+Cz+D format + const REAL *triangle, // the source triangle. + uint32_t tstride, // stride in bytes of the input and output *vertices* + REAL epsilon, // the co-planar epsilon value. + REAL *front, // the triangle in front of the + uint32_t &fcount, // number of vertices in the 'front' triangle + REAL *back, // the triangle in back of the plane + uint32_t &bcount) // the number of vertices in the 'back' triangle. +{ + + fcount = 0; + bcount = 0; + + const char *tsource = (const char *) triangle; + + // get the three vertices of the triangle. + const REAL *p1 = (const REAL *) (tsource); + const REAL *p2 = (const REAL *) (tsource+tstride); + const REAL *p3 = (const REAL *) (tsource+tstride*2); + + + PlaneTriResult r1 = fm_getSidePlane(p1,_plane,epsilon); // compute the side of the plane each vertex is on + PlaneTriResult r2 = fm_getSidePlane(p2,_plane,epsilon); + PlaneTriResult r3 = fm_getSidePlane(p3,_plane,epsilon); + + // If any of the points lay right *on* the plane.... + if ( r1 == PTR_ON_PLANE || r2 == PTR_ON_PLANE || r3 == PTR_ON_PLANE ) + { + // If the triangle is completely co-planar, then just treat it as 'front' and return! + if ( r1 == PTR_ON_PLANE && r2 == PTR_ON_PLANE && r3 == PTR_ON_PLANE ) + { + add(p1,front,tstride,fcount); + add(p2,front,tstride,fcount); + add(p3,front,tstride,fcount); + return PTR_FRONT; + } + // Decide to place the co-planar points on the same side as the co-planar point. + PlaneTriResult r= PTR_ON_PLANE; + if ( r1 != PTR_ON_PLANE ) + r = r1; + else if ( r2 != PTR_ON_PLANE ) + r = r2; + else if ( r3 != PTR_ON_PLANE ) + r = r3; + + if ( r1 == PTR_ON_PLANE ) r1 = r; + if ( r2 == PTR_ON_PLANE ) r2 = r; + if ( r3 == PTR_ON_PLANE ) r3 = r; + + } + + if ( r1 == r2 && r1 == r3 ) // if all three vertices are on the same side of the plane. + { + if ( r1 == PTR_FRONT ) // if all three are in front of the plane, then copy to the 'front' output triangle. + { + add(p1,front,tstride,fcount); + add(p2,front,tstride,fcount); + add(p3,front,tstride,fcount); + } + else + { + add(p1,back,tstride,bcount); // if all three are in 'back' then copy to the 'back' output triangle. + add(p2,back,tstride,bcount); + add(p3,back,tstride,bcount); + } + return r1; // if all three points are on the same side of the plane return result + } + + + polygon pi(p1,p2,p3); + polygon pfront,pback; + + plane part(_plane); + + pi.Split_Polygon(&pi,&part,pfront,pback); + + for (int32_t i=0; i bmax[0] ) bmax[0] = t[0]; + if ( t[1] > bmax[1] ) bmax[1] = t[1]; + if ( t[2] > bmax[2] ) bmax[2] = t[2]; + + src+=pstride; + } + + REAL center[3]; + + sides[0] = bmax[0]-bmin[0]; + sides[1] = bmax[1]-bmin[1]; + sides[2] = bmax[2]-bmin[2]; + + center[0] = sides[0]*0.5f+bmin[0]; + center[1] = sides[1]*0.5f+bmin[1]; + center[2] = sides[2]*0.5f+bmin[2]; + + REAL ocenter[3]; + + fm_rotate(matrix,center,ocenter); + + matrix[12]+=ocenter[0]; + matrix[13]+=ocenter[1]; + matrix[14]+=ocenter[2]; + +} + +void fm_computeBestFitOBB(uint32_t vcount,const REAL *points,uint32_t pstride,REAL *sides,REAL *matrix,bool bruteForce) +{ + REAL plane[4]; + REAL center[3]; + fm_computeBestFitPlane(vcount,points,pstride,0,0,plane,center); + fm_planeToMatrix(plane,matrix); + computeOBB( vcount, points, pstride, sides, matrix ); + + REAL refmatrix[16]; + memcpy(refmatrix,matrix,16*sizeof(REAL)); + + REAL volume = sides[0]*sides[1]*sides[2]; + if ( bruteForce ) + { + for (REAL a=10; a<180; a+=10) + { + REAL quat[4]; + fm_eulerToQuat(0,a*FM_DEG_TO_RAD,0,quat); + REAL temp[16]; + REAL pmatrix[16]; + fm_quatToMatrix(quat,temp); + fm_matrixMultiply(temp,refmatrix,pmatrix); + REAL psides[3]; + computeOBB( vcount, points, pstride, psides, pmatrix ); + REAL v = psides[0]*psides[1]*psides[2]; + if ( v < volume ) + { + volume = v; + memcpy(matrix,pmatrix,sizeof(REAL)*16); + sides[0] = psides[0]; + sides[1] = psides[1]; + sides[2] = psides[2]; + } + } + } +} + +void fm_computeBestFitOBB(uint32_t vcount,const REAL *points,uint32_t pstride,REAL *sides,REAL *pos,REAL *quat,bool bruteForce) +{ + REAL matrix[16]; + fm_computeBestFitOBB(vcount,points,pstride,sides,matrix,bruteForce); + fm_getTranslation(matrix,pos); + fm_matrixToQuat(matrix,quat); +} + +void fm_computeBestFitABB(uint32_t vcount,const REAL *points,uint32_t pstride,REAL *sides,REAL *pos) +{ + REAL bmin[3]; + REAL bmax[3]; + + bmin[0] = points[0]; + bmin[1] = points[1]; + bmin[2] = points[2]; + + bmax[0] = points[0]; + bmax[1] = points[1]; + bmax[2] = points[2]; + + const char *cp = (const char *) points; + for (uint32_t i=0; i bmax[0] ) bmax[0] = p[0]; + if ( p[1] > bmax[1] ) bmax[1] = p[1]; + if ( p[2] > bmax[2] ) bmax[2] = p[2]; + + cp+=pstride; + } + + + sides[0] = bmax[0] - bmin[0]; + sides[1] = bmax[1] - bmin[1]; + sides[2] = bmax[2] - bmin[2]; + + pos[0] = bmin[0]+sides[0]*0.5f; + pos[1] = bmin[1]+sides[1]*0.5f; + pos[2] = bmin[2]+sides[2]*0.5f; + +} + + +void fm_planeToMatrix(const REAL *plane,REAL *matrix) // convert a plane equation to a 4x4 rotation matrix +{ + REAL ref[3] = { 0, 1, 0 }; + REAL quat[4]; + fm_rotationArc(ref,plane,quat); + fm_quatToMatrix(quat,matrix); + REAL origin[3] = { 0, -plane[3], 0 }; + REAL center[3]; + fm_transform(matrix,origin,center); + fm_setTranslation(center,matrix); +} + +void fm_planeToQuat(const REAL *plane,REAL *quat,REAL *pos) // convert a plane equation to a quaternion and translation +{ + REAL ref[3] = { 0, 1, 0 }; + REAL matrix[16]; + fm_rotationArc(ref,plane,quat); + fm_quatToMatrix(quat,matrix); + REAL origin[3] = { 0, plane[3], 0 }; + fm_transform(matrix,origin,pos); +} + +void fm_eulerMatrix(REAL ax,REAL ay,REAL az,REAL *matrix) // convert euler (in radians) to a dest 4x4 matrix (translation set to zero) +{ + REAL quat[4]; + fm_eulerToQuat(ax,ay,az,quat); + fm_quatToMatrix(quat,matrix); +} + + +//********************************************************** +//********************************************************** +//**** Vertex Welding +//********************************************************** +//********************************************************** + +#ifndef VERTEX_INDEX_H + +#define VERTEX_INDEX_H + +namespace VERTEX_INDEX +{ + +class KdTreeNode; + +typedef std::vector< KdTreeNode * > KdTreeNodeVector; + +enum Axes +{ + X_AXIS = 0, + Y_AXIS = 1, + Z_AXIS = 2 +}; + +class KdTreeFindNode +{ +public: + KdTreeFindNode(void) + { + mNode = 0; + mDistance = 0; + } + KdTreeNode *mNode; + double mDistance; +}; + +class KdTreeInterface +{ +public: + virtual const double * getPositionDouble(uint32_t index) const = 0; + virtual const float * getPositionFloat(uint32_t index) const = 0; +}; + +class KdTreeNode +{ +public: + KdTreeNode(void) + { + mIndex = 0; + mLeft = 0; + mRight = 0; + } + + KdTreeNode(uint32_t index) + { + mIndex = index; + mLeft = 0; + mRight = 0; + }; + + ~KdTreeNode(void) + { + } + + + void addDouble(KdTreeNode *node,Axes dim,const KdTreeInterface *iface) + { + const double *nodePosition = iface->getPositionDouble( node->mIndex ); + const double *position = iface->getPositionDouble( mIndex ); + switch ( dim ) + { + case X_AXIS: + if ( nodePosition[0] <= position[0] ) + { + if ( mLeft ) + mLeft->addDouble(node,Y_AXIS,iface); + else + mLeft = node; + } + else + { + if ( mRight ) + mRight->addDouble(node,Y_AXIS,iface); + else + mRight = node; + } + break; + case Y_AXIS: + if ( nodePosition[1] <= position[1] ) + { + if ( mLeft ) + mLeft->addDouble(node,Z_AXIS,iface); + else + mLeft = node; + } + else + { + if ( mRight ) + mRight->addDouble(node,Z_AXIS,iface); + else + mRight = node; + } + break; + case Z_AXIS: + if ( nodePosition[2] <= position[2] ) + { + if ( mLeft ) + mLeft->addDouble(node,X_AXIS,iface); + else + mLeft = node; + } + else + { + if ( mRight ) + mRight->addDouble(node,X_AXIS,iface); + else + mRight = node; + } + break; + } + + } + + + void addFloat(KdTreeNode *node,Axes dim,const KdTreeInterface *iface) + { + const float *nodePosition = iface->getPositionFloat( node->mIndex ); + const float *position = iface->getPositionFloat( mIndex ); + switch ( dim ) + { + case X_AXIS: + if ( nodePosition[0] <= position[0] ) + { + if ( mLeft ) + mLeft->addFloat(node,Y_AXIS,iface); + else + mLeft = node; + } + else + { + if ( mRight ) + mRight->addFloat(node,Y_AXIS,iface); + else + mRight = node; + } + break; + case Y_AXIS: + if ( nodePosition[1] <= position[1] ) + { + if ( mLeft ) + mLeft->addFloat(node,Z_AXIS,iface); + else + mLeft = node; + } + else + { + if ( mRight ) + mRight->addFloat(node,Z_AXIS,iface); + else + mRight = node; + } + break; + case Z_AXIS: + if ( nodePosition[2] <= position[2] ) + { + if ( mLeft ) + mLeft->addFloat(node,X_AXIS,iface); + else + mLeft = node; + } + else + { + if ( mRight ) + mRight->addFloat(node,X_AXIS,iface); + else + mRight = node; + } + break; + } + + } + + + uint32_t getIndex(void) const { return mIndex; }; + + void search(Axes axis,const double *pos,double radius,uint32_t &count,uint32_t maxObjects,KdTreeFindNode *found,const KdTreeInterface *iface) + { + + const double *position = iface->getPositionDouble(mIndex); + + double dx = pos[0] - position[0]; + double dy = pos[1] - position[1]; + double dz = pos[2] - position[2]; + + KdTreeNode *search1 = 0; + KdTreeNode *search2 = 0; + + switch ( axis ) + { + case X_AXIS: + if ( dx <= 0 ) // JWR if we are to the left + { + search1 = mLeft; // JWR then search to the left + if ( -dx < radius ) // JWR if distance to the right is less than our search radius, continue on the right as well. + search2 = mRight; + } + else + { + search1 = mRight; // JWR ok, we go down the left tree + if ( dx < radius ) // JWR if the distance from the right is less than our search radius + search2 = mLeft; + } + axis = Y_AXIS; + break; + case Y_AXIS: + if ( dy <= 0 ) + { + search1 = mLeft; + if ( -dy < radius ) + search2 = mRight; + } + else + { + search1 = mRight; + if ( dy < radius ) + search2 = mLeft; + } + axis = Z_AXIS; + break; + case Z_AXIS: + if ( dz <= 0 ) + { + search1 = mLeft; + if ( -dz < radius ) + search2 = mRight; + } + else + { + search1 = mRight; + if ( dz < radius ) + search2 = mLeft; + } + axis = X_AXIS; + break; + } + + double r2 = radius*radius; + double m = dx*dx+dy*dy+dz*dz; + + if ( m < r2 ) + { + switch ( count ) + { + case 0: + found[count].mNode = this; + found[count].mDistance = m; + break; + case 1: + if ( m < found[0].mDistance ) + { + if ( maxObjects == 1 ) + { + found[0].mNode = this; + found[0].mDistance = m; + } + else + { + found[1] = found[0]; + found[0].mNode = this; + found[0].mDistance = m; + } + } + else if ( maxObjects > 1) + { + found[1].mNode = this; + found[1].mDistance = m; + } + break; + default: + { + bool inserted = false; + + for (uint32_t i=0; i= maxObjects ) scan=maxObjects-1; + for (uint32_t j=scan; j>i; j--) + { + found[j] = found[j-1]; + } + found[i].mNode = this; + found[i].mDistance = m; + inserted = true; + break; + } + } + + if ( !inserted && count < maxObjects ) + { + found[count].mNode = this; + found[count].mDistance = m; + } + } + break; + } + count++; + if ( count > maxObjects ) + { + count = maxObjects; + } + } + + + if ( search1 ) + search1->search( axis, pos,radius, count, maxObjects, found, iface); + + if ( search2 ) + search2->search( axis, pos,radius, count, maxObjects, found, iface); + + } + + void search(Axes axis,const float *pos,float radius,uint32_t &count,uint32_t maxObjects,KdTreeFindNode *found,const KdTreeInterface *iface) + { + + const float *position = iface->getPositionFloat(mIndex); + + float dx = pos[0] - position[0]; + float dy = pos[1] - position[1]; + float dz = pos[2] - position[2]; + + KdTreeNode *search1 = 0; + KdTreeNode *search2 = 0; + + switch ( axis ) + { + case X_AXIS: + if ( dx <= 0 ) // JWR if we are to the left + { + search1 = mLeft; // JWR then search to the left + if ( -dx < radius ) // JWR if distance to the right is less than our search radius, continue on the right as well. + search2 = mRight; + } + else + { + search1 = mRight; // JWR ok, we go down the left tree + if ( dx < radius ) // JWR if the distance from the right is less than our search radius + search2 = mLeft; + } + axis = Y_AXIS; + break; + case Y_AXIS: + if ( dy <= 0 ) + { + search1 = mLeft; + if ( -dy < radius ) + search2 = mRight; + } + else + { + search1 = mRight; + if ( dy < radius ) + search2 = mLeft; + } + axis = Z_AXIS; + break; + case Z_AXIS: + if ( dz <= 0 ) + { + search1 = mLeft; + if ( -dz < radius ) + search2 = mRight; + } + else + { + search1 = mRight; + if ( dz < radius ) + search2 = mLeft; + } + axis = X_AXIS; + break; + } + + float r2 = radius*radius; + float m = dx*dx+dy*dy+dz*dz; + + if ( m < r2 ) + { + switch ( count ) + { + case 0: + found[count].mNode = this; + found[count].mDistance = m; + break; + case 1: + if ( m < found[0].mDistance ) + { + if ( maxObjects == 1 ) + { + found[0].mNode = this; + found[0].mDistance = m; + } + else + { + found[1] = found[0]; + found[0].mNode = this; + found[0].mDistance = m; + } + } + else if ( maxObjects > 1) + { + found[1].mNode = this; + found[1].mDistance = m; + } + break; + default: + { + bool inserted = false; + + for (uint32_t i=0; i= maxObjects ) scan=maxObjects-1; + for (uint32_t j=scan; j>i; j--) + { + found[j] = found[j-1]; + } + found[i].mNode = this; + found[i].mDistance = m; + inserted = true; + break; + } + } + + if ( !inserted && count < maxObjects ) + { + found[count].mNode = this; + found[count].mDistance = m; + } + } + break; + } + count++; + if ( count > maxObjects ) + { + count = maxObjects; + } + } + + + if ( search1 ) + search1->search( axis, pos,radius, count, maxObjects, found, iface); + + if ( search2 ) + search2->search( axis, pos,radius, count, maxObjects, found, iface); + + } + +private: + + void setLeft(KdTreeNode *left) { mLeft = left; }; + void setRight(KdTreeNode *right) { mRight = right; }; + + KdTreeNode *getLeft(void) { return mLeft; } + KdTreeNode *getRight(void) { return mRight; } + + uint32_t mIndex; + KdTreeNode *mLeft; + KdTreeNode *mRight; +}; + + +#define MAX_BUNDLE_SIZE 1024 // 1024 nodes at a time, to minimize memory allocation and guarantee that pointers are persistent. + +class KdTreeNodeBundle +{ +public: + + KdTreeNodeBundle(void) + { + mNext = 0; + mIndex = 0; + } + + bool isFull(void) const + { + return (bool)( mIndex == MAX_BUNDLE_SIZE ); + } + + KdTreeNode * getNextNode(void) + { + assert(mIndex DoubleVector; +typedef std::vector< float > FloatVector; + +class KdTree : public KdTreeInterface +{ +public: + KdTree(void) + { + mRoot = 0; + mBundle = 0; + mVcount = 0; + mUseDouble = false; + } + + virtual ~KdTree(void) + { + reset(); + } + + const double * getPositionDouble(uint32_t index) const + { + assert( mUseDouble ); + assert ( index < mVcount ); + return &mVerticesDouble[index*3]; + } + + const float * getPositionFloat(uint32_t index) const + { + assert( !mUseDouble ); + assert ( index < mVcount ); + return &mVerticesFloat[index*3]; + } + + uint32_t search(const double *pos,double radius,uint32_t maxObjects,KdTreeFindNode *found) const + { + assert( mUseDouble ); + if ( !mRoot ) return 0; + uint32_t count = 0; + mRoot->search(X_AXIS,pos,radius,count,maxObjects,found,this); + return count; + } + + uint32_t search(const float *pos,float radius,uint32_t maxObjects,KdTreeFindNode *found) const + { + assert( !mUseDouble ); + if ( !mRoot ) return 0; + uint32_t count = 0; + mRoot->search(X_AXIS,pos,radius,count,maxObjects,found,this); + return count; + } + + void reset(void) + { + mRoot = 0; + mVerticesDouble.clear(); + mVerticesFloat.clear(); + KdTreeNodeBundle *bundle = mBundle; + while ( bundle ) + { + KdTreeNodeBundle *next = bundle->mNext; + delete bundle; + bundle = next; + } + mBundle = 0; + mVcount = 0; + } + + uint32_t add(double x,double y,double z) + { + assert(mUseDouble); + uint32_t ret = mVcount; + mVerticesDouble.push_back(x); + mVerticesDouble.push_back(y); + mVerticesDouble.push_back(z); + mVcount++; + KdTreeNode *node = getNewNode(ret); + if ( mRoot ) + { + mRoot->addDouble(node,X_AXIS,this); + } + else + { + mRoot = node; + } + return ret; + } + + uint32_t add(float x,float y,float z) + { + assert(!mUseDouble); + uint32_t ret = mVcount; + mVerticesFloat.push_back(x); + mVerticesFloat.push_back(y); + mVerticesFloat.push_back(z); + mVcount++; + KdTreeNode *node = getNewNode(ret); + if ( mRoot ) + { + mRoot->addFloat(node,X_AXIS,this); + } + else + { + mRoot = node; + } + return ret; + } + + KdTreeNode * getNewNode(uint32_t index) + { + if ( mBundle == 0 ) + { + mBundle = new KdTreeNodeBundle; + } + if ( mBundle->isFull() ) + { + KdTreeNodeBundle *bundle = new KdTreeNodeBundle; + mBundle->mNext = bundle; + mBundle = bundle; + } + KdTreeNode *node = mBundle->getNextNode(); + new ( node ) KdTreeNode(index); + return node; + } + + uint32_t getNearest(const double *pos,double radius,bool &_found) const // returns the nearest possible neighbor's index. + { + assert( mUseDouble ); + uint32_t ret = 0; + + _found = false; + KdTreeFindNode found[1]; + uint32_t count = search(pos,radius,1,found); + if ( count ) + { + KdTreeNode *node = found[0].mNode; + ret = node->getIndex(); + _found = true; + } + return ret; + } + + uint32_t getNearest(const float *pos,float radius,bool &_found) const // returns the nearest possible neighbor's index. + { + assert( !mUseDouble ); + uint32_t ret = 0; + + _found = false; + KdTreeFindNode found[1]; + uint32_t count = search(pos,radius,1,found); + if ( count ) + { + KdTreeNode *node = found[0].mNode; + ret = node->getIndex(); + _found = true; + } + return ret; + } + + const double * getVerticesDouble(void) const + { + assert( mUseDouble ); + const double *ret = 0; + if ( !mVerticesDouble.empty() ) + { + ret = &mVerticesDouble[0]; + } + return ret; + } + + const float * getVerticesFloat(void) const + { + assert( !mUseDouble ); + const float * ret = 0; + if ( !mVerticesFloat.empty() ) + { + ret = &mVerticesFloat[0]; + } + return ret; + } + + uint32_t getVcount(void) const { return mVcount; }; + + void setUseDouble(bool useDouble) + { + mUseDouble = useDouble; + } + +private: + bool mUseDouble; + KdTreeNode *mRoot; + KdTreeNodeBundle *mBundle; + uint32_t mVcount; + DoubleVector mVerticesDouble; + FloatVector mVerticesFloat; +}; + +}; // end of namespace VERTEX_INDEX + +class MyVertexIndex : public fm_VertexIndex +{ +public: + MyVertexIndex(double granularity,bool snapToGrid) + { + mDoubleGranularity = granularity; + mFloatGranularity = (float)granularity; + mSnapToGrid = snapToGrid; + mUseDouble = true; + mKdTree.setUseDouble(true); + } + + MyVertexIndex(float granularity,bool snapToGrid) + { + mDoubleGranularity = granularity; + mFloatGranularity = (float)granularity; + mSnapToGrid = snapToGrid; + mUseDouble = false; + mKdTree.setUseDouble(false); + } + + virtual ~MyVertexIndex(void) + { + + } + + + double snapToGrid(double p) + { + double m = fmod(p,mDoubleGranularity); + p-=m; + return p; + } + + float snapToGrid(float p) + { + float m = fmodf(p,mFloatGranularity); + p-=m; + return p; + } + + uint32_t getIndex(const float *_p,bool &newPos) // get index for a vector float + { + uint32_t ret; + + if ( mUseDouble ) + { + double p[3]; + p[0] = _p[0]; + p[1] = _p[1]; + p[2] = _p[2]; + return getIndex(p,newPos); + } + + newPos = false; + + float p[3]; + + if ( mSnapToGrid ) + { + p[0] = snapToGrid(_p[0]); + p[1] = snapToGrid(_p[1]); + p[2] = snapToGrid(_p[2]); + } + else + { + p[0] = _p[0]; + p[1] = _p[1]; + p[2] = _p[2]; + } + + bool found; + ret = mKdTree.getNearest(p,mFloatGranularity,found); + if ( !found ) + { + newPos = true; + ret = mKdTree.add(p[0],p[1],p[2]); + } + + + return ret; + } + + uint32_t getIndex(const double *_p,bool &newPos) // get index for a vector double + { + uint32_t ret; + + if ( !mUseDouble ) + { + float p[3]; + p[0] = (float)_p[0]; + p[1] = (float)_p[1]; + p[2] = (float)_p[2]; + return getIndex(p,newPos); + } + + newPos = false; + + double p[3]; + + if ( mSnapToGrid ) + { + p[0] = snapToGrid(_p[0]); + p[1] = snapToGrid(_p[1]); + p[2] = snapToGrid(_p[2]); + } + else + { + p[0] = _p[0]; + p[1] = _p[1]; + p[2] = _p[2]; + } + + bool found; + ret = mKdTree.getNearest(p,mDoubleGranularity,found); + if ( !found ) + { + newPos = true; + ret = mKdTree.add(p[0],p[1],p[2]); + } + + + return ret; + } + + const float * getVerticesFloat(void) const + { + const float * ret = 0; + + assert( !mUseDouble ); + + ret = mKdTree.getVerticesFloat(); + + return ret; + } + + const double * getVerticesDouble(void) const + { + const double * ret = 0; + + assert( mUseDouble ); + + ret = mKdTree.getVerticesDouble(); + + return ret; + } + + const float * getVertexFloat(uint32_t index) const + { + const float * ret = 0; + assert( !mUseDouble ); +#ifdef _DEBUG + uint32_t vcount = mKdTree.getVcount(); + assert( index < vcount ); +#endif + ret = mKdTree.getVerticesFloat(); + ret = &ret[index*3]; + return ret; + } + + const double * getVertexDouble(uint32_t index) const + { + const double * ret = 0; + assert( mUseDouble ); +#ifdef _DEBUG + uint32_t vcount = mKdTree.getVcount(); + assert( index < vcount ); +#endif + ret = mKdTree.getVerticesDouble(); + ret = &ret[index*3]; + + return ret; + } + + uint32_t getVcount(void) const + { + return mKdTree.getVcount(); + } + + bool isDouble(void) const + { + return mUseDouble; + } + + + bool saveAsObj(const char *fname,uint32_t tcount,uint32_t *indices) + { + bool ret = false; + + + FILE *fph = fopen(fname,"wb"); + if ( fph ) + { + ret = true; + + uint32_t vcount = getVcount(); + if ( mUseDouble ) + { + const double *v = getVerticesDouble(); + for (uint32_t i=0; i(ret); +} + +fm_VertexIndex * fm_createVertexIndex(float granularity,bool snapToGrid) // create an indexed vertext system for floats +{ + MyVertexIndex *ret = new MyVertexIndex(granularity,snapToGrid); + return static_cast< fm_VertexIndex *>(ret); +} + +void fm_releaseVertexIndex(fm_VertexIndex *vindex) +{ + MyVertexIndex *m = static_cast< MyVertexIndex *>(vindex); + delete m; +} + +#endif // END OF VERTEX WELDING CODE + + +REAL fm_computeBestFitAABB(uint32_t vcount,const REAL *points,uint32_t pstride,REAL *bmin,REAL *bmax) // returns the diagonal distance +{ + + const uint8_t *source = (const uint8_t *) points; + + bmin[0] = points[0]; + bmin[1] = points[1]; + bmin[2] = points[2]; + + bmax[0] = points[0]; + bmax[1] = points[1]; + bmax[2] = points[2]; + + + for (uint32_t i=1; i bmax[0] ) bmax[0] = p[0]; + if ( p[1] > bmax[1] ) bmax[1] = p[1]; + if ( p[2] > bmax[2] ) bmax[2] = p[2]; + + } + + REAL dx = bmax[0] - bmin[0]; + REAL dy = bmax[1] - bmin[1]; + REAL dz = bmax[2] - bmin[2]; + + return (REAL) sqrt( dx*dx + dy*dy + dz*dz ); + +} + + + +/* a = b - c */ +#define vector(a,b,c) \ + (a)[0] = (b)[0] - (c)[0]; \ + (a)[1] = (b)[1] - (c)[1]; \ + (a)[2] = (b)[2] - (c)[2]; + + + +#define innerProduct(v,q) \ + ((v)[0] * (q)[0] + \ + (v)[1] * (q)[1] + \ + (v)[2] * (q)[2]) + +#define crossProduct(a,b,c) \ + (a)[0] = (b)[1] * (c)[2] - (c)[1] * (b)[2]; \ + (a)[1] = (b)[2] * (c)[0] - (c)[2] * (b)[0]; \ + (a)[2] = (b)[0] * (c)[1] - (c)[0] * (b)[1]; + + +bool fm_lineIntersectsTriangle(const REAL *rayStart,const REAL *rayEnd,const REAL *p1,const REAL *p2,const REAL *p3,REAL *sect) +{ + REAL dir[3]; + + dir[0] = rayEnd[0] - rayStart[0]; + dir[1] = rayEnd[1] - rayStart[1]; + dir[2] = rayEnd[2] - rayStart[2]; + + REAL d = (REAL)sqrt(dir[0]*dir[0] + dir[1]*dir[1] + dir[2]*dir[2]); + REAL r = 1.0f / d; + + dir[0]*=r; + dir[1]*=r; + dir[2]*=r; + + + REAL t; + + bool ret = fm_rayIntersectsTriangle(rayStart, dir, p1, p2, p3, t ); + + if ( ret ) + { + if ( t > d ) + { + sect[0] = rayStart[0] + dir[0]*t; + sect[1] = rayStart[1] + dir[1]*t; + sect[2] = rayStart[2] + dir[2]*t; + } + else + { + ret = false; + } + } + + return ret; +} + + + +bool fm_rayIntersectsTriangle(const REAL *p,const REAL *d,const REAL *v0,const REAL *v1,const REAL *v2,REAL &t) +{ + REAL e1[3],e2[3],h[3],s[3],q[3]; + REAL a,f,u,v; + + vector(e1,v1,v0); + vector(e2,v2,v0); + crossProduct(h,d,e2); + a = innerProduct(e1,h); + + if (a > -0.00001 && a < 0.00001) + return(false); + + f = 1/a; + vector(s,p,v0); + u = f * (innerProduct(s,h)); + + if (u < 0.0 || u > 1.0) + return(false); + + crossProduct(q,s,e1); + v = f * innerProduct(d,q); + if (v < 0.0 || u + v > 1.0) + return(false); + // at this stage we can compute t to find out where + // the intersection point is on the line + t = f * innerProduct(e2,q); + if (t > 0) // ray intersection + return(true); + else // this means that there is a line intersection + // but not a ray intersection + return (false); +} + + +inline REAL det(const REAL *p1,const REAL *p2,const REAL *p3) +{ + return p1[0]*p2[1]*p3[2] + p2[0]*p3[1]*p1[2] + p3[0]*p1[1]*p2[2] -p1[0]*p3[1]*p2[2] - p2[0]*p1[1]*p3[2] - p3[0]*p2[1]*p1[2]; +} + + +REAL fm_computeMeshVolume(const REAL *vertices,uint32_t tcount,const uint32_t *indices) +{ + REAL volume = 0; + + for (uint32_t i=0; i= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); +} + + +REAL fm_areaPolygon2d(uint32_t pcount,const REAL *points,uint32_t pstride) +{ + int32_t n = (int32_t)pcount; + + REAL A=0.0f; + for(int32_t p=n-1,q=0; q= y) || (y2 < y && y1 >= y) ) + { + if (x1+(y-y1)/(y2-y1)*(x2-x1)= 3 ) + { + const REAL *prev = fm_getPoint(points,pstride,pcount-1); + const REAL *current = points; + const REAL *next = fm_getPoint(points,pstride,1); + REAL *dest = _dest; + + for (uint32_t i=0; i class Rect3d +{ +public: + Rect3d(void) { }; + + Rect3d(const T *bmin,const T *bmax) + { + + mMin[0] = bmin[0]; + mMin[1] = bmin[1]; + mMin[2] = bmin[2]; + + mMax[0] = bmax[0]; + mMax[1] = bmax[1]; + mMax[2] = bmax[2]; + + } + + void SetMin(const T *bmin) + { + mMin[0] = bmin[0]; + mMin[1] = bmin[1]; + mMin[2] = bmin[2]; + } + + void SetMax(const T *bmax) + { + mMax[0] = bmax[0]; + mMax[1] = bmax[1]; + mMax[2] = bmax[2]; + } + + void SetMin(T x,T y,T z) + { + mMin[0] = x; + mMin[1] = y; + mMin[2] = z; + } + + void SetMax(T x,T y,T z) + { + mMax[0] = x; + mMax[1] = y; + mMax[2] = z; + } + + T mMin[3]; + T mMax[3]; +}; + +#endif + +void splitRect(uint32_t axis, + const Rect3d &source, + Rect3d &b1, + Rect3d &b2, + const REAL *midpoint) +{ + switch ( axis ) + { + case 0: + b1.SetMin(source.mMin); + b1.SetMax( midpoint[0], source.mMax[1], source.mMax[2] ); + + b2.SetMin( midpoint[0], source.mMin[1], source.mMin[2] ); + b2.SetMax(source.mMax); + + break; + case 1: + b1.SetMin(source.mMin); + b1.SetMax( source.mMax[0], midpoint[1], source.mMax[2] ); + + b2.SetMin( source.mMin[0], midpoint[1], source.mMin[2] ); + b2.SetMax(source.mMax); + + break; + case 2: + b1.SetMin(source.mMin); + b1.SetMax( source.mMax[0], source.mMax[1], midpoint[2] ); + + b2.SetMin( source.mMin[0], source.mMin[1], midpoint[2] ); + b2.SetMax(source.mMax); + + break; + } +} + +bool fm_computeSplitPlane(uint32_t vcount, + const REAL *vertices, + uint32_t /* tcount */, + const uint32_t * /* indices */, + REAL *plane) +{ + + REAL sides[3]; + REAL matrix[16]; + + fm_computeBestFitOBB( vcount, vertices, sizeof(REAL)*3, sides, matrix ); + + REAL bmax[3]; + REAL bmin[3]; + + bmax[0] = sides[0]*0.5f; + bmax[1] = sides[1]*0.5f; + bmax[2] = sides[2]*0.5f; + + bmin[0] = -bmax[0]; + bmin[1] = -bmax[1]; + bmin[2] = -bmax[2]; + + + REAL dx = sides[0]; + REAL dy = sides[1]; + REAL dz = sides[2]; + + + uint32_t axis = 0; + + if ( dy > dx ) + { + axis = 1; + } + + if ( dz > dx && dz > dy ) + { + axis = 2; + } + + REAL p1[3]; + REAL p2[3]; + REAL p3[3]; + + p3[0] = p2[0] = p1[0] = bmin[0] + dx*0.5f; + p3[1] = p2[1] = p1[1] = bmin[1] + dy*0.5f; + p3[2] = p2[2] = p1[2] = bmin[2] + dz*0.5f; + + Rect3d b(bmin,bmax); + + Rect3d b1,b2; + + splitRect(axis,b,b1,b2,p1); + + + switch ( axis ) + { + case 0: + p2[1] = bmin[1]; + p2[2] = bmin[2]; + + if ( dz > dy ) + { + p3[1] = bmax[1]; + p3[2] = bmin[2]; + } + else + { + p3[1] = bmin[1]; + p3[2] = bmax[2]; + } + + break; + case 1: + p2[0] = bmin[0]; + p2[2] = bmin[2]; + + if ( dx > dz ) + { + p3[0] = bmax[0]; + p3[2] = bmin[2]; + } + else + { + p3[0] = bmin[0]; + p3[2] = bmax[2]; + } + + break; + case 2: + p2[0] = bmin[0]; + p2[1] = bmin[1]; + + if ( dx > dy ) + { + p3[0] = bmax[0]; + p3[1] = bmin[1]; + } + else + { + p3[0] = bmin[0]; + p3[1] = bmax[1]; + } + + break; + } + + REAL tp1[3]; + REAL tp2[3]; + REAL tp3[3]; + + fm_transform(matrix,p1,tp1); + fm_transform(matrix,p2,tp2); + fm_transform(matrix,p3,tp3); + + plane[3] = fm_computePlane(tp1,tp2,tp3,plane); + + return true; + +} + +#pragma warning(disable:4100) + +void fm_nearestPointInTriangle(const REAL * /*nearestPoint*/,const REAL * /*p1*/,const REAL * /*p2*/,const REAL * /*p3*/,REAL * /*nearest*/) +{ + +} + +static REAL Partial(const REAL *a,const REAL *p) +{ + return (a[0]*p[1]) - (p[0]*a[1]); +} + +REAL fm_areaTriangle(const REAL *p0,const REAL *p1,const REAL *p2) +{ + REAL A = Partial(p0,p1); + A+= Partial(p1,p2); + A+= Partial(p2,p0); + return A*0.5f; +} + +void fm_subtract(const REAL *A,const REAL *B,REAL *diff) // compute A-B and store the result in 'diff' +{ + diff[0] = A[0]-B[0]; + diff[1] = A[1]-B[1]; + diff[2] = A[2]-B[2]; +} + + +void fm_multiplyTransform(const REAL *pA,const REAL *pB,REAL *pM) +{ + + REAL a = pA[0*4+0] * pB[0*4+0] + pA[0*4+1] * pB[1*4+0] + pA[0*4+2] * pB[2*4+0] + pA[0*4+3] * pB[3*4+0]; + REAL b = pA[0*4+0] * pB[0*4+1] + pA[0*4+1] * pB[1*4+1] + pA[0*4+2] * pB[2*4+1] + pA[0*4+3] * pB[3*4+1]; + REAL c = pA[0*4+0] * pB[0*4+2] + pA[0*4+1] * pB[1*4+2] + pA[0*4+2] * pB[2*4+2] + pA[0*4+3] * pB[3*4+2]; + REAL d = pA[0*4+0] * pB[0*4+3] + pA[0*4+1] * pB[1*4+3] + pA[0*4+2] * pB[2*4+3] + pA[0*4+3] * pB[3*4+3]; + + REAL e = pA[1*4+0] * pB[0*4+0] + pA[1*4+1] * pB[1*4+0] + pA[1*4+2] * pB[2*4+0] + pA[1*4+3] * pB[3*4+0]; + REAL f = pA[1*4+0] * pB[0*4+1] + pA[1*4+1] * pB[1*4+1] + pA[1*4+2] * pB[2*4+1] + pA[1*4+3] * pB[3*4+1]; + REAL g = pA[1*4+0] * pB[0*4+2] + pA[1*4+1] * pB[1*4+2] + pA[1*4+2] * pB[2*4+2] + pA[1*4+3] * pB[3*4+2]; + REAL h = pA[1*4+0] * pB[0*4+3] + pA[1*4+1] * pB[1*4+3] + pA[1*4+2] * pB[2*4+3] + pA[1*4+3] * pB[3*4+3]; + + REAL i = pA[2*4+0] * pB[0*4+0] + pA[2*4+1] * pB[1*4+0] + pA[2*4+2] * pB[2*4+0] + pA[2*4+3] * pB[3*4+0]; + REAL j = pA[2*4+0] * pB[0*4+1] + pA[2*4+1] * pB[1*4+1] + pA[2*4+2] * pB[2*4+1] + pA[2*4+3] * pB[3*4+1]; + REAL k = pA[2*4+0] * pB[0*4+2] + pA[2*4+1] * pB[1*4+2] + pA[2*4+2] * pB[2*4+2] + pA[2*4+3] * pB[3*4+2]; + REAL l = pA[2*4+0] * pB[0*4+3] + pA[2*4+1] * pB[1*4+3] + pA[2*4+2] * pB[2*4+3] + pA[2*4+3] * pB[3*4+3]; + + REAL m = pA[3*4+0] * pB[0*4+0] + pA[3*4+1] * pB[1*4+0] + pA[3*4+2] * pB[2*4+0] + pA[3*4+3] * pB[3*4+0]; + REAL n = pA[3*4+0] * pB[0*4+1] + pA[3*4+1] * pB[1*4+1] + pA[3*4+2] * pB[2*4+1] + pA[3*4+3] * pB[3*4+1]; + REAL o = pA[3*4+0] * pB[0*4+2] + pA[3*4+1] * pB[1*4+2] + pA[3*4+2] * pB[2*4+2] + pA[3*4+3] * pB[3*4+2]; + REAL p = pA[3*4+0] * pB[0*4+3] + pA[3*4+1] * pB[1*4+3] + pA[3*4+2] * pB[2*4+3] + pA[3*4+3] * pB[3*4+3]; + + pM[0] = a; pM[1] = b; pM[2] = c; pM[3] = d; + + pM[4] = e; pM[5] = f; pM[6] = g; pM[7] = h; + + pM[8] = i; pM[9] = j; pM[10] = k; pM[11] = l; + + pM[12] = m; pM[13] = n; pM[14] = o; pM[15] = p; +} + +void fm_multiply(REAL *A,REAL scalar) +{ + A[0]*=scalar; + A[1]*=scalar; + A[2]*=scalar; +} + +void fm_add(const REAL *A,const REAL *B,REAL *sum) +{ + sum[0] = A[0]+B[0]; + sum[1] = A[1]+B[1]; + sum[2] = A[2]+B[2]; +} + +void fm_copy3(const REAL *source,REAL *dest) +{ + dest[0] = source[0]; + dest[1] = source[1]; + dest[2] = source[2]; +} + + +uint32_t fm_copyUniqueVertices(uint32_t vcount,const REAL *input_vertices,REAL *output_vertices,uint32_t tcount,const uint32_t *input_indices,uint32_t *output_indices) +{ + uint32_t ret = 0; + + REAL *vertices = (REAL *)malloc(sizeof(REAL)*vcount*3); + memcpy(vertices,input_vertices,sizeof(REAL)*vcount*3); + REAL *dest = output_vertices; + + uint32_t *reindex = (uint32_t *)malloc(sizeof(uint32_t)*vcount); + memset(reindex,0xFF,sizeof(uint32_t)*vcount); + + uint32_t icount = tcount*3; + + for (uint32_t i=0; i 0 ) + { + uint32_t i1 = indices[0]; + uint32_t i2 = indices[1]; + uint32_t i3 = indices[2]; + const REAL *p1 = &vertices[i1*3]; + const REAL *p2 = &vertices[i2*3]; + const REAL *p3 = &vertices[i3*3]; + REAL plane[4]; + plane[3] = fm_computePlane(p1,p2,p3,plane); + const uint32_t *scan = &indices[3]; + for (uint32_t i=1; i= dmin && dot <= dmax ) + { + ret = true; // then the plane equation is for practical purposes identical. + } + } +#endif + return ret; +} + + +void fm_initMinMax(REAL bmin[3],REAL bmax[3]) +{ + bmin[0] = FLT_MAX; + bmin[1] = FLT_MAX; + bmin[2] = FLT_MAX; + + bmax[0] = -FLT_MAX; + bmax[1] = -FLT_MAX; + bmax[2] = -FLT_MAX; +} + +void fm_inflateMinMax(REAL bmin[3], REAL bmax[3], REAL ratio) +{ + REAL inflate = fm_distance(bmin, bmax)*0.5f*ratio; + + bmin[0] -= inflate; + bmin[1] -= inflate; + bmin[2] -= inflate; + + bmax[0] += inflate; + bmax[1] += inflate; + bmax[2] += inflate; +} + +#ifndef TESSELATE_H + +#define TESSELATE_H + +typedef std::vector< uint32_t > UintVector; + +class Myfm_Tesselate : public fm_Tesselate +{ +public: + virtual ~Myfm_Tesselate(void) + { + + } + + const uint32_t * tesselate(fm_VertexIndex *vindex,uint32_t tcount,const uint32_t *indices,float longEdge,uint32_t maxDepth,uint32_t &outcount) + { + const uint32_t *ret = 0; + + mMaxDepth = maxDepth; + mLongEdge = longEdge*longEdge; + mLongEdgeD = mLongEdge; + mVertices = vindex; + + if ( mVertices->isDouble() ) + { + uint32_t vcount = mVertices->getVcount(); + double *vertices = (double *)malloc(sizeof(double)*vcount*3); + memcpy(vertices,mVertices->getVerticesDouble(),sizeof(double)*vcount*3); + + for (uint32_t i=0; igetVcount(); + float *vertices = (float *)malloc(sizeof(float)*vcount*3); + memcpy(vertices,mVertices->getVerticesFloat(),sizeof(float)*vcount*3); + + + for (uint32_t i=0; i mLongEdge || l2 > mLongEdge || l3 > mLongEdge ) + split = true; + + } + + if ( split ) + { + uint32_t edge; + + if ( l1 >= l2 && l1 >= l3 ) + edge = 0; + else if ( l2 >= l1 && l2 >= l3 ) + edge = 1; + else + edge = 2; + + float splits[3]; + + switch ( edge ) + { + case 0: + { + fm_lerp(p1,p2,splits,0.5f); + tesselate(p1,splits,p3, recurse+1 ); + tesselate(splits,p2,p3, recurse+1 ); + } + break; + case 1: + { + fm_lerp(p2,p3,splits,0.5f); + tesselate(p1,p2,splits, recurse+1 ); + tesselate(p1,splits,p3, recurse+1 ); + } + break; + case 2: + { + fm_lerp(p3,p1,splits,0.5f); + tesselate(p1,p2,splits, recurse+1 ); + tesselate(splits,p2,p3, recurse+1 ); + } + break; + } + } + else + { + bool newp; + + uint32_t i1 = mVertices->getIndex(p1,newp); + uint32_t i2 = mVertices->getIndex(p2,newp); + uint32_t i3 = mVertices->getIndex(p3,newp); + + mIndices.push_back(i1); + mIndices.push_back(i2); + mIndices.push_back(i3); + } + + } + + void tesselate(const double *p1,const double *p2,const double *p3,uint32_t recurse) + { + bool split = false; + double l1,l2,l3; + + l1 = l2 = l3 = 0; + + if ( recurse < mMaxDepth ) + { + l1 = fm_distanceSquared(p1,p2); + l2 = fm_distanceSquared(p2,p3); + l3 = fm_distanceSquared(p3,p1); + + if ( l1 > mLongEdgeD || l2 > mLongEdgeD || l3 > mLongEdgeD ) + split = true; + + } + + if ( split ) + { + uint32_t edge; + + if ( l1 >= l2 && l1 >= l3 ) + edge = 0; + else if ( l2 >= l1 && l2 >= l3 ) + edge = 1; + else + edge = 2; + + double splits[3]; + + switch ( edge ) + { + case 0: + { + fm_lerp(p1,p2,splits,0.5); + tesselate(p1,splits,p3, recurse+1 ); + tesselate(splits,p2,p3, recurse+1 ); + } + break; + case 1: + { + fm_lerp(p2,p3,splits,0.5); + tesselate(p1,p2,splits, recurse+1 ); + tesselate(p1,splits,p3, recurse+1 ); + } + break; + case 2: + { + fm_lerp(p3,p1,splits,0.5); + tesselate(p1,p2,splits, recurse+1 ); + tesselate(splits,p2,p3, recurse+1 ); + } + break; + } + } + else + { + bool newp; + + uint32_t i1 = mVertices->getIndex(p1,newp); + uint32_t i2 = mVertices->getIndex(p2,newp); + uint32_t i3 = mVertices->getIndex(p3,newp); + + mIndices.push_back(i1); + mIndices.push_back(i2); + mIndices.push_back(i3); + } + + } + +private: + float mLongEdge; + double mLongEdgeD; + fm_VertexIndex *mVertices; + UintVector mIndices; + uint32_t mMaxDepth; +}; + +fm_Tesselate * fm_createTesselate(void) +{ + Myfm_Tesselate *m = new Myfm_Tesselate; + return static_cast< fm_Tesselate * >(m); +} + +void fm_releaseTesselate(fm_Tesselate *t) +{ + Myfm_Tesselate *m = static_cast< Myfm_Tesselate *>(t); + delete m; +} + +#endif + + +#ifndef RAY_ABB_INTERSECT + +#define RAY_ABB_INTERSECT + +//! Integer representation of a floating-point value. +#define IR(x) ((uint32_t&)x) + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** +* A method to compute a ray-AABB intersection. +* Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 +* Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) +* Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) +* +* Hence this version is faster as well as more robust than the original one. +* +* Should work provided: +* 1) the integer representation of 0.0f is 0x00000000 +* 2) the sign bit of the float is the most significant one +* +* Report bugs: p.terdiman@codercorner.com +* +* \param aabb [in] the axis-aligned bounding box +* \param origin [in] ray origin +* \param dir [in] ray direction +* \param coord [out] impact coordinates +* \return true if ray intersects AABB +*/ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define RAYAABB_EPSILON 0.00001f +bool fm_intersectRayAABB(const float MinB[3],const float MaxB[3],const float origin[3],const float dir[3],float coord[3]) +{ + bool Inside = true; + float MaxT[3]; + MaxT[0]=MaxT[1]=MaxT[2]=-1.0f; + + // Find candidate planes. + for(uint32_t i=0;i<3;i++) + { + if(origin[i] < MinB[i]) + { + coord[i] = MinB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MinB[i] - origin[i]) / dir[i]; + } + else if(origin[i] > MaxB[i]) + { + coord[i] = MaxB[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir[i])) MaxT[i] = (MaxB[i] - origin[i]) / dir[i]; + } + } + + // Ray origin inside bounding box + if(Inside) + { + coord[0] = origin[0]; + coord[1] = origin[1]; + coord[2] = origin[2]; + return true; + } + + // Get largest of the maxT's for final choice of intersection + uint32_t WhichPlane = 0; + if(MaxT[1] > MaxT[WhichPlane]) WhichPlane = 1; + if(MaxT[2] > MaxT[WhichPlane]) WhichPlane = 2; + + // Check final candidate actually inside box + if(IR(MaxT[WhichPlane])&0x80000000) return false; + + for(uint32_t i=0;i<3;i++) + { + if(i!=WhichPlane) + { + coord[i] = origin[i] + MaxT[WhichPlane] * dir[i]; +#ifdef RAYAABB_EPSILON + if(coord[i] < MinB[i] - RAYAABB_EPSILON || coord[i] > MaxB[i] + RAYAABB_EPSILON) return false; +#else + if(coord[i] < MinB[i] || coord[i] > MaxB[i]) return false; +#endif + } + } + return true; // ray hits box +} + +bool fm_intersectLineSegmentAABB(const float bmin[3],const float bmax[3],const float p1[3],const float p2[3],float intersect[3]) +{ + bool ret = false; + + float dir[3]; + dir[0] = p2[0] - p1[0]; + dir[1] = p2[1] - p1[1]; + dir[2] = p2[2] - p1[2]; + float dist = fm_normalize(dir); + if ( dist > RAYAABB_EPSILON ) + { + ret = fm_intersectRayAABB(bmin,bmax,p1,dir,intersect); + if ( ret ) + { + float d = fm_distanceSquared(p1,intersect); + if ( d > (dist*dist) ) + { + ret = false; + } + } + } + return ret; +} + +#endif + +#ifndef OBB_TO_AABB + +#define OBB_TO_AABB + +#pragma warning(disable:4100) + +void fm_OBBtoAABB(const float /*obmin*/[3],const float /*obmax*/[3],const float /*matrix*/[16],float /*abmin*/[3],float /*abmax*/[3]) +{ + assert(0); // not yet implemented. +} + + +const REAL * computePos(uint32_t index,const REAL *vertices,uint32_t vstride) +{ + const char *tmp = (const char *)vertices; + tmp+=(index*vstride); + return (const REAL*)tmp; +} + +void computeNormal(uint32_t index,REAL *normals,uint32_t nstride,const REAL *normal) +{ + char *tmp = (char *)normals; + tmp+=(index*nstride); + REAL *dest = (REAL *)tmp; + dest[0]+=normal[0]; + dest[1]+=normal[1]; + dest[2]+=normal[2]; +} + +void fm_computeMeanNormals(uint32_t vcount, // the number of vertices + const REAL *vertices, // the base address of the vertex position data. + uint32_t vstride, // the stride between position data. + REAL *normals, // the base address of the destination for mean vector normals + uint32_t nstride, // the stride between normals + uint32_t tcount, // the number of triangles + const uint32_t *indices) // the triangle indices +{ + + // Step #1 : Zero out the vertex normals + char *dest = (char *)normals; + for (uint32_t i=0; ixmax[0]) + Copy(xmax,caller_p); + if (caller_p[1]ymax[1]) + Copy(ymax,caller_p); + if (caller_p[2]zmax[2]) + Copy(zmax,caller_p); + scan+=pstride; + } + } + + /* Set xspan = distance between the 2 points xmin & xmax (squared) */ + REAL dx = xmax[0] - xmin[0]; + REAL dy = xmax[1] - xmin[1]; + REAL dz = xmax[2] - xmin[2]; + REAL xspan = dx*dx + dy*dy + dz*dz; + +/* Same for y & z spans */ + dx = ymax[0] - ymin[0]; + dy = ymax[1] - ymin[1]; + dz = ymax[2] - ymin[2]; + REAL yspan = dx*dx + dy*dy + dz*dz; + + dx = zmax[0] - zmin[0]; + dy = zmax[1] - zmin[1]; + dz = zmax[2] - zmin[2]; + REAL zspan = dx*dx + dy*dy + dz*dz; + + /* Set points dia1 & dia2 to the maximally separated pair */ + Copy(dia1,xmin); + Copy(dia2,xmax); /* assume xspan biggest */ + REAL maxspan = xspan; + + if (yspan>maxspan) + { + maxspan = yspan; + Copy(dia1,ymin); + Copy(dia2,ymax); + } + + if (zspan>maxspan) + { + maxspan = zspan; + Copy(dia1,zmin); + Copy(dia2,zmax); + } + + + /* dia1,dia2 is a diameter of initial sphere */ + /* calc initial center */ + center[0] = (dia1[0]+dia2[0])*0.5f; + center[1] = (dia1[1]+dia2[1])*0.5f; + center[2] = (dia1[2]+dia2[2])*0.5f; + + /* calculate initial radius**2 and radius */ + + dx = dia2[0]-center[0]; /* x component of radius vector */ + dy = dia2[1]-center[1]; /* y component of radius vector */ + dz = dia2[2]-center[2]; /* z component of radius vector */ + + radius2 = dx*dx + dy*dy + dz*dz; + radius = REAL(sqrt(radius2)); + + /* SECOND PASS: increment current sphere */ + { + const char *scan = (const char *)points; + for (uint32_t i=0; i radius2) /* do r**2 test first */ + { /* this point is outside of current sphere */ + REAL old_to_p = REAL(sqrt(old_to_p_sq)); + /* calc radius of new sphere */ + radius = (radius + old_to_p) * 0.5f; + radius2 = radius*radius; /* for next r**2 compare */ + REAL old_to_new = old_to_p - radius; + /* calc center of new sphere */ + REAL recip = 1.0f /old_to_p; + REAL cx = (radius*center[0] + old_to_new*caller_p[0]) * recip; + REAL cy = (radius*center[1] + old_to_new*caller_p[1]) * recip; + REAL cz = (radius*center[2] + old_to_new*caller_p[2]) * recip; + Set(center,cx,cy,cz); + scan+=pstride; + } + } + } + return radius; +} + + +void fm_computeBestFitCapsule(uint32_t vcount,const REAL *points,uint32_t pstride,REAL &radius,REAL &height,REAL matrix[16],bool bruteForce) +{ + REAL sides[3]; + REAL omatrix[16]; + fm_computeBestFitOBB(vcount,points,pstride,sides,omatrix,bruteForce); + + int32_t axis = 0; + if ( sides[0] > sides[1] && sides[0] > sides[2] ) + axis = 0; + else if ( sides[1] > sides[0] && sides[1] > sides[2] ) + axis = 1; + else + axis = 2; + + REAL localTransform[16]; + + REAL maxDist = 0; + REAL maxLen = 0; + + switch ( axis ) + { + case 0: + { + fm_eulerMatrix(0,0,FM_PI/2,localTransform); + fm_matrixMultiply(localTransform,omatrix,matrix); + + const uint8_t *scan = (const uint8_t *)points; + for (uint32_t i=0; i maxDist ) + { + maxDist = dist; + } + REAL l = (REAL) fabs(t[0]); + if ( l > maxLen ) + { + maxLen = l; + } + scan+=pstride; + } + } + height = sides[0]; + break; + case 1: + { + fm_eulerMatrix(0,FM_PI/2,0,localTransform); + fm_matrixMultiply(localTransform,omatrix,matrix); + + const uint8_t *scan = (const uint8_t *)points; + for (uint32_t i=0; i maxDist ) + { + maxDist = dist; + } + REAL l = (REAL) fabs(t[1]); + if ( l > maxLen ) + { + maxLen = l; + } + scan+=pstride; + } + } + height = sides[1]; + break; + case 2: + { + fm_eulerMatrix(FM_PI/2,0,0,localTransform); + fm_matrixMultiply(localTransform,omatrix,matrix); + + const uint8_t *scan = (const uint8_t *)points; + for (uint32_t i=0; i maxDist ) + { + maxDist = dist; + } + REAL l = (REAL) fabs(t[2]); + if ( l > maxLen ) + { + maxLen = l; + } + scan+=pstride; + } + } + height = sides[2]; + break; + } + radius = (REAL)sqrt(maxDist); + height = (maxLen*2)-(radius*2); +} + + +//************* Triangulation + +#ifndef TRIANGULATE_H + +#define TRIANGULATE_H + +typedef uint32_t TU32; + +class TVec +{ +public: + TVec(double _x,double _y,double _z) { x = _x; y = _y; z = _z; }; + TVec(void) { }; + + double x; + double y; + double z; +}; + +typedef std::vector< TVec > TVecVector; +typedef std::vector< TU32 > TU32Vector; + +class CTriangulator +{ +public: + /// Default constructor + CTriangulator(); + + /// Default destructor + virtual ~CTriangulator(); + + /// Triangulates the contour + void triangulate(TU32Vector &indices); + + /// Returns the given point in the triangulator array + inline TVec get(const TU32 id) { return mPoints[id]; } + + virtual void reset(void) + { + mInputPoints.clear(); + mPoints.clear(); + mIndices.clear(); + } + + virtual void addPoint(double x,double y,double z) + { + TVec v(x,y,z); + // update bounding box... + if ( mInputPoints.empty() ) + { + mMin = v; + mMax = v; + } + else + { + if ( x < mMin.x ) mMin.x = x; + if ( y < mMin.y ) mMin.y = y; + if ( z < mMin.z ) mMin.z = z; + + if ( x > mMax.x ) mMax.x = x; + if ( y > mMax.y ) mMax.y = y; + if ( z > mMax.z ) mMax.z = z; + } + mInputPoints.push_back(v); + } + + // Triangulation happens in 2d. We could inverse transform the polygon around the normal direction, or we just use the two most significant axes + // Here we find the two longest axes and use them to triangulate. Inverse transforming them would introduce more doubling point error and isn't worth it. + virtual uint32_t * triangulate(uint32_t &tcount,double epsilon) + { + uint32_t *ret = 0; + tcount = 0; + mEpsilon = epsilon; + + if ( !mInputPoints.empty() ) + { + mPoints.clear(); + + double dx = mMax.x - mMin.x; // locate the first, second and third longest edges and store them in i1, i2, i3 + double dy = mMax.y - mMin.y; + double dz = mMax.z - mMin.z; + + uint32_t i1,i2,i3; + + if ( dx > dy && dx > dz ) + { + i1 = 0; + if ( dy > dz ) + { + i2 = 1; + i3 = 2; + } + else + { + i2 = 2; + i3 = 1; + } + } + else if ( dy > dx && dy > dz ) + { + i1 = 1; + if ( dx > dz ) + { + i2 = 0; + i3 = 2; + } + else + { + i2 = 2; + i3 = 0; + } + } + else + { + i1 = 2; + if ( dx > dy ) + { + i2 = 0; + i3 = 1; + } + else + { + i2 = 1; + i3 = 0; + } + } + + uint32_t pcount = (uint32_t)mInputPoints.size(); + const double *points = &mInputPoints[0].x; + for (uint32_t i=0; i 2;) + { + if (0 >= (count--)) + return; + + int32_t u = v; + if (nv <= u) + u = 0; + v = u + 1; + if (nv <= v) + v = 0; + int32_t w = v + 1; + if (nv <= w) + w = 0; + + if (_snip(u, v, w, nv, V)) + { + int32_t a, b, c, s, t; + a = V[u]; + b = V[v]; + c = V[w]; + if ( flipped ) + { + indices.push_back(a); + indices.push_back(b); + indices.push_back(c); + } + else + { + indices.push_back(c); + indices.push_back(b); + indices.push_back(a); + } + m++; + for (s = v, t = v + 1; t < nv; s++, t++) + V[s] = V[t]; + nv--; + count = 2 * nv; + } + } + + free(V); +} + +/// Returns the area of the contour +double CTriangulator::_area() +{ + int32_t n = (uint32_t)mPoints.size(); + double A = 0.0f; + for (int32_t p = n - 1, q = 0; q < n; p = q++) + { + const TVec &pval = mPoints[p]; + const TVec &qval = mPoints[q]; + A += pval.x * qval.y - qval.x * pval.y; + } + A*=0.5f; + return A; +} + +bool CTriangulator::_snip(int32_t u, int32_t v, int32_t w, int32_t n, int32_t *V) +{ + int32_t p; + + const TVec &A = mPoints[ V[u] ]; + const TVec &B = mPoints[ V[v] ]; + const TVec &C = mPoints[ V[w] ]; + + if (mEpsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))) ) + return false; + + for (p = 0; p < n; p++) + { + if ((p == u) || (p == v) || (p == w)) + continue; + const TVec &P = mPoints[ V[p] ]; + if (_insideTriangle(A, B, C, P)) + return false; + } + return true; +} + +/// Tests if a point is inside the given triangle +bool CTriangulator::_insideTriangle(const TVec& A, const TVec& B, const TVec& C,const TVec& P) +{ + double ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy; + double cCROSSap, bCROSScp, aCROSSbp; + + ax = C.x - B.x; ay = C.y - B.y; + bx = A.x - C.x; by = A.y - C.y; + cx = B.x - A.x; cy = B.y - A.y; + apx = P.x - A.x; apy = P.y - A.y; + bpx = P.x - B.x; bpy = P.y - B.y; + cpx = P.x - C.x; cpy = P.y - C.y; + + aCROSSbp = ax * bpy - ay * bpx; + cCROSSap = cx * apy - cy * apx; + bCROSScp = bx * cpy - by * cpx; + + return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f)); +} + +class Triangulate : public fm_Triangulate +{ +public: + Triangulate(void) + { + mPointsFloat = 0; + mPointsDouble = 0; + } + + virtual ~Triangulate(void) + { + reset(); + } + void reset(void) + { + free(mPointsFloat); + free(mPointsDouble); + mPointsFloat = 0; + mPointsDouble = 0; + } + + virtual const double * triangulate3d(uint32_t pcount, + const double *_points, + uint32_t vstride, + uint32_t &tcount, + bool consolidate, + double epsilon) + { + reset(); + + double *points = (double *)malloc(sizeof(double)*pcount*3); + if ( consolidate ) + { + pcount = fm_consolidatePolygon(pcount,_points,vstride,points,1-epsilon); + } + else + { + double *dest = points; + for (uint32_t i=0; i= 3 ) + { + CTriangulator ct; + for (uint32_t i=0; i(t); +} + +void fm_releaseTriangulate(fm_Triangulate *t) +{ + Triangulate *tt = static_cast< Triangulate *>(t); + delete tt; +} + +#endif + +bool validDistance(const REAL *p1,const REAL *p2,REAL epsilon) +{ + bool ret = true; + + REAL dx = p1[0] - p2[0]; + REAL dy = p1[1] - p2[1]; + REAL dz = p1[2] - p2[2]; + REAL dist = dx*dx+dy*dy+dz*dz; + if ( dist < (epsilon*epsilon) ) + { + ret = false; + } + return ret; +} + +bool fm_isValidTriangle(const REAL *p1,const REAL *p2,const REAL *p3,REAL epsilon) +{ + bool ret = false; + + if ( validDistance(p1,p2,epsilon) && + validDistance(p1,p3,epsilon) && + validDistance(p2,p3,epsilon) ) + { + + REAL area = fm_computeArea(p1,p2,p3); + if ( area > epsilon ) + { + REAL _vertices[3*3],vertices[64*3]; + + _vertices[0] = p1[0]; + _vertices[1] = p1[1]; + _vertices[2] = p1[2]; + + _vertices[3] = p2[0]; + _vertices[4] = p2[1]; + _vertices[5] = p2[2]; + + _vertices[6] = p3[0]; + _vertices[7] = p3[1]; + _vertices[8] = p3[2]; + + uint32_t pcount = fm_consolidatePolygon(3,_vertices,sizeof(REAL)*3,vertices,1-epsilon); + if ( pcount == 3 ) + { + ret = true; + } + } + } + return ret; +} + + +void fm_multiplyQuat(const REAL *left,const REAL *right,REAL *quat) +{ + REAL a,b,c,d; + + a = left[3]*right[3] - left[0]*right[0] - left[1]*right[1] - left[2]*right[2]; + b = left[3]*right[0] + right[3]*left[0] + left[1]*right[2] - right[1]*left[2]; + c = left[3]*right[1] + right[3]*left[1] + left[2]*right[0] - right[2]*left[0]; + d = left[3]*right[2] + right[3]*left[2] + left[0]*right[1] - right[0]*left[1]; + + quat[3] = a; + quat[0] = b; + quat[1] = c; + quat[2] = d; +} + +bool fm_computeCentroid(uint32_t vcount, // number of input data points + const REAL *points, // starting address of points array. + REAL *center) + +{ + bool ret = false; + if ( vcount ) + { + center[0] = 0; + center[1] = 0; + center[2] = 0; + const REAL *p = points; + for (uint32_t i=0; i class Vec3 +{ +public: + Vec3(void) + { + + } + Vec3(Type _x,Type _y,Type _z) + { + x = _x; + y = _y; + z = _z; + } + Type x; + Type y; + Type z; +}; +#endif + +void fm_transformAABB(const REAL bmin[3],const REAL bmax[3],const REAL matrix[16],REAL tbmin[3],REAL tbmax[3]) +{ + Vec3 box[8]; + box[0] = Vec3< REAL >( bmin[0], bmin[1], bmin[2] ); + box[1] = Vec3< REAL >( bmax[0], bmin[1], bmin[2] ); + box[2] = Vec3< REAL >( bmax[0], bmax[1], bmin[2] ); + box[3] = Vec3< REAL >( bmin[0], bmax[1], bmin[2] ); + box[4] = Vec3< REAL >( bmin[0], bmin[1], bmax[2] ); + box[5] = Vec3< REAL >( bmax[0], bmin[1], bmax[2] ); + box[6] = Vec3< REAL >( bmax[0], bmax[1], bmax[2] ); + box[7] = Vec3< REAL >( bmin[0], bmax[1], bmax[2] ); + // transform all 8 corners of the box and then recompute a new AABB + for (unsigned int i=0; i<8; i++) + { + Vec3< REAL > &p = box[i]; + fm_transform(matrix,&p.x,&p.x); + if ( i == 0 ) + { + tbmin[0] = tbmax[0] = p.x; + tbmin[1] = tbmax[1] = p.y; + tbmin[2] = tbmax[2] = p.z; + } + else + { + if ( p.x < tbmin[0] ) tbmin[0] = p.x; + if ( p.y < tbmin[1] ) tbmin[1] = p.y; + if ( p.z < tbmin[2] ) tbmin[2] = p.z; + if ( p.x > tbmax[0] ) tbmax[0] = p.x; + if ( p.y > tbmax[1] ) tbmax[1] = p.y; + if ( p.z > tbmax[2] ) tbmax[2] = p.z; + } + } +} + +REAL fm_normalizeQuat(REAL n[4]) // normalize this quat +{ + REAL dx = n[0]*n[0]; + REAL dy = n[1]*n[1]; + REAL dz = n[2]*n[2]; + REAL dw = n[3]*n[3]; + + REAL dist = dx*dx+dy*dy+dz*dz+dw*dw; + + dist = (REAL)sqrt(dist); + + REAL recip = 1.0f / dist; + + n[0]*=recip; + n[1]*=recip; + n[2]*=recip; + n[3]*=recip; + + return dist; +} + + +}; // end of namespace diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 98975ca60..ceef8a992 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -29,6 +29,7 @@ #define ENABLE_VHACD_IMPLEMENTATION 1 #define VHACD_DISABLE_THREADING 0 #include +#include //----------------------------------------------------------------------------- @@ -102,18 +103,18 @@ public: void fitBox( U32 vertCount, const F32* verts ) { - //FLOAT_MATH::fm_computeBestFitOBB( vertCount, verts, sizeof(F32)*3, (F32*)mBoxSides, (F32*)mBoxTransform, false ); + FLOAT_MATH::fm_computeBestFitOBB( vertCount, verts, sizeof(F32)*3, (F32*)mBoxSides, (F32*)mBoxTransform, false ); mBoxTransform.transpose(); } void fitSphere( U32 vertCount, const F32* verts ) { - //mSphereRadius = FLOAT_MATH::fm_computeBestFitSphere( vertCount, verts, sizeof(F32)*3, (F32*)mSphereCenter ); + mSphereRadius = FLOAT_MATH::fm_computeBestFitSphere( vertCount, verts, sizeof(F32)*3, (F32*)mSphereCenter ); } void fitCapsule( U32 vertCount, const F32* verts ) { - //FLOAT_MATH::fm_computeBestFitCapsule( vertCount, verts, sizeof(F32)*3, mCapRadius, mCapHeight, (F32*)mCapTransform ); + FLOAT_MATH::fm_computeBestFitCapsule( vertCount, verts, sizeof(F32)*3, mCapRadius, mCapHeight, (F32*)mCapTransform ); mCapTransform.transpose(); } }; @@ -696,23 +697,39 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh if (( boxMaxError > 0 ) || ( sphereMaxError > 0 ) || ( capsuleMaxError > 0 )) { // Compute error between actual mesh and fitted primitives - F32 meshVolume = 10.0f; // FLOAT_MATH::fm_computeMeshVolume((F32*)&ch.m_points, ch.m_triangles.size(), (U32*)&ch.m_triangles); + F32* points = new F32[ch.m_points.size() * 3]; + for (U32 i = 0; i < ch.m_points.size(); i++) + { + points[i * 3 + 0] = ch.m_points[i].mX; + points[i * 3 + 1] = ch.m_points[i].mY; + points[i * 3 + 2] = ch.m_points[i].mZ; + } + + U32* indices = new U32[ch.m_triangles.size() * 3]; + for (U32 i = 0; i < ch.m_triangles.size(); i++) + { + indices[i * 3 + 0] = ch.m_triangles[i].mI0; + indices[i * 3 + 1] = ch.m_triangles[i].mI1; + indices[i * 3 + 2] = ch.m_triangles[i].mI2; + } + + F32 meshVolume = FLOAT_MATH::fm_computeMeshVolume(points, ch.m_triangles.size(), indices); PrimFit primFitter; F32 boxError = 100.0f, sphereError = 100.0f, capsuleError = 100.0f; if ( boxMaxError > 0 ) { - primFitter.fitBox(ch.m_points.size(), (F32*)&ch.m_points); + primFitter.fitBox(ch.m_points.size(), points); boxError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getBoxVolume() ) ); } if ( sphereMaxError > 0 ) { - primFitter.fitSphere(ch.m_points.size(), (F32*)&ch.m_points); + primFitter.fitSphere(ch.m_points.size(), points); sphereError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getSphereVolume() ) ); } if ( capsuleMaxError > 0 ) { - primFitter.fitCapsule(ch.m_points.size(), (F32*)&ch.m_points); + primFitter.fitCapsule(ch.m_points.size(), points); capsuleError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getCapsuleVolume() ) ); } diff --git a/Tools/CMake/torque_configs.cmake b/Tools/CMake/torque_configs.cmake index 113710b5e..7cfbd2f6b 100644 --- a/Tools/CMake/torque_configs.cmake +++ b/Tools/CMake/torque_configs.cmake @@ -15,7 +15,7 @@ set(TORQUE_COMPILE_DEFINITIONS ICE_NO_DLL PCRE_STATIC TORQUE_ADVANCED_LIGHTING T # All link libraries. Modules should append to this the path to specify additional link libraries (.a, .lib, .dylib, .so) set(TORQUE_LINK_LIBRARIES tinyxml collada squish opcode assimp FLAC FLAC++ ogg vorbis - vorbisfile vorbisenc opus sndfile SDL2 glad pcre zlib) + vorbisfile vorbisenc opus sndfile SDL2 glad pcre convexMath zlib) if(TORQUE_TESTING) set(TORQUE_LINK_LIBRARIES ${TORQUE_LINK_LIBRARIES} gtest gmock) From 948d2e5cefd6c15f548f60d005eef55fee4e46bf Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 15:21:59 +0100 Subject: [PATCH 05/65] Update tsMeshFit.cpp code cleanup, fix warnings etc --- Engine/source/ts/tsMeshFit.cpp | 61 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index ceef8a992..88e105261 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -633,23 +633,23 @@ void MeshFit::fitK_DOP( const Vector& planes ) lastMesh.transform.identity(); U32* indices = new U32[ch.m_triangles.size() * 3]; - for (U32 i = 0; i < ch.m_triangles.size(); i++) + for (U32 ind = 0; ind < ch.m_triangles.size(); ind++) { - indices[i * 3 + 0] = ch.m_triangles[i].mI0; - indices[i * 3 + 1] = ch.m_triangles[i].mI1; - indices[i * 3 + 2] = ch.m_triangles[i].mI2; + indices[ind * 3 + 0] = ch.m_triangles[ind].mI0; + indices[ind * 3 + 1] = ch.m_triangles[ind].mI1; + indices[ind * 3 + 2] = ch.m_triangles[ind].mI2; } F32* resultPts = new F32[ch.m_points.size() * 3]; - for (U32 i = 0; i < ch.m_points.size(); i++) + for (U32 pts = 0; pts < ch.m_points.size(); pts++) { - resultPts[i * 3 + 0] = ch.m_points[i].mX; - resultPts[i * 3 + 1] = ch.m_points[i].mY; - resultPts[i * 3 + 2] = ch.m_points[i].mZ; + resultPts[pts * 3 + 0] = ch.m_points[pts].mX; + resultPts[pts * 3 + 1] = ch.m_points[pts].mY; + resultPts[pts * 3 + 2] = ch.m_points[pts].mZ; } - lastMesh.tsmesh = createTriMesh(resultPts, ch.m_points.size(), - indices, ch.m_triangles.size()); + lastMesh.tsmesh = createTriMesh(resultPts, (S32)ch.m_points.size(), + indices, (S32)ch.m_triangles.size()); lastMesh.tsmesh->computeBounds(); iface->Release(); @@ -663,9 +663,6 @@ void MeshFit::fitK_DOP( const Vector& planes ) void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThreshold, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ) { - const F32 SkinWidth = 0.0f; - const F32 SplitThreshold = 2.0f; - VHACD::IVHACD::Parameters p; p.m_fillMode = VHACD::FillMode::FLOOD_FILL; p.m_maxNumVerticesPerCH = maxHullVerts; @@ -698,25 +695,25 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh { // Compute error between actual mesh and fitted primitives F32* points = new F32[ch.m_points.size() * 3]; - for (U32 i = 0; i < ch.m_points.size(); i++) + for (U32 pt = 0; pt < ch.m_points.size(); pt++) { - points[i * 3 + 0] = ch.m_points[i].mX; - points[i * 3 + 1] = ch.m_points[i].mY; - points[i * 3 + 2] = ch.m_points[i].mZ; + points[pt * 3 + 0] = ch.m_points[pt].mX; + points[pt * 3 + 1] = ch.m_points[pt].mY; + points[pt * 3 + 2] = ch.m_points[pt].mZ; } U32* indices = new U32[ch.m_triangles.size() * 3]; - for (U32 i = 0; i < ch.m_triangles.size(); i++) + for (U32 ind = 0; ind < ch.m_triangles.size(); ind++) { - indices[i * 3 + 0] = ch.m_triangles[i].mI0; - indices[i * 3 + 1] = ch.m_triangles[i].mI1; - indices[i * 3 + 2] = ch.m_triangles[i].mI2; + indices[ind * 3 + 0] = ch.m_triangles[ind].mI0; + indices[ind * 3 + 1] = ch.m_triangles[ind].mI1; + indices[ind * 3 + 2] = ch.m_triangles[ind].mI2; } F32 meshVolume = FLOAT_MATH::fm_computeMeshVolume(points, ch.m_triangles.size(), indices); PrimFit primFitter; - F32 boxError = 100.0f, sphereError = 100.0f, capsuleError = 100.0f; + F32 boxError = 100.0f, sphereError = 100.0f, capsuleError = 100.0; if ( boxMaxError > 0 ) { primFitter.fitBox(ch.m_points.size(), points); @@ -759,6 +756,10 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh else if ( meshType == MeshFit::Capsule ) addCapsule( primFitter.mCapRadius, primFitter.mCapHeight, primFitter.mCapTransform ); // else fall through to Hull processing + + // cleanup + delete[] points; + delete[] indices; } if ( meshType == MeshFit::Hull ) @@ -770,19 +771,19 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh lastMesh.transform.identity(); U32* indices = new U32[ch.m_triangles.size() * 3]; - for (U32 i = 0; i < ch.m_triangles.size(); i++) + for (U32 ind = 0; ind < ch.m_triangles.size(); ind++) { - indices[i * 3 + 0] = ch.m_triangles[i].mI0; - indices[i * 3 + 1] = ch.m_triangles[i].mI1; - indices[i * 3 + 2] = ch.m_triangles[i].mI2; + indices[ind * 3 + 0] = ch.m_triangles[ind].mI0; + indices[ind * 3 + 1] = ch.m_triangles[ind].mI1; + indices[ind * 3 + 2] = ch.m_triangles[ind].mI2; } F32* points = new F32[ch.m_points.size() * 3]; - for (U32 i = 0; i < ch.m_points.size(); i++) + for (U32 pt = 0; pt < ch.m_points.size(); pt++) { - points[i * 3 + 0] = ch.m_points[i].mX; - points[i * 3 + 1] = ch.m_points[i].mY; - points[i * 3 + 2] = ch.m_points[i].mZ; + points[pt * 3 + 0] = ch.m_points[pt].mX; + points[pt * 3 + 1] = ch.m_points[pt].mY; + points[pt * 3 + 2] = ch.m_points[pt].mZ; } lastMesh.tsmesh = createTriMesh(points, ch.m_points.size(), indices, ch.m_triangles.size()); From 399844f7f1f5a1f226b8a8dca10d361f4612005f Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 16:31:30 +0100 Subject: [PATCH 06/65] linux and tests fix linux build change thread test to use TorqueThreadPool --- Engine/source/testing/threadPoolTest.cpp | 10 +++++----- Engine/source/ts/tsMeshFit.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Engine/source/testing/threadPoolTest.cpp b/Engine/source/testing/threadPoolTest.cpp index cdd39c50b..228b7ba61 100644 --- a/Engine/source/testing/threadPoolTest.cpp +++ b/Engine/source/testing/threadPoolTest.cpp @@ -30,7 +30,7 @@ FIXTURE(ThreadPool) public: // Represents a single unit of work. In this test we just set an element in // a result vector. - struct TestItem : public ThreadPool::WorkItem + struct TestItem : public TorqueThreadPool::WorkItem { U32 mIndex; Vector& mResults; @@ -46,7 +46,7 @@ public: // A worker that delays for some time. We'll use this to test the ThreadPool's // synchronous and asynchronous operations. - struct DelayItem : public ThreadPool::WorkItem + struct DelayItem : public TorqueThreadPool::WorkItem { U32 ms; DelayItem(U32 _ms) : ms(_ms) {} @@ -69,7 +69,7 @@ TEST_FIX(ThreadPool, BasicAPI) results[i] = U32(-1); // Launch the work items. - ThreadPool* pool = &ThreadPool::GLOBAL(); + TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(); for (U32 i = 0; i < numItems; i++) { ThreadSafeRef item(new TestItem(i, results)); @@ -89,7 +89,7 @@ TEST_FIX(ThreadPool, Asynchronous) const U32 delay = 500; //ms // Launch a single delaying work item. - ThreadPool* pool = &ThreadPool::GLOBAL(); + TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(); ThreadSafeRef item(new DelayItem(delay)); pool->queueWorkItem(item); @@ -107,7 +107,7 @@ TEST_FIX(ThreadPool, Synchronous) const U32 delay = 500; //ms // Launch a single delaying work item. - ThreadPool* pool = &ThreadPool::GLOBAL(); + TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(); ThreadSafeRef item(new DelayItem(delay)); pool->queueWorkItem(item); diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 88e105261..3f2ef8a23 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -28,7 +28,7 @@ #define ENABLE_VHACD_IMPLEMENTATION 1 #define VHACD_DISABLE_THREADING 0 -#include +#include "ts/vhacd/VHACD.h" #include //----------------------------------------------------------------------------- From 8cf2b1d0efe66ada483c8e6e0a574f677b109000 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 17:51:21 +0100 Subject: [PATCH 07/65] Update tsMeshFit.cpp fix addSphere fix addCapsule now spheres and capsules scale correctly.... hopefully.... --- Engine/source/ts/tsMeshFit.cpp | 49 +++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 3f2ef8a23..6c6b4ced2 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -443,12 +443,27 @@ void MeshFit::addSphere( F32 radius, const Point3F& center ) if ( !mesh ) return; - for ( S32 i = 0; i < mesh->mVertexData.size(); i++ ) + if (mesh->mVerts.size() > 0) { - TSMesh::__TSMeshVertexBase &vdata = mesh->mVertexData.getBase(i); - Point3F v = vdata.vert(); - vdata.vert( v * radius ); + for (S32 i = 0; i < mesh->mVerts.size(); i++) + { + Point3F v = mesh->mVerts[i]; + mesh->mVerts[i] = v * radius; + } + + mesh->mVertexData.setReady(false); } + else + { + for (S32 i = 0; i < mesh->mVertexData.size(); i++) + { + TSMesh::__TSMeshVertexBase& vdata = mesh->mVertexData.getBase(i); + Point3F v = vdata.vert(); + vdata.vert(v * radius); + } + } + + mesh->computeBounds(); mMeshes.increment(); @@ -477,12 +492,28 @@ void MeshFit::addCapsule( F32 radius, F32 height, const MatrixF& mat ) // Translate and scale the mesh verts height = mMax( 0, height ); F32 offset = ( height / ( 2 * radius ) ) - 0.5f; - for ( S32 i = 0; i < mesh->mVertexData.size(); i++ ) + if (mesh->mVerts.size() > 0) { - Point3F v = mesh->mVertexData.getBase(i).vert(); - v.y += ( ( v.y > 0 ) ? offset : -offset ); - mesh->mVertexData.getBase(i).vert( v * radius ); + for (S32 i = 0; i < mesh->mVerts.size(); i++) + { + Point3F v = mesh->mVerts[i]; + v.y += ((v.y > 0) ? offset : -offset); + mesh->mVerts[i] = v * radius; + } + + mesh->mVertexData.setReady(false); } + else + { + for (S32 i = 0; i < mesh->mVertexData.size(); i++) + { + TSMesh::__TSMeshVertexBase& vdata = mesh->mVertexData.getBase(i); + Point3F v = vdata.vert(); + v.y += ((v.y > 0) ? offset : -offset); + vdata.vert(v * radius); + } + } + mesh->computeBounds(); mMeshes.increment(); @@ -722,7 +753,7 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThresh if ( sphereMaxError > 0 ) { primFitter.fitSphere(ch.m_points.size(), points); - sphereError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getSphereVolume() ) ); + sphereError = 100.0f * ( 1.0f - ( meshVolume / primFitter.getSphereVolume())); } if ( capsuleMaxError > 0 ) { From 81a913616c70abdfaf25f4274530006b932dc1a0 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 12 May 2024 21:59:18 +0100 Subject: [PATCH 08/65] revert ThreadPool rename revert ThreadPool rename, resources ThreadPool class is already nested in namespace VHACD --- Engine/source/app/mainLoop.cpp | 8 +-- Engine/source/gfx/bitmap/imageUtils.cpp | 4 +- Engine/source/gfx/video/theoraTexture.cpp | 2 +- Engine/source/gfx/video/theoraTexture.h | 2 +- .../platform/async/asyncBufferedStream.h | 10 +-- .../source/platform/async/asyncPacketStream.h | 6 +- Engine/source/platform/platformNetAsync.cpp | 8 +-- Engine/source/platform/threads/threadPool.cpp | 62 +++++++++---------- Engine/source/platform/threads/threadPool.h | 18 +++--- .../platform/threads/threadPoolAsyncIO.h | 2 +- Engine/source/sfx/sfxInternal.h | 6 +- Engine/source/testing/threadPoolTest.cpp | 10 +-- 12 files changed, 69 insertions(+), 69 deletions(-) diff --git a/Engine/source/app/mainLoop.cpp b/Engine/source/app/mainLoop.cpp index c2b037252..2acb64845 100644 --- a/Engine/source/app/mainLoop.cpp +++ b/Engine/source/app/mainLoop.cpp @@ -262,7 +262,7 @@ void StandardMainLoop::init() RedBook::init(); Platform::initConsole(); - TorqueThreadPool::GlobalThreadPool::createSingleton(); + ThreadPool::GlobalThreadPool::createSingleton(); // Set engineAPI initialized to true engineAPI::gIsInitialized = true; @@ -293,7 +293,7 @@ void StandardMainLoop::init() Con::setVariable("TorqueScriptFileExtension", TORQUE_SCRIPT_EXTENSION); - Con::addVariable( "_forceAllMainThread", TypeBool, &TorqueThreadPool::getForceAllMainThread(), "Force all work items to execute on main thread. turns this into a single-threaded system. Primarily useful to find whether malfunctions are caused by parallel execution or not.\n" + Con::addVariable( "_forceAllMainThread", TypeBool, &ThreadPool::getForceAllMainThread(), "Force all work items to execute on main thread. turns this into a single-threaded system. Primarily useful to find whether malfunctions are caused by parallel execution or not.\n" "@ingroup platform" ); #if defined( TORQUE_MINIDUMP ) && defined( TORQUE_RELEASE ) @@ -351,7 +351,7 @@ void StandardMainLoop::shutdown() EngineModuleManager::shutdownSystem(); - TorqueThreadPool::GlobalThreadPool::deleteSingleton(); + ThreadPool::GlobalThreadPool::deleteSingleton(); #ifdef TORQUE_ENABLE_VFS closeEmbeddedVFSArchive(); @@ -636,7 +636,7 @@ bool StandardMainLoop::doMainLoop() if(!Process::processEvents()) keepRunning = false; - TorqueThreadPool::processMainThreadWorkItems(); + ThreadPool::processMainThreadWorkItems(); Sampler::endFrame(); ConsoleValue::resetConversionBuffer(); PROFILE_END_NAMED(MainLoop); diff --git a/Engine/source/gfx/bitmap/imageUtils.cpp b/Engine/source/gfx/bitmap/imageUtils.cpp index 1b289d31d..3426eecd3 100644 --- a/Engine/source/gfx/bitmap/imageUtils.cpp +++ b/Engine/source/gfx/bitmap/imageUtils.cpp @@ -65,7 +65,7 @@ namespace ImageUtil } //Thread work job for compression - struct CompressJob : public TorqueThreadPool::WorkItem + struct CompressJob : public ThreadPool::WorkItem { S32 width; S32 height; @@ -124,7 +124,7 @@ namespace ImageUtil srcDDS->mFlags.set(DDSFile::CompressedData); //grab global thread pool - TorqueThreadPool* pThreadPool = &TorqueThreadPool::GLOBAL(); + ThreadPool* pThreadPool = &ThreadPool::GLOBAL(); if (cubemap) { diff --git a/Engine/source/gfx/video/theoraTexture.cpp b/Engine/source/gfx/video/theoraTexture.cpp index 3b1403a1b..83063e3be 100644 --- a/Engine/source/gfx/video/theoraTexture.cpp +++ b/Engine/source/gfx/video/theoraTexture.cpp @@ -336,7 +336,7 @@ void TheoraTexture::_onTextureEvent( GFXTexCallbackCode code ) { // Blast out work items and then release all texture locks. - TorqueThreadPool::GLOBAL().flushWorkItems(); + ThreadPool::GLOBAL().flushWorkItems(); mAsyncState->getFrameStream()->releaseTextureLocks(); // The Theora decoder does not implement seeking at the moment, diff --git a/Engine/source/gfx/video/theoraTexture.h b/Engine/source/gfx/video/theoraTexture.h index 899673ad4..8c0748068 100644 --- a/Engine/source/gfx/video/theoraTexture.h +++ b/Engine/source/gfx/video/theoraTexture.h @@ -177,7 +177,7 @@ class TheoraTexture : private IOutputStream< TheoraTextureFrame* >, /// FrameReadItem( AsyncBufferedInputStream< TheoraTextureFrame*, IInputStream< OggTheoraFrame* >* >* stream, - TorqueThreadPool::Context* context ); + ThreadPool::Context* context ); }; /// Stream filter that turns a stream of OggTheoraFrames into a buffered background stream of TheoraTextureFrame diff --git a/Engine/source/platform/async/asyncBufferedStream.h b/Engine/source/platform/async/asyncBufferedStream.h index 15e44a693..8c421c5c9 100644 --- a/Engine/source/platform/async/asyncBufferedStream.h +++ b/Engine/source/platform/async/asyncBufferedStream.h @@ -107,7 +107,7 @@ class AsyncBufferedInputStream : public IInputStreamFilter< T, Stream >, ElementList mBufferedElements; /// The thread pool to which read items are queued. - TorqueThreadPool* mThreadPool; + ThreadPool* mThreadPool; /// The thread context used for prioritizing read items in the pool. ThreadContext* mThreadContext; @@ -132,7 +132,7 @@ class AsyncBufferedInputStream : public IInputStreamFilter< T, Stream >, U32 numSourceElementsToRead = 0, U32 numReadAhead = DEFAULT_STREAM_LOOKAHEAD, bool isLooping = false, - TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(), + ThreadPool* pool = &ThreadPool::GLOBAL(), ThreadContext* context = ThreadContext::ROOT_CONTEXT() ); virtual ~AsyncBufferedInputStream(); @@ -162,7 +162,7 @@ AsyncBufferedInputStream< T, Stream >::AsyncBufferedInputStream U32 numSourceElementsToRead, U32 numReadAhead, bool isLooping, - TorqueThreadPool* threadPool, + ThreadPool* threadPool, ThreadContext* threadContext ) : Parent( stream ), mIsLooping( isLooping ), @@ -321,7 +321,7 @@ class AsyncBufferedReadItem : public ThreadWorkItem /// AsyncBufferedReadItem( const AsyncStreamRef& asyncStream, - TorqueThreadPool::Context* context = NULL + ThreadPool::Context* context = NULL ) : Parent( context ), mAsyncStream( asyncStream ), @@ -376,7 +376,7 @@ class AsyncSingleBufferedInputStream : public AsyncBufferedInputStream< T, Strea U32 numSourceElementsToRead = 0, U32 numReadAhead = Parent::DEFAULT_STREAM_LOOKAHEAD, bool isLooping = false, - TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(), + ThreadPool* pool = &ThreadPool::GLOBAL(), ThreadContext* context = ThreadContext::ROOT_CONTEXT() ) : Parent( stream, numSourceElementsToRead, diff --git a/Engine/source/platform/async/asyncPacketStream.h b/Engine/source/platform/async/asyncPacketStream.h index 517990a41..374118784 100644 --- a/Engine/source/platform/async/asyncPacketStream.h +++ b/Engine/source/platform/async/asyncPacketStream.h @@ -113,7 +113,7 @@ class AsyncPacketBufferedInputStream : public AsyncBufferedInputStream< Packet*, PacketReadItem( const ThreadSafeRef< AsyncPacketBufferedInputStream< Stream, Packet > >& asyncStream, PacketType* packet, U32 numElements, - TorqueThreadPool::Context* context = NULL ) + ThreadPool::Context* context = NULL ) : Parent( asyncStream->getSourceStream(), numElements, 0, *packet, false, 0, context ), mAsyncStream( asyncStream ), mPacket( packet ) {} @@ -227,7 +227,7 @@ class AsyncPacketBufferedInputStream : public AsyncBufferedInputStream< Packet*, U32 numSourceElementsToRead = 0, U32 numReadAhead = Parent::DEFAULT_STREAM_LOOKAHEAD, bool isLooping = false, - TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(), + ThreadPool* pool = &ThreadPool::GLOBAL(), ThreadContext* context = ThreadContext::ROOT_CONTEXT() ); /// @return the size of stream packets returned by this stream in number of elements. @@ -241,7 +241,7 @@ AsyncPacketBufferedInputStream< Stream, Packet >::AsyncPacketBufferedInputStream U32 numSourceElementsToRead, U32 numReadAhead, bool isLooping, - TorqueThreadPool* threadPool, + ThreadPool* threadPool, ThreadContext* threadContext ) : Parent( stream, numSourceElementsToRead, numReadAhead, isLooping, threadPool, threadContext ), mPacketSize( packetSize ), diff --git a/Engine/source/platform/platformNetAsync.cpp b/Engine/source/platform/platformNetAsync.cpp index 57873a312..1a2a33df2 100644 --- a/Engine/source/platform/platformNetAsync.cpp +++ b/Engine/source/platform/platformNetAsync.cpp @@ -65,11 +65,11 @@ struct NetAsync::NameLookupRequest /// Work item issued to the thread pool for each lookup request. -struct NetAsync::NameLookupWorkItem : public TorqueThreadPool::WorkItem +struct NetAsync::NameLookupWorkItem : public ThreadPool::WorkItem { - typedef TorqueThreadPool::WorkItem Parent; + typedef ThreadPool::WorkItem Parent; - NameLookupWorkItem( NameLookupRequest& request, TorqueThreadPool::Context* context = 0 ) + NameLookupWorkItem( NameLookupRequest& request, ThreadPool::Context* context = 0 ) : Parent( context ), mRequest( request ) { @@ -133,7 +133,7 @@ void NetAsync::queueLookup(const char* remoteAddr, NetSocket socket) dStrncpy(lookupRequest.remoteAddr, remoteAddr, sizeof(lookupRequest.remoteAddr)); ThreadSafeRef< NameLookupWorkItem > workItem( new NameLookupWorkItem( lookupRequest ) ); - TorqueThreadPool::GLOBAL().queueWorkItem( workItem ); + ThreadPool::GLOBAL().queueWorkItem( workItem ); } bool NetAsync::checkLookup(NetSocket socket, void* out_h_addr, diff --git a/Engine/source/platform/threads/threadPool.cpp b/Engine/source/platform/threads/threadPool.cpp index a3e4f9266..42f262726 100644 --- a/Engine/source/platform/threads/threadPool.cpp +++ b/Engine/source/platform/threads/threadPool.cpp @@ -34,11 +34,11 @@ // ThreadPool::Context. //============================================================================= -TorqueThreadPool::Context TorqueThreadPool::Context::smRootContext( "ROOT", NULL, 1.0 ); +ThreadPool::Context ThreadPool::Context::smRootContext( "ROOT", NULL, 1.0 ); //-------------------------------------------------------------------------- -TorqueThreadPool::Context::Context( const char* name, TorqueThreadPool::Context* parent, F32 priorityBias ) +ThreadPool::Context::Context( const char* name, ThreadPool::Context* parent, F32 priorityBias ) : mParent( parent ), mName( name ), mChildren( 0 ), @@ -55,7 +55,7 @@ TorqueThreadPool::Context::Context( const char* name, TorqueThreadPool::Context* //-------------------------------------------------------------------------- -TorqueThreadPool::Context::~Context() +ThreadPool::Context::~Context() { if( mParent ) for( Context* context = mParent->mChildren, *prev = 0; context != 0; prev = context, context = context->mSibling ) @@ -70,7 +70,7 @@ TorqueThreadPool::Context::~Context() //-------------------------------------------------------------------------- -TorqueThreadPool::Context* TorqueThreadPool::Context::getChild( const char* name ) +ThreadPool::Context* ThreadPool::Context::getChild( const char* name ) { for( Context* child = getChildren(); child != 0; child = child->getSibling() ) if( dStricmp( child->getName(), name ) == 0 ) @@ -80,7 +80,7 @@ TorqueThreadPool::Context* TorqueThreadPool::Context::getChild( const char* name //-------------------------------------------------------------------------- -F32 TorqueThreadPool::Context::getAccumulatedPriorityBias() +F32 ThreadPool::Context::getAccumulatedPriorityBias() { if( !mAccumulatedPriorityBias ) updateAccumulatedPriorityBiases(); @@ -89,7 +89,7 @@ F32 TorqueThreadPool::Context::getAccumulatedPriorityBias() //-------------------------------------------------------------------------- -void TorqueThreadPool::Context::setPriorityBias( F32 value ) +void ThreadPool::Context::setPriorityBias( F32 value ) { mPriorityBias = value; mAccumulatedPriorityBias = 0.0; @@ -97,7 +97,7 @@ void TorqueThreadPool::Context::setPriorityBias( F32 value ) //-------------------------------------------------------------------------- -void TorqueThreadPool::Context::updateAccumulatedPriorityBiases() +void ThreadPool::Context::updateAccumulatedPriorityBiases() { // Update our own priority bias. @@ -117,7 +117,7 @@ void TorqueThreadPool::Context::updateAccumulatedPriorityBiases() //-------------------------------------------------------------------------- -void TorqueThreadPool::WorkItem::process() +void ThreadPool::WorkItem::process() { execute(); mExecuted = true; @@ -125,14 +125,14 @@ void TorqueThreadPool::WorkItem::process() //-------------------------------------------------------------------------- -bool TorqueThreadPool::WorkItem::isCancellationRequested() +bool ThreadPool::WorkItem::isCancellationRequested() { return false; } //-------------------------------------------------------------------------- -bool TorqueThreadPool::WorkItem::cancellationPoint() +bool ThreadPool::WorkItem::cancellationPoint() { if( isCancellationRequested() ) { @@ -145,7 +145,7 @@ bool TorqueThreadPool::WorkItem::cancellationPoint() //-------------------------------------------------------------------------- -F32 TorqueThreadPool::WorkItem::getPriority() +F32 ThreadPool::WorkItem::getPriority() { return 1.0; } @@ -160,7 +160,7 @@ F32 TorqueThreadPool::WorkItem::getPriority() /// @see ThreadSafePriorityQueueWithUpdate /// @see ThreadPool::WorkItem /// -struct TorqueThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > +struct ThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > { typedef ThreadSafeRef< WorkItem > Parent; @@ -172,7 +172,7 @@ struct TorqueThreadPool::WorkItemWrapper : public ThreadSafeRef< WorkItem > F32 getPriority(); }; -inline bool TorqueThreadPool::WorkItemWrapper::isAlive() +inline bool ThreadPool::WorkItemWrapper::isAlive() { WorkItem* item = ptr(); if( !item ) @@ -186,7 +186,7 @@ inline bool TorqueThreadPool::WorkItemWrapper::isAlive() return true; } -inline F32 TorqueThreadPool::WorkItemWrapper::getPriority() +inline F32 ThreadPool::WorkItemWrapper::getPriority() { WorkItem* item = ptr(); AssertFatal( item != 0, "ThreadPool::WorkItemWrapper::getPriority - called on dead item" ); @@ -201,20 +201,20 @@ inline F32 TorqueThreadPool::WorkItemWrapper::getPriority() /// /// -struct TorqueThreadPool::WorkerThread : public Thread +struct ThreadPool::WorkerThread : public Thread { - WorkerThread( TorqueThreadPool* pool, U32 index ); + WorkerThread( ThreadPool* pool, U32 index ); WorkerThread* getNext(); void run( void* arg = 0 ) override; private: U32 mIndex; - TorqueThreadPool* mPool; + ThreadPool* mPool; WorkerThread* mNext; }; -TorqueThreadPool::WorkerThread::WorkerThread( TorqueThreadPool* pool, U32 index ) +ThreadPool::WorkerThread::WorkerThread( ThreadPool* pool, U32 index ) : mIndex( index ), mPool( pool ) { @@ -224,12 +224,12 @@ TorqueThreadPool::WorkerThread::WorkerThread( TorqueThreadPool* pool, U32 index pool->mThreads = this; } -inline TorqueThreadPool::WorkerThread* TorqueThreadPool::WorkerThread::getNext() +inline ThreadPool::WorkerThread* ThreadPool::WorkerThread::getNext() { return mNext; } -void TorqueThreadPool::WorkerThread::run( void* arg ) +void ThreadPool::WorkerThread::run( void* arg ) { #ifdef TORQUE_DEBUG { @@ -300,13 +300,13 @@ void TorqueThreadPool::WorkerThread::run( void* arg ) // ThreadPool. //============================================================================= -bool TorqueThreadPool::smForceAllMainThread; -U32 TorqueThreadPool::smMainThreadTimeMS; -TorqueThreadPool::QueueType TorqueThreadPool::smMainThreadQueue; +bool ThreadPool::smForceAllMainThread; +U32 ThreadPool::smMainThreadTimeMS; +ThreadPool::QueueType ThreadPool::smMainThreadQueue; //-------------------------------------------------------------------------- -TorqueThreadPool::TorqueThreadPool( const char* name, U32 numThreads ) +ThreadPool::ThreadPool( const char* name, U32 numThreads ) : mName( name ), mNumThreads( numThreads ), mNumThreadsAwake( 0 ), @@ -347,14 +347,14 @@ TorqueThreadPool::TorqueThreadPool( const char* name, U32 numThreads ) //-------------------------------------------------------------------------- -TorqueThreadPool::~TorqueThreadPool() +ThreadPool::~ThreadPool() { shutdown(); } //-------------------------------------------------------------------------- -void TorqueThreadPool::shutdown() +void ThreadPool::shutdown() { const U32 numThreads = mNumThreads; @@ -387,7 +387,7 @@ void TorqueThreadPool::shutdown() //-------------------------------------------------------------------------- -void TorqueThreadPool::queueWorkItem( WorkItem* item ) +void ThreadPool::queueWorkItem( WorkItem* item ) { bool executeRightAway = ( getForceAllMainThread() ); #ifdef DEBUG_SPEW @@ -410,7 +410,7 @@ void TorqueThreadPool::queueWorkItem( WorkItem* item ) //-------------------------------------------------------------------------- -void TorqueThreadPool::flushWorkItems( S32 timeOut ) +void ThreadPool::flushWorkItems( S32 timeOut ) { AssertFatal( mNumThreads, "ThreadPool::flushWorkItems() - no worker threads in pool" ); @@ -432,7 +432,7 @@ void TorqueThreadPool::flushWorkItems( S32 timeOut ) } } -void TorqueThreadPool::waitForAllItems( S32 timeOut ) +void ThreadPool::waitForAllItems( S32 timeOut ) { U32 endTime = 0; if( timeOut != -1 ) @@ -454,14 +454,14 @@ void TorqueThreadPool::waitForAllItems( S32 timeOut ) //-------------------------------------------------------------------------- -void TorqueThreadPool::queueWorkItemOnMainThread( WorkItem* item ) +void ThreadPool::queueWorkItemOnMainThread( WorkItem* item ) { smMainThreadQueue.insert( item->getPriority(), item ); } //-------------------------------------------------------------------------- -void TorqueThreadPool::processMainThreadWorkItems() +void ThreadPool::processMainThreadWorkItems() { AssertFatal( ThreadManager::isMainThread(), "ThreadPool::processMainThreadWorkItems - this function must only be called on the main thread" ); diff --git a/Engine/source/platform/threads/threadPool.h b/Engine/source/platform/threads/threadPool.h index 59abc8620..5fb8cc60b 100644 --- a/Engine/source/platform/threads/threadPool.h +++ b/Engine/source/platform/threads/threadPool.h @@ -70,7 +70,7 @@ /// automatically being released once the last concurrent work item has been /// processed or discarded. /// -class TorqueThreadPool +class ThreadPool { public: @@ -298,9 +298,9 @@ class TorqueThreadPool /// will be based on the number of CPU cores available. /// /// @param numThreads Number of threads to create or zero for default. - TorqueThreadPool( const char* name, U32 numThreads = 0 ); + ThreadPool( const char* name, U32 numThreads = 0 ); - ~TorqueThreadPool(); + ~ThreadPool(); /// Manually shutdown threads outside of static destructors. void shutdown(); @@ -397,16 +397,16 @@ class TorqueThreadPool } /// Return the global thread pool singleton. - static TorqueThreadPool& GLOBAL(); + static ThreadPool& GLOBAL(); }; -typedef TorqueThreadPool::Context ThreadContext; -typedef TorqueThreadPool::WorkItem ThreadWorkItem; +typedef ThreadPool::Context ThreadContext; +typedef ThreadPool::WorkItem ThreadWorkItem; -struct TorqueThreadPool::GlobalThreadPool : public TorqueThreadPool, public ManagedSingleton< GlobalThreadPool > +struct ThreadPool::GlobalThreadPool : public ThreadPool, public ManagedSingleton< GlobalThreadPool > { - typedef TorqueThreadPool Parent; + typedef ThreadPool Parent; GlobalThreadPool() : Parent( "GLOBAL" ) {} @@ -415,7 +415,7 @@ struct TorqueThreadPool::GlobalThreadPool : public TorqueThreadPool, public Mana static const char* getSingletonName() { return "GlobalThreadPool"; } }; -inline TorqueThreadPool& TorqueThreadPool::GLOBAL() +inline ThreadPool& ThreadPool::GLOBAL() { return *( GlobalThreadPool::instance() ); } diff --git a/Engine/source/platform/threads/threadPoolAsyncIO.h b/Engine/source/platform/threads/threadPoolAsyncIO.h index 7e78f1752..35738d091 100644 --- a/Engine/source/platform/threads/threadPoolAsyncIO.h +++ b/Engine/source/platform/threads/threadPoolAsyncIO.h @@ -54,7 +54,7 @@ /// /// @param T Type of elements being streamed. template< typename T, class Stream > -class AsyncIOItem : public TorqueThreadPool::WorkItem +class AsyncIOItem : public ThreadPool::WorkItem { public: diff --git a/Engine/source/sfx/sfxInternal.h b/Engine/source/sfx/sfxInternal.h index e5109727a..7d2c49df2 100644 --- a/Engine/source/sfx/sfxInternal.h +++ b/Engine/source/sfx/sfxInternal.h @@ -358,11 +358,11 @@ enum /// @note Don't use this directly but rather use THREAD_POOL() instead. /// This way, the sound code may be easily switched to using a common /// pool later on. -class SFXThreadPool : public TorqueThreadPool, public ManagedSingleton< SFXThreadPool > +class SFXThreadPool : public ThreadPool, public ManagedSingleton< SFXThreadPool > { public: - typedef TorqueThreadPool Parent; + typedef ThreadPool Parent; /// Create a ThreadPool called "SFX" with two threads. SFXThreadPool() @@ -399,7 +399,7 @@ extern ThreadSafeRef< SFXBufferProcessList > gBufferUpdateList; extern ThreadSafeDeque< SFXBuffer* > gDeadBufferList; /// Return the thread pool used for SFX work. -inline TorqueThreadPool& THREAD_POOL() +inline ThreadPool& THREAD_POOL() { return *( SFXThreadPool::instance() ); } diff --git a/Engine/source/testing/threadPoolTest.cpp b/Engine/source/testing/threadPoolTest.cpp index 228b7ba61..cdd39c50b 100644 --- a/Engine/source/testing/threadPoolTest.cpp +++ b/Engine/source/testing/threadPoolTest.cpp @@ -30,7 +30,7 @@ FIXTURE(ThreadPool) public: // Represents a single unit of work. In this test we just set an element in // a result vector. - struct TestItem : public TorqueThreadPool::WorkItem + struct TestItem : public ThreadPool::WorkItem { U32 mIndex; Vector& mResults; @@ -46,7 +46,7 @@ public: // A worker that delays for some time. We'll use this to test the ThreadPool's // synchronous and asynchronous operations. - struct DelayItem : public TorqueThreadPool::WorkItem + struct DelayItem : public ThreadPool::WorkItem { U32 ms; DelayItem(U32 _ms) : ms(_ms) {} @@ -69,7 +69,7 @@ TEST_FIX(ThreadPool, BasicAPI) results[i] = U32(-1); // Launch the work items. - TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(); + ThreadPool* pool = &ThreadPool::GLOBAL(); for (U32 i = 0; i < numItems; i++) { ThreadSafeRef item(new TestItem(i, results)); @@ -89,7 +89,7 @@ TEST_FIX(ThreadPool, Asynchronous) const U32 delay = 500; //ms // Launch a single delaying work item. - TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(); + ThreadPool* pool = &ThreadPool::GLOBAL(); ThreadSafeRef item(new DelayItem(delay)); pool->queueWorkItem(item); @@ -107,7 +107,7 @@ TEST_FIX(ThreadPool, Synchronous) const U32 delay = 500; //ms // Launch a single delaying work item. - TorqueThreadPool* pool = &TorqueThreadPool::GLOBAL(); + ThreadPool* pool = &ThreadPool::GLOBAL(); ThreadSafeRef item(new DelayItem(delay)); pool->queueWorkItem(item); From afaf228d05f5798b8b0b93adc90aca887003170e Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 14 May 2024 02:25:28 +0100 Subject: [PATCH 09/65] remove upload -Removed: Upload artifact -Simplified the test reporter steps GIT CI seems to have major issues with the upload/download steps at the moment, so remove these and just report the test results --- .github/actions/upload-artifact/action.yml | 20 ----------- .github/workflows/build-linux-gcc.yml | 9 ++--- .github/workflows/build-macos-clang.yml | 9 ++--- .github/workflows/build-windows-msvc.yml | 9 ++--- .github/workflows/test-results-linux.yml | 39 ---------------------- .github/workflows/test-results-mac.yml | 39 ---------------------- .github/workflows/test-results-windows.yml | 39 ---------------------- 7 files changed, 15 insertions(+), 149 deletions(-) delete mode 100644 .github/actions/upload-artifact/action.yml delete mode 100644 .github/workflows/test-results-linux.yml delete mode 100644 .github/workflows/test-results-mac.yml delete mode 100644 .github/workflows/test-results-windows.yml diff --git a/.github/actions/upload-artifact/action.yml b/.github/actions/upload-artifact/action.yml deleted file mode 100644 index f876c5b8e..000000000 --- a/.github/actions/upload-artifact/action.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Upload Torque Test Report -description: Upload Torques unit test artifact. -inputs: - name: - description: The name of the unit test. - default: "${{github.job}}" - path: - description: The path to the upload. - required: true - default: "Torque3D/My Projects/Torque3D/game/test_detail.xml" -runs: - using: "composite" - steps: - - name: Upload Torque Test Report - uses: actions/upload-artifact@v4 - with: - name: ${{inputs.name}} - path: ${{inputs.path}} - retention-days: 1 - overwrite: true diff --git a/.github/workflows/build-linux-gcc.yml b/.github/workflows/build-linux-gcc.yml index 511cd401f..f4afbfe99 100644 --- a/.github/workflows/build-linux-gcc.yml +++ b/.github/workflows/build-linux-gcc.yml @@ -84,8 +84,9 @@ jobs: cd "${{github.workspace}}/My Projects/Torque3D/game" ./Torque3D runTests.tscript - - name: Upload Artifact - uses: ./.github/actions/upload-artifact + - name: Test Reporter + uses: phoenix-actions/test-reporting@v14 with: - name: torque3dLinuxGCCUnitTest - path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" + name: ${{matrix.config.name}} + path: "**/My Projects/Torque3D/game/test_detail.xml" + reporter: java-junit diff --git a/.github/workflows/build-macos-clang.yml b/.github/workflows/build-macos-clang.yml index bf8a6318c..0d356d51a 100644 --- a/.github/workflows/build-macos-clang.yml +++ b/.github/workflows/build-macos-clang.yml @@ -65,8 +65,9 @@ jobs: cd "${{github.workspace}}/My Projects/Torque3D/game" ./Torque3D.app/Contents/MacOS/Torque3D runTests.tscript - - name: Upload Artifact - uses: ./.github/actions/upload-artifact + - name: Test Reporter + uses: phoenix-actions/test-reporting@v14 with: - name: torque3dMacOSXCLANGUnitTest - path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" + name: ${{matrix.config.name}} + path: "**/My Projects/Torque3D/game/test_detail.xml" + reporter: java-junit diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml index 603a41e17..5d3f7b09c 100644 --- a/.github/workflows/build-windows-msvc.yml +++ b/.github/workflows/build-windows-msvc.yml @@ -61,8 +61,9 @@ jobs: cd "${{github.workspace}}/My Projects/Torque3D/game" ./Torque3D.exe runTests.tscript - - name: Upload Artifact - uses: ./.github/actions/upload-artifact + - name: Test Reporter + uses: phoenix-actions/test-reporting@v14 with: - name: torque3dWindowsMSVCUnitTest - path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" + name: ${{matrix.config.name}} + path: "**/My Projects/Torque3D/game/test_detail.xml" + reporter: java-junit diff --git a/.github/workflows/test-results-linux.yml b/.github/workflows/test-results-linux.yml deleted file mode 100644 index 725bbd572..000000000 --- a/.github/workflows/test-results-linux.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Linux Test Results -on: - workflow_run: - workflows: ["Linux Build"] - types: - - completed - -permissions: - checks: write - -jobs: - checks: - name: ${{matrix.config.name}} - runs-on: ${{matrix.config.runos}} - strategy: - fail-fast: false - matrix: - config: - - { - name: "Linux Test Results", - runos: ubuntu-latest, - artifact-name: "torque3dLinuxGCCUnitTest" - } - - steps: - - name: Download Linux Test Report - uses: dawidd6/action-download-artifact@v3 - with: - path: Linux - name: ${{matrix.config.artifact-name}} - workflow: ${{ github.event.workflow.id }} - run_id: ${{ github.event.workflow_run.id }} - - - name: Test Reporter - uses: phoenix-actions/test-reporting@v14 - with: - name: ${{matrix.config.name}} - path: "**/My Projects/Torque3D/game/test_detail.xml" - reporter: java-junit diff --git a/.github/workflows/test-results-mac.yml b/.github/workflows/test-results-mac.yml deleted file mode 100644 index f1337b00a..000000000 --- a/.github/workflows/test-results-mac.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Mac Test Results -on: - workflow_run: - workflows: ["MacOSX Build"] - types: - - completed - -permissions: - checks: write - -jobs: - checks: - name: ${{matrix.config.name}} - runs-on: ${{matrix.config.runos}} - strategy: - fail-fast: false - matrix: - config: - - { - name: "Mac Test Results", - runos: macos-13, - artifact-name: "torque3dMacOSXCLANGUnitTest" - } - - steps: - - name: Download Mac Test Report - uses: dawidd6/action-download-artifact@v3 - with: - path: macOS - name: ${{matrix.config.artifact-name}} - workflow: ${{ github.event.workflow.id }} - run_id: ${{ github.event.workflow_run.id }} - - - name: Test Reporter - uses: phoenix-actions/test-reporting@v14 - with: - name: ${{matrix.config.name}} - path: "**/My Projects/Torque3D/game/test_detail.xml" - reporter: java-junit diff --git a/.github/workflows/test-results-windows.yml b/.github/workflows/test-results-windows.yml deleted file mode 100644 index 6a8964d4d..000000000 --- a/.github/workflows/test-results-windows.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Windows Test Results -on: - workflow_run: - workflows: ["Windows Build"] - types: - - completed - -permissions: - checks: write - -jobs: - checks: - name: ${{matrix.config.name}} - runs-on: ${{matrix.config.runos}} - strategy: - fail-fast: false - matrix: - config: - - { - name: "Windows Test Results", - runos: windows-latest, - artifact-name: "torque3dWindowsMSVCUnitTest" - } - - steps: - - name: Download Windows Test Report - uses: dawidd6/action-download-artifact@v3 - with: - path: Windows - name: ${{matrix.config.artifact-name}} - workflow: ${{ github.event.workflow.id }} - run_id: ${{ github.event.workflow_run.id }} - - - name: Test Reporter - uses: phoenix-actions/test-reporting@v14 - with: - name: ${{matrix.config.name}} - path: "**/My Projects/Torque3D/game/test_detail.xml" - reporter: java-junit From cbf7b3a479d3bdcaaf547e02c356373fc87336b7 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 14 May 2024 02:48:28 +0100 Subject: [PATCH 10/65] replace path path fix --- .github/workflows/build-linux-gcc.yml | 4 ++-- .github/workflows/build-macos-clang.yml | 4 ++-- .github/workflows/build-windows-msvc.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build-linux-gcc.yml b/.github/workflows/build-linux-gcc.yml index f4afbfe99..8214d1a5c 100644 --- a/.github/workflows/build-linux-gcc.yml +++ b/.github/workflows/build-linux-gcc.yml @@ -87,6 +87,6 @@ jobs: - name: Test Reporter uses: phoenix-actions/test-reporting@v14 with: - name: ${{matrix.config.name}} - path: "**/My Projects/Torque3D/game/test_detail.xml" + name: Linux test results + path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit diff --git a/.github/workflows/build-macos-clang.yml b/.github/workflows/build-macos-clang.yml index 0d356d51a..3d99ab883 100644 --- a/.github/workflows/build-macos-clang.yml +++ b/.github/workflows/build-macos-clang.yml @@ -68,6 +68,6 @@ jobs: - name: Test Reporter uses: phoenix-actions/test-reporting@v14 with: - name: ${{matrix.config.name}} - path: "**/My Projects/Torque3D/game/test_detail.xml" + name: MacOS Test results + path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml index 5d3f7b09c..fc53024bb 100644 --- a/.github/workflows/build-windows-msvc.yml +++ b/.github/workflows/build-windows-msvc.yml @@ -64,6 +64,6 @@ jobs: - name: Test Reporter uses: phoenix-actions/test-reporting@v14 with: - name: ${{matrix.config.name}} - path: "**/My Projects/Torque3D/game/test_detail.xml" + name: Windows test results + path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit From 8e89765a79b5970b0418949cd7a5a3aa38e1ce35 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 14 May 2024 03:12:46 +0100 Subject: [PATCH 11/65] ugh add permissions required for test reporter no longer fail on error of creating a test report --- .github/workflows/build-linux-gcc.yml | 11 ++++++++++- .github/workflows/build-macos-clang.yml | 11 ++++++++++- .github/workflows/build-windows-msvc.yml | 11 ++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-linux-gcc.yml b/.github/workflows/build-linux-gcc.yml index 8214d1a5c..4bf7bcb2a 100644 --- a/.github/workflows/build-linux-gcc.yml +++ b/.github/workflows/build-linux-gcc.yml @@ -12,6 +12,14 @@ env: concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-linux cancel-in-progress: true + +permissions: + statuses: write + checks: write + contents: write + pull-requests: write + actions: write + jobs: build-linux: if: github.repository == 'TorqueGameEngines/Torque3D' @@ -88,5 +96,6 @@ jobs: uses: phoenix-actions/test-reporting@v14 with: name: Linux test results - path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" + path: "**/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit + fail-on-error: false diff --git a/.github/workflows/build-macos-clang.yml b/.github/workflows/build-macos-clang.yml index 3d99ab883..6ce380a3b 100644 --- a/.github/workflows/build-macos-clang.yml +++ b/.github/workflows/build-macos-clang.yml @@ -12,6 +12,14 @@ env: concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-macosx cancel-in-progress: true + +permissions: + statuses: write + checks: write + contents: write + pull-requests: write + actions: write + jobs: build-linux: if: github.repository == 'TorqueGameEngines/Torque3D' @@ -69,5 +77,6 @@ jobs: uses: phoenix-actions/test-reporting@v14 with: name: MacOS Test results - path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" + path: "**/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit + fail-on-error: false diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml index fc53024bb..6b6e438ce 100644 --- a/.github/workflows/build-windows-msvc.yml +++ b/.github/workflows/build-windows-msvc.yml @@ -7,6 +7,14 @@ on: concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-windows cancel-in-progress: true + +permissions: + statuses: write + checks: write + contents: write + pull-requests: write + actions: write + jobs: build-windows: if: github.repository == 'TorqueGameEngines/Torque3D' @@ -65,5 +73,6 @@ jobs: uses: phoenix-actions/test-reporting@v14 with: name: Windows test results - path: "${{github.workspace}}/My Projects/Torque3D/game/test_detail.xml" + path: "**/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit + fail-on-error: false From fe5e81a27aad535c92e5ffe565ee647d1f586619 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 13 May 2024 21:31:20 -0500 Subject: [PATCH 12/65] fix fighting itterators --- Engine/source/gui/editor/inspector/field.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Engine/source/gui/editor/inspector/field.cpp b/Engine/source/gui/editor/inspector/field.cpp index 75c7cd923..84060ac1a 100644 --- a/Engine/source/gui/editor/inspector/field.cpp +++ b/Engine/source/gui/editor/inspector/field.cpp @@ -451,16 +451,16 @@ void GuiInspectorField::setWordData(const S32& wordIndex, const char* data, bool StringBuilder newFieldData; const U32 wordCount = StringUnit::getUnitCount(fieldData, " \t\n"); - for (U32 i = 0; i < wordCount; i++) + for (U32 wc = 0; wc < wordCount; wc++) { - if (i != 0) + if (wc != 0) newFieldData.append(" "); - if (i == wordIndex) + if (wc == wordIndex) newFieldData.append(data); else { - newFieldData.append(StringUnit::getUnit(fieldData, i, " \t\n")); + newFieldData.append(StringUnit::getUnit(fieldData, wc, " \t\n")); } } From 6c3a412275d695c58e64c77fa44810bde3c31c6d Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 13 May 2024 21:33:45 -0500 Subject: [PATCH 13/65] add search bar to datablock inspector --- .../DatablockEditorInspectorWindow.ed.gui | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/Templates/BaseGame/game/tools/datablockEditor/DatablockEditorInspectorWindow.ed.gui b/Templates/BaseGame/game/tools/datablockEditor/DatablockEditorInspectorWindow.ed.gui index a2596841b..bc74b47cd 100644 --- a/Templates/BaseGame/game/tools/datablockEditor/DatablockEditorInspectorWindow.ed.gui +++ b/Templates/BaseGame/game/tools/datablockEditor/DatablockEditorInspectorWindow.ed.gui @@ -44,7 +44,37 @@ $guiContent = new GuiControl() { hovertime = "1000"; internalName = "DatablockEditorInspectorWindow"; canSaveDynamicFields = "0"; + + new GuiTextEditCtrl( DatablockEditorInspectorFilter ) { + position = "5 -4"; + extent = "341 20"; + profile = "ToolsGuiTextEditProfile"; + horizSizing = "width"; + vertSizing = "bottom"; + placeholderText = "Filter..."; + validate = "DatablockEditorInspector.setSearchText($ThisControl.getText());"; + }; + new GuiBitmapButtonCtrl() { + bitmapAsset = "ToolsModule:clear_icon_n_image"; + groupNum = "-1"; + buttonType = "PushButton"; + useMouseEvents = "0"; + isContainer = "0"; + Profile = "ToolsGuiDefaultProfile"; + HorizSizing = "left"; + VertSizing = "bottom"; + position = "325 -2"; + Extent = "17 17"; + MinExtent = "8 2"; + canSave = "1"; + Visible = "1"; + tooltipprofile = "ToolsGuiToolTipProfile"; + hovertime = "1000"; + canSaveDynamicFields = "0"; + command = "DatablockEditorInspectorFilter.setText(\"\");DatablockEditorInspector.setSearchText(\"\");"; + }; + new GuiContainer(DatablockEditorInspectorPanel) { Docking = "Client"; Margin = "22 41 3 3"; From 0400bec34f069c830ca8e40c3ec982de59ab5c0f Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 14 May 2024 03:43:09 +0100 Subject: [PATCH 14/65] required permissions only --- .github/workflows/build-linux-gcc.yml | 4 ---- .github/workflows/build-macos-clang.yml | 4 ---- .github/workflows/build-windows-msvc.yml | 4 ---- 3 files changed, 12 deletions(-) diff --git a/.github/workflows/build-linux-gcc.yml b/.github/workflows/build-linux-gcc.yml index 4bf7bcb2a..7734a60d7 100644 --- a/.github/workflows/build-linux-gcc.yml +++ b/.github/workflows/build-linux-gcc.yml @@ -14,11 +14,7 @@ concurrency: cancel-in-progress: true permissions: - statuses: write checks: write - contents: write - pull-requests: write - actions: write jobs: build-linux: diff --git a/.github/workflows/build-macos-clang.yml b/.github/workflows/build-macos-clang.yml index 6ce380a3b..a27f04287 100644 --- a/.github/workflows/build-macos-clang.yml +++ b/.github/workflows/build-macos-clang.yml @@ -14,11 +14,7 @@ concurrency: cancel-in-progress: true permissions: - statuses: write checks: write - contents: write - pull-requests: write - actions: write jobs: build-linux: diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml index 6b6e438ce..0fbcd0ecd 100644 --- a/.github/workflows/build-windows-msvc.yml +++ b/.github/workflows/build-windows-msvc.yml @@ -9,11 +9,7 @@ concurrency: cancel-in-progress: true permissions: - statuses: write checks: write - contents: write - pull-requests: write - actions: write jobs: build-windows: From 78f6206cde4a9cca4ab3526073ffb3ae66de5109 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 14 May 2024 17:20:17 +0100 Subject: [PATCH 15/65] repurposed sliders repurposed and renamed the 2 sliders in the gui to be for settings in vhacd added the drop down for fillMode types but it is not hooked up to source yet --- Engine/source/ts/tsMeshFit.cpp | 18 +- Engine/source/ts/tsShapeConstruct.h | 2 +- .../gui/shapeEdAdvancedWindow.ed.gui | 1823 ++++++----------- .../scripts/shapeEditor.ed.tscript | 6 + 4 files changed, 615 insertions(+), 1234 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 6c6b4ced2..ab4c57b10 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -183,7 +183,7 @@ public: void fit26_DOP(); // Convex Hulls - void fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThreshold, U32 maxHullVerts, + void fitConvexHulls( U32 depth, F32 mergeThreshold, U32 concavityThreshold, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ); }; @@ -691,17 +691,17 @@ void MeshFit::fitK_DOP( const Vector& planes ) //--------------------------- // Best-fit set of convex hulls -void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, F32 concavityThreshold, U32 maxHullVerts, +void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, U32 concavityThreshold, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ) { VHACD::IVHACD::Parameters p; p.m_fillMode = VHACD::FillMode::FLOOD_FILL; p.m_maxNumVerticesPerCH = maxHullVerts; p.m_shrinkWrap = true; - p.m_maxRecursionDepth = 64; - p.m_minimumVolumePercentErrorAllowed = 10; + p.m_maxRecursionDepth = depth; + p.m_minimumVolumePercentErrorAllowed = mergeThreshold; p.m_resolution = 10000; - p.m_maxConvexHulls = depth; + p.m_maxConvexHulls = concavityThreshold; VHACD::IVHACD* iface = VHACD::CreateVHACD_ASYNC(); @@ -929,8 +929,8 @@ DefineTSShapeConstructorMethod( addPrimitive, bool, ( const char* meshName, cons return true; }} -DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 merge, F32 concavity, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( 4, 30, 30, 32, 0, 0, 0 ), - ( size, type, target, depth, merge, concavity, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, +DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 merge, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( 4, 30, 30, 32, 0, 0, 0 ), + ( size, type, target, depth, merge, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, "Autofit a mesh primitive or set of convex hulls to the shape geometry. Hulls " "may optionally be converted to boxes, spheres and/or capsules based on their " "volume.\n" @@ -942,7 +942,7 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char "whole shape), or the name of an object in the shape\n" "@param depth maximum split recursion depth (hulls only)\n" "@param merge volume % threshold used to merge hulls together (hulls only)\n" - "@param concavity volume % threshold used to detect concavity (hulls only)\n" + "@param maxHulls allowed to be generated (hulls only)\n" "@param maxVerts maximum number of vertices per hull (hulls only)\n" "@param boxMaxError max % volume difference for a hull to be converted to a " "box (hulls only)\n" @@ -984,7 +984,7 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char fit.fit26_DOP(); else if ( !dStricmp( type, "convex hulls" ) ) { - fit.fitConvexHulls( depth, merge, concavity, maxVerts, + fit.fitConvexHulls( depth, merge, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ); } else diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index b85337a83..497141ec1 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -315,7 +315,7 @@ public: const char* getImposterSettings(S32 index); S32 addImposter(S32 size, S32 equatorSteps, S32 polarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle); bool removeImposter(); - bool addCollisionDetail(S32 size, const char* type, const char* target, S32 depth = 4, F32 merge = 30.0f, F32 concavity = 30.0f, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); + bool addCollisionDetail(S32 size, const char* type, const char* target, S32 depth = 4, F32 merge = 30.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); ///@} /// @name Sequences diff --git a/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui b/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui index c8824248a..7a52de1fa 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui +++ b/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui @@ -9,7 +9,7 @@ else } //--- OBJECT WRITE BEGIN --- -$guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow, EditorGuiGroup) { +$guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow,EditorGuiGroup) { text = ":: Shape Editor - Advanced Properties"; resizeWidth = "0"; resizeHeight = "0"; @@ -38,112 +38,67 @@ $guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow, EditorGuiGroup) { canSave = "1"; canSaveDynamicFields = "0"; minSize = "50 50"; - + new GuiTabBookCtrl() { - TabPosition = "Top"; - TabMargin = "6"; - MinTabWidth = "32"; - docking = "client"; - Margin = "3 1 3 3"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - position = "4 24"; - extent = "220 243"; - MinExtent = "8 -500"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiTabBookProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; + tabMargin = "6"; + minTabWidth = "32"; + tabHeight = "20"; + selectedPage = "3"; + docking = "Client"; + margin = "3 1 3 3"; + position = "9 29"; + extent = "1262 682"; + minExtent = "8 -500"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiTabBookProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; internalName = "tabBook"; - canSave = "1"; - canSaveDynamicFields = "0"; new GuiTabPageCtrl() { text = "Details"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - Position = "0 19"; - extent = "220 224"; - MinExtent = "0 -500"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiTabPageProfile"; - Visible = "0"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; + position = "0 19"; + extent = "1262 663"; + minExtent = "0 -500"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiTabPageProfile"; + visible = "0"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hidden = "1"; new GuiContainer() { - docking = "client"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - position = "0 0"; - extent = "202 224"; - MinExtent = "8 8"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; + docking = "Client"; + extent = "1262 663"; + minExtent = "8 8"; + horizSizing = "width"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; new GuiContainer() { - position = "0 0"; - extent = "202 157"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "inspectorStyleRolloutDarkProfile"; + extent = "1262 596"; + horizSizing = "width"; + vertSizing = "height"; + profile = "inspectorStyleRolloutDarkProfile"; + tooltipProfile = "GuiToolTipProfile"; new GuiTextCtrl() { text = "Levels:"; position = "4 1"; - Extent = "272 16"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; + extent = "1332 16"; + horizSizing = "width"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiCheckBoxCtrl() { - useInactiveState = "0"; text = "Levels"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; position = "5 22"; - Extent = "49 13"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiCheckBoxProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.fixedDetail"; - Command = "ShapeEdAdvancedWindow-->detailSlider.setActive($ThisControl.getValue()); ShapeEdAdvancedWindow-->levelsInactive.setVisible( !$ThisControl.getValue() );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Allow the slider to select the current detail level"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "49 13"; + profile = "ToolsGuiCheckBoxProfile"; + variable = "ShapeEdShapeView.fixedDetail"; + command = "ShapeEdAdvancedWindow-->detailSlider.setActive($ThisControl.getValue()); ShapeEdAdvancedWindow-->levelsInactive.setVisible( !$ThisControl.getValue() );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Allow the slider to select the current detail level"; }; new GuiSliderCtrl() { range = "0 0"; @@ -151,558 +106,319 @@ $guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow, EditorGuiGroup) { snap = "1"; value = "0"; position = "57 22"; - Extent = "118 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.currentDL"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Drag the slider to change the current detail level"; - hovertime = "1000"; - isContainer = "0"; + extent = "1178 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + variable = "ShapeEdShapeView.currentDL"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Drag the slider to change the current detail level"; internalName = "detailSlider"; - canSave = "1"; - canSaveDynamicFields = "0"; }; - new GuiBitmapCtrl(){ - bitmapAsset = "ToolsModule:inactive_overlay_image"; + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:inactive_overlay_image"; position = "57 19"; - Extent = "290 20"; + extent = "290 20"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; tooltip = "Levels needs to be selected to enable the detail level slider"; hovertime = "500"; - isContainer = true; + isContainer = "1"; internalName = "levelsInactive"; }; new GuiTextCtrl() { text = "0"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - position = "182 20"; - Extent = "15 16"; - MinExtent = "8 2"; - HorizSizing = "left"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.currentDL"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Index of the current detail level"; - hovertime = "1000"; + position = "1242 20"; + extent = "15 16"; + horizSizing = "left"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.currentDL"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Index of the current detail level"; isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "Polys"; position = "37 40"; extent = "26 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { text = "0"; position = "77 40"; - Extent = "40 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.detailPolys"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Number of polygons in the current detail level"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "40 16"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.detailPolys"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of polygons in the current detail level"; }; new GuiTextCtrl() { text = "Size"; position = "127 40"; extent = "24 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextEditCtrl() { position = "160 39"; - extent = "35 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.detailSize"; - AltCommand = "ShapeEdAdvancedWindow.onEditDetailSize();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Edit this value to change the size of the current detail"; - hovertime = "1000"; + extent = "35 20"; + profile = "ToolsGuiTextEditProfile"; + variable = "ShapeEdShapeView.detailSize"; + altCommand = "ShapeEdAdvancedWindow.onEditDetailSize();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Edit this value to change the size of the current detail"; internalName = "detailSize"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "Pixels"; position = "35 60"; extent = "28 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { text = "0"; position = "77 60"; - Extent = "40 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.pixelSize"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Current size (in pixels) of the shape"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "40 16"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.pixelSize"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Current size (in pixels) of the shape"; }; new GuiTextCtrl() { text = "Distance"; position = "109 60"; - Extent = "42 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "42 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { - text = ""; + text = "5"; position = "160 60"; extent = "38 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.orbitDist"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Current distance from the shape to the camera"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.orbitDist"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Current distance from the shape to the camera"; }; new GuiTextCtrl() { text = "Materials"; position = "20 80"; extent = "43 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { - text = ""; + text = "0"; position = "77 80"; extent = "40 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.numMaterials"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Number of materials used by all meshes at this detail level"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.numMaterials"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of materials used by all meshes at this detail level"; }; new GuiTextCtrl() { text = "Bones"; position = "120 80"; extent = "31 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { - text = "5"; + text = "0"; position = "160 80"; extent = "38 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.numBones"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Number of bones at this detail level (skins only)"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.numBones"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of bones at this detail level (skins only)"; }; new GuiTextCtrl() { text = "Primitives"; position = "19 100"; extent = "44 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { - text = ""; + text = "0"; position = "77 100"; extent = "40 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.numDrawCalls"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Total number of mesh primitives (triangle lists) at this detail level"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.numDrawCalls"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Total number of mesh primitives (triangle lists) at this detail level"; }; new GuiTextCtrl() { text = "Weights"; position = "109 100"; - Extent = "42 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "42 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextCtrl() { - text = "5"; + text = "0"; position = "160 100"; extent = "38 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.numWeights"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Number of vertex weights at this detail level (skins only)"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiTextProfile"; + variable = "ShapeEdShapeView.numWeights"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of vertex weights at this detail level (skins only)"; }; new GuiTextCtrl() { - Profile = "ToolsGuiTextProfile"; text = "Col Meshes"; position = "7 120"; extent = "56 16"; - horizSizing = "right"; - vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiTextCtrl() { - text = ""; + text = "0"; position = "74 120"; extent = "40 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Variable = "ShapeEdShapeView.colMeshes"; + profile = "GuiTextProfile"; + variable = "ShapeEdShapeView.colMeshes"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiTextCtrl() { - Profile = "ToolsGuiTextProfile"; text = "Col Polys"; position = "108 120"; extent = "43 16"; - horizSizing = "right"; - vertSizing = "bottom"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiTextCtrl() { - text = ""; + text = "0"; position = "160 120"; extent = "38 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Variable = "ShapeEdShapeView.colPolys"; + profile = "GuiTextProfile"; + variable = "ShapeEdShapeView.colPolys"; + tooltipProfile = "GuiToolTipProfile"; }; }; new GuiContainer() { position = "0 138"; - Extent = "202 90"; - MinExtent = "8 8"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "inspectorStyleRolloutDarkProfile"; - isContainer = "1"; + extent = "1262 90"; + minExtent = "8 8"; + horizSizing = "width"; + profile = "inspectorStyleRolloutDarkProfile"; + tooltipProfile = "GuiToolTipProfile"; - new GuiTextCtrl() { // Header + new GuiTextCtrl() { text = "Imposters:"; position = "5 1"; - Extent = "192 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; + extent = "192 16"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiCheckBoxCtrl() { - useInactiveState = "0"; text = "Use Imposters"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; position = "72 2"; - Extent = "83 13"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiCheckBoxProfile"; - Visible = "1"; - Command = "ShapeEdDetails.onToggleImposter( $ThisControl.getValue() );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Controls whether this shape uses an imposter detail level"; - hovertime = "1000"; - isContainer = "0"; + extent = "83 13"; + profile = "ToolsGuiCheckBoxProfile"; + command = "ShapeEdDetails.onToggleImposter( $ThisControl.getValue() );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Controls whether this shape uses an imposter detail level"; internalName = "bbUseImposters"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "Detail Level"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; position = "6 23"; - Extent = "57 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; + extent = "57 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextEditCtrl() { position = "68 22"; - Extent = "36 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - AltCommand = "ShapeEdDetails.onEditImposter();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Specifies the detail level used to generate the imposters"; - hovertime = "1000"; + extent = "36 20"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "ShapeEdDetails.onEditImposter();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Specifies the detail level used to generate the imposters"; internalName = "bbDetailLevel"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "Dimension"; position = "6 43"; - Extent = "57 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "57 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextEditCtrl() { position = "72 43"; - Extent = "36 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - AltCommand = "ShapeEdDetails.onEditImposter();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Specifies the dimension (width/height) of the imposters in pixels"; - hovertime = "1000"; + extent = "36 20"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "ShapeEdDetails.onEditImposter();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Specifies the dimension (width/height) of the imposters in pixels"; internalName = "bbDimension"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "X Steps"; position = "6 65"; - Extent = "57 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "57 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; new GuiTextEditCtrl() { position = "68 64"; - Extent = "36 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - AltCommand = "ShapeEdDetails.onEditImposter();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Number of steps in the horizontal axis"; - hovertime = "1000"; + extent = "36 20"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "ShapeEdDetails.onEditImposter();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of steps in the horizontal axis"; internalName = "bbEquatorSteps"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiCheckBoxCtrl() { - useInactiveState = "0"; text = "Include Poles"; - groupNum = "-1"; - buttonType = "ToggleButton"; position = "113 24"; - Extent = "83 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiCheckBoxProfile"; - Visible = "1"; - Command = "ShapeEdDetails.onEditImposter();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Specifies whether to include the poles (top and bottom) of the shape"; - hovertime = "1000"; + extent = "83 18"; + profile = "ToolsGuiCheckBoxProfile"; + command = "ShapeEdDetails.onEditImposter();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Specifies whether to include the poles (top and bottom) of the shape"; internalName = "bbIncludePoles"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "Y Steps"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; position = "116 44"; - Extent = "41 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - Tooltip = "Number of steps in the vertical axis"; - hovertime = "1000"; + extent = "41 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of steps in the vertical axis"; isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextEditCtrl() { position = "161 43"; - Extent = "36 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - AltCommand = "ShapeEdDetails.onEditImposter();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; + extent = "36 20"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "ShapeEdDetails.onEditImposter();"; + tooltipProfile = "ToolsGuiToolTipProfile"; internalName = "bbPolarSteps"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "Y Angle"; position = "116 65"; - Extent = "41 16"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; + extent = "41 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; tooltip = "Polar Angle - Y axis"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextEditCtrl() { position = "161 64"; - Extent = "36 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - AltCommand = "ShapeEdDetails.onEditImposter();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; + extent = "36 20"; + profile = "ToolsGuiTextEditProfile"; + altCommand = "ShapeEdDetails.onEditImposter();"; + tooltipProfile = "ToolsGuiToolTipProfile"; internalName = "bbPolarAngle"; - canSave = "1"; - canSaveDynamicFields = "0"; }; - new GuiBitmapCtrl(){ - bitmapAsset = "ToolsModule:inactive_overlay_image"; + new GuiBitmapCtrl() { + BitmapAsset = "ToolsModule:inactive_overlay_image"; position = "4 18"; - Extent = "193 68"; + extent = "193 68"; + profile = "GuiDefaultProfile"; + tooltipProfile = "GuiToolTipProfile"; tooltip = "Imposters must be enabled, and an imposter detail level selected to edit these properties"; hovertime = "500"; isContainer = "1"; @@ -713,1060 +429,719 @@ $guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow, EditorGuiGroup) { }; new GuiTabPageCtrl() { text = "Mounting"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - Position = "0 19"; - extent = "220 224"; - MinExtent = "0 -500"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiTabPageProfile"; - Visible = "0"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; - isContainer = "1"; - - new GuiControl(){ - docking = "client"; - Margin = "0 0 0 0"; - Profile = "ToolsGuiScrollProfile"; - position = "0 0"; + position = "0 19"; + extent = "1262 663"; + minExtent = "0 -500"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiTabPageProfile"; + visible = "0"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hidden = "1"; + + new GuiControl() { extent = "294 224"; - + profile = "ToolsGuiScrollProfile"; + tooltipProfile = "GuiToolTipProfile"; + isContainer = "1"; }; new GuiContainer(ShapeEdMountWindow) { - docking = "none"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - isContainer = "1"; - position = "0 0"; - extent = "220 224"; - MinExtent = "8 8"; - HorizSizing = "width"; + docking = "None"; + extent = "1262 663"; + minExtent = "8 8"; + horizSizing = "width"; vertSizing = "height"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; new GuiCheckBoxCtrl() { - useInactiveState = "0"; text = " Render mounted shapes"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; position = "2 2"; extent = "200 13"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiCheckBoxProfile"; - Visible = "1"; - Variable = "ShapeEdShapeView.renderMounts"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Controls whether mounted shapes will be rendered in the 3D view"; - hovertime = "1000"; - isContainer = "0"; + profile = "ToolsGuiCheckBoxProfile"; + variable = "ShapeEdShapeView.renderMounts"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Controls whether mounted shapes will be rendered in the 3D view"; internalName = "renderMounts"; - canSave = "1"; - canSaveDynamicFields = "0"; }; - new GuiScrollCtrl() { - willFirstRespond = "1"; hScrollBar = "alwaysOff"; vScrollBar = "dynamic"; - lockHorizScroll = "true"; - lockVertScroll = "false"; - constantThumbHeight = "0"; - childMargin = "0 0"; - mouseWheelScrollSpeed = "-1"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "0"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; + lockHorizScroll = "1"; + anchorTop = "0"; position = "0 17"; - extent = "202 124"; - MinExtent = "8 8"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiShapeEdScrollProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSaveDynamicFields = "0"; - canSave = "1"; - isContainer = "1"; + extent = "1244 563"; + minExtent = "8 8"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiScrollProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; new GuiContainer() { - position = "0 0"; - extent = "200 121"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "inspectorStyleRolloutListProfile"; + position = "1 1"; + extent = "1242 560"; + horizSizing = "width"; + vertSizing = "height"; + profile = "inspectorStyleRolloutListProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiTextListCtrl() { columns = "-1 0 110 152"; - fitParentWidth = "1"; clipColumnText = "1"; - Position = "1 1"; - Extent = "200 11"; - MinExtent = "8 11"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiShapeEdTextListProfile"; - Visible = "1"; - Command = "ShapeEdMountWindow.update_onMountSelectionChanged();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; + position = "1 1"; + extent = "1242 11"; + minExtent = "8 11"; + profile = "GuiDefaultProfile"; + command = "ShapeEdMountWindow.update_onMountSelectionChanged();"; + tooltipProfile = "ToolsGuiToolTipProfile"; internalName = "mountList"; - canSave = "1"; - canSaveDynamicFields = "0"; }; }; new GuiContainer() { - position = "0 140"; - extent = "202 85"; - HorizSizing = "width"; - VertSizing = "top"; - Profile = "inspectorStyleRolloutDarkProfile"; - + position = "0 579"; + extent = "1244 85"; + horizSizing = "width"; + vertSizing = "top"; + profile = "inspectorStyleRolloutDarkProfile"; + tooltipProfile = "GuiToolTipProfile"; + new GuiTextCtrl() { text = "Mount Item"; position = "5 1"; extent = "134 16"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:delete_n_image"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "182 1"; - Extent = "16 16"; - MinExtent = "8 2"; - HorizSizing = "left"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - Command = "ShapeEdMountWindow.unmountShape();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Delete the selected mount item"; - canSaveDynamicFields = "0"; - canSave = "1"; - isContainer = "0"; + BitmapAsset = "ToolsModule:delete_n_image"; + position = "1224 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "ShapeEdMountWindow.unmountShape();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Delete the selected mount item"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:new_n_image"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "168 1"; - Extent = "16 16"; - MinExtent = "8 2"; - HorizSizing = "left"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - Command = "ShapeEdMountWindow.mountShape(-1);"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Mounts a new shape to the current model"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + BitmapAsset = "ToolsModule:new_n_image"; + position = "1210 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "ShapeEdMountWindow.mountShape(-1);"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Mounts a new shape to the current model"; }; - /*new GuiButtonCtrl() { - text = "Unmount All"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "109 97"; - extent = "78 18"; - MinExtent = "8 2"; - HorizSizing = "right"; - vertSizing = "top"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdMountWindow.unmountAll();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Unmount all shapes"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; - };*/ new GuiTextCtrl() { text = "Shape"; position = "5 21"; extent = "33 16"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl(ShapeEdMountShapeMenu) { + text = "Browse..."; position = "42 20"; - extent = "156 18"; - HorizSizing = "width"; - vertSizing = "bottom"; - Profile = "ToolsGuiPopUpMenuProfile"; - ToolTip = "Select the model to mount"; + extent = "1198 18"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the model to mount"; }; new GuiTextCtrl() { text = "Node"; position = "5 42"; extent = "33 16"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl() { position = "42 41"; - extent = "62 18"; - minExtent = "8 2"; + extent = "1104 18"; horizSizing = "width"; - vertSizing = "bottom"; - Profile = "ToolsGuiPopUpMenuProfile"; - Command = "ShapeEdMountWindow.updateSelectedMount();"; - ToolTip = "Select the node on which to mount the model"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "ShapeEdMountWindow.updateSelectedMount();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the node on which to mount the model"; internalName = "mountNode"; }; new GuiTextCtrl() { text = "Type"; - position = "110 42"; + position = "1152 42"; extent = "24 16"; - minExtent = "8 2"; horizSizing = "left"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl() { - position = "138 41"; + text = "Image"; + position = "1180 41"; extent = "60 18"; horizSizing = "left"; - vertSizing = "bottom"; - Profile = "ToolsGuiPopUpMenuProfile"; - Command = "ShapeEdMountWindow.updateSelectedMount();"; - ToolTip = "Select the type of mounting to use"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "ShapeEdMountWindow.updateSelectedMount();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the type of mounting to use"; internalName = "mountType"; }; new GuiPopUpMenuCtrl() { + text = ""; position = "5 62"; - extent = "99 18"; - HorizSizing = "width"; - vertSizing = "bottom"; - Profile = "ToolsGuiPopUpMenuProfile"; - Command = "ShapeEdMountWindow.setMountThreadSequence();"; - ToolTip = "Select the sequence to play on the mounted model"; + extent = "1141 18"; + horizSizing = "width"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "ShapeEdMountWindow.setMountThreadSequence();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the sequence to play on the mounted model"; internalName = "mountSeq"; }; new GuiSliderCtrl(ShapeEdMountSeqSlider) { - range = "0 1"; ticks = "0"; - snap = "0"; value = "0"; position = "109 64"; - extent = "68 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "top"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Drag the slider to scrub through the sequence keyframes"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "1110 14"; + horizSizing = "width"; + vertSizing = "top"; + profile = "ToolsGuiSliderBoxProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Drag the slider to scrub through the sequence keyframes"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:playfwd_btn_n_image"; + BitmapAsset = "ToolsModule:playfwd_btn_n_image"; groupNum = "0"; buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "180 62"; - Extent = "18 18"; - MinExtent = "8 2"; - HorizSizing = "left"; + position = "1222 62"; + extent = "18 18"; + horizSizing = "left"; vertSizing = "top"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdMountWindow.toggleMountThreadPlayback();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Play forwards"; - hovertime = "1000"; - isContainer = "0"; + profile = "ToolsGuiButtonProfile"; + command = "ShapeEdMountWindow.toggleMountThreadPlayback();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Play forwards"; internalName = "mountPlayBtn"; - canSave = "1"; - canSaveDynamicFields = "0"; }; }; }; }; new GuiTabPageCtrl() { text = "Threads"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - Position = "0 19"; - extent = "220 224"; - MinExtent = "0 -500"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiTabPageProfile"; - Visible = "0"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; - + position = "0 19"; + extent = "1262 663"; + minExtent = "0 -500"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiTabPageProfile"; + visible = "0"; + tooltipProfile = "ToolsGuiToolTipProfile"; + hidden = "1"; + new GuiContainer(ShapeEdThreadWindow) { - docking = "client"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - position = "0 0"; - extent = "202 224"; - MinExtent = "8 8"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; - + docking = "Client"; + extent = "1262 663"; + minExtent = "8 8"; + horizSizing = "width"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + new GuiContainer() { - position = "0 0"; - extent = "203 141"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "inspectorStyleRolloutDarkProfile"; - + extent = "1263 580"; + horizSizing = "width"; + vertSizing = "height"; + profile = "inspectorStyleRolloutDarkProfile"; + tooltipProfile = "GuiToolTipProfile"; + new GuiTextCtrl() { text = "Thread"; position = "4 1"; extent = "41 16"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiScrollCtrl() { - willFirstRespond = "1"; hScrollBar = "alwaysOff"; vScrollBar = "dynamic"; - lockHorizScroll = "true"; - lockVertScroll = "false"; - constantThumbHeight = "0"; - childMargin = "0 0"; - mouseWheelScrollSpeed = "-1"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "0"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; + lockHorizScroll = "1"; + anchorTop = "0"; position = "0 17"; - extent = "47 124"; - MinExtent = "8 8"; - HorizSizing = "right"; - VertSizing = "height"; - Profile = "ToolsGuiShapeEdScrollProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "47 563"; + minExtent = "8 8"; + vertSizing = "height"; + profile = "GuiScrollProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; new GuiTextListCtrl(ShapeEdThreadList) { - fitParentWidth = "1"; clipColumnText = "1"; position = "1 1"; extent = "45 11"; - MinExtent = "8 11"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiShapeEdTextListProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - canSave = "1"; - canSaveDynamicFields = "0"; + minExtent = "8 11"; + profile = "GuiDefaultProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; }; }; new GuiTextCtrl() { text = "Sequence"; position = "52 1"; extent = "53 16"; - minExtent = "8 2"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiScrollCtrl() { - willFirstRespond = "1"; hScrollBar = "dynamic"; vScrollBar = "dynamic"; - lockHorizScroll = "true"; - lockVertScroll = "false"; - constantThumbHeight = "0"; - childMargin = "0 0"; - mouseWheelScrollSpeed = "-1"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "0"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; + lockHorizScroll = "1"; + anchorTop = "0"; position = "46 17"; - extent = "202 124"; - MinExtent = "8 8"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiShapeEdScrollProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; + extent = "1262 563"; + minExtent = "8 8"; + horizSizing = "width"; + vertSizing = "height"; + profile = "GuiScrollProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; new GuiTextListCtrl() { - fitParentWidth = "1"; clipColumnText = "1"; - Position = "1 1"; - extent = "202 11"; - MinExtent = "8 11"; - HorizSizing = "right"; - VertSizing = "bottom"; - Profile = "ToolsGuiShapeEdTextListProfile"; - Visible = "1"; - Command = "ShapeEdSequenceList.setSelectedById( $ThisControl.getSelectedId() );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; + position = "1 1"; + extent = "1260 11"; + minExtent = "8 11"; + profile = "GuiDefaultProfile"; + command = "ShapeEdSequenceList.setSelectedById( $ThisControl.getSelectedId() );"; + tooltipProfile = "ToolsGuiToolTipProfile"; internalName = "seqList"; - canSave = "1"; - canSaveDynamicFields = "0"; }; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:delete_n_image"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "184 1"; - Extent = "16 16"; - MinExtent = "8 2"; - HorizSizing = "left"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - Command = "ShapeEdThreadWindow.onRemoveThread();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Delete the selected thread"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + BitmapAsset = "ToolsModule:delete_n_image"; + position = "1244 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "ShapeEdThreadWindow.onRemoveThread();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Delete the selected thread"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:new_n_image"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "171 1"; - Extent = "16 16"; - MinExtent = "8 2"; - HorizSizing = "left"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - Command = "ShapeEdThreadWindow.onAddThread();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Add a new thread"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + BitmapAsset = "ToolsModule:new_n_image"; + position = "1231 1"; + extent = "16 16"; + horizSizing = "left"; + profile = "ToolsGuiDefaultProfile"; + command = "ShapeEdThreadWindow.onAddThread();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Add a new thread"; }; }; new GuiSliderCtrl(ShapeEdThreadSlider) { range = "0 0"; ticks = "0"; - snap = "0"; value = "0"; - position = "29 146"; - extent = "133 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "top"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Drag the slider to scrub through the sequence keyframes"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + position = "29 585"; + extent = "1193 14"; + horizSizing = "width"; + vertSizing = "top"; + profile = "ToolsGuiSliderBoxProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Drag the slider to scrub through the sequence keyframes"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:playbkwd_btn_n_image"; + BitmapAsset = "ToolsModule:playbkwd_btn_n_image"; groupNum = "0"; buttonType = "RadioButton"; - useMouseEvents = "0"; - position = "6 144"; + position = "6 583"; extent = "18 18"; - MinExtent = "8 2"; - HorizSizing = "right"; vertSizing = "top"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdAnimWindow-->playBkwdBtn.performClick();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Play backwards"; - hovertime = "1000"; - isContainer = "0"; + profile = "ToolsGuiButtonProfile"; + command = "ShapeEdAnimWindow-->playBkwdBtn.performClick();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Play backwards"; internalName = "playBkwdBtn"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:pause_btn_n_image"; + BitmapAsset = "ToolsModule:pause_btn_n_image"; groupNum = "0"; buttonType = "RadioButton"; - useMouseEvents = "0"; - position = "166 144"; - Extent = "18 18"; - MinExtent = "8 2"; - HorizSizing = "left"; + position = "1226 583"; + extent = "18 18"; + horizSizing = "left"; vertSizing = "top"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdAnimWindow-->pauseBtn.performClick();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Toggle Pause (SPACE)"; - hovertime = "1000"; - isContainer = "0"; + profile = "ToolsGuiButtonProfile"; + command = "ShapeEdAnimWindow-->pauseBtn.performClick();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Toggle Pause (SPACE)"; internalName = "pauseBtn"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiBitmapButtonCtrl() { - bitmapAsset = "ToolsModule:playfwd_btn_n_image"; + BitmapAsset = "ToolsModule:playfwd_btn_n_image"; groupNum = "0"; buttonType = "RadioButton"; - useMouseEvents = "0"; - position = "184 144"; - Extent = "18 18"; - MinExtent = "8 2"; - HorizSizing = "left"; + position = "1244 583"; + extent = "18 18"; + horizSizing = "left"; vertSizing = "top"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdAnimWindow-->playFwdBtn.performClick();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Play forwards"; - hovertime = "1000"; - isContainer = "0"; + profile = "ToolsGuiButtonProfile"; + command = "ShapeEdAnimWindow-->playFwdBtn.performClick();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Play forwards"; internalName = "playFwdBtn"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiCheckBoxCtrl() { - useInactiveState = "0"; text = " Transition lasts"; - groupNum = "-1"; - buttonType = "ToggleButton"; - useMouseEvents = "0"; - position = "3 167"; + position = "3 606"; extent = "88 13"; - MinExtent = "8 2"; - HorizSizing = "right"; - VertSizing = "top"; - Profile = "ToolsGuiCheckBoxProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Controls whether the thread will smoothly transition when a new sequence is selected"; - hovertime = "1000"; - isContainer = "0"; + vertSizing = "top"; + profile = "ToolsGuiCheckBoxProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Controls whether the thread will smoothly transition when a new sequence is selected"; internalName = "useTransitions"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextEditCtrl() { - position = "98 164"; - extent = "49 18"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "top"; - Profile = "ToolsGuiTextEditProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Number of seconds over which to transition to the new sequence"; - hovertime = "1000"; + text = "0.5"; + position = "98 603"; + extent = "1109 20"; + horizSizing = "width"; + vertSizing = "top"; + profile = "ToolsGuiTextEditProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Number of seconds over which to transition to the new sequence"; internalName = "transitionTime"; - canSave = "1"; - canSaveDynamicFields = "0"; }; new GuiTextCtrl() { text = "seconds"; - position = "153 165"; + position = "1213 604"; extent = "44 16"; - minExtent = "8 2"; horizSizing = "left"; vertSizing = "top"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiTextCtrl() { text = "Transition to"; - position = "4 186"; + position = "4 625"; extent = "62 16"; - minExtent = "8 2"; - horizSizing = "right"; vertSizing = "top"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl() { - position = "68 185"; - extent = "133 18"; - HorizSizing = "width"; + text = "synched position"; + position = "68 624"; + extent = "1193 18"; + horizSizing = "width"; vertSizing = "top"; - Profile = "ToolsGuiPopUpMenuProfile"; - ToolTip = "Select the start position of the new sequence"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the start position of the new sequence"; internalName = "transitionTo"; }; new GuiTextCtrl() { text = "Target anim"; - position = "4 207"; + position = "4 646"; extent = "58 16"; - minExtent = "8 2"; - horizSizing = "right"; vertSizing = "top"; - Profile = "ToolsGuiTextProfile"; + profile = "ToolsGuiTextProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl() { - position = "68 206"; - extent = "133 18"; - minExtent = "8 2"; + text = "plays during transition"; + position = "68 645"; + extent = "1193 18"; horizSizing = "width"; vertSizing = "top"; - Profile = "ToolsGuiPopUpMenuProfile"; - ToolTip = "Select the initial play state of the new sequence"; + profile = "ToolsGuiPopUpMenuProfile"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the initial play state of the new sequence"; internalName = "transitionTarget"; }; }; }; new GuiTabPageCtrl() { text = "Collision"; - maxLength = "1024"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - Position = "0 19"; - extent = "220 224"; - MinExtent = "0 -500"; - HorizSizing = "width"; - VertSizing = "height"; - Profile = "ToolsGuiTabPageProfile"; - Visible = "0"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; - + position = "0 19"; + extent = "1262 663"; + minExtent = "0 -500"; + horizSizing = "width"; + vertSizing = "height"; + profile = "ToolsGuiTabPageProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + new GuiContainer(ShapeEdColWindow) { - docking = "client"; - Margin = "0 0 0 0"; - Padding = "0 0 0 0"; - AnchorTop = "1"; - AnchorBottom = "0"; - AnchorLeft = "1"; - AnchorRight = "0"; - position = "0 0"; - extent = "202 225"; - MinExtent = "8 8"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; - Visible = "1"; - tooltipprofile = "ToolsGuiToolTipProfile"; - hovertime = "1000"; - isContainer = "1"; - canSave = "1"; - canSaveDynamicFields = "0"; - + docking = "Client"; + extent = "1262 663"; + minExtent = "8 8"; + horizSizing = "width"; + profile = "ToolsGuiDefaultProfile"; + tooltipProfile = "ToolsGuiToolTipProfile"; + new GuiTextCtrl() { text = "Fit Type"; position = "5 5"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl() { position = "85 4"; extent = "170 18"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiPopUpMenuProfile"; - Command = "ShapeEdColWindow.editCollision();"; - ToolTip = "Select the method used to auto-generate the collision geometry"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "ShapeEdColWindow.editCollision();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the method used to auto-generate the collision geometry"; internalName = "colType"; }; new GuiTextCtrl() { text = "Fit Target"; position = "5 25"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiPopUpMenuCtrl() { position = "85 24"; extent = "170 18"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiPopUpMenuProfile"; - Command = "ShapeEdColWindow.editCollision();"; - ToolTip = "Select the object to fit collision geometry to"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "ShapeEdColWindow.editCollision();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the object to fit collision geometry to"; internalName = "colTarget"; }; new GuiTextCtrl() { - text = "Max Depth"; - position = "5 47"; + text = "Fill Mode"; + position = "5 44"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; + }; + new GuiPopUpMenuCtrl() { + position = "85 43"; + extent = "170 18"; + profile = "ToolsGuiPopUpMenuProfile"; + command = "ShapeEdColWindow.editCollision();"; + tooltipProfile = "GuiToolTipProfile"; + tooltip = "Select the object to fit collision geometry to"; + internalName = "fillMode"; + }; + new GuiTextCtrl() { + text = "Max Depth"; + position = "5 71"; + extent = "70 16"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { - range = "0 8"; + range = "2 64"; ticks = "4"; - snap = "0"; value = "4"; - position = "80 48"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullDepthText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Maximum hull split depth"; - hovertime = "1000"; - isContainer = "0"; + position = "80 72"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullDepthText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Maximum hull split depth"; internalName = "hullDepth"; }; new GuiTextCtrl() { text = "4"; - position = "320 47"; + position = "320 71"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullDepthText"; }; new GuiTextCtrl() { - text = "Merge %"; - position = "5 68"; + text = "Error %"; + position = "5 92"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { - range = "0 60"; + range = "0.001 10"; ticks = "4"; - snap = "0"; - value = "30"; - position = "80 69"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullMergeText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Hull volume merge threshold"; - hovertime = "1000"; - isContainer = "0"; + value = "10"; + position = "80 93"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullMergeText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Hull volume merge threshold"; internalName = "hullMergeThreshold"; }; new GuiTextCtrl() { - text = "30"; - position = "320 68"; + text = "10"; + position = "320 92"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullMergeText"; }; new GuiTextCtrl() { - text = "Concavity %"; - position = "5 89"; + text = "Max Hulls"; + position = "5 113"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { - range = "0 60"; + range = "1 100"; ticks = "4"; - snap = "0"; value = "30"; - position = "80 90"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullConcaveText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Hull concavity threshold"; - hovertime = "1000"; - isContainer = "0"; + position = "80 114"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullConcaveText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Hull concavity threshold"; internalName = "hullConcaveThreshold"; }; new GuiTextCtrl() { text = "30"; - position = "320 89"; + position = "320 113"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullConcaveText"; }; new GuiTextCtrl() { text = "Max Verts"; - position = "5 110"; + position = "5 134"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { range = "8 64"; ticks = "4"; - snap = "0"; value = "32"; - position = "80 111"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullMaxVertsText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Maximum number of verts in a convex hull"; - hovertime = "1000"; - isContainer = "0"; + position = "80 135"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullMaxVertsText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Maximum number of verts in a convex hull"; internalName = "hullMaxVerts"; }; new GuiTextCtrl() { text = "32"; - position = "320 110"; + position = "320 134"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullMaxVertsText"; }; new GuiTextCtrl() { text = "Box %"; - position = "5 131"; + position = "5 155"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { range = "0 100"; ticks = "4"; - snap = "0"; value = "30"; - position = "80 132"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullMaxBoxErrorText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Maximum box volume error %"; - hovertime = "1000"; - isContainer = "0"; + position = "80 156"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullMaxBoxErrorText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Maximum box volume error %"; internalName = "hullMaxBoxError"; }; new GuiTextCtrl() { text = "30"; - position = "320 131"; + position = "320 155"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullMaxBoxErrorText"; }; new GuiTextCtrl() { text = "Sphere %"; - position = "5 152"; + position = "5 176"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { range = "0 100"; ticks = "4"; - snap = "0"; value = "30"; - position = "80 153"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullMaxSphereErrorText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Maximum sphere volume error %"; - hovertime = "1000"; - isContainer = "0"; + position = "80 177"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullMaxSphereErrorText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Maximum sphere volume error %"; internalName = "hullMaxSphereError"; }; new GuiTextCtrl() { text = "30"; - position = "320 152"; + position = "320 176"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullMaxSphereErrorText"; }; new GuiTextCtrl() { text = "Capsule %"; - position = "5 173"; + position = "5 197"; extent = "70 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; }; new GuiSliderCtrl() { range = "0 100"; ticks = "4"; - snap = "0"; value = "30"; - position = "80 174"; - extent = "90 14"; - MinExtent = "8 2"; - HorizSizing = "width"; - VertSizing = "bottom"; - Profile = "ToolsGuiSliderBoxProfile"; - Visible = "1"; - AltCommand = "ShapeEdColWindow-->hullMaxCapsuleErrorText.setText( mFloor($ThisControl.getValue()) );"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Maximum capsule volume error %"; - hovertime = "1000"; - isContainer = "0"; + position = "80 198"; + extent = "1150 14"; + horizSizing = "width"; + profile = "ToolsGuiSliderBoxProfile"; + altCommand = "ShapeEdColWindow-->hullMaxCapsuleErrorText.setText( mFloor($ThisControl.getValue()) );"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Maximum capsule volume error %"; internalName = "hullMaxCapsuleError"; }; new GuiTextCtrl() { text = "30"; - position = "320 173"; + position = "320 197"; extent = "25 16"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiTextRightProfile"; + profile = "ToolsGuiTextRightProfile"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullMaxCapsuleErrorText"; }; new GuiButtonCtrl() { text = "Update Hulls"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "7 200"; + position = "7 224"; extent = "100 20"; - MinExtent = "8 2"; - HorizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdColWindow.editCollision();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Update the convex hull(s)"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiButtonProfile"; + command = "ShapeEdColWindow.editCollision();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Update the convex hull(s)"; }; new GuiButtonCtrl() { text = "Revert Changes"; - groupNum = "-1"; - buttonType = "PushButton"; - useMouseEvents = "0"; - position = "115 200"; + position = "115 224"; extent = "100 20"; - MinExtent = "8 2"; - HorizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiButtonProfile"; - Visible = "1"; - Command = "ShapeEdColWindow.update_onCollisionChanged();"; - tooltipprofile = "ToolsGuiToolTipProfile"; - ToolTip = "Revert changes to settings"; - hovertime = "1000"; - isContainer = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + profile = "ToolsGuiButtonProfile"; + command = "ShapeEdColWindow.update_onCollisionChanged();"; + tooltipProfile = "ToolsGuiToolTipProfile"; + tooltip = "Revert changes to settings"; }; new GuiBitmapCtrl() { - bitmapAsset = "ToolsModule:inactive_overlay_image"; + BitmapAsset = "ToolsModule:inactive_overlay_image"; position = "0 47"; extent = "199 178"; - horizSizing = "right"; - vertSizing = "bottom"; - Profile = "ToolsGuiDefaultProfile"; + profile = "ToolsGuiDefaultProfile"; visible = "0"; - canSave = "1"; - canSaveDynamicFields = "0"; + tooltipProfile = "GuiToolTipProfile"; internalName = "hullInactive"; + hidden = "1"; }; }; }; diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript index de06ac9a3..6339fcaac 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript @@ -2935,6 +2935,10 @@ function ShapeEdColWindow::onWake( %this ) %this-->colType.add( "18-DOP" ); %this-->colType.add( "26-DOP" ); %this-->colType.add( "Convex Hulls" ); + + %this-->fillMode.add("Flood fill"); + %this-->fillMode.add("Surface only"); + %this-->fillMode.add("Raycast Fill"); } function ShapeEdColWindow::update_onShapeSelectionChanged( %this ) @@ -2949,6 +2953,8 @@ function ShapeEdColWindow::update_onShapeSelectionChanged( %this ) %this-->colTarget.add( ShapeEditor.shape.getObjectName( %i ) ); %this-->colTarget.setSelected( %this-->colTarget.findText( "Bounds" ), false ); + + %this-->fillMode.setSelected( %this-->fillMode.findText( "Flood fill" ), false ); } function ShapeEdColWindow::update_onCollisionChanged( %this ) From f963a7844681049e884906e025fdade22876bede Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 15 May 2024 07:32:26 +0100 Subject: [PATCH 16/65] TEST progress Adding multiple collision hulls and shapes through the shape editor now works as intended though with multiple convex hulls it does produce a few lag spikes on first load of the objects. --- Engine/source/ts/tsMeshFit.cpp | 25 +++++++++++-- .../gui/shapeEdAdvancedWindow.ed.gui | 3 -- .../scripts/shapeEditorActions.ed.tscript | 37 ++++++++++--------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index ab4c57b10..bd7fbee0f 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -1032,9 +1032,28 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char default: objName = "ColConvex"; break; } - for ( S32 suffix = i; suffix != 0; suffix /= 26 ) - objName += ('A' + ( suffix % 26 ) ); - String meshName = objName + String::ToString( "%d", size ); + S32 suffix = i; + while (true) + { + String tempName = objName; + if (suffix == 0) + suffix = 1; + + for (S32 s = suffix; s != 0; s /= 26) { + tempName += ('A' + (s % 26)); + } + + if (mShape->findName(tempName) == -1) + break; + + suffix++; + } + + for (S32 s = suffix; s != 0; s /= 26) { + objName += ('A' + (s % 26)); + } + + String meshName = objName + String::ToString("%d", size); mShape->addMesh( mesh->tsmesh, meshName ); diff --git a/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui b/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui index 7a52de1fa..74c61fe37 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui +++ b/Templates/BaseGame/game/tools/shapeEditor/gui/shapeEdAdvancedWindow.ed.gui @@ -882,7 +882,6 @@ $guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow,EditorGuiGroup) { position = "85 4"; extent = "170 18"; profile = "ToolsGuiPopUpMenuProfile"; - command = "ShapeEdColWindow.editCollision();"; tooltipProfile = "GuiToolTipProfile"; tooltip = "Select the method used to auto-generate the collision geometry"; internalName = "colType"; @@ -898,7 +897,6 @@ $guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow,EditorGuiGroup) { position = "85 24"; extent = "170 18"; profile = "ToolsGuiPopUpMenuProfile"; - command = "ShapeEdColWindow.editCollision();"; tooltipProfile = "GuiToolTipProfile"; tooltip = "Select the object to fit collision geometry to"; internalName = "colTarget"; @@ -914,7 +912,6 @@ $guiContent = new GuiWindowCollapseCtrl(ShapeEdAdvancedWindow,EditorGuiGroup) { position = "85 43"; extent = "170 18"; profile = "ToolsGuiPopUpMenuProfile"; - command = "ShapeEdColWindow.editCollision();"; tooltipProfile = "GuiToolTipProfile"; tooltip = "Select the object to fit collision geometry to"; internalName = "fillMode"; diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript index f3c27f8a3..c93beb996 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript @@ -1081,32 +1081,35 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %depth, %m { %colDetailSize = -1; %colNode = "Col" @ %colDetailSize; - + %colData = ShapeEdColWindow.lastColSettings; + %oldTarget = getField( %colData, 1 ); // TreeView items are case sensitive, but TSShape names are not, so fixup case // if needed %index = ShapeEditor.shape.getNodeIndex( %colNode ); if ( %index != -1 ) %colNode = ShapeEditor.shape.getNodeName( %index ); - // First remove the old detail and collision nodes - %meshList = ShapeEditor.getDetailMeshList( %colDetailSize ); - %meshCount = getFieldCount( %meshList ); - if ( %meshCount > 0 ) + if(%target $= "Bounds" || %oldTarget $= "Bounds" || %target $= %oldTarget) { - ShapeEditor.shape.removeDetailLevel( %colDetailSize ); - for ( %i = 0; %i < %meshCount; %i++ ) - ShapeEdPropWindow.update_onMeshRemoved( getField( %meshList, %i ) ); - } + // First remove the old detail and collision nodes + %meshList = ShapeEditor.getDetailMeshList( %colDetailSize ); + %meshCount = getFieldCount( %meshList ); + if ( %meshCount > 0 ) + { + ShapeEditor.shape.removeDetailLevel( %colDetailSize ); + for ( %i = 0; %i < %meshCount; %i++ ) + ShapeEdPropWindow.update_onMeshRemoved( getField( %meshList, %i ) ); + } - %nodeList = ShapeEditor.getNodeNames( %colNode, "" ); - %nodeCount = getFieldCount( %nodeList ); - if ( %nodeCount > 0 ) - { - for ( %i = 0; %i < %nodeCount; %i++ ) - ShapeEditor.shape.removeNode( getField( %nodeList, %i ) ); - ShapeEdPropWindow.update_onNodeRemoved( %nodeList, %nodeCount ); + %nodeList = ShapeEditor.getNodeNames( %colNode, "" ); + %nodeCount = getFieldCount( %nodeList ); + if ( %nodeCount > 0 ) + { + for ( %i = 0; %i < %nodeCount; %i++ ) + ShapeEditor.shape.removeNode( getField( %nodeList, %i ) ); + ShapeEdPropWindow.update_onNodeRemoved( %nodeList, %nodeCount ); + } } - // Add the new node and geometry if ( %type $= "" ) return; From bd93ce3637839f89421f4b8245746f2df0fd0edc Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Wed, 15 May 2024 07:55:52 +0100 Subject: [PATCH 17/65] only publish test report on push/merge --- .github/workflows/build-linux-gcc.yml | 1 + .github/workflows/build-macos-clang.yml | 1 + .github/workflows/build-windows-msvc.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/.github/workflows/build-linux-gcc.yml b/.github/workflows/build-linux-gcc.yml index 7734a60d7..8382d27a2 100644 --- a/.github/workflows/build-linux-gcc.yml +++ b/.github/workflows/build-linux-gcc.yml @@ -95,3 +95,4 @@ jobs: path: "**/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit fail-on-error: false + if: github.event_name != 'pull_request' diff --git a/.github/workflows/build-macos-clang.yml b/.github/workflows/build-macos-clang.yml index a27f04287..a6e5b0dfb 100644 --- a/.github/workflows/build-macos-clang.yml +++ b/.github/workflows/build-macos-clang.yml @@ -76,3 +76,4 @@ jobs: path: "**/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit fail-on-error: false + if: github.event_name != 'pull_request' diff --git a/.github/workflows/build-windows-msvc.yml b/.github/workflows/build-windows-msvc.yml index 0fbcd0ecd..2b9ac82ef 100644 --- a/.github/workflows/build-windows-msvc.yml +++ b/.github/workflows/build-windows-msvc.yml @@ -72,3 +72,4 @@ jobs: path: "**/My Projects/Torque3D/game/test_detail.xml" reporter: java-junit fail-on-error: false + if: github.event_name != 'pull_request' From 48848f970621c97a2e6f8f9f7848c96f1542d5ea Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 16 May 2024 03:39:18 +0100 Subject: [PATCH 18/65] rename vars renamed vars to make more sense with the new option params --- Engine/source/ts/tsMeshFit.cpp | 14 +++++++------- Engine/source/ts/tsShapeConstruct.h | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index bd7fbee0f..f5623dc96 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -183,7 +183,7 @@ public: void fit26_DOP(); // Convex Hulls - void fitConvexHulls( U32 depth, F32 mergeThreshold, U32 concavityThreshold, U32 maxHullVerts, + void fitConvexHulls( U32 depth, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ); }; @@ -691,7 +691,7 @@ void MeshFit::fitK_DOP( const Vector& planes ) //--------------------------- // Best-fit set of convex hulls -void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, U32 concavityThreshold, U32 maxHullVerts, +void MeshFit::fitConvexHulls( U32 depth, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ) { VHACD::IVHACD::Parameters p; @@ -699,9 +699,9 @@ void MeshFit::fitConvexHulls( U32 depth, F32 mergeThreshold, U32 concavityThres p.m_maxNumVerticesPerCH = maxHullVerts; p.m_shrinkWrap = true; p.m_maxRecursionDepth = depth; - p.m_minimumVolumePercentErrorAllowed = mergeThreshold; + p.m_minimumVolumePercentErrorAllowed = minPercentage; p.m_resolution = 10000; - p.m_maxConvexHulls = concavityThreshold; + p.m_maxConvexHulls = maxHulls; VHACD::IVHACD* iface = VHACD::CreateVHACD_ASYNC(); @@ -929,8 +929,8 @@ DefineTSShapeConstructorMethod( addPrimitive, bool, ( const char* meshName, cons return true; }} -DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 merge, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( 4, 30, 30, 32, 0, 0, 0 ), - ( size, type, target, depth, merge, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, +DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( 4, 30, 30, 32, 0, 0, 0 ), + ( size, type, target, depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, "Autofit a mesh primitive or set of convex hulls to the shape geometry. Hulls " "may optionally be converted to boxes, spheres and/or capsules based on their " "volume.\n" @@ -984,7 +984,7 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char fit.fit26_DOP(); else if ( !dStricmp( type, "convex hulls" ) ) { - fit.fitConvexHulls( depth, merge, maxHulls, maxVerts, + fit.fitConvexHulls( depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ); } else diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index 497141ec1..f4c9d3efd 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -315,7 +315,7 @@ public: const char* getImposterSettings(S32 index); S32 addImposter(S32 size, S32 equatorSteps, S32 polarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle); bool removeImposter(); - bool addCollisionDetail(S32 size, const char* type, const char* target, S32 depth = 4, F32 merge = 30.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); + bool addCollisionDetail(S32 size, const char* type, const char* target, S32 depth = 4, F32 minPercentage = 10.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); ///@} /// @name Sequences From 25b0c5e2b1c2c99b65a73f2edf943b0e2f63895f Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 16 May 2024 04:32:14 +0100 Subject: [PATCH 19/65] finish fill mode setting now fill mode actually takes effect and changes the fill mode type used to generate the convex hull --- Engine/source/ts/tsMeshFit.cpp | 18 +++++++---- Engine/source/ts/tsShapeConstruct.h | 4 +-- .../scripts/shapeEditor.ed.tscript | 27 ++++++++++------- .../scripts/shapeEditorActions.ed.tscript | 30 ++++++++++--------- 4 files changed, 46 insertions(+), 33 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index f5623dc96..28ab8736a 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -183,7 +183,7 @@ public: void fit26_DOP(); // Convex Hulls - void fitConvexHulls( U32 depth, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, + void fitConvexHulls( U32 depth, U32 fillType, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ); }; @@ -691,11 +691,11 @@ void MeshFit::fitK_DOP( const Vector& planes ) //--------------------------- // Best-fit set of convex hulls -void MeshFit::fitConvexHulls( U32 depth, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, +void MeshFit::fitConvexHulls( U32 depth, U32 fillType, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ) { VHACD::IVHACD::Parameters p; - p.m_fillMode = VHACD::FillMode::FLOOD_FILL; + p.m_fillMode = (VHACD::FillMode)fillType; p.m_maxNumVerticesPerCH = maxHullVerts; p.m_shrinkWrap = true; p.m_maxRecursionDepth = depth; @@ -929,8 +929,8 @@ DefineTSShapeConstructorMethod( addPrimitive, bool, ( const char* meshName, cons return true; }} -DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( 4, 30, 30, 32, 0, 0, 0 ), - ( size, type, target, depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, +DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, const char* fillMode, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( "flood fill", 4, 10, 30, 32, 0, 0, 0), + ( size, type, target, fillMode, depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, "Autofit a mesh primitive or set of convex hulls to the shape geometry. Hulls " "may optionally be converted to boxes, spheres and/or capsules based on their " "volume.\n" @@ -984,7 +984,13 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char fit.fit26_DOP(); else if ( !dStricmp( type, "convex hulls" ) ) { - fit.fitConvexHulls( depth, minPercentage, maxHulls, maxVerts, + + U32 fillType = 0; + if (!dStricmp(fillMode, "surface only")) + fillType = 1; + if (!dStricmp(fillMode, "raycast fill")) + fillType = 2; + fit.fitConvexHulls( depth, fillType, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ); } else diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index f4c9d3efd..98220d880 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -100,7 +100,7 @@ public: { eCommandType type; // Command type StringTableEntry name; // Command name - static const U32 MAX_ARGS = 10; + static const U32 MAX_ARGS = 11; String argv[MAX_ARGS]; // Command arguments S32 argc; // Number of arguments Command() : type(CmdInvalid), name(0), argc(0) { } @@ -315,7 +315,7 @@ public: const char* getImposterSettings(S32 index); S32 addImposter(S32 size, S32 equatorSteps, S32 polarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle); bool removeImposter(); - bool addCollisionDetail(S32 size, const char* type, const char* target, S32 depth = 4, F32 minPercentage = 10.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); + bool addCollisionDetail(S32 size, const char* type, const char* target, const char* fillMode = "flood fill", S32 depth = 4, F32 minPercentage = 10.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); ///@} /// @name Sequences diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript index 6339fcaac..bf3bee27b 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript @@ -2936,14 +2936,17 @@ function ShapeEdColWindow::onWake( %this ) %this-->colType.add( "26-DOP" ); %this-->colType.add( "Convex Hulls" ); + %this-->fillMode.clear(); %this-->fillMode.add("Flood fill"); %this-->fillMode.add("Surface only"); %this-->fillMode.add("Raycast Fill"); + + %this-->fillMode.setSelected( %this-->fillMode.findText( "Flood fill" ), false ); } function ShapeEdColWindow::update_onShapeSelectionChanged( %this ) { - %this.lastColSettings = "" TAB "Bounds"; + %this.lastColSettings = "" TAB "Bounds" TAB "Flood fill"; // Initialise collision mesh target list %this-->colTarget.clear(); @@ -2953,8 +2956,6 @@ function ShapeEdColWindow::update_onShapeSelectionChanged( %this ) %this-->colTarget.add( ShapeEditor.shape.getObjectName( %i ) ); %this-->colTarget.setSelected( %this-->colTarget.findText( "Bounds" ), false ); - - %this-->fillMode.setSelected( %this-->fillMode.findText( "Flood fill" ), false ); } function ShapeEdColWindow::update_onCollisionChanged( %this ) @@ -2968,22 +2969,25 @@ function ShapeEdColWindow::update_onCollisionChanged( %this ) %targetId = %this-->colTarget.findText( getField( %colData, 1 ) ); %this-->colTarget.setSelected( %targetId, false ); + %fillModeID = %this-->fillMode.findText( getField( %colData, 2 ) ); + %this-->fillMode.setSelected( %fillModeID, false ); + if ( %this-->colType.getText() $= "Convex Hulls" ) { %this-->hullInactive.setVisible( false ); - %this-->hullDepth.setValue( getField( %colData, 2 ) ); + %this-->hullDepth.setValue( getField( %colData, 3 ) ); %this-->hullDepthText.setText( mFloor( %this-->hullDepth.getValue() ) ); - %this-->hullMergeThreshold.setValue( getField( %colData, 3 ) ); + %this-->hullMergeThreshold.setValue( getField( %colData, 4 ) ); %this-->hullMergeText.setText( mFloor( %this-->hullMergeThreshold.getValue() ) ); - %this-->hullConcaveThreshold.setValue( getField( %colData, 4 ) ); + %this-->hullConcaveThreshold.setValue( getField( %colData, 5 ) ); %this-->hullConcaveText.setText( mFloor( %this-->hullConcaveThreshold.getValue() ) ); - %this-->hullMaxVerts.setValue( getField( %colData, 5 ) ); + %this-->hullMaxVerts.setValue( getField( %colData, 6 ) ); %this-->hullMaxVertsText.setText( mFloor( %this-->hullMaxVerts.getValue() ) ); - %this-->hullMaxBoxError.setValue( getField( %colData, 6 ) ); + %this-->hullMaxBoxError.setValue( getField( %colData, 7 ) ); %this-->hullMaxBoxErrorText.setText( mFloor( %this-->hullMaxBoxError.getValue() ) ); - %this-->hullMaxSphereError.setValue( getField( %colData, 7 ) ); + %this-->hullMaxSphereError.setValue( getField( %colData, 8 ) ); %this-->hullMaxSphereErrorText.setText( mFloor( %this-->hullMaxSphereError.getValue() ) ); - %this-->hullMaxCapsuleError.setValue( getField( %colData, 8 ) ); + %this-->hullMaxCapsuleError.setValue( getField( %colData, 9 ) ); %this-->hullMaxCapsuleErrorText.setText( mFloor( %this-->hullMaxCapsuleError.getValue() ) ); } else @@ -3013,6 +3017,7 @@ function ShapeEdColWindow::editCollisionOK( %this ) { %type = %this-->colType.getText(); %target = %this-->colTarget.getText(); + %fillMode = %this-->fillMode.getText(); %depth = %this-->hullDepth.getValue(); %merge = %this-->hullMergeThreshold.getValue(); %concavity = %this-->hullConcaveThreshold.getValue(); @@ -3021,7 +3026,7 @@ function ShapeEdColWindow::editCollisionOK( %this ) %maxSphere = %this-->hullMaxSphereError.getValue(); %maxCapsule = %this-->hullMaxCapsuleError.getValue(); - ShapeEditor.doEditCollision( %type, %target, %depth, %merge, %concavity, %maxVerts, + ShapeEditor.doEditCollision( %type, %target, %fillMode, %depth, %merge, %concavity, %maxVerts, %maxBox, %maxSphere, %maxCapsule ); } diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript index c93beb996..a82e9ea40 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript @@ -1046,7 +1046,7 @@ function ActionAddMeshFromFile::undo( %this ) //------------------------------------------------------------------------------ // Add/edit collision geometry -function ShapeEditor::doEditCollision( %this, %type, %target, %depth, %merge, %concavity, +function ShapeEditor::doEditCollision( %this, %type, %target, %fillMode, %depth, %merge, %concavity, %maxVerts, %boxMax, %sphereMax, %capsuleMax ) { %colData = ShapeEdColWindow.lastColSettings; @@ -1055,16 +1055,18 @@ function ShapeEditor::doEditCollision( %this, %type, %target, %depth, %merge, %c %action.oldType = getField( %colData, 0 ); %action.oldTarget = getField( %colData, 1 ); - %action.oldDepth = getField( %colData, 2 ); - %action.oldMerge = getField( %colData, 3 ); - %action.oldConcavity = getField( %colData, 4 ); - %action.oldMaxVerts = getField( %colData, 5 ); - %action.oldBoxMax = getField( %colData, 6 ); - %action.oldSphereMax = getField( %colData, 7 ); - %action.oldCapsuleMax = getField( %colData, 8 ); + %action.oldFillMode = getField(%colData, 2); + %action.oldDepth = getField( %colData, 3 ); + %action.oldMerge = getField( %colData, 4 ); + %action.oldConcavity = getField( %colData, 5 ); + %action.oldMaxVerts = getField( %colData, 6 ); + %action.oldBoxMax = getField( %colData, 7 ); + %action.oldSphereMax = getField( %colData, 8 ); + %action.oldCapsuleMax = getField( %colData, 9 ); %action.newType = %type; %action.newTarget = %target; + %action.newFillMode = %fillMode; %action.newDepth = %depth; %action.newMerge = %merge; %action.newConcavity = %concavity; @@ -1076,7 +1078,7 @@ function ShapeEditor::doEditCollision( %this, %type, %target, %depth, %merge, %c %this.doAction( %action ); } -function ActionEditCollision::updateCollision( %this, %type, %target, %depth, %merge, %concavity, +function ActionEditCollision::updateCollision( %this, %type, %target, %fillMode, %depth, %merge, %concavity, %maxVerts, %boxMax, %sphereMax, %capsuleMax ) { %colDetailSize = -1; @@ -1089,7 +1091,7 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %depth, %m if ( %index != -1 ) %colNode = ShapeEditor.shape.getNodeName( %index ); - if(%target $= "Bounds" || %oldTarget $= "Bounds" || %target $= %oldTarget) + if(%target $= %oldTarget) { // First remove the old detail and collision nodes %meshList = ShapeEditor.getDetailMeshList( %colDetailSize ); @@ -1114,7 +1116,7 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %depth, %m if ( %type $= "" ) return; - if ( !ShapeEditor.shape.addCollisionDetail( %colDetailSize, %type, %target, + if ( !ShapeEditor.shape.addCollisionDetail( %colDetailSize, %type, %target, %fillMode, %depth, %merge, %concavity, %maxVerts, %boxMax, %sphereMax, %capsuleMax ) ) return false; @@ -1126,7 +1128,7 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %depth, %m for ( %i = 0; %i < %count; %i++ ) ShapeEdPropWindow.update_onMeshAdded( getField( %meshList, %i ) ); - ShapeEdColWindow.lastColSettings = %type TAB %target TAB %depth TAB %merge TAB + ShapeEdColWindow.lastColSettings = %type TAB %target TAB %fillMode TAB %depth TAB %merge TAB %concavity TAB %maxVerts TAB %boxMax TAB %sphereMax TAB %capsuleMax; ShapeEdColWindow.update_onCollisionChanged(); @@ -1136,7 +1138,7 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %depth, %m function ActionEditCollision::doit( %this ) { ShapeEdWaitGui.show( "Generating collision geometry..." ); - %success = %this.updateCollision( %this.newType, %this.newTarget, %this.newDepth, %this.newMerge, + %success = %this.updateCollision( %this.newType, %this.newTarget, %this.newFillMode, %this.newDepth, %this.newMerge, %this.newConcavity, %this.newMaxVerts, %this.newBoxMax, %this.newSphereMax, %this.newCapsuleMax ); ShapeEdWaitGui.hide(); @@ -1149,7 +1151,7 @@ function ActionEditCollision::undo( %this ) Parent::undo( %this ); ShapeEdWaitGui.show( "Generating collision geometry..." ); - %this.updateCollision( %this.oldType, %this.oldTarget, %this.oldDepth, %this.oldMerge, + %this.updateCollision( %this.oldType, %this.oldTarget, %this.oldFillMode, %this.oldDepth, %this.oldMerge, %this.oldConcavity, %this.oldMaxVerts, %this.oldBoxMax, %this.oldSphereMax, %this.oldCapsuleMax ); ShapeEdWaitGui.hide(); From 92b10df7eb902a15fa97b15f9cdae4ac693d4b9c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 16 May 2024 07:04:54 +0100 Subject: [PATCH 20/65] cleanup nodes ADDED: functionality to clean nodes out of the script that are related to a specific target ADDED: functionality to clean multiple addCollisionDetails from the script ADDED: ColConvex get added as nodes now and dont just get skipped (for future reference ColMeshes are checked for colConvex are not but this will be needed in future) Removed: erroneous deletion of nodes and meshes from shapeEditorActions tscript file. --- Engine/source/ts/tsMeshFit.cpp | 14 ++--- Engine/source/ts/tsShapeConstruct.cpp | 56 ++++++++++++++++++- Engine/source/ts/tsShapeConstruct.h | 4 +- .../scripts/shapeEditorActions.ed.tscript | 25 +-------- 4 files changed, 64 insertions(+), 35 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 28ab8736a..495a469c4 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -1021,6 +1021,9 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char mShape->getNodeWorldTransform( nodeIndex, &mat ); if ( !mat.isIdentity() ) setNodeTransform( colNodeName, TransformF::Identity ); + + // clean node commands that are related to this target. + cleanTargetNodes(colNodeName, target); } // Add the meshes to the shape => @@ -1064,15 +1067,8 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char mShape->addMesh( mesh->tsmesh, meshName ); // Add a node for this object if needed (non-identity transform) - if ( mesh->transform.isIdentity() ) - { - mShape->setObjectNode( objName, colNodeName ); - } - else - { - addNode( meshName, colNodeName, TransformF( mesh->transform ) ); - mShape->setObjectNode( objName, meshName ); - } + addNode( meshName, colNodeName, target, TransformF( mesh->transform ) ); + mShape->setObjectNode( objName, meshName ); } mShape->init(); diff --git a/Engine/source/ts/tsShapeConstruct.cpp b/Engine/source/ts/tsShapeConstruct.cpp index 74582cefd..3031ec110 100644 --- a/Engine/source/ts/tsShapeConstruct.cpp +++ b/Engine/source/ts/tsShapeConstruct.cpp @@ -1111,8 +1111,8 @@ DefineTSShapeConstructorMethod(renameNode, bool, (const char* oldName, const cha return true; }} -DefineTSShapeConstructorMethod(addNode, bool, (const char* name, const char* parentName, TransformF txfm, bool isWorld), (TransformF::Identity, false), - (name, parentName, txfm, isWorld), false, +DefineTSShapeConstructorMethod(addNode, bool, (const char* name, const char* parentName, const char* target, TransformF txfm, bool isWorld), (TransformF::Identity, false), + (name, parentName, target, txfm, isWorld), false, "Add a new node.\n" "@param name name for the new node (must not already exist)\n" "@param parentName name of an existing node to be the parent of the new node. " @@ -2433,6 +2433,7 @@ void TSShapeConstructor::ChangeSet::add( TSShapeConstructor::ChangeSet::Command& // Detail level commands case CmdRenameDetailLevel: addCommand = addCmd_renameDetailLevel(cmd); break; case CmdRemoveDetailLevel: addCommand = addCmd_removeDetailLevel(cmd); break; + case CmdAddCollisionDetail: addCommand = addCmd_addDetailLevel(cmd); break; case CmdSetDetailLevelSize: addCommand = addCmd_setDetailSize(cmd); break; case CmdAddImposter: addCommand = addCmd_addImposter(cmd); break; case CmdRemoveImposter: addCommand = addCmd_removeImposter(cmd); break; @@ -3302,6 +3303,28 @@ bool TSShapeConstructor::ChangeSet::addCmd_renameDetailLevel(const TSShapeConstr return true; } +bool TSShapeConstructor::ChangeSet::addCmd_addDetailLevel(const TSShapeConstructor::ChangeSet::Command& newCmd) +{ + const char* targ = newCmd.argv[2]; + for (S32 index = mCommands.size() - 1; index >= 0; index--) + { + Command& cmd = mCommands[index]; + switch (cmd.type) + { + case CmdAddCollisionDetail: + if (!dStricmp(targ, cmd.argv[2])) + { + mCommands.erase(index); + } + break; + default: + break; + } + } + + return true; +} + bool TSShapeConstructor::ChangeSet::addCmd_removeDetailLevel(const TSShapeConstructor::ChangeSet::Command& newCmd) { // Remove any previous command that references the detail, but stop if a mesh @@ -3480,3 +3503,32 @@ void TSShapeConstructor::onActionPerformed() } } } + +void TSShapeConstructor::cleanTargetNodes(const char* detail, const char* target) +{ + for (S32 index = mChangeSet.mCommands.size() - 1; index >= 0; index--) + { + ChangeSet::Command& cmd = mChangeSet.mCommands[index]; + switch (cmd.type) + { + case ChangeSet::eCommandType::CmdAddNode: + // node name starts with col + if (dStrStartsWith(cmd.argv[0], "Col")) + { + // node has the same detail and same target + if (!dStricmp(detail, cmd.argv[1]) && !dStricmp(target, cmd.argv[2])) + { + // now remove it from shape + mShape->removeMesh(cmd.argv[0]); + mShape->removeNode(cmd.argv[0]); + + // erase the command + mChangeSet.mCommands.erase(index); + } + } + break; + default: + break; + } + } +} diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index 98220d880..7cbbc0a49 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -142,6 +142,7 @@ public: bool addCmd_setBounds(const Command& newCmd); bool addCmd_renameDetailLevel(const Command& newCmd); + bool addCmd_addDetailLevel(const Command& newCmd); bool addCmd_removeDetailLevel(const Command& newCmd); bool addCmd_setDetailSize(const Command& newCmd); bool addCmd_addImposter(const Command& newCmd); @@ -253,6 +254,7 @@ public: /// @name Nodes ///@{ + void cleanTargetNodes(const char* detail, const char* target); S32 getNodeCount(); S32 getNodeIndex(const char* name); const char* getNodeName(S32 index); @@ -265,7 +267,7 @@ public: TransformF getNodeTransform(const char* name, bool isWorld = false); bool setNodeTransform(const char* name, TransformF txfm, bool isWorld = false); bool renameNode(const char* oldName, const char* newName); - bool addNode(const char* name, const char* parentName, TransformF txfm = TransformF::Identity, bool isWorld = false); + bool addNode(const char* name, const char* parentName, const char* target = "", TransformF txfm = TransformF::Identity, bool isWorld = false); bool removeNode(const char* name); ///@} diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript index a82e9ea40..94a06ad95 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript @@ -1091,36 +1091,15 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %fillMode, if ( %index != -1 ) %colNode = ShapeEditor.shape.getNodeName( %index ); - if(%target $= %oldTarget) - { - // First remove the old detail and collision nodes - %meshList = ShapeEditor.getDetailMeshList( %colDetailSize ); - %meshCount = getFieldCount( %meshList ); - if ( %meshCount > 0 ) - { - ShapeEditor.shape.removeDetailLevel( %colDetailSize ); - for ( %i = 0; %i < %meshCount; %i++ ) - ShapeEdPropWindow.update_onMeshRemoved( getField( %meshList, %i ) ); - } - - %nodeList = ShapeEditor.getNodeNames( %colNode, "" ); - %nodeCount = getFieldCount( %nodeList ); - if ( %nodeCount > 0 ) - { - for ( %i = 0; %i < %nodeCount; %i++ ) - ShapeEditor.shape.removeNode( getField( %nodeList, %i ) ); - ShapeEdPropWindow.update_onNodeRemoved( %nodeList, %nodeCount ); - } - } // Add the new node and geometry if ( %type $= "" ) return; - + if ( !ShapeEditor.shape.addCollisionDetail( %colDetailSize, %type, %target, %fillMode, %depth, %merge, %concavity, %maxVerts, %boxMax, %sphereMax, %capsuleMax ) ) return false; - + // Update UI %meshList = ShapeEditor.getDetailMeshList( %colDetailSize ); ShapeEdPropWindow.update_onNodeAdded( %colNode, ShapeEditor.shape.getNodeCount() ); // will also add child nodes From 280102d5656a61163f3748eb97840102d45b6364 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 16 May 2024 08:18:17 +0100 Subject: [PATCH 21/65] Update tsMeshFit.cpp update addCollisionDetail documentation --- Engine/source/ts/tsMeshFit.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 495a469c4..17db2daac 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -938,23 +938,20 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char "@param type one of: box, sphere, capsule, 10-dop x, 10-dop y, 10-dop z, 18-dop, " "26-dop, convex hulls. See the Shape Editor documentation for more details " "about these types.\n" - "@param target geometry to fit collision mesh(es) to; either \"bounds\" (for the " - "whole shape), or the name of an object in the shape\n" + "@param target geometry to fit collision mesh(es) to; either \"bounds\" (for the whole shape), or the name of an object in the shape\n" + "@param fillMode method for filling the voxels in the volume (hulls only)\n" "@param depth maximum split recursion depth (hulls only)\n" - "@param merge volume % threshold used to merge hulls together (hulls only)\n" + "@param minPercentage volume % error threshold (hulls only)\n" "@param maxHulls allowed to be generated (hulls only)\n" "@param maxVerts maximum number of vertices per hull (hulls only)\n" - "@param boxMaxError max % volume difference for a hull to be converted to a " - "box (hulls only)\n" - "@param sphereMaxError max % volume difference for a hull to be converted to " - "a sphere (hulls only)\n" - "@param capsuleMaxError max % volume difference for a hull to be converted to " - "a capsule (hulls only)\n" + "@param boxMaxError max % volume difference for a hull to be converted to a box (hulls only)\n" + "@param sphereMaxError max % volume difference for a hull to be converted to a sphere (hulls only)\n" + "@param capsuleMaxError max % volume difference for a hull to be converted to a capsule (hulls only)\n" "@return true if successful, false otherwise\n\n" "@tsexample\n" "%this.addCollisionDetail( -1, \"box\", \"bounds\" );\n" "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\", 4, 30, 30, 32, 0, 0, 0 );\n" - "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\", 4, 30, 30, 32, 50, 50, 50 );\n" + "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\",\"flood fill\", 4, 30, 30, 32, 50, 50, 50 );\n" "@endtsexample\n" ) { MeshFit fit( mShape ); From 4b2165668f305b504fb4c20939e5113cdc3e9a8c Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 16 May 2024 20:36:47 +0100 Subject: [PATCH 22/65] moar fixes Fixed: convex and shape nodes are now the same transform as the target nodes Changed: addNode now has the target at the end of its call for backwards compat Fixed: renameNode was overwriting addNode calls, should not have been so --- Engine/source/ts/tsMeshFit.cpp | 157 +++++++++++++------------- Engine/source/ts/tsShapeConstruct.cpp | 16 +-- Engine/source/ts/tsShapeConstruct.h | 2 +- 3 files changed, 86 insertions(+), 89 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 17db2daac..13a08ee04 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -165,15 +165,15 @@ public: // Box void addBox( const Point3F& sides, const MatrixF& mat ); - void fitOBB(); + void fitOBB(const char* target); // Sphere void addSphere( F32 radius, const Point3F& center ); - void fitSphere(); + void fitSphere(const char* target); // Capsule void addCapsule( F32 radius, F32 height, const MatrixF& mat ); - void fitCapsule(); + void fitCapsule(const char* target); // k-DOP void fit10_DOP_X(); @@ -183,7 +183,7 @@ public: void fit26_DOP(); // Convex Hulls - void fitConvexHulls( U32 depth, U32 fillType, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, + void fitConvexHulls( const char* target, U32 depth, U32 fillType, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ); }; @@ -404,6 +404,7 @@ void MeshFit::addBox( const Point3F& sides, const MatrixF& mat ) { Point3F v = mesh->mVerts[i]; v.convolve(sides); + mat.mulP(v); mesh->mVerts[i] = v; } @@ -416,23 +417,27 @@ void MeshFit::addBox( const Point3F& sides, const MatrixF& mat ) TSMesh::__TSMeshVertexBase &vdata = mesh->mVertexData.getBase(i); Point3F v = vdata.vert(); v.convolve(sides); + mat.mulP(v); vdata.vert(v); } } mesh->computeBounds(); - - mMeshes.increment(); mMeshes.last().type = MeshFit::Box; - mMeshes.last().transform = mat; mMeshes.last().tsmesh = mesh; } -void MeshFit::fitOBB() +void MeshFit::fitOBB(const char* target) { + mMeshes.increment(); + MatrixF worldtrans; + worldtrans.identity(); + mShape->getNodeWorldTransform(mShape->findNode(target), &worldtrans); + mMeshes.last().transform = worldtrans; + PrimFit primFitter; primFitter.fitBox( mVerts.size(), (F32*)mVerts.address() ); - addBox( primFitter.mBoxSides, primFitter.mBoxTransform ); + addBox( primFitter.mBoxSides, worldtrans.inverse() * primFitter.mBoxTransform ); } //--------------------------- @@ -443,11 +448,15 @@ void MeshFit::addSphere( F32 radius, const Point3F& center ) if ( !mesh ) return; + MatrixF sphereMat(true); + sphereMat.setPosition(center); + if (mesh->mVerts.size() > 0) { for (S32 i = 0; i < mesh->mVerts.size(); i++) { Point3F v = mesh->mVerts[i]; + sphereMat.mulP(v); mesh->mVerts[i] = v * radius; } @@ -459,26 +468,30 @@ void MeshFit::addSphere( F32 radius, const Point3F& center ) { TSMesh::__TSMeshVertexBase& vdata = mesh->mVertexData.getBase(i); Point3F v = vdata.vert(); + sphereMat.mulP(v); vdata.vert(v * radius); } } mesh->computeBounds(); - - mMeshes.increment(); - MeshFit::Mesh& lastMesh = mMeshes.last(); - lastMesh.type = MeshFit::Sphere; - lastMesh.transform.identity(); - lastMesh.transform.setPosition(center); - lastMesh.tsmesh = mesh; + mMeshes.last().type = MeshFit::Sphere; + mMeshes.last().tsmesh = mesh; } -void MeshFit::fitSphere() +void MeshFit::fitSphere(const char* target) { + mMeshes.increment(); + MatrixF worldtrans; + worldtrans.identity(); + mShape->getNodeWorldTransform(mShape->findNode(target), &worldtrans); + mMeshes.last().transform = worldtrans; + PrimFit primFitter; primFitter.fitSphere( mVerts.size(), (F32*)mVerts.address() ); - addSphere( primFitter.mSphereRadius, primFitter.mSphereCenter ); + worldtrans.inverse(); + worldtrans.mulP(primFitter.mSphereCenter); + addSphere( primFitter.mSphereRadius, primFitter.mSphereCenter); } //--------------------------- @@ -489,6 +502,7 @@ void MeshFit::addCapsule( F32 radius, F32 height, const MatrixF& mat ) if ( !mesh ) return; + MatrixF capTrans = mMeshes.last().transform * mat; // Translate and scale the mesh verts height = mMax( 0, height ); F32 offset = ( height / ( 2 * radius ) ) - 0.5f; @@ -498,6 +512,7 @@ void MeshFit::addCapsule( F32 radius, F32 height, const MatrixF& mat ) { Point3F v = mesh->mVerts[i]; v.y += ((v.y > 0) ? offset : -offset); + capTrans.mulP(v); mesh->mVerts[i] = v * radius; } @@ -510,22 +525,29 @@ void MeshFit::addCapsule( F32 radius, F32 height, const MatrixF& mat ) TSMesh::__TSMeshVertexBase& vdata = mesh->mVertexData.getBase(i); Point3F v = vdata.vert(); v.y += ((v.y > 0) ? offset : -offset); + capTrans.mulP(v); vdata.vert(v * radius); } } mesh->computeBounds(); - mMeshes.increment(); + mMeshes.last().type = MeshFit::Capsule; - mMeshes.last().transform = mat; mMeshes.last().tsmesh = mesh; } -void MeshFit::fitCapsule() +void MeshFit::fitCapsule(const char* target) { + mMeshes.increment(); + MatrixF worldtrans; + worldtrans.identity(); + mShape->getNodeWorldTransform(mShape->findNode(target), &worldtrans); + mMeshes.last().transform = worldtrans; + PrimFit primFitter; primFitter.fitCapsule( mVerts.size(), (F32*)mVerts.address() ); + addCapsule( primFitter.mCapRadius, primFitter.mCapHeight, primFitter.mCapTransform ); } @@ -691,7 +713,7 @@ void MeshFit::fitK_DOP( const Vector& planes ) //--------------------------- // Best-fit set of convex hulls -void MeshFit::fitConvexHulls( U32 depth, U32 fillType, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, +void MeshFit::fitConvexHulls(const char* target, U32 depth, U32 fillType, F32 minPercentage, U32 maxHulls, U32 maxHullVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ) { VHACD::IVHACD::Parameters p; @@ -718,29 +740,37 @@ void MeshFit::fitConvexHulls( U32 depth, U32 fillType, F32 minPercentage, U32 m { VHACD::IVHACD::ConvexHull ch; iface->GetConvexHull(i, ch); - + mMeshes.increment(); + MeshFit::Mesh& lastMesh = mMeshes.last(); eMeshType meshType = MeshFit::Hull; + MatrixF worldtrans; + worldtrans.identity(); + mShape->getNodeWorldTransform(mShape->findNode(target), &worldtrans); + lastMesh.transform = worldtrans; + + worldtrans.inverse(); + // Compute error between actual mesh and fitted primitives + F32* points = new F32[ch.m_points.size() * 3]; + for (U32 pt = 0; pt < ch.m_points.size(); pt++) + { + Point3F point(ch.m_points[pt].mX, ch.m_points[pt].mY, ch.m_points[pt].mZ); + worldtrans.mulP(point); + points[pt * 3 + 0] = point.x; + points[pt * 3 + 1] = point.y; + points[pt * 3 + 2] = point.z; + } + + U32* indices = new U32[ch.m_triangles.size() * 3]; + for (U32 ind = 0; ind < ch.m_triangles.size(); ind++) + { + indices[ind * 3 + 0] = ch.m_triangles[ind].mI0; + indices[ind * 3 + 1] = ch.m_triangles[ind].mI1; + indices[ind * 3 + 2] = ch.m_triangles[ind].mI2; + } // Check if we can use a box, sphere or capsule primitive for this hull if (( boxMaxError > 0 ) || ( sphereMaxError > 0 ) || ( capsuleMaxError > 0 )) { - // Compute error between actual mesh and fitted primitives - F32* points = new F32[ch.m_points.size() * 3]; - for (U32 pt = 0; pt < ch.m_points.size(); pt++) - { - points[pt * 3 + 0] = ch.m_points[pt].mX; - points[pt * 3 + 1] = ch.m_points[pt].mY; - points[pt * 3 + 2] = ch.m_points[pt].mZ; - } - - U32* indices = new U32[ch.m_triangles.size() * 3]; - for (U32 ind = 0; ind < ch.m_triangles.size(); ind++) - { - indices[ind * 3 + 0] = ch.m_triangles[ind].mI0; - indices[ind * 3 + 1] = ch.m_triangles[ind].mI1; - indices[ind * 3 + 2] = ch.m_triangles[ind].mI2; - } - F32 meshVolume = FLOAT_MATH::fm_computeMeshVolume(points, ch.m_triangles.size(), indices); PrimFit primFitter; @@ -787,42 +817,19 @@ void MeshFit::fitConvexHulls( U32 depth, U32 fillType, F32 minPercentage, U32 m else if ( meshType == MeshFit::Capsule ) addCapsule( primFitter.mCapRadius, primFitter.mCapHeight, primFitter.mCapTransform ); // else fall through to Hull processing - - // cleanup - delete[] points; - delete[] indices; } if ( meshType == MeshFit::Hull ) { // Create TSMesh from convex hull - mMeshes.increment(); - MeshFit::Mesh& lastMesh = mMeshes.last(); lastMesh.type = MeshFit::Hull; - lastMesh.transform.identity(); - - U32* indices = new U32[ch.m_triangles.size() * 3]; - for (U32 ind = 0; ind < ch.m_triangles.size(); ind++) - { - indices[ind * 3 + 0] = ch.m_triangles[ind].mI0; - indices[ind * 3 + 1] = ch.m_triangles[ind].mI1; - indices[ind * 3 + 2] = ch.m_triangles[ind].mI2; - } - - F32* points = new F32[ch.m_points.size() * 3]; - for (U32 pt = 0; pt < ch.m_points.size(); pt++) - { - points[pt * 3 + 0] = ch.m_points[pt].mX; - points[pt * 3 + 1] = ch.m_points[pt].mY; - points[pt * 3 + 2] = ch.m_points[pt].mZ; - } - lastMesh.tsmesh = createTriMesh(points, ch.m_points.size(), indices, ch.m_triangles.size()); lastMesh.tsmesh->computeBounds(); - delete[] points; - delete[] indices; } + + delete[] points; + delete[] indices; } iface->Release(); @@ -929,7 +936,7 @@ DefineTSShapeConstructorMethod( addPrimitive, bool, ( const char* meshName, cons return true; }} -DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, const char* fillMode, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( "flood fill", 4, 10, 30, 32, 0, 0, 0), +DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, const char* fillMode, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( "bounds", "flood fill", 4, 10, 30, 32, 0, 0, 0), ( size, type, target, fillMode, depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, "Autofit a mesh primitive or set of convex hulls to the shape geometry. Hulls " "may optionally be converted to boxes, spheres and/or capsules based on their " @@ -950,8 +957,8 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char "@return true if successful, false otherwise\n\n" "@tsexample\n" "%this.addCollisionDetail( -1, \"box\", \"bounds\" );\n" - "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\", 4, 30, 30, 32, 0, 0, 0 );\n" - "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\",\"flood fill\", 4, 30, 30, 32, 50, 50, 50 );\n" + "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\", 4, 10, 30, 32, 0, 0, 0 );\n" + "%this.addCollisionDetail( -1, \"convex hulls\", \"bounds\",\"flood fill\", 4, 10, 30, 32, 50, 50, 50 );\n" "@endtsexample\n" ) { MeshFit fit( mShape ); @@ -964,11 +971,11 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char } if ( !dStricmp( type, "box" ) ) - fit.fitOBB(); + fit.fitOBB(target); else if ( !dStricmp( type, "sphere" ) ) - fit.fitSphere(); + fit.fitSphere(target); else if ( !dStricmp( type, "capsule" ) ) - fit.fitCapsule(); + fit.fitCapsule(target); else if ( !dStricmp( type, "10-dop x" ) ) fit.fit10_DOP_X(); else if ( !dStricmp( type, "10-dop y" ) ) @@ -987,7 +994,7 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char fillType = 1; if (!dStricmp(fillMode, "raycast fill")) fillType = 2; - fit.fitConvexHulls( depth, fillType, minPercentage, maxHulls, maxVerts, + fit.fitConvexHulls( target, depth, fillType, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ); } else @@ -1042,8 +1049,6 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char while (true) { String tempName = objName; - if (suffix == 0) - suffix = 1; for (S32 s = suffix; s != 0; s /= 26) { tempName += ('A' + (s % 26)); @@ -1064,7 +1069,7 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char mShape->addMesh( mesh->tsmesh, meshName ); // Add a node for this object if needed (non-identity transform) - addNode( meshName, colNodeName, target, TransformF( mesh->transform ) ); + addNode( meshName, colNodeName, TransformF( mesh->transform ), false, target); mShape->setObjectNode( objName, meshName ); } diff --git a/Engine/source/ts/tsShapeConstruct.cpp b/Engine/source/ts/tsShapeConstruct.cpp index 3031ec110..4a0cf272c 100644 --- a/Engine/source/ts/tsShapeConstruct.cpp +++ b/Engine/source/ts/tsShapeConstruct.cpp @@ -1111,8 +1111,8 @@ DefineTSShapeConstructorMethod(renameNode, bool, (const char* oldName, const cha return true; }} -DefineTSShapeConstructorMethod(addNode, bool, (const char* name, const char* parentName, const char* target, TransformF txfm, bool isWorld), (TransformF::Identity, false), - (name, parentName, target, txfm, isWorld), false, +DefineTSShapeConstructorMethod(addNode, bool, (const char* name, const char* parentName, TransformF txfm, bool isWorld, const char* target), (TransformF::Identity, false, ""), + (name, parentName, txfm, isWorld, target), false, "Add a new node.\n" "@param name name for the new node (must not already exist)\n" "@param parentName name of an existing node to be the parent of the new node. " @@ -1125,7 +1125,7 @@ DefineTSShapeConstructorMethod(addNode, bool, (const char* name, const char* par "@tsexample\n" "%this.addNode( \"Nose\", \"Bip01 Head\", \"0 2 2 0 0 1 0\" );\n" "%this.addNode( \"myRoot\", \"\", \"0 0 4 0 0 1 1.57\" );\n" - "%this.addNode( \"Nodes\", \"Bip01 Head\", \"0 2 0 0 0 1 0\", true );\n" + "%this.addNode( \"Nodes\", \"Bip01 Head\", \"0 2 0 0 0 1 0\", true,\"Bounds\");\n" "@endtsexample\n") { Point3F pos(txfm.getPosition()); @@ -2539,14 +2539,6 @@ bool TSShapeConstructor::ChangeSet::addCmd_renameNode(const TSShapeConstructor:: Command& cmd = mCommands[index]; switch (cmd.type) { - case CmdAddNode: - if (namesEqual(cmd.argv[0], newCmd.argv[0])) - { - cmd.argv[0] = newCmd.argv[1]; // Replace initial name argument - return false; - } - break; - case CmdRenameNode: if (namesEqual(cmd.argv[1], newCmd.argv[0])) { @@ -3516,7 +3508,7 @@ void TSShapeConstructor::cleanTargetNodes(const char* detail, const char* target if (dStrStartsWith(cmd.argv[0], "Col")) { // node has the same detail and same target - if (!dStricmp(detail, cmd.argv[1]) && !dStricmp(target, cmd.argv[2])) + if (!dStricmp(detail, cmd.argv[1]) && !dStricmp(target, cmd.argv[4])) { // now remove it from shape mShape->removeMesh(cmd.argv[0]); diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index 7cbbc0a49..d9b9e1e01 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -267,7 +267,7 @@ public: TransformF getNodeTransform(const char* name, bool isWorld = false); bool setNodeTransform(const char* name, TransformF txfm, bool isWorld = false); bool renameNode(const char* oldName, const char* newName); - bool addNode(const char* name, const char* parentName, const char* target = "", TransformF txfm = TransformF::Identity, bool isWorld = false); + bool addNode(const char* name, const char* parentName, TransformF txfm = TransformF::Identity, bool isWorld = false, const char* target = ""); bool removeNode(const char* name); ///@} From 2132379b05e5eeae9621f7684e1cab698255e670 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 16 May 2024 21:21:34 +0100 Subject: [PATCH 23/65] backwards compat Changed: addCollisionDetail now has its fill mode at the end for easier backwards compat --- Engine/source/ts/tsMeshFit.cpp | 6 ++--- Engine/source/ts/tsShapeConstruct.h | 2 +- .../scripts/shapeEditor.ed.tscript | 19 +++++++------- .../scripts/shapeEditorActions.ed.tscript | 26 +++++++++---------- 4 files changed, 26 insertions(+), 27 deletions(-) diff --git a/Engine/source/ts/tsMeshFit.cpp b/Engine/source/ts/tsMeshFit.cpp index 13a08ee04..944a72cc2 100644 --- a/Engine/source/ts/tsMeshFit.cpp +++ b/Engine/source/ts/tsMeshFit.cpp @@ -936,8 +936,8 @@ DefineTSShapeConstructorMethod( addPrimitive, bool, ( const char* meshName, cons return true; }} -DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, const char* fillMode, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError ), ( "bounds", "flood fill", 4, 10, 30, 32, 0, 0, 0), - ( size, type, target, fillMode, depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError ), false, +DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char* type, const char* target, S32 depth, F32 minPercentage, S32 maxHulls, S32 maxVerts, F32 boxMaxError, F32 sphereMaxError, F32 capsuleMaxError, const char* fillMode), ( "bounds", 4, 10, 30, 32, 0, 0, 0, "flood fill"), + ( size, type, target, depth, minPercentage, maxHulls, maxVerts, boxMaxError, sphereMaxError, capsuleMaxError, fillMode), false, "Autofit a mesh primitive or set of convex hulls to the shape geometry. Hulls " "may optionally be converted to boxes, spheres and/or capsules based on their " "volume.\n" @@ -946,7 +946,6 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char "26-dop, convex hulls. See the Shape Editor documentation for more details " "about these types.\n" "@param target geometry to fit collision mesh(es) to; either \"bounds\" (for the whole shape), or the name of an object in the shape\n" - "@param fillMode method for filling the voxels in the volume (hulls only)\n" "@param depth maximum split recursion depth (hulls only)\n" "@param minPercentage volume % error threshold (hulls only)\n" "@param maxHulls allowed to be generated (hulls only)\n" @@ -954,6 +953,7 @@ DefineTSShapeConstructorMethod( addCollisionDetail, bool, ( S32 size, const char "@param boxMaxError max % volume difference for a hull to be converted to a box (hulls only)\n" "@param sphereMaxError max % volume difference for a hull to be converted to a sphere (hulls only)\n" "@param capsuleMaxError max % volume difference for a hull to be converted to a capsule (hulls only)\n" + "@param fillMode method for filling the voxels in the volume (hulls only)\n" "@return true if successful, false otherwise\n\n" "@tsexample\n" "%this.addCollisionDetail( -1, \"box\", \"bounds\" );\n" diff --git a/Engine/source/ts/tsShapeConstruct.h b/Engine/source/ts/tsShapeConstruct.h index d9b9e1e01..df04078de 100644 --- a/Engine/source/ts/tsShapeConstruct.h +++ b/Engine/source/ts/tsShapeConstruct.h @@ -317,7 +317,7 @@ public: const char* getImposterSettings(S32 index); S32 addImposter(S32 size, S32 equatorSteps, S32 polarSteps, S32 dl, S32 dim, bool includePoles, F32 polarAngle); bool removeImposter(); - bool addCollisionDetail(S32 size, const char* type, const char* target, const char* fillMode = "flood fill", S32 depth = 4, F32 minPercentage = 10.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0); + bool addCollisionDetail(S32 size, const char* type, const char* target, S32 depth = 4, F32 minPercentage = 10.0f, S32 maxHull = 30, S32 maxVerts = 32, F32 boxMaxError = 0, F32 sphereMaxError = 0, F32 capsuleMaxError = 0, const char* fillMode = "flood fill"); ///@} /// @name Sequences diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript index bf3bee27b..a7114117f 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditor.ed.tscript @@ -2969,26 +2969,25 @@ function ShapeEdColWindow::update_onCollisionChanged( %this ) %targetId = %this-->colTarget.findText( getField( %colData, 1 ) ); %this-->colTarget.setSelected( %targetId, false ); - %fillModeID = %this-->fillMode.findText( getField( %colData, 2 ) ); - %this-->fillMode.setSelected( %fillModeID, false ); - if ( %this-->colType.getText() $= "Convex Hulls" ) { %this-->hullInactive.setVisible( false ); - %this-->hullDepth.setValue( getField( %colData, 3 ) ); + %this-->hullDepth.setValue( getField( %colData, 2 ) ); %this-->hullDepthText.setText( mFloor( %this-->hullDepth.getValue() ) ); - %this-->hullMergeThreshold.setValue( getField( %colData, 4 ) ); + %this-->hullMergeThreshold.setValue( getField( %colData, 3 ) ); %this-->hullMergeText.setText( mFloor( %this-->hullMergeThreshold.getValue() ) ); - %this-->hullConcaveThreshold.setValue( getField( %colData, 5 ) ); + %this-->hullConcaveThreshold.setValue( getField( %colData, 4 ) ); %this-->hullConcaveText.setText( mFloor( %this-->hullConcaveThreshold.getValue() ) ); - %this-->hullMaxVerts.setValue( getField( %colData, 6 ) ); + %this-->hullMaxVerts.setValue( getField( %colData, 5 ) ); %this-->hullMaxVertsText.setText( mFloor( %this-->hullMaxVerts.getValue() ) ); - %this-->hullMaxBoxError.setValue( getField( %colData, 7 ) ); + %this-->hullMaxBoxError.setValue( getField( %colData, 6 ) ); %this-->hullMaxBoxErrorText.setText( mFloor( %this-->hullMaxBoxError.getValue() ) ); - %this-->hullMaxSphereError.setValue( getField( %colData, 8 ) ); + %this-->hullMaxSphereError.setValue( getField( %colData, 7 ) ); %this-->hullMaxSphereErrorText.setText( mFloor( %this-->hullMaxSphereError.getValue() ) ); - %this-->hullMaxCapsuleError.setValue( getField( %colData, 9 ) ); + %this-->hullMaxCapsuleError.setValue( getField( %colData, 8 ) ); %this-->hullMaxCapsuleErrorText.setText( mFloor( %this-->hullMaxCapsuleError.getValue() ) ); + %fillModeID = %this-->fillMode.findText( getField( %colData, 9 ) ); + %this-->fillMode.setSelected( %fillModeID, false ); } else { diff --git a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript index 94a06ad95..09168cbca 100644 --- a/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript +++ b/Templates/BaseGame/game/tools/shapeEditor/scripts/shapeEditorActions.ed.tscript @@ -1055,18 +1055,17 @@ function ShapeEditor::doEditCollision( %this, %type, %target, %fillMode, %depth, %action.oldType = getField( %colData, 0 ); %action.oldTarget = getField( %colData, 1 ); - %action.oldFillMode = getField(%colData, 2); - %action.oldDepth = getField( %colData, 3 ); - %action.oldMerge = getField( %colData, 4 ); - %action.oldConcavity = getField( %colData, 5 ); - %action.oldMaxVerts = getField( %colData, 6 ); - %action.oldBoxMax = getField( %colData, 7 ); - %action.oldSphereMax = getField( %colData, 8 ); - %action.oldCapsuleMax = getField( %colData, 9 ); + %action.oldDepth = getField( %colData, 2 ); + %action.oldMerge = getField( %colData, 3 ); + %action.oldConcavity = getField( %colData, 4 ); + %action.oldMaxVerts = getField( %colData, 5 ); + %action.oldBoxMax = getField( %colData, 6 ); + %action.oldSphereMax = getField( %colData, 7 ); + %action.oldCapsuleMax = getField( %colData, 8 ); + %action.oldFillMode = getField(%colData, 9); %action.newType = %type; %action.newTarget = %target; - %action.newFillMode = %fillMode; %action.newDepth = %depth; %action.newMerge = %merge; %action.newConcavity = %concavity; @@ -1074,6 +1073,7 @@ function ShapeEditor::doEditCollision( %this, %type, %target, %fillMode, %depth, %action.newBoxMax = %boxMax; %action.newSphereMax = %sphereMax; %action.newCapsuleMax = %capsuleMax; + %action.newFillMode = %fillMode; %this.doAction( %action ); } @@ -1095,9 +1095,9 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %fillMode, if ( %type $= "" ) return; - if ( !ShapeEditor.shape.addCollisionDetail( %colDetailSize, %type, %target, %fillMode, + if ( !ShapeEditor.shape.addCollisionDetail( %colDetailSize, %type, %target, %depth, %merge, %concavity, %maxVerts, - %boxMax, %sphereMax, %capsuleMax ) ) + %boxMax, %sphereMax, %capsuleMax, %fillMode) ) return false; // Update UI @@ -1107,8 +1107,8 @@ function ActionEditCollision::updateCollision( %this, %type, %target, %fillMode, for ( %i = 0; %i < %count; %i++ ) ShapeEdPropWindow.update_onMeshAdded( getField( %meshList, %i ) ); - ShapeEdColWindow.lastColSettings = %type TAB %target TAB %fillMode TAB %depth TAB %merge TAB - %concavity TAB %maxVerts TAB %boxMax TAB %sphereMax TAB %capsuleMax; + ShapeEdColWindow.lastColSettings = %type TAB %target TAB %depth TAB %merge TAB + %concavity TAB %maxVerts TAB %boxMax TAB %sphereMax TAB %capsuleMax TAB %fillMode ; ShapeEdColWindow.update_onCollisionChanged(); return true; From 2d2d3c7560dcf661e000f2daedddc35dcf3471ce Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 19 May 2024 01:18:50 +0100 Subject: [PATCH 24/65] PlaneConvex Working example of a plane convex type, now groundplane does not need to create a box for its collisions. --- Engine/source/T3D/groundPlane.cpp | 24 +++---- Engine/source/collision/boxConvex.cpp | 97 +++++++++++++++++++++++++++ Engine/source/collision/boxConvex.h | 22 +++++- Engine/source/collision/convex.h | 3 +- 4 files changed, 130 insertions(+), 16 deletions(-) diff --git a/Engine/source/T3D/groundPlane.cpp b/Engine/source/T3D/groundPlane.cpp index 87fca458f..061ec5d78 100644 --- a/Engine/source/T3D/groundPlane.cpp +++ b/Engine/source/T3D/groundPlane.cpp @@ -272,37 +272,37 @@ void GroundPlane::buildConvex( const Box3F& box, Convex* convex ) return; // See if we already have a convex in the working set. - BoxConvex *boxConvex = NULL; + PlaneConvex *planeConvex = NULL; CollisionWorkingList &wl = convex->getWorkingList(); CollisionWorkingList *itr = wl.wLink.mNext; for ( ; itr != &wl; itr = itr->wLink.mNext ) { - if ( itr->mConvex->getType() == BoxConvexType && + if ( itr->mConvex->getType() == PlaneConvexType && itr->mConvex->getObject() == this ) { - boxConvex = (BoxConvex*)itr->mConvex; + planeConvex = (PlaneConvex*)itr->mConvex; break; } } - if ( !boxConvex ) + if ( !planeConvex) { - boxConvex = new BoxConvex; - mConvexList->registerObject( boxConvex ); - boxConvex->init( this ); + planeConvex = new PlaneConvex; + mConvexList->registerObject(planeConvex); + planeConvex->init( this ); - convex->addToWorkingList( boxConvex ); + convex->addToWorkingList(planeConvex); } // Update our convex to best match the queried box - if ( boxConvex ) + if (planeConvex) { Point3F queryCenter = box.getCenter(); - boxConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, -GROUND_PLANE_BOX_HEIGHT_HALF ); - boxConvex->mSize = Point3F( box.getExtents().x, + planeConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, 0 ); + planeConvex->mSize = Point3F( box.getExtents().x, box.getExtents().y, - GROUND_PLANE_BOX_HEIGHT_HALF ); + 0 ); } } diff --git a/Engine/source/collision/boxConvex.cpp b/Engine/source/collision/boxConvex.cpp index 2bd80ba44..ea1b4ad92 100644 --- a/Engine/source/collision/boxConvex.cpp +++ b/Engine/source/collision/boxConvex.cpp @@ -215,3 +215,100 @@ const MatrixF& OrthoBoxConvex::getTransform() const return mOrthoMatrixCache; } +Point3F PlaneConvex::getVertex(S32 v) +{ + Point3F p = mCenter; + p.x += (v & 1) ? mSize.x : -mSize.x; + p.y += (v & 2) ? mSize.y : -mSize.y; + + return p; +} + +void PlaneConvex::emitEdge(S32 v1, S32 v2, const MatrixF& mat, ConvexFeature* cf) +{ + S32 vc = cf->mVertexList.size(); + cf->mVertexList.increment(2); + Point3F* vp = cf->mVertexList.begin(); + mat.mulP(getVertex(v1), &vp[vc]); + mat.mulP(getVertex(v2), &vp[vc + 1]); + + cf->mEdgeList.increment(); + ConvexFeature::Edge& edge = cf->mEdgeList.last(); + edge.vertex[0] = vc; + edge.vertex[1] = vc + 1; +} + +void PlaneConvex::emitFace(const MatrixF& mat, ConvexFeature* cf) { + // Assuming sFace contains a single face definition for the plane + Face& face = sFace[4]; + + // Emit vertices + S32 vc = cf->mVertexList.size(); + cf->mVertexList.increment(4); + Point3F* vp = cf->mVertexList.begin(); + for (S32 v = 0; v < 4; v++) { + mat.mulP(getVertex(face.vertex[v]), &vp[vc + v]); + } + + // Emit edges + cf->mEdgeList.increment(4); + ConvexFeature::Edge* edge = cf->mEdgeList.end() - 4; + for (S32 e = 0; e < 4; e++) { + edge[e].vertex[0] = vc + e; + edge[e].vertex[1] = vc + ((e + 1) & 3); + } + + // Emit 2 triangle faces + cf->mFaceList.increment(2); + ConvexFeature::Face* ef = cf->mFaceList.end() - 2; + mat.getColumn(face.axis, &ef->normal); + ef[1].normal = ef[0].normal; + ef[1].vertex[0] = ef[0].vertex[0] = vc; + ef[1].vertex[1] = ef[0].vertex[2] = vc + 2; + ef[0].vertex[1] = vc + 1; + ef[1].vertex[2] = vc + 3; +} + +Point3F PlaneConvex::support(const VectorF& v) const { + Point3F p = mCenter; + p.x += (v.x >= 0) ? mSize.x : -mSize.x; + p.y += (v.y >= 0) ? mSize.y : -mSize.y; + return p; +} + +void PlaneConvex::getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) +{ + cf->material = 0; + cf->mObject = mObject; + + // Emit edges + for (S32 i = 0; i < 4; ++i) { + S32 next = (i + 1) % 4; + emitEdge(i, next, mat, cf); + } + + emitFace(mat, cf); +} + +void PlaneConvex::getPolyList(AbstractPolyList* list) +{ + list->setTransform(&getTransform(), getScale()); + list->setObject(getObject()); + + U32 base = list->addPoint(mCenter + Point3F(-mSize.x, -mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F(mSize.x, -mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F(-mSize.x, mSize.y, -mSize.z)); + list->addPoint(mCenter + Point3F(mSize.x, mSize.y, -mSize.z)); + + list->begin(0, 0); + + list->vertex(base + sFace[4].vertex[3]); + list->vertex(base + sFace[4].vertex[2]); + list->vertex(base + sFace[4].vertex[1]); + list->vertex(base + sFace[4].vertex[0]); + + list->plane(base + sFace[4].vertex[2], + base + sFace[4].vertex[1], + base + sFace[4].vertex[0]); + list->end(); +} diff --git a/Engine/source/collision/boxConvex.h b/Engine/source/collision/boxConvex.h index cc2ca260f..80cc42854 100644 --- a/Engine/source/collision/boxConvex.h +++ b/Engine/source/collision/boxConvex.h @@ -48,16 +48,32 @@ public: void getPolyList(AbstractPolyList* list) override; }; - -class OrthoBoxConvex: public BoxConvex +class OrthoBoxConvex : public BoxConvex { typedef BoxConvex Parent; mutable MatrixF mOrthoMatrixCache; - public: +public: OrthoBoxConvex() { mOrthoMatrixCache.identity(); } const MatrixF& getTransform() const override; }; +class PlaneConvex : public Convex +{ + Point3F getVertex(S32 v); + void emitEdge(S32 v1, S32 v2, const MatrixF& mat, ConvexFeature* cf); + void emitFace(const MatrixF& mat, ConvexFeature* cf); +public: + Point3F mCenter; + VectorF mSize; + + PlaneConvex() { mType = PlaneConvexType; } + void init(SceneObject* obj) { mObject = obj; } + + Point3F support(const VectorF& v) const override; + void getFeatures(const MatrixF& mat, const VectorF& n, ConvexFeature* cf) override; + void getPolyList(AbstractPolyList* list) override; +}; + #endif diff --git a/Engine/source/collision/convex.h b/Engine/source/collision/convex.h index 6ad33b2b5..9ae2e2a97 100644 --- a/Engine/source/collision/convex.h +++ b/Engine/source/collision/convex.h @@ -90,7 +90,8 @@ enum ConvexType { TSPolysoupConvexType, MeshRoadConvexType, ConvexShapeCollisionConvexType, - ForestConvexType + ForestConvexType, + PlaneConvexType }; From 25d6ee5372cbbef252315534ee9d19d9bb6b58da Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Mon, 20 May 2024 12:21:37 +0100 Subject: [PATCH 25/65] backup backup commit --- Engine/source/T3D/groundPlane.cpp | 6 ++-- Engine/source/collision/concretePolyList.cpp | 31 ++++++++++++++++++-- 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/Engine/source/T3D/groundPlane.cpp b/Engine/source/T3D/groundPlane.cpp index 061ec5d78..08898c3b5 100644 --- a/Engine/source/T3D/groundPlane.cpp +++ b/Engine/source/T3D/groundPlane.cpp @@ -299,10 +299,8 @@ void GroundPlane::buildConvex( const Box3F& box, Convex* convex ) { Point3F queryCenter = box.getCenter(); - planeConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, 0 ); - planeConvex->mSize = Point3F( box.getExtents().x, - box.getExtents().y, - 0 ); + planeConvex->mCenter = Point3F( queryCenter.x, queryCenter.y, 0 ); + planeConvex->mSize = Point3F( box.getExtents().x, box.getExtents().y, 0 ); } } diff --git a/Engine/source/collision/concretePolyList.cpp b/Engine/source/collision/concretePolyList.cpp index ab6688151..8f21fe154 100644 --- a/Engine/source/collision/concretePolyList.cpp +++ b/Engine/source/collision/concretePolyList.cpp @@ -150,13 +150,13 @@ void ConcretePolyList::render() GFXStateBlockRef sb = GFX->createStateBlock( solidZDisable ); GFX->setStateBlock( sb ); - PrimBuild::color3i( 255, 0, 255 ); - Poly *p; Point3F *pnt; for ( p = mPolyList.begin(); p < mPolyList.end(); p++ ) { + PrimBuild::color3i(255, 0, 255); + PrimBuild::begin( GFXLineStrip, p->vertexCount + 1 ); for ( U32 i = 0; i < p->vertexCount; i++ ) @@ -169,6 +169,31 @@ void ConcretePolyList::render() PrimBuild::vertex3fv( pnt ); PrimBuild::end(); + + // Calculate the center of the polygon + Point3F centroid(0, 0, 0); + for (U32 i = 0; i < p->vertexCount; i++) + { + pnt = &mVertexList[mIndexList[p->vertexStart + i]]; + centroid += *pnt; + } + centroid /= p->vertexCount; + + // Calculate the end point of the normal line + Point3F norm = p->plane.getNormal(); + + U8 red = static_cast((norm.x + 1.0f) * 0.5f * 255); + U8 green = static_cast((norm.y + 1.0f) * 0.5f * 255); + U8 blue = static_cast((norm.z + 1.0f) * 0.5f * 255); + + PrimBuild::color3i(red, green, blue); + Point3F normalEnd = centroid + norm; + + // Draw the normal line + PrimBuild::begin(GFXLineList, 2); + PrimBuild::vertex3fv(centroid); + PrimBuild::vertex3fv(normalEnd); + PrimBuild::end(); } } @@ -220,4 +245,4 @@ void ConcretePolyList::triangulate() mPolyList = polyList; mIndexList = indexList; -} \ No newline at end of file +} From b338458a1dbffd95565c5b3bf609674e513d81c8 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 09:48:42 +0100 Subject: [PATCH 26/65] possible fix for torsion lineno --- Engine/source/console/torquescript/CMDscan.cpp | 1 + Engine/source/console/torquescript/CMDscan.l | 1 + 2 files changed, 2 insertions(+) diff --git a/Engine/source/console/torquescript/CMDscan.cpp b/Engine/source/console/torquescript/CMDscan.cpp index ccf1aacb5..5ca6ca7d9 100644 --- a/Engine/source/console/torquescript/CMDscan.cpp +++ b/Engine/source/console/torquescript/CMDscan.cpp @@ -2741,6 +2741,7 @@ void CMDSetScanBuffer(const char *sb, const char *fn) scanBuffer = sb; fileName = fn; scanIndex = 0; + yylineno = 1; } int CMDgetc() diff --git a/Engine/source/console/torquescript/CMDscan.l b/Engine/source/console/torquescript/CMDscan.l index 57e8f0b3f..d6a03186a 100644 --- a/Engine/source/console/torquescript/CMDscan.l +++ b/Engine/source/console/torquescript/CMDscan.l @@ -291,6 +291,7 @@ void CMDSetScanBuffer(const char *sb, const char *fn) scanBuffer = sb; fileName = fn; scanIndex = 0; + yylineno = 1; } int CMDgetc() From 482eb28dedf44296c3a47f2312278cb5939bc1f1 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 14:00:21 +0100 Subject: [PATCH 27/65] Update sfxSndStream.cpp add different file type modes and reset stream after reading (torque still reads the full thing) --- Engine/source/sfx/media/sfxSndStream.cpp | 33 +++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index 3c1d21b17..aad9fa897 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -105,14 +105,45 @@ void SFXSndStream::reset() U32 SFXSndStream::read(U8* buffer, U32 length) { + if (!sndFile) + { + Con::errorf("SFXSndStream - read: Called on uninitialized stream."); + return 0; + } + + U32 framesToRead = length / mFormat.getBytesPerSample(); U32 framesRead = 0; - framesRead = sf_readf_short(sndFile, (short*)buffer, sfinfo.frames); + switch (sfinfo.format & SF_FORMAT_SUBMASK) + { + case SF_FORMAT_PCM_S8: + case SF_FORMAT_PCM_U8: + framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); + break; + case SF_FORMAT_PCM_16: + case SF_FORMAT_VORBIS: + framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); + break; + case SF_FORMAT_PCM_24: + framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); // 24-bit usually stored in 32-bit containers + break; + case SF_FORMAT_PCM_32: + case SF_FORMAT_FLOAT: + framesRead = sf_readf_float(sndFile, reinterpret_cast(buffer), framesToRead); + break; + default: + Con::errorf("SFXSndStream - read: Unsupported format."); + return 0; + } + if (framesRead != sfinfo.frames) { Con::errorf("SFXSndStream - read: %s", sf_strerror(sndFile)); } + // reset stream + setPosition(0); + return framesRead * mFormat.getBytesPerSample(); } From bf34d3daa8a2dc87fe732713fe6c61fad178eff6 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 14:12:01 +0100 Subject: [PATCH 28/65] Update sfxSndStream.cpp fix distortion issue on some sounds, if vorbis requires a scale set for float conversion --- Engine/source/sfx/media/sfxSndStream.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index aad9fa897..2ccad264f 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -57,8 +57,11 @@ bool SFXSndStream::_readHeader() bitsPerSample = 8; break; case SF_FORMAT_PCM_16: + bitsPerSample = 16; + break; case SF_FORMAT_VORBIS: bitsPerSample = 16; + sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); break; case SF_FORMAT_PCM_24: bitsPerSample = 24; From ebdc40838523de2bedeab644d613420f3ac44a29 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 15:11:18 +0100 Subject: [PATCH 29/65] Update sfxSndStream.cpp streaming file fixes, also only wrap back around when we have read the whole file. --- Engine/source/sfx/media/sfxSndStream.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index 2ccad264f..a2a508804 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -139,13 +139,22 @@ U32 SFXSndStream::read(U8* buffer, U32 length) return 0; } - if (framesRead != sfinfo.frames) + if (framesRead != framesToRead) { Con::errorf("SFXSndStream - read: %s", sf_strerror(sndFile)); } - // reset stream - setPosition(0); + // make sure we are more than 0 position. + if (getPosition() > 0) + { + // (convert to frames) == number of frames available? + if ((getPosition() / mFormat.getBytesPerSample()) == sfinfo.frames) + { + // reset stream + setPosition(0); + } + } + return framesRead * mFormat.getBytesPerSample(); } From c28cedc2d898f2c2fa00d02bc715f535878879ea Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 16:19:10 +0100 Subject: [PATCH 30/65] 32 bit float test 32 bit floating point sounds --- Engine/source/sfx/media/sfxSndStream.cpp | 7 ++----- Engine/source/sfx/openal/sfxALBuffer.h | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index a2a508804..829d73f37 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -59,15 +59,12 @@ bool SFXSndStream::_readHeader() case SF_FORMAT_PCM_16: bitsPerSample = 16; break; - case SF_FORMAT_VORBIS: - bitsPerSample = 16; - sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); - break; case SF_FORMAT_PCM_24: bitsPerSample = 24; break; case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: + case SF_FORMAT_VORBIS: bitsPerSample = 32; break; default: @@ -124,7 +121,6 @@ U32 SFXSndStream::read(U8* buffer, U32 length) framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); break; case SF_FORMAT_PCM_16: - case SF_FORMAT_VORBIS: framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); break; case SF_FORMAT_PCM_24: @@ -132,6 +128,7 @@ U32 SFXSndStream::read(U8* buffer, U32 length) break; case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: + case SF_FORMAT_VORBIS: framesRead = sf_readf_float(sndFile, reinterpret_cast(buffer), framesToRead); break; default: diff --git a/Engine/source/sfx/openal/sfxALBuffer.h b/Engine/source/sfx/openal/sfxALBuffer.h index 26864a3d0..7589485bd 100644 --- a/Engine/source/sfx/openal/sfxALBuffer.h +++ b/Engine/source/sfx/openal/sfxALBuffer.h @@ -84,14 +84,24 @@ class SFXALBuffer : public SFXBuffer return AL_FORMAT_STEREO8; else if( bps == 32 ) return AL_FORMAT_STEREO16; + else if (bps == 64) + { + if (alIsExtensionPresent("AL_EXT_FLOAT32")) + return AL_FORMAT_STEREO_FLOAT32; + } } else if( format.getChannels() == 1 ) { const U32 bps = format.getBitsPerSample(); - if( bps == 8 ) + if (bps == 8) return AL_FORMAT_MONO8; - else if( bps == 16 ) + else if (bps == 16) return AL_FORMAT_MONO16; + else if (bps == 32) + { + if(alIsExtensionPresent("AL_EXT_FLOAT32")) + return AL_FORMAT_MONO_FLOAT32; + } } return 0; } @@ -116,4 +126,4 @@ class SFXALBuffer : public SFXBuffer virtual ~SFXALBuffer(); }; -#endif // _SFXALBUFFER_H_ \ No newline at end of file +#endif // _SFXALBUFFER_H_ From de454dc793d0bb760586296cbd9e127cc6375e04 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 16:25:26 +0100 Subject: [PATCH 31/65] Update sfxSndStream.cpp revert vorbis back to 16bit add normalisation option. --- Engine/source/sfx/media/sfxSndStream.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index 829d73f37..1eeffab31 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -59,12 +59,16 @@ bool SFXSndStream::_readHeader() case SF_FORMAT_PCM_16: bitsPerSample = 16; break; + case SF_FORMAT_VORBIS: + bitsPerSample = 16; + sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); + sf_command(sndFile, SFC_SET_NORM_FLOAT, NULL, SF_TRUE); + break; case SF_FORMAT_PCM_24: bitsPerSample = 24; break; case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: - case SF_FORMAT_VORBIS: bitsPerSample = 32; break; default: @@ -121,6 +125,7 @@ U32 SFXSndStream::read(U8* buffer, U32 length) framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); break; case SF_FORMAT_PCM_16: + case SF_FORMAT_VORBIS: framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); break; case SF_FORMAT_PCM_24: @@ -128,7 +133,6 @@ U32 SFXSndStream::read(U8* buffer, U32 length) break; case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: - case SF_FORMAT_VORBIS: framesRead = sf_readf_float(sndFile, reinterpret_cast(buffer), framesToRead); break; default: From aa9cb63789037916719c9ba6ad0efa9d4d042089 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 24 May 2024 17:18:35 +0100 Subject: [PATCH 32/65] Update sfxSndStream.cpp --- Engine/source/sfx/media/sfxSndStream.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index 1eeffab31..26d774c44 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -62,7 +62,6 @@ bool SFXSndStream::_readHeader() case SF_FORMAT_VORBIS: bitsPerSample = 16; sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); - sf_command(sndFile, SFC_SET_NORM_FLOAT, NULL, SF_TRUE); break; case SF_FORMAT_PCM_24: bitsPerSample = 24; @@ -148,8 +147,8 @@ U32 SFXSndStream::read(U8* buffer, U32 length) // make sure we are more than 0 position. if (getPosition() > 0) { - // (convert to frames) == number of frames available? - if ((getPosition() / mFormat.getBytesPerSample()) == sfinfo.frames) + // (convert to frames) - number of frames available < MAX_BUFFER? + if (((getPosition() / mFormat.getBytesPerSample()) - sfinfo.frames) < MAX_BUFFER) { // reset stream setPosition(0); From 0ae0d633e99751f7a0db070fa4dc2899be475399 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 25 May 2024 08:16:43 +0100 Subject: [PATCH 33/65] Update sfxSndStream.cpp --- Engine/source/sfx/media/sfxSndStream.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index 26d774c44..955f118d8 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -64,8 +64,6 @@ bool SFXSndStream::_readHeader() sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); break; case SF_FORMAT_PCM_24: - bitsPerSample = 24; - break; case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: bitsPerSample = 32; @@ -124,12 +122,12 @@ U32 SFXSndStream::read(U8* buffer, U32 length) framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); break; case SF_FORMAT_PCM_16: + framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); + break; case SF_FORMAT_VORBIS: framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); break; case SF_FORMAT_PCM_24: - framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); // 24-bit usually stored in 32-bit containers - break; case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: framesRead = sf_readf_float(sndFile, reinterpret_cast(buffer), framesToRead); From e3d977b8e710852eea8368593756629ba71364c3 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 25 May 2024 09:10:47 +0100 Subject: [PATCH 34/65] Update sfxALBuffer.h mac dont like --- Engine/source/sfx/openal/sfxALBuffer.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Engine/source/sfx/openal/sfxALBuffer.h b/Engine/source/sfx/openal/sfxALBuffer.h index 7589485bd..d3260d704 100644 --- a/Engine/source/sfx/openal/sfxALBuffer.h +++ b/Engine/source/sfx/openal/sfxALBuffer.h @@ -84,11 +84,12 @@ class SFXALBuffer : public SFXBuffer return AL_FORMAT_STEREO8; else if( bps == 32 ) return AL_FORMAT_STEREO16; + /* MAC doesnt like 32bit openal (doesnt use the ext) else if (bps == 64) { if (alIsExtensionPresent("AL_EXT_FLOAT32")) return AL_FORMAT_STEREO_FLOAT32; - } + }*/ } else if( format.getChannels() == 1 ) { @@ -97,11 +98,12 @@ class SFXALBuffer : public SFXBuffer return AL_FORMAT_MONO8; else if (bps == 16) return AL_FORMAT_MONO16; + /* else if (bps == 32) { if(alIsExtensionPresent("AL_EXT_FLOAT32")) return AL_FORMAT_MONO_FLOAT32; - } + }*/ } return 0; } From 0d1dc234fac406cce67e872dfa8391db2c64fc4e Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 25 May 2024 10:04:51 +0100 Subject: [PATCH 35/65] Update sfxSndStream.cpp we always want shorts --- Engine/source/sfx/media/sfxSndStream.cpp | 26 ++++++++---------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/Engine/source/sfx/media/sfxSndStream.cpp b/Engine/source/sfx/media/sfxSndStream.cpp index 955f118d8..96ad07711 100644 --- a/Engine/source/sfx/media/sfxSndStream.cpp +++ b/Engine/source/sfx/media/sfxSndStream.cpp @@ -60,13 +60,11 @@ bool SFXSndStream::_readHeader() bitsPerSample = 16; break; case SF_FORMAT_VORBIS: - bitsPerSample = 16; - sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); - break; case SF_FORMAT_PCM_24: case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: - bitsPerSample = 32; + bitsPerSample = 16; + sf_command(sndFile, SFC_SET_SCALE_FLOAT_INT_READ, NULL, SF_TRUE); break; default: // missed, set it to 16 anyway. @@ -119,18 +117,14 @@ U32 SFXSndStream::read(U8* buffer, U32 length) { case SF_FORMAT_PCM_S8: case SF_FORMAT_PCM_U8: - framesRead = sf_readf_int(sndFile, reinterpret_cast(buffer), framesToRead); + framesRead = sf_readf_int(sndFile, (int*)buffer, framesToRead); break; case SF_FORMAT_PCM_16: - framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); - break; case SF_FORMAT_VORBIS: - framesRead = sf_readf_short(sndFile, reinterpret_cast(buffer), framesToRead); - break; case SF_FORMAT_PCM_24: case SF_FORMAT_PCM_32: case SF_FORMAT_FLOAT: - framesRead = sf_readf_float(sndFile, reinterpret_cast(buffer), framesToRead); + framesRead = sf_readf_short(sndFile, (short*)buffer, framesToRead); break; default: Con::errorf("SFXSndStream - read: Unsupported format."); @@ -142,15 +136,11 @@ U32 SFXSndStream::read(U8* buffer, U32 length) Con::errorf("SFXSndStream - read: %s", sf_strerror(sndFile)); } - // make sure we are more than 0 position. - if (getPosition() > 0) + // (convert to frames) - number of frames available < MAX_BUFFER? reset + if (((getPosition() / mFormat.getBytesPerSample()) - sfinfo.frames) < MAX_BUFFER) { - // (convert to frames) - number of frames available < MAX_BUFFER? - if (((getPosition() / mFormat.getBytesPerSample()) - sfinfo.frames) < MAX_BUFFER) - { - // reset stream - setPosition(0); - } + // reset stream + setPosition(0); } From 79dfd14bea11a22888b03fa30b7978e219846dc3 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sat, 25 May 2024 10:20:14 +0100 Subject: [PATCH 36/65] Update sfxALBuffer.h revert to head --- Engine/source/sfx/openal/sfxALBuffer.h | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/Engine/source/sfx/openal/sfxALBuffer.h b/Engine/source/sfx/openal/sfxALBuffer.h index d3260d704..26864a3d0 100644 --- a/Engine/source/sfx/openal/sfxALBuffer.h +++ b/Engine/source/sfx/openal/sfxALBuffer.h @@ -84,26 +84,14 @@ class SFXALBuffer : public SFXBuffer return AL_FORMAT_STEREO8; else if( bps == 32 ) return AL_FORMAT_STEREO16; - /* MAC doesnt like 32bit openal (doesnt use the ext) - else if (bps == 64) - { - if (alIsExtensionPresent("AL_EXT_FLOAT32")) - return AL_FORMAT_STEREO_FLOAT32; - }*/ } else if( format.getChannels() == 1 ) { const U32 bps = format.getBitsPerSample(); - if (bps == 8) + if( bps == 8 ) return AL_FORMAT_MONO8; - else if (bps == 16) + else if( bps == 16 ) return AL_FORMAT_MONO16; - /* - else if (bps == 32) - { - if(alIsExtensionPresent("AL_EXT_FLOAT32")) - return AL_FORMAT_MONO_FLOAT32; - }*/ } return 0; } @@ -128,4 +116,4 @@ class SFXALBuffer : public SFXBuffer virtual ~SFXALBuffer(); }; -#endif // _SFXALBUFFER_H_ +#endif // _SFXALBUFFER_H_ \ No newline at end of file From 56e4484ff612a70456e2ebd1e6ffff4c1508c47e Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Thu, 30 May 2024 17:29:42 -0500 Subject: [PATCH 37/65] remove glowbin as it's own render pass --- .../glsl/deferredShadingFeaturesGLSL.cpp | 6 +- .../hlsl/deferredShadingFeaturesHLSL.cpp | 6 +- .../source/materials/materialFeatureTypes.cpp | 3 +- Engine/source/materials/processedMaterial.cpp | 14 +- Engine/source/materials/processedMaterial.h | 7 +- .../materials/processedShaderMaterial.cpp | 4 +- Engine/source/materials/sceneData.h | 4 - .../renderInstance/renderDeferredMgr.cpp | 3 +- .../source/renderInstance/renderGlowMgr.cpp | 282 ------------------ Engine/source/renderInstance/renderGlowMgr.h | 90 ------ .../renderInstance/renderPassManager.cpp | 1 - .../shaderGen/GLSL/shaderFeatureGLSL.cpp | 23 +- .../source/shaderGen/GLSL/shaderFeatureGLSL.h | 4 +- .../shaderGen/HLSL/shaderFeatureHLSL.cpp | 23 +- .../source/shaderGen/HLSL/shaderFeatureHLSL.h | 4 +- .../DepthOfField/DepthOfFieldPostFX.tscript | 2 +- .../postFX/scripts/Glow/GlowPostFX.tscript | 71 ----- .../Turbulence/TurbulencePostFX.tscript | 2 +- .../rendering/scripts/renderManager.tscript | 5 +- 19 files changed, 44 insertions(+), 510 deletions(-) delete mode 100644 Engine/source/renderInstance/renderGlowMgr.cpp delete mode 100644 Engine/source/renderInstance/renderGlowMgr.h diff --git a/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp index 3d881b398..66e451ab7 100644 --- a/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp +++ b/Engine/source/lighting/advanced/glsl/deferredShadingFeaturesGLSL.cpp @@ -261,16 +261,16 @@ void GlowMapGLSL::processPix(Vector& componentList, const Mate targ->setType("vec4"); targ->setName(getOutputTargetVarName(ShaderFeature::RenderTarget3)); targ->setStructName("OUT"); - output = new GenOp("@ = vec4(@.rgb*@,0);", targ, texOp, glowMul); + output = new GenOp("@ = vec4(pow(max(@.rgb*@,0.0),vec3(3.54406804435,3.54406804435,3.54406804435)),0);", targ, texOp, glowMul); } else { - output = new GenOp("@ += vec4(@.rgb*@,0);", targ, texOp, glowMul); + output = new GenOp("@ += vec4(pow(max(@.rgb*@,0.0),vec3(3.54406804435,3.54406804435,3.54406804435)),0);", targ, texOp, glowMul); } } else { - output = new GenOp("@ += vec4(@.rgb*@,@.a);", targ, texOp, glowMul, targ); + output = new GenOp("@ += vec4(pow(max(@.rgb*@,0.0),vec3(3.54406804435,3.54406804435,3.54406804435)),@.a);", targ, texOp, glowMul, targ); } } diff --git a/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp index 42518830a..8b7d3c0bb 100644 --- a/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp +++ b/Engine/source/lighting/advanced/hlsl/deferredShadingFeaturesHLSL.cpp @@ -264,16 +264,16 @@ void GlowMapHLSL::processPix(Vector &componentList, const Mate targ->setType("fragout"); targ->setName(getOutputTargetVarName(ShaderFeature::RenderTarget3)); targ->setStructName("OUT"); - output = new GenOp("@ = float4(@.rgb*@,0);", targ, texOp, glowMul); + output = new GenOp("@ = float4(pow(max(@.rgb*@,0.0),3.54406804435),0);", targ, texOp, glowMul); } else { - output = new GenOp("@ += float4(@.rgb*@,0);", targ, texOp, glowMul); + output = new GenOp("@ += float4(pow(max(@.rgb*@,0.0),3.54406804435),0);", targ, texOp, glowMul); } } else { - output = new GenOp("@ += float4(@.rgb*@,@.a);", targ, texOp, glowMul, targ); + output = new GenOp("@ += float4(pow(max(@.rgb*@,0.0),3.54406804435),@.a);", targ, texOp, glowMul, targ); } } diff --git a/Engine/source/materials/materialFeatureTypes.cpp b/Engine/source/materials/materialFeatureTypes.cpp index 44220a9b1..2a5736ae3 100644 --- a/Engine/source/materials/materialFeatureTypes.cpp +++ b/Engine/source/materials/materialFeatureTypes.cpp @@ -56,6 +56,7 @@ ImplementFeatureType( MFT_AccuMap, MFG_PreLighting, 2.0f, true ); ImplementFeatureType(MFT_ReflectionProbes, MFG_Lighting, 1.0f, true); ImplementFeatureType( MFT_RTLighting, MFG_Lighting, 2.0f, true ); ImplementFeatureType( MFT_GlowMap, MFG_Lighting, 3.0f, true ); +ImplementFeatureType( MFT_GlowMask, MFG_Lighting, 3.1f, true ); ImplementFeatureType( MFT_LightMap, MFG_Lighting, 4.0f, true ); ImplementFeatureType( MFT_ToneMap, MFG_Lighting, 5.0f, true ); ImplementFeatureType( MFT_VertLitTone, MFG_Lighting, 6.0f, false ); @@ -65,8 +66,6 @@ ImplementFeatureType( MFT_SubSurface, MFG_Lighting, 8.0f, true ); ImplementFeatureType( MFT_VertLit, MFG_Lighting, 9.0f, true ); ImplementFeatureType( MFT_MinnaertShading, MFG_Lighting, 10.0f, true ); - -ImplementFeatureType( MFT_GlowMask, MFG_PostLighting, 1.0f, true ); ImplementFeatureType( MFT_Visibility, MFG_PostLighting, 2.0f, true ); ImplementFeatureType( MFT_Fog, MFG_PostProcess, 3.0f, true ); diff --git a/Engine/source/materials/processedMaterial.cpp b/Engine/source/materials/processedMaterial.cpp index 79b08249a..f4b0d2417 100644 --- a/Engine/source/materials/processedMaterial.cpp +++ b/Engine/source/materials/processedMaterial.cpp @@ -197,7 +197,7 @@ void ProcessedMaterial::addStateBlockDesc(const GFXStateBlockDesc& sb) mUserDefined = sb; } -void ProcessedMaterial::_initStateBlockTemplates(GFXStateBlockDesc& stateTranslucent, GFXStateBlockDesc& stateGlow, GFXStateBlockDesc& stateReflect) +void ProcessedMaterial::_initStateBlockTemplates(GFXStateBlockDesc& stateTranslucent, GFXStateBlockDesc& stateReflect) { // Translucency stateTranslucent.blendDefined = true; @@ -211,10 +211,6 @@ void ProcessedMaterial::_initStateBlockTemplates(GFXStateBlockDesc& stateTranslu stateTranslucent.alphaTestFunc = GFXCmpGreaterEqual; stateTranslucent.samplersDefined = true; - // Glow - stateGlow.zDefined = true; - stateGlow.zWriteEnable = false; - // Reflect stateReflect.cullDefined = true; stateReflect.cullMode = mMaterial->mDoubleSided ? GFXCullNone : GFXCullCW; @@ -316,11 +312,10 @@ void ProcessedMaterial::_initPassStateBlock( RenderPassData *rpd, GFXStateBlockD void ProcessedMaterial::_initRenderStateStateBlocks( RenderPassData *rpd ) { GFXStateBlockDesc stateTranslucent; - GFXStateBlockDesc stateGlow; GFXStateBlockDesc stateReflect; GFXStateBlockDesc statePass; - _initStateBlockTemplates( stateTranslucent, stateGlow, stateReflect ); + _initStateBlockTemplates( stateTranslucent, stateReflect ); _initPassStateBlock( rpd, statePass ); // Ok, we've got our templates set up, let's combine them together based on state and @@ -333,8 +328,6 @@ void ProcessedMaterial::_initRenderStateStateBlocks( RenderPassData *rpd ) stateFinal.addDesc(stateReflect); if (i & RenderPassData::STATE_TRANSLUCENT) stateFinal.addDesc(stateTranslucent); - if (i & RenderPassData::STATE_GLOW) - stateFinal.addDesc(stateGlow); stateFinal.addDesc(statePass); @@ -359,9 +352,6 @@ U32 ProcessedMaterial::_getRenderStateIndex( const SceneRenderState *sceneState, // For example sgData.visibility would be bad to use // in here without changing how RenderMeshMgr works. - if ( sgData.binType == SceneData::GlowBin ) - currState |= RenderPassData::STATE_GLOW; - if ( sceneState && sceneState->isReflectPass() ) currState |= RenderPassData::STATE_REFLECT; diff --git a/Engine/source/materials/processedMaterial.h b/Engine/source/materials/processedMaterial.h index d316fc450..4efc338bc 100644 --- a/Engine/source/materials/processedMaterial.h +++ b/Engine/source/materials/processedMaterial.h @@ -98,9 +98,8 @@ public: { STATE_REFLECT = 1, STATE_TRANSLUCENT = 2, - STATE_GLOW = 4, - STATE_WIREFRAME = 8, - STATE_MAX = 16 + STATE_WIREFRAME = 4, + STATE_MAX = 8 }; /// @@ -301,7 +300,7 @@ protected: /// @{ /// Creates the default state block templates, used by initStateBlocks. - virtual void _initStateBlockTemplates(GFXStateBlockDesc& stateTranslucent, GFXStateBlockDesc& stateGlow, GFXStateBlockDesc& stateReflect); + virtual void _initStateBlockTemplates(GFXStateBlockDesc& stateTranslucent, GFXStateBlockDesc& stateReflect); /// Does the base render state block setting, normally per pass. virtual void _initPassStateBlock( RenderPassData *rpd, GFXStateBlockDesc& result); diff --git a/Engine/source/materials/processedShaderMaterial.cpp b/Engine/source/materials/processedShaderMaterial.cpp index 77566ef29..521ec04c6 100644 --- a/Engine/source/materials/processedShaderMaterial.cpp +++ b/Engine/source/materials/processedShaderMaterial.cpp @@ -348,7 +348,6 @@ void ProcessedShaderMaterial::_determineFeatures( U32 stageNum, // if ( features.hasFeature( MFT_UseInstancing ) && mMaxStages == 1 && - !mMaterial->mGlow[0] && shaderVersion >= 3.0f ) fd.features.addFeature( MFT_UseInstancing ); @@ -520,6 +519,9 @@ void ProcessedShaderMaterial::_determineFeatures( U32 stageNum, if ( mMaterial->mVertColor[ stageNum ] && mVertexFormat->hasColor() ) fd.features.addFeature( MFT_DiffuseVertColor ); + + if (mMaterial->mGlow[stageNum]) + fd.features.addFeature(MFT_GlowMask); // Allow features to add themselves. for ( U32 i = 0; i < FEATUREMGR->getFeatureCount(); i++ ) diff --git a/Engine/source/materials/sceneData.h b/Engine/source/materials/sceneData.h index 67a521923..bd99e3e00 100644 --- a/Engine/source/materials/sceneData.h +++ b/Engine/source/materials/sceneData.h @@ -45,10 +45,6 @@ struct SceneData /// the special bins we care about. RegularBin = 0, - /// The glow render bin. - /// @see RenderGlowMgr - GlowBin, - /// The deferred render bin. /// @RenderDeferredMgr DeferredBin, diff --git a/Engine/source/renderInstance/renderDeferredMgr.cpp b/Engine/source/renderInstance/renderDeferredMgr.cpp index 664302528..65276a111 100644 --- a/Engine/source/renderInstance/renderDeferredMgr.cpp +++ b/Engine/source/renderInstance/renderDeferredMgr.cpp @@ -683,7 +683,8 @@ void ProcessedDeferredMaterial::_determineFeatures( U32 stageNum, type == MFT_UseInstancing || type == MFT_DiffuseVertColor || type == MFT_DetailMap || - type == MFT_DiffuseMapAtlas) + type == MFT_DiffuseMapAtlas|| + type == MFT_GlowMask) newFeatures.addFeature( type ); // Add any transform features. diff --git a/Engine/source/renderInstance/renderGlowMgr.cpp b/Engine/source/renderInstance/renderGlowMgr.cpp deleted file mode 100644 index 17ae78c60..000000000 --- a/Engine/source/renderInstance/renderGlowMgr.cpp +++ /dev/null @@ -1,282 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -#include "platform/platform.h" -#include "renderInstance/renderGlowMgr.h" -#include "renderInstance/renderParticleMgr.h" - -#include "scene/sceneManager.h" -#include "scene/sceneRenderState.h" -#include "materials/sceneData.h" -#include "materials/matInstance.h" -#include "materials/materialFeatureTypes.h" -#include "materials/processedMaterial.h" -#include "postFx/postEffect.h" -#include "gfx/gfxTransformSaver.h" -#include "gfx/gfxDebugEvent.h" -#include "math/util/matrixSet.h" - -IMPLEMENT_CONOBJECT( RenderGlowMgr ); - - -ConsoleDocClass( RenderGlowMgr, - "@brief A render bin for the glow pass.\n\n" - "When the glow buffer PostEffect is enabled this bin gathers mesh render " - "instances with glow materials and renders them to the #glowbuffer offscreen " - "render target.\n\n" - "This render target is then used by the 'GlowPostFx' PostEffect to blur and " - "render the glowing portions of the screen.\n\n" - "@ingroup RenderBin\n" ); - -const MatInstanceHookType RenderGlowMgr::GlowMaterialHook::Type( "Glow" ); - - -RenderGlowMgr::GlowMaterialHook::GlowMaterialHook( BaseMatInstance *matInst ) - : mGlowMatInst( NULL ) -{ - mGlowMatInst = (MatInstance*)matInst->getMaterial()->createMatInstance(); - mGlowMatInst->getFeaturesDelegate().bind( &GlowMaterialHook::_overrideFeatures ); - mGlowMatInst->setUserObject(matInst->getUserObject()); - mGlowMatInst->init( matInst->getRequestedFeatures(), - matInst->getVertexFormat() ); -} - -RenderGlowMgr::GlowMaterialHook::~GlowMaterialHook() -{ - SAFE_DELETE( mGlowMatInst ); -} - -void RenderGlowMgr::GlowMaterialHook::_overrideFeatures( ProcessedMaterial *mat, - U32 stageNum, - MaterialFeatureData &fd, - const FeatureSet &features ) -{ - // If this isn't a glow pass... then add the glow mask feature. - if ( mat->getMaterial() && - !mat->getMaterial()->mGlow[stageNum] ) - fd.features.addFeature( MFT_GlowMask ); - - // Don't allow fog or HDR encoding on - // the glow materials. - fd.features.removeFeature( MFT_Fog ); - fd.features.addFeature( MFT_Imposter ); -} - -RenderGlowMgr::RenderGlowMgr() - : RenderTexTargetBinManager( RenderPassManager::RIT_Mesh, - 1.0f, - 1.0f, - GFXFormatR8G8B8A8, - Point2I( 512, 512 ) ) -{ - notifyType( RenderPassManager::RIT_Decal ); - notifyType( RenderPassManager::RIT_DecalRoad ); - notifyType( RenderPassManager::RIT_Translucent ); - notifyType( RenderPassManager::RIT_Particle ); - - mParticleRenderMgr = NULL; - - mNamedTarget.registerWithName( "glowbuffer" ); - mTargetSizeType = WindowSize; -} - -RenderGlowMgr::~RenderGlowMgr() -{ -} - -PostEffect* RenderGlowMgr::getGlowEffect() -{ - if ( !mGlowEffect ) - mGlowEffect = dynamic_cast( Sim::findObject( "GlowPostFx" ) ); - - return mGlowEffect; -} - -bool RenderGlowMgr::isGlowEnabled() -{ - return getGlowEffect() && getGlowEffect()->isEnabled(); -} - -void RenderGlowMgr::addElement( RenderInst *inst ) -{ - // Skip out if we don't have the glow post - // effect enabled at this time. - if ( !isGlowEnabled() ) - return; - - // TODO: We need to get the scene state here in a more reliable - // manner so we can skip glow in a non-diffuse render pass. - //if ( !mParentManager->getSceneManager()->getSceneState()->isDiffusePass() ) - //return RenderBinManager::arSkipped; - ParticleRenderInst *particleInst = NULL; - if(inst->type == RenderPassManager::RIT_Particle) - particleInst = static_cast(inst); - if(particleInst && particleInst->glow) - { - internalAddElement(inst); - return; - } - - // Skip it if we don't have a glowing material. - BaseMatInstance *matInst = getMaterial( inst ); - if ( !matInst || !matInst->hasGlow() ) - return; - - internalAddElement(inst); -} - -void RenderGlowMgr::render( SceneRenderState *state ) -{ - PROFILE_SCOPE( RenderGlowMgr_Render ); - - if ( !isGlowEnabled() ) - return; - - const U32 binSize = mElementList.size(); - - // If this is a non-diffuse pass or we have no objects to - // render then tell the effect to skip rendering. - if ( !state->isDiffusePass() || binSize == 0 ) - { - getGlowEffect()->setSkip( true ); - return; - } - - GFXDEBUGEVENT_SCOPE( RenderGlowMgr_Render, ColorI::GREEN ); - - GFXTransformSaver saver; - - // Respect the current viewport - mNamedTarget.setViewport(GFX->getViewport()); - - // Tell the superclass we're about to render, preserve contents - const bool isRenderingToTarget = _onPreRender( state, true ); - - // Clear all the buffers to black. - GFX->clear( GFXClearTarget, ColorI::BLACK, 1.0f, 0); - - // Restore transforms - MatrixSet &matrixSet = getRenderPass()->getMatrixSet(); - matrixSet.restoreSceneViewProjection(); - - // init loop data - SceneData sgData; - sgData.init( state, SceneData::GlowBin ); - - for( U32 j=0; jtype == RenderPassManager::RIT_Particle) - { - // Find the particle render manager (if we don't have it) - if(mParticleRenderMgr == NULL) - { - RenderPassManager *rpm = state->getRenderPass(); - for( U32 i = 0; i < rpm->getManagerCount(); i++ ) - { - RenderBinManager *bin = rpm->getManager(i); - if( bin->getRenderInstType() == RenderParticleMgr::RIT_Particles ) - { - mParticleRenderMgr = reinterpret_cast(bin); - break; - } - } - } - - ParticleRenderInst *ri = static_cast(_ri); - - GFX->setStateBlock(mParticleRenderMgr->_getHighResStateBlock(ri)); - mParticleRenderMgr->_getShaderConsts().mShaderConsts->setSafe(mParticleRenderMgr->_getShaderConsts().mModelViewProjSC, *ri->modelViewProj); - - mParticleRenderMgr->renderParticle(ri, state); - j++; - continue; - } - - MeshRenderInst *ri = static_cast(_ri); - - setupSGData( ri, sgData ); - - BaseMatInstance *mat = ri->matInst; - GlowMaterialHook *hook = mat->getHook(); - if ( !hook ) - { - hook = new GlowMaterialHook( ri->matInst ); - ri->matInst->addHook( hook ); - } - BaseMatInstance *glowMat = hook->getMatInstance(); - - U32 matListEnd = j; - - while( glowMat && glowMat->setupPass( state, sgData ) ) - { - U32 a; - for( a=j; atype == RenderPassManager::RIT_Particle) - break; - - MeshRenderInst *passRI = static_cast(mElementList[a].inst); - - if ( newPassNeeded( ri, passRI ) ) - break; - - matrixSet.setWorld(*passRI->objectToWorld); - matrixSet.setView(*passRI->worldToCamera); - matrixSet.setProjection(*passRI->projection); - glowMat->setTransforms(matrixSet, state); - - // Setup HW skinning transforms if applicable - if (glowMat->usesHardwareSkinning()) - { - glowMat->setNodeTransforms(passRI->mNodeTransforms, passRI->mNodeTransformCount); - } - - //push along any overriden fields that are instance-specific as well - if (passRI->mCustomShaderData.size() > 0) - { - mat->setCustomShaderData(passRI->mCustomShaderData); - } - - glowMat->setSceneInfo(state, sgData); - glowMat->setBuffers(passRI->vertBuff, passRI->primBuff); - - if ( passRI->prim ) - GFX->drawPrimitive( *passRI->prim ); - else - GFX->drawPrimitive( passRI->primBuffIndex ); - } - matListEnd = a; - setupSGData( ri, sgData ); - } - - // force increment if none happened, otherwise go to end of batch - j = ( j == matListEnd ) ? j+1 : matListEnd; - } - - // Finish up. - if ( isRenderingToTarget ) - _onPostRender(); - - // Make sure the effect is gonna render. - getGlowEffect()->setSkip( false ); -} diff --git a/Engine/source/renderInstance/renderGlowMgr.h b/Engine/source/renderInstance/renderGlowMgr.h deleted file mode 100644 index 27ff7a03f..000000000 --- a/Engine/source/renderInstance/renderGlowMgr.h +++ /dev/null @@ -1,90 +0,0 @@ -//----------------------------------------------------------------------------- -// Copyright (c) 2012 GarageGames, LLC -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -//----------------------------------------------------------------------------- - -#ifndef _RENDERGLOWMGR_H_ -#define _RENDERGLOWMGR_H_ - -#ifndef _TEXTARGETBIN_MGR_H_ -#include "renderInstance/renderTexTargetBinManager.h" -#endif -#include - - -class PostEffect; - - -/// -class RenderGlowMgr : public RenderTexTargetBinManager -{ - typedef RenderTexTargetBinManager Parent; - -public: - - RenderGlowMgr(); - virtual ~RenderGlowMgr(); - - /// Returns the glow post effect. - PostEffect* getGlowEffect(); - - /// Returns true if the glow post effect is - /// enabled and the glow buffer should be updated. - bool isGlowEnabled(); - - // RenderBinManager - void addElement( RenderInst *inst ) override; - void render( SceneRenderState *state ) override; - - // ConsoleObject - DECLARE_CONOBJECT( RenderGlowMgr ); - -protected: - - class GlowMaterialHook : public MatInstanceHook - { - public: - - GlowMaterialHook( BaseMatInstance *matInst ); - virtual ~GlowMaterialHook(); - - virtual BaseMatInstance *getMatInstance() { return mGlowMatInst; } - - const MatInstanceHookType& getType() const override { return Type; } - - /// Our material hook type. - static const MatInstanceHookType Type; - - protected: - - static void _overrideFeatures( ProcessedMaterial *mat, - U32 stageNum, - MaterialFeatureData &fd, - const FeatureSet &features ); - - BaseMatInstance *mGlowMatInst; - }; - - SimObjectPtr mGlowEffect; - RenderParticleMgr *mParticleRenderMgr; -}; - - -#endif // _RENDERGLOWMGR_H_ diff --git a/Engine/source/renderInstance/renderPassManager.cpp b/Engine/source/renderInstance/renderPassManager.cpp index 5c88041c7..fdcf76936 100644 --- a/Engine/source/renderInstance/renderPassManager.cpp +++ b/Engine/source/renderInstance/renderPassManager.cpp @@ -35,7 +35,6 @@ #include "renderInstance/renderObjectMgr.h" #include "renderInstance/renderMeshMgr.h" #include "renderInstance/renderTranslucentMgr.h" -#include "renderInstance/renderGlowMgr.h" #include "renderInstance/renderTerrainMgr.h" #include "core/util/safeDelete.h" #include "math/util/matrixSet.h" diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp index 33c3f520d..1fd6276d3 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.cpp @@ -2519,23 +2519,22 @@ void AlphaTestGLSL::processPix( Vector &componentList, //**************************************************************************** // GlowMask //**************************************************************************** - void GlowMaskGLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - output = NULL; - - // Get the output color... and make it black to mask out - // glow passes rendered before us. - // - // The shader compiler will optimize out all the other - // code above that doesn't contribute to the alpha mask. - Var *color = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::DefaultTarget)); - if ( color ) - output = new GenOp( " @.rgb = vec3(0);\r\n", color ); + //determine output target + ShaderFeature::OutputTarget inTarg, outTarg; + inTarg = outTarg = ShaderFeature::DefaultTarget; + if (fd.features[MFT_isDeferred]) + { + inTarg = ShaderFeature::RenderTarget1; + outTarg = ShaderFeature::RenderTarget3; + } + Var* inCol = (Var*)LangElement::find(getOutputTargetVarName(inTarg)); + Var* outCol = (Var*)LangElement::find(getOutputTargetVarName(outTarg)); + output = new GenOp(" @.rgb += @.rgb*10;\r\n", outCol, inCol); } - //**************************************************************************** // RenderTargetZero //**************************************************************************** diff --git a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h index 5db741ded..e7bcea21f 100644 --- a/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h +++ b/Engine/source/shaderGen/GLSL/shaderFeatureGLSL.h @@ -552,11 +552,9 @@ public: class GlowMaskGLSL : public ShaderFeatureGLSL { public: + void processPix( Vector &componentList, const MaterialFeatureData &fd ) override; - - Material::BlendOp getBlendOp() override { return Material::None; } - String getName() override { return "Glow Mask"; diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp index 0f1bf06fd..8e7542052 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.cpp @@ -2592,23 +2592,22 @@ void AlphaTestHLSL::processPix( Vector &componentList, //**************************************************************************** // GlowMask //**************************************************************************** - void GlowMaskHLSL::processPix( Vector &componentList, const MaterialFeatureData &fd ) { - output = NULL; - - // Get the output color... and make it black to mask out - // glow passes rendered before us. - // - // The shader compiler will optimize out all the other - // code above that doesn't contribute to the alpha mask. - Var *color = (Var*)LangElement::find(getOutputTargetVarName(ShaderFeature::DefaultTarget)); - if ( color ) - output = new GenOp( " @.rgb = 0;\r\n", color ); + //determine output target + ShaderFeature::OutputTarget inTarg, outTarg; + inTarg = outTarg = ShaderFeature::DefaultTarget; + if (fd.features[MFT_isDeferred]) + { + inTarg = ShaderFeature::RenderTarget1; + outTarg = ShaderFeature::RenderTarget3; + } + Var* inCol = (Var*)LangElement::find(getOutputTargetVarName(inTarg)); + Var* outCol = (Var*)LangElement::find(getOutputTargetVarName(outTarg)); + output = new GenOp(" @.rgb += @.rgb*10;\r\n", outCol, inCol); } - //**************************************************************************** // RenderTargetZero //**************************************************************************** diff --git a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h index 49c32e66e..627f1019a 100644 --- a/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h +++ b/Engine/source/shaderGen/HLSL/shaderFeatureHLSL.h @@ -555,11 +555,9 @@ public: class GlowMaskHLSL : public ShaderFeatureHLSL { public: + void processPix( Vector &componentList, const MaterialFeatureData &fd ) override; - - Material::BlendOp getBlendOp() override { return Material::None; } - String getName() override { return "Glow Mask"; diff --git a/Templates/BaseGame/game/core/postFX/scripts/DepthOfField/DepthOfFieldPostFX.tscript b/Templates/BaseGame/game/core/postFX/scripts/DepthOfField/DepthOfFieldPostFX.tscript index 74feb289d..d77dc2511 100644 --- a/Templates/BaseGame/game/core/postFX/scripts/DepthOfField/DepthOfFieldPostFX.tscript +++ b/Templates/BaseGame/game/core/postFX/scripts/DepthOfField/DepthOfFieldPostFX.tscript @@ -407,7 +407,7 @@ singleton PostEffect( DepthOfFieldPostFX ) enabled = false; renderTime = "PFXAfterBin"; - renderBin = "GlowBin"; + renderBin = "FogBin"; renderPriority = 0.1; shader = PFX_DOFDownSampleShader; diff --git a/Templates/BaseGame/game/core/postFX/scripts/Glow/GlowPostFX.tscript b/Templates/BaseGame/game/core/postFX/scripts/Glow/GlowPostFX.tscript index 8f2c5a72d..0e3ce4457 100644 --- a/Templates/BaseGame/game/core/postFX/scripts/Glow/GlowPostFX.tscript +++ b/Templates/BaseGame/game/core/postFX/scripts/Glow/GlowPostFX.tscript @@ -20,29 +20,6 @@ // IN THE SOFTWARE. //----------------------------------------------------------------------------- - -singleton ShaderData( PFX_GlowBlurVertShader ) -{ - DXVertexShaderFile = "./glowBlurV.hlsl"; - DXPixelShaderFile = "./glowBlurP.hlsl"; - - OGLVertexShaderFile = "./glowBlurV.glsl"; - OGLPixelShaderFile = "./glowBlurP.glsl"; - - defines = "BLUR_DIR=float2(0.0,1.0)"; - - samplerNames[0] = "$diffuseMap"; - - pixVersion = 2.0; -}; - - -singleton ShaderData( PFX_GlowBlurHorzShader : PFX_GlowBlurVertShader ) -{ - defines = "BLUR_DIR=float2(1.0,0.0)"; -}; - - singleton GFXStateBlockData( PFX_GlowCombineStateBlock : PFX_DefaultStateBlock ) { // Use alpha test to save some fillrate @@ -59,54 +36,6 @@ singleton GFXStateBlockData( PFX_GlowCombineStateBlock : PFX_DefaultStateBlock ) blendDest = GFXBlendOne; }; - -singleton PostEffect( GlowPostFX ) -{ - // Do not allow the glow effect to work in reflection - // passes by default so we don't do the extra drawing. - allowReflectPass = false; - - renderTime = "PFXAfterBin"; - renderBin = "GlowBin"; - renderPriority = 1; - - // First we down sample the glow buffer. - shader = PFX_PassthruShader; - stateBlock = PFX_DefaultStateBlock; - texture[0] = "#glowbuffer"; - target = "$outTex"; - targetScale = "0.5 0.5"; - - enabled = true; - - // Blur vertically - new PostEffect() - { - shader = PFX_GlowBlurVertShader; - stateBlock = PFX_DefaultStateBlock; - texture[0] = "$inTex"; - target = "$outTex"; - }; - - // Blur horizontally - new PostEffect() - { - shader = PFX_GlowBlurHorzShader; - stateBlock = PFX_DefaultStateBlock; - texture[0] = "$inTex"; - target = "$outTex"; - }; - - // Upsample and combine with the back buffer. - new PostEffect() - { - shader = PFX_PassthruShader; - stateBlock = PFX_GlowCombineStateBlock; - texture[0] = "$inTex"; - target = "$backBuffer"; - }; -}; - singleton ShaderData( PFX_VolFogGlowBlurVertShader ) { DXVertexShaderFile = "./glowBlurV.hlsl"; diff --git a/Templates/BaseGame/game/core/postFX/scripts/Turbulence/TurbulencePostFX.tscript b/Templates/BaseGame/game/core/postFX/scripts/Turbulence/TurbulencePostFX.tscript index 2d30550a5..781333dce 100644 --- a/Templates/BaseGame/game/core/postFX/scripts/Turbulence/TurbulencePostFX.tscript +++ b/Templates/BaseGame/game/core/postFX/scripts/Turbulence/TurbulencePostFX.tscript @@ -48,7 +48,7 @@ singleton PostEffect( TurbulencePostFX ) allowReflectPass = true; renderTime = "PFXAfterDiffuse"; - renderBin = "GlowBin"; + renderBin = "FogBin"; renderPriority = 0.5; // Render after the glows themselves shader = PFX_TurbulenceShader; diff --git a/Templates/BaseGame/game/core/rendering/scripts/renderManager.tscript b/Templates/BaseGame/game/core/rendering/scripts/renderManager.tscript index 34a48f64a..e3461b199 100644 --- a/Templates/BaseGame/game/core/rendering/scripts/renderManager.tscript +++ b/Templates/BaseGame/game/core/rendering/scripts/renderManager.tscript @@ -79,10 +79,7 @@ function initRenderManager() DiffuseRenderPassManager.addManager( new RenderTranslucentMgr(TranslucentBin){ renderOrder = 1.4; processAddOrder = 1.4; } ); DiffuseRenderPassManager.addManager(new RenderObjectMgr(FogBin){ bintype = "ObjectVolumetricFog"; renderOrder = 1.45; processAddOrder = 1.45; } ); - - // Note that the GlowPostFx is triggered after this bin. - DiffuseRenderPassManager.addManager( new RenderGlowMgr(GlowBin) { renderOrder = 1.5; processAddOrder = 1.5; } ); - + // We render any editor stuff from this bin. Note that the HDR is // completed before this bin to keep editor elements from tone mapping. DiffuseRenderPassManager.addManager( new RenderObjectMgr(EditorBin) { bintype = "Editor"; renderOrder = 1.6; processAddOrder = 1.6; } ); From 8140ed9b64ae822811cb821c0bd1e48ea5f94389 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 7 Jun 2024 20:13:56 +0100 Subject: [PATCH 38/65] clear clear lines, and dont try to print lines when there is no file. --- Engine/source/console/torquescript/CMDgram.y | 15 +++++++++------ Engine/source/console/torquescript/CMDscan.cpp | 1 + Engine/source/console/torquescript/CMDscan.l | 1 + Engine/source/console/torquescript/cmdgram.cpp | 15 +++++++++------ 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/Engine/source/console/torquescript/CMDgram.y b/Engine/source/console/torquescript/CMDgram.y index 1e118ae1b..8aea2b470 100644 --- a/Engine/source/console/torquescript/CMDgram.y +++ b/Engine/source/console/torquescript/CMDgram.y @@ -645,15 +645,18 @@ yyreport_syntax_error (const yypcontext_t *ctx) output += String::ToString("%s %s", i == 0 ? ": expected" : "or", yysymbol_name(expected[i])); } - if (lines.size() > 0) + if(CMDGetCurrentFile()) { - output += "\n"; - for (int i = 0; i < lines.size(); i++) + if (lines.size() > 0) { - int line = lines.size() - i; - output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; + output += "\n"; + for (int i = 0; i < lines.size(); i++) + { + int line = lines.size() - i; + output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; + } + output += String::ToString("%5s | %*s", "", loc->first_column, "^"); } - output += String::ToString("%5s | %*s", "", loc->first_column, "^"); } yyerror(output.c_str()); diff --git a/Engine/source/console/torquescript/CMDscan.cpp b/Engine/source/console/torquescript/CMDscan.cpp index 5ca6ca7d9..36baa2099 100644 --- a/Engine/source/console/torquescript/CMDscan.cpp +++ b/Engine/source/console/torquescript/CMDscan.cpp @@ -2742,6 +2742,7 @@ void CMDSetScanBuffer(const char *sb, const char *fn) fileName = fn; scanIndex = 0; yylineno = 1; + lines.clear(); } int CMDgetc() diff --git a/Engine/source/console/torquescript/CMDscan.l b/Engine/source/console/torquescript/CMDscan.l index d6a03186a..751f92936 100644 --- a/Engine/source/console/torquescript/CMDscan.l +++ b/Engine/source/console/torquescript/CMDscan.l @@ -292,6 +292,7 @@ void CMDSetScanBuffer(const char *sb, const char *fn) fileName = fn; scanIndex = 0; yylineno = 1; + lines.clear(); } int CMDgetc() diff --git a/Engine/source/console/torquescript/cmdgram.cpp b/Engine/source/console/torquescript/cmdgram.cpp index 8b7d536c6..52161e92b 100644 --- a/Engine/source/console/torquescript/cmdgram.cpp +++ b/Engine/source/console/torquescript/cmdgram.cpp @@ -3352,15 +3352,18 @@ yyreport_syntax_error (const yypcontext_t *ctx) output += String::ToString("%s %s", i == 0 ? ": expected" : "or", yysymbol_name(expected[i])); } - if (lines.size() > 0) + if(CMDGetCurrentFile()) { - output += "\n"; - for (int i = 0; i < lines.size(); i++) + if (lines.size() > 0) { - int line = lines.size() - i; - output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; + output += "\n"; + for (int i = 0; i < lines.size(); i++) + { + int line = lines.size() - i; + output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; + } + output += String::ToString("%5s | %*s", "", loc->first_column, "^"); } - output += String::ToString("%5s | %*s", "", loc->first_column, "^"); } yyerror(output.c_str()); From 1c43959c07b9bc7d4afee527a6d11441f02a3110 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 7 Jun 2024 20:44:44 +0100 Subject: [PATCH 39/65] multiline eval support --- Engine/source/console/torquescript/CMDgram.y | 15 +- .../source/console/torquescript/CMDscan.cpp | 185 +++++++++--------- Engine/source/console/torquescript/CMDscan.l | 3 +- .../source/console/torquescript/cmdgram.cpp | 15 +- 4 files changed, 107 insertions(+), 111 deletions(-) diff --git a/Engine/source/console/torquescript/CMDgram.y b/Engine/source/console/torquescript/CMDgram.y index 8aea2b470..1e118ae1b 100644 --- a/Engine/source/console/torquescript/CMDgram.y +++ b/Engine/source/console/torquescript/CMDgram.y @@ -645,18 +645,15 @@ yyreport_syntax_error (const yypcontext_t *ctx) output += String::ToString("%s %s", i == 0 ? ": expected" : "or", yysymbol_name(expected[i])); } - if(CMDGetCurrentFile()) + if (lines.size() > 0) { - if (lines.size() > 0) + output += "\n"; + for (int i = 0; i < lines.size(); i++) { - output += "\n"; - for (int i = 0; i < lines.size(); i++) - { - int line = lines.size() - i; - output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; - } - output += String::ToString("%5s | %*s", "", loc->first_column, "^"); + int line = lines.size() - i; + output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; } + output += String::ToString("%5s | %*s", "", loc->first_column, "^"); } yyerror(output.c_str()); diff --git a/Engine/source/console/torquescript/CMDscan.cpp b/Engine/source/console/torquescript/CMDscan.cpp index 36baa2099..4497cb961 100644 --- a/Engine/source/console/torquescript/CMDscan.cpp +++ b/Engine/source/console/torquescript/CMDscan.cpp @@ -1301,7 +1301,8 @@ case 5: /* rule 5 can match eol */ YY_RULE_SETUP #line 120 "CMDscan.l" -{ yycolumn = 1; +{ + yycolumn = 1; lines.push_back(String::ToString("%s", yytext+1)); if (lines.size() > Con::getIntVariable("$scriptErrorLineCount", 10)) lines.erase(lines.begin()); @@ -1311,162 +1312,162 @@ YY_RULE_SETUP YY_BREAK case 6: YY_RULE_SETUP -#line 127 "CMDscan.l" +#line 128 "CMDscan.l" { return(Sc_ScanString(STRATOM)); } YY_BREAK case 7: YY_RULE_SETUP -#line 128 "CMDscan.l" +#line 129 "CMDscan.l" { return(Sc_ScanString(TAGATOM)); } YY_BREAK case 8: YY_RULE_SETUP -#line 129 "CMDscan.l" +#line 130 "CMDscan.l" { CMDlval.i = MakeToken< int >( opEQ, yylineno ); return opEQ; } YY_BREAK case 9: YY_RULE_SETUP -#line 130 "CMDscan.l" +#line 131 "CMDscan.l" { CMDlval.i = MakeToken< int >( opNE, yylineno ); return opNE; } YY_BREAK case 10: YY_RULE_SETUP -#line 131 "CMDscan.l" +#line 132 "CMDscan.l" { CMDlval.i = MakeToken< int >( opGE, yylineno ); return opGE; } YY_BREAK case 11: YY_RULE_SETUP -#line 132 "CMDscan.l" +#line 133 "CMDscan.l" { CMDlval.i = MakeToken< int >( opLE, yylineno ); return opLE; } YY_BREAK case 12: YY_RULE_SETUP -#line 133 "CMDscan.l" +#line 134 "CMDscan.l" { CMDlval.i = MakeToken< int >( opAND, yylineno ); return opAND; } YY_BREAK case 13: YY_RULE_SETUP -#line 134 "CMDscan.l" +#line 135 "CMDscan.l" { CMDlval.i = MakeToken< int >( opOR, yylineno ); return opOR; } YY_BREAK case 14: YY_RULE_SETUP -#line 135 "CMDscan.l" +#line 136 "CMDscan.l" { CMDlval.i = MakeToken< int >( opCOLONCOLON, yylineno ); return opCOLONCOLON; } YY_BREAK case 15: YY_RULE_SETUP -#line 136 "CMDscan.l" +#line 137 "CMDscan.l" { CMDlval.i = MakeToken< int >( opMINUSMINUS, yylineno ); return opMINUSMINUS; } YY_BREAK case 16: YY_RULE_SETUP -#line 137 "CMDscan.l" +#line 138 "CMDscan.l" { CMDlval.i = MakeToken< int >( opPLUSPLUS, yylineno ); return opPLUSPLUS; } YY_BREAK case 17: YY_RULE_SETUP -#line 138 "CMDscan.l" +#line 139 "CMDscan.l" { CMDlval.i = MakeToken< int >( opSTREQ, yylineno ); return opSTREQ; } YY_BREAK case 18: YY_RULE_SETUP -#line 139 "CMDscan.l" +#line 140 "CMDscan.l" { CMDlval.i = MakeToken< int >( opSTRNE, yylineno ); return opSTRNE; } YY_BREAK case 19: YY_RULE_SETUP -#line 140 "CMDscan.l" +#line 141 "CMDscan.l" { CMDlval.i = MakeToken< int >( opSHL, yylineno ); return opSHL; } YY_BREAK case 20: YY_RULE_SETUP -#line 141 "CMDscan.l" +#line 142 "CMDscan.l" { CMDlval.i = MakeToken< int >( opSHR, yylineno ); return opSHR; } YY_BREAK case 21: YY_RULE_SETUP -#line 142 "CMDscan.l" +#line 143 "CMDscan.l" { CMDlval.i = MakeToken< int >( opPLASN, yylineno ); return opPLASN; } YY_BREAK case 22: YY_RULE_SETUP -#line 143 "CMDscan.l" +#line 144 "CMDscan.l" { CMDlval.i = MakeToken< int >( opMIASN, yylineno ); return opMIASN; } YY_BREAK case 23: YY_RULE_SETUP -#line 144 "CMDscan.l" +#line 145 "CMDscan.l" { CMDlval.i = MakeToken< int >( opMLASN, yylineno ); return opMLASN; } YY_BREAK case 24: YY_RULE_SETUP -#line 145 "CMDscan.l" +#line 146 "CMDscan.l" { CMDlval.i = MakeToken< int >( opDVASN, yylineno ); return opDVASN; } YY_BREAK case 25: YY_RULE_SETUP -#line 146 "CMDscan.l" +#line 147 "CMDscan.l" { CMDlval.i = MakeToken< int >( opMODASN, yylineno ); return opMODASN; } YY_BREAK case 26: YY_RULE_SETUP -#line 147 "CMDscan.l" +#line 148 "CMDscan.l" { CMDlval.i = MakeToken< int >( opANDASN, yylineno ); return opANDASN; } YY_BREAK case 27: YY_RULE_SETUP -#line 148 "CMDscan.l" +#line 149 "CMDscan.l" { CMDlval.i = MakeToken< int >( opXORASN, yylineno ); return opXORASN; } YY_BREAK case 28: YY_RULE_SETUP -#line 149 "CMDscan.l" +#line 150 "CMDscan.l" { CMDlval.i = MakeToken< int >( opORASN, yylineno ); return opORASN; } YY_BREAK case 29: YY_RULE_SETUP -#line 150 "CMDscan.l" +#line 151 "CMDscan.l" { CMDlval.i = MakeToken< int >( opSLASN, yylineno ); return opSLASN; } YY_BREAK case 30: YY_RULE_SETUP -#line 151 "CMDscan.l" +#line 152 "CMDscan.l" { CMDlval.i = MakeToken< int >( opSRASN, yylineno ); return opSRASN; } YY_BREAK case 31: YY_RULE_SETUP -#line 152 "CMDscan.l" +#line 153 "CMDscan.l" { CMDlval.i = MakeToken< int >( opINTNAME, yylineno ); return opINTNAME; } YY_BREAK case 32: YY_RULE_SETUP -#line 153 "CMDscan.l" +#line 154 "CMDscan.l" { CMDlval.i = MakeToken< int >( opINTNAMER, yylineno ); return opINTNAMER; } YY_BREAK case 33: YY_RULE_SETUP -#line 154 "CMDscan.l" +#line 155 "CMDscan.l" { CMDlval.i = MakeToken< int >( '\n', yylineno ); return '@'; } YY_BREAK case 34: YY_RULE_SETUP -#line 155 "CMDscan.l" +#line 156 "CMDscan.l" { CMDlval.i = MakeToken< int >( '\t', yylineno ); return '@'; } YY_BREAK case 35: YY_RULE_SETUP -#line 156 "CMDscan.l" +#line 157 "CMDscan.l" { CMDlval.i = MakeToken< int >( ' ', yylineno ); return '@'; } YY_BREAK case 36: YY_RULE_SETUP -#line 157 "CMDscan.l" +#line 158 "CMDscan.l" { CMDlval.i = MakeToken< int >( 0, yylineno ); return '@'; } YY_BREAK case 37: YY_RULE_SETUP -#line 158 "CMDscan.l" +#line 159 "CMDscan.l" { /* this comment stops syntax highlighting from getting messed up when editing the lexer in TextPad */ int c = 0, l; for ( ; ; ) @@ -1488,222 +1489,222 @@ YY_RULE_SETUP } YY_BREAK case 38: -#line 178 "CMDscan.l" -case 39: #line 179 "CMDscan.l" -case 40: +case 39: #line 180 "CMDscan.l" -case 41: +case 40: #line 181 "CMDscan.l" -case 42: +case 41: #line 182 "CMDscan.l" -case 43: +case 42: #line 183 "CMDscan.l" -case 44: +case 43: #line 184 "CMDscan.l" -case 45: +case 44: #line 185 "CMDscan.l" -case 46: +case 45: #line 186 "CMDscan.l" -case 47: +case 46: #line 187 "CMDscan.l" -case 48: +case 47: #line 188 "CMDscan.l" -case 49: +case 48: #line 189 "CMDscan.l" -case 50: +case 49: #line 190 "CMDscan.l" -case 51: +case 50: #line 191 "CMDscan.l" -case 52: +case 51: #line 192 "CMDscan.l" -case 53: +case 52: #line 193 "CMDscan.l" -case 54: +case 53: #line 194 "CMDscan.l" -case 55: +case 54: #line 195 "CMDscan.l" -case 56: +case 55: #line 196 "CMDscan.l" -case 57: +case 56: #line 197 "CMDscan.l" -case 58: +case 57: #line 198 "CMDscan.l" -case 59: +case 58: #line 199 "CMDscan.l" -case 60: +case 59: #line 200 "CMDscan.l" +case 60: +#line 201 "CMDscan.l" case 61: YY_RULE_SETUP -#line 200 "CMDscan.l" +#line 201 "CMDscan.l" { CMDlval.i = MakeToken< int >( CMDtext[ 0 ], yylineno ); return CMDtext[ 0 ]; } YY_BREAK case 62: YY_RULE_SETUP -#line 201 "CMDscan.l" +#line 202 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwIN, yylineno ); return(rwIN); } YY_BREAK case 63: YY_RULE_SETUP -#line 202 "CMDscan.l" +#line 203 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwCASEOR, yylineno ); return(rwCASEOR); } YY_BREAK case 64: YY_RULE_SETUP -#line 203 "CMDscan.l" +#line 204 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwBREAK, yylineno ); return(rwBREAK); } YY_BREAK case 65: YY_RULE_SETUP -#line 204 "CMDscan.l" +#line 205 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwRETURN, yylineno ); return(rwRETURN); } YY_BREAK case 66: YY_RULE_SETUP -#line 205 "CMDscan.l" +#line 206 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwELSE, yylineno ); return(rwELSE); } YY_BREAK case 67: YY_RULE_SETUP -#line 206 "CMDscan.l" +#line 207 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwASSERT, yylineno ); return(rwASSERT); } YY_BREAK case 68: YY_RULE_SETUP -#line 207 "CMDscan.l" +#line 208 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwWHILE, yylineno ); return(rwWHILE); } YY_BREAK case 69: YY_RULE_SETUP -#line 208 "CMDscan.l" +#line 209 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwDO, yylineno ); return(rwDO); } YY_BREAK case 70: YY_RULE_SETUP -#line 209 "CMDscan.l" +#line 210 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwIF, yylineno ); return(rwIF); } YY_BREAK case 71: YY_RULE_SETUP -#line 210 "CMDscan.l" +#line 211 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwFOREACHSTR, yylineno ); return(rwFOREACHSTR); } YY_BREAK case 72: YY_RULE_SETUP -#line 211 "CMDscan.l" +#line 212 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwFOREACH, yylineno ); return(rwFOREACH); } YY_BREAK case 73: YY_RULE_SETUP -#line 212 "CMDscan.l" +#line 213 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwFOR, yylineno ); return(rwFOR); } YY_BREAK case 74: YY_RULE_SETUP -#line 213 "CMDscan.l" +#line 214 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwCONTINUE, yylineno ); return(rwCONTINUE); } YY_BREAK case 75: YY_RULE_SETUP -#line 214 "CMDscan.l" +#line 215 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwDEFINE, yylineno ); return(rwDEFINE); } YY_BREAK case 76: YY_RULE_SETUP -#line 215 "CMDscan.l" +#line 216 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwDECLARE, yylineno ); return(rwDECLARE); } YY_BREAK case 77: YY_RULE_SETUP -#line 216 "CMDscan.l" +#line 217 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwDECLARESINGLETON, yylineno ); return(rwDECLARESINGLETON); } YY_BREAK case 78: YY_RULE_SETUP -#line 217 "CMDscan.l" +#line 218 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwDATABLOCK, yylineno ); return(rwDATABLOCK); } YY_BREAK case 79: YY_RULE_SETUP -#line 218 "CMDscan.l" +#line 219 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwCASE, yylineno ); return(rwCASE); } YY_BREAK case 80: YY_RULE_SETUP -#line 219 "CMDscan.l" +#line 220 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwSWITCHSTR, yylineno ); return(rwSWITCHSTR); } YY_BREAK case 81: YY_RULE_SETUP -#line 220 "CMDscan.l" +#line 221 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwSWITCH, yylineno ); return(rwSWITCH); } YY_BREAK case 82: YY_RULE_SETUP -#line 221 "CMDscan.l" +#line 222 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwDEFAULT, yylineno ); return(rwDEFAULT); } YY_BREAK case 83: YY_RULE_SETUP -#line 222 "CMDscan.l" +#line 223 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwPACKAGE, yylineno ); return(rwPACKAGE); } YY_BREAK case 84: YY_RULE_SETUP -#line 223 "CMDscan.l" +#line 224 "CMDscan.l" { CMDlval.i = MakeToken< int >( rwNAMESPACE, yylineno ); return(rwNAMESPACE); } YY_BREAK case 85: YY_RULE_SETUP -#line 224 "CMDscan.l" +#line 225 "CMDscan.l" { CMDlval.i = MakeToken< int >( 1, yylineno ); return INTCONST; } YY_BREAK case 86: YY_RULE_SETUP -#line 225 "CMDscan.l" +#line 226 "CMDscan.l" { CMDlval.i = MakeToken< int >( 0, yylineno ); return INTCONST; } YY_BREAK case 87: YY_RULE_SETUP -#line 226 "CMDscan.l" +#line 227 "CMDscan.l" { return(Sc_ScanVar()); } YY_BREAK case 88: YY_RULE_SETUP -#line 228 "CMDscan.l" +#line 229 "CMDscan.l" { return Sc_ScanIdent(); } YY_BREAK case 89: YY_RULE_SETUP -#line 229 "CMDscan.l" +#line 230 "CMDscan.l" return(Sc_ScanHex()); YY_BREAK case 90: YY_RULE_SETUP -#line 230 "CMDscan.l" +#line 231 "CMDscan.l" { CMDtext[CMDleng] = 0; CMDlval.i = MakeToken< int >( dAtoi(CMDtext), yylineno ); return INTCONST; } YY_BREAK case 91: YY_RULE_SETUP -#line 231 "CMDscan.l" +#line 232 "CMDscan.l" return Sc_ScanNum(); YY_BREAK case 92: YY_RULE_SETUP -#line 232 "CMDscan.l" +#line 233 "CMDscan.l" return(ILLEGAL_TOKEN); YY_BREAK case 93: YY_RULE_SETUP -#line 233 "CMDscan.l" +#line 234 "CMDscan.l" return(ILLEGAL_TOKEN); YY_BREAK case 94: YY_RULE_SETUP -#line 234 "CMDscan.l" +#line 235 "CMDscan.l" ECHO; YY_BREAK -#line 1706 "CMDscan.cpp" +#line 1707 "CMDscan.cpp" case YY_STATE_EOF(INITIAL): yyterminate(); @@ -2679,7 +2680,7 @@ void yyfree (void * ptr ) #define YYTABLES_NAME "yytables" -#line 234 "CMDscan.l" +#line 235 "CMDscan.l" static const char *scanBuffer; diff --git a/Engine/source/console/torquescript/CMDscan.l b/Engine/source/console/torquescript/CMDscan.l index 751f92936..aa3a72733 100644 --- a/Engine/source/console/torquescript/CMDscan.l +++ b/Engine/source/console/torquescript/CMDscan.l @@ -116,7 +116,8 @@ HEXDIGIT [a-fA-F0-9] ("///"([^/\n\r][^\n\r]*)?[\n\r]+)+ { return(Sc_ScanDocBlock()); } "//"[^\n\r]* ; [\r] ; -\n.* { yycolumn = 1; +\n.* { + yycolumn = 1; lines.push_back(String::ToString("%s", yytext+1)); if (lines.size() > Con::getIntVariable("$scriptErrorLineCount", 10)) lines.erase(lines.begin()); diff --git a/Engine/source/console/torquescript/cmdgram.cpp b/Engine/source/console/torquescript/cmdgram.cpp index 52161e92b..8b7d536c6 100644 --- a/Engine/source/console/torquescript/cmdgram.cpp +++ b/Engine/source/console/torquescript/cmdgram.cpp @@ -3352,18 +3352,15 @@ yyreport_syntax_error (const yypcontext_t *ctx) output += String::ToString("%s %s", i == 0 ? ": expected" : "or", yysymbol_name(expected[i])); } - if(CMDGetCurrentFile()) + if (lines.size() > 0) { - if (lines.size() > 0) + output += "\n"; + for (int i = 0; i < lines.size(); i++) { - output += "\n"; - for (int i = 0; i < lines.size(); i++) - { - int line = lines.size() - i; - output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; - } - output += String::ToString("%5s | %*s", "", loc->first_column, "^"); + int line = lines.size() - i; + output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; } + output += String::ToString("%5s | %*s", "", loc->first_column, "^"); } yyerror(output.c_str()); From 5c701fe09e0b1b4375a2f13201398aeb2918ecf3 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 10 Jun 2024 13:15:27 -0500 Subject: [PATCH 40/65] file write clarifications handle clang complaints about hidden virtuals in the context of file writes that have thier own routes and I/O needs. --- Engine/source/core/fileObject.cpp | 20 ++++++++++---------- Engine/source/core/fileObject.h | 4 +++- Engine/source/module/moduleDefinition.h | 2 ++ Engine/source/persistence/taml/taml.h | 1 + Engine/source/util/settings.h | 1 + 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/Engine/source/core/fileObject.cpp b/Engine/source/core/fileObject.cpp index 1305074c9..c5efbd654 100644 --- a/Engine/source/core/fileObject.cpp +++ b/Engine/source/core/fileObject.cpp @@ -85,18 +85,18 @@ FileObject::FileObject() mFileBuffer = NULL; mBufferSize = 0; mCurPos = 0; - stream = NULL; + mStream = NULL; } FileObject::~FileObject() { SAFE_DELETE_ARRAY(mFileBuffer); - SAFE_DELETE(stream); + SAFE_DELETE(mStream); } void FileObject::close() { - SAFE_DELETE(stream); + SAFE_DELETE(mStream); SAFE_DELETE_ARRAY(mFileBuffer); mFileBuffer = NULL; mBufferSize = mCurPos = 0; @@ -112,10 +112,10 @@ bool FileObject::openForWrite(const char *fileName, const bool append) if( !buffer[ 0 ] ) return false; - if((stream = FileStream::createAndOpen( fileName, append ? Torque::FS::File::WriteAppend : Torque::FS::File::Write )) == NULL) + if((mStream = FileStream::createAndOpen( fileName, append ? Torque::FS::File::WriteAppend : Torque::FS::File::Write )) == NULL) return false; - stream->setPosition( stream->getStreamSize() ); + mStream->setPosition(mStream->getStreamSize() ); return( true ); } @@ -225,17 +225,17 @@ void FileObject::peekLine( S32 peekLineOffset, U8* line, S32 length ) void FileObject::writeLine(const U8 *line) { - stream->write(dStrlen((const char *) line), line); - stream->write(2, "\r\n"); + mStream->write(dStrlen((const char *) line), line); + mStream->write(2, "\r\n"); } void FileObject::writeObject( SimObject* object, const U8* objectPrepend ) { if( objectPrepend == NULL ) - stream->write(2, "\r\n"); + mStream->write(2, "\r\n"); else - stream->write(dStrlen((const char *) objectPrepend), objectPrepend ); - object->write( *stream, 0 ); + mStream->write(dStrlen((const char *) objectPrepend), objectPrepend ); + object->write( *mStream, 0 ); } DefineEngineMethod( FileObject, openForRead, bool, ( const char* filename ),, diff --git a/Engine/source/core/fileObject.h b/Engine/source/core/fileObject.h index 64559e7db..ae2f20bd2 100644 --- a/Engine/source/core/fileObject.h +++ b/Engine/source/core/fileObject.h @@ -36,7 +36,7 @@ class FileObject : public SimObject U8 *mFileBuffer; U32 mBufferSize; U32 mCurPos; - FileStream *stream; + FileStream *mStream; public: FileObject(); ~FileObject(); @@ -50,6 +50,8 @@ public: bool isEOF(); void writeLine(const U8 *line); void close(); + + bool writeObject(Stream* stream) override { Con::errorf("FileObject:Can Not write a file interface object to a file"); return false; }; //Don't allow writing FileObject *to* files void writeObject( SimObject* object, const U8* objectPrepend = NULL ); DECLARE_CONOBJECT(FileObject); diff --git a/Engine/source/module/moduleDefinition.h b/Engine/source/module/moduleDefinition.h index f26c2ae2c..0fb12f7b0 100644 --- a/Engine/source/module/moduleDefinition.h +++ b/Engine/source/module/moduleDefinition.h @@ -194,6 +194,8 @@ public: inline void setModuleLocked( const bool status ) { mLocked = status; } inline bool getModuleLocked( void ) const { return mLocked; } inline ModuleManager* getModuleManager( void ) const { return mpModuleManager; } + + using Parent::save; bool save( void ); /// Declare Console Object. diff --git a/Engine/source/persistence/taml/taml.h b/Engine/source/persistence/taml/taml.h index 70290684f..dd093e164 100644 --- a/Engine/source/persistence/taml/taml.h +++ b/Engine/source/persistence/taml/taml.h @@ -111,6 +111,7 @@ private: void compileCustomState( TamlWriteNode* pTamlWriteNode ); void compileCustomNodeState( TamlCustomNode* pCustomNode ); + void write(Stream& stream, U32 tabStop, U32 flags = 0) override { Con::errorf("Taml:Can Not write a file interface object to a file"); }; //Don't allow writing Taml objects *to* files bool write( FileStream& stream, SimObject* pSimObject, const TamlFormatMode formatMode ); SimObject* read( FileStream& stream, const TamlFormatMode formatMode ); template inline T* read( FileStream& stream, const TamlFormatMode formatMode ) diff --git a/Engine/source/util/settings.h b/Engine/source/util/settings.h index 3368fe759..41d5a2f84 100644 --- a/Engine/source/util/settings.h +++ b/Engine/source/util/settings.h @@ -52,6 +52,7 @@ public: const UTF8 *value(const UTF8 *settingName, const UTF8 *defaultValue = ""); void remove(const UTF8 *settingName, bool includeDefaults = false); void clearAllFields(); + void write(Stream& stream, U32 tabStop, U32 flags = 0) override { Con::errorf("Settings: Can Not write a file interface object to a file"); }; //Don't allow writing Settings objects *to* files bool write(); bool read(); void readLayer(SimXMLDocument *document, String groupStack = String("")); From a58f98167fc7fbb9b100a9d8395a8e160c0e5c66 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 10 Jun 2024 13:20:09 -0500 Subject: [PATCH 41/65] handle missing virtual destructors clang translation: destructinplace needs to know what to erase. --- Engine/source/T3D/gameBase/moveManager.h | 2 +- Engine/source/gfx/video/theoraTexture.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/T3D/gameBase/moveManager.h b/Engine/source/T3D/gameBase/moveManager.h index 6b26c4196..539046dc4 100644 --- a/Engine/source/T3D/gameBase/moveManager.h +++ b/Engine/source/T3D/gameBase/moveManager.h @@ -57,7 +57,7 @@ struct Move bool trigger[MaxTriggerKeys]; Move(); - + virtual ~Move() {}; virtual void pack(BitStream *stream, const Move * move = NULL); virtual void unpack(BitStream *stream, const Move * move = NULL); virtual void clamp(); diff --git a/Engine/source/gfx/video/theoraTexture.h b/Engine/source/gfx/video/theoraTexture.h index 8c0748068..d262dfae7 100644 --- a/Engine/source/gfx/video/theoraTexture.h +++ b/Engine/source/gfx/video/theoraTexture.h @@ -263,7 +263,7 @@ class TheoraTexture : private IOutputStream< TheoraTextureFrame* >, /// AsyncState( const ThreadSafeRef< OggInputStream >& oggStream, bool looping = false ); - + virtual ~AsyncState() {}; /// Return the Theora decoder substream. OggTheoraDecoder* getTheora() const { return mTheoraDecoder; } From 61978fa4da95adbd1b261ecac24d6935b372dde8 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 11 Jun 2024 15:21:24 -0500 Subject: [PATCH 42/65] pickanimation filter fix, with docs sorts the order of operations flaws clang was complaining about, with explainations on why --- Engine/source/T3D/player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index f2e54900a..623b5f716 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -4014,9 +4014,9 @@ void Player::updateActionThread() mActionAnimation.callbackTripped = true; } - if ((mActionAnimation.action == PlayerData::NullAnimation) || - ((!mActionAnimation.waitForEnd || mActionAnimation.atEnd) && - (!mActionAnimation.holdAtEnd && (mActionAnimation.delayTicks -= !mMountPending) <= 0))) + if (mActionAnimation.action == PlayerData::NullAnimation || //no animation + ((!mActionAnimation.waitForEnd || (mActionAnimation.atEnd && !mActionAnimation.holdAtEnd) && //either not waiting till the end, or not holding that state + (mActionAnimation.delayTicks -= mMountPending) <= 0))) //not waiting to mount { pickActionAnimation(); } From 7ac714606f93d3759a5a19ea9441d764901616f8 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 11 Jun 2024 16:08:07 -0500 Subject: [PATCH 43/65] proper formulation --- Engine/source/T3D/player.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index 623b5f716..7c0fd1aca 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -4014,9 +4014,9 @@ void Player::updateActionThread() mActionAnimation.callbackTripped = true; } - if (mActionAnimation.action == PlayerData::NullAnimation || //no animation - ((!mActionAnimation.waitForEnd || (mActionAnimation.atEnd && !mActionAnimation.holdAtEnd) && //either not waiting till the end, or not holding that state - (mActionAnimation.delayTicks -= mMountPending) <= 0))) //not waiting to mount + if (mActionAnimation.action == PlayerData::NullAnimation || !mActionAnimation.waitForEnd || //either no animation or not waiting till the end + ((mActionAnimation.atEnd && !mActionAnimation.holdAtEnd) && //or not holding that state and + (mActionAnimation.delayTicks -= mMountPending) <= 0)) //not waiting to mount { pickActionAnimation(); } From e56f4cb6a60bc965db2601310b946c6907418606 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 16 Jun 2024 15:04:20 +0100 Subject: [PATCH 44/65] if statements Changed: if check on vals now return true if the value has a string value %val = "test me" if(%val) will now return true since %val is not null Script side: string checks for "true" and "false" will now be parsed as integer values of 1 and 0. TEST VIGOUROUSLY --- Engine/source/console/console.h | 2 +- Engine/source/console/torquescript/CMDscan.cpp | 12 ++++++++++++ Engine/source/console/torquescript/CMDscan.l | 12 ++++++++++++ Engine/source/console/torquescript/astNodes.cpp | 16 +++++++++++++--- .../source/console/torquescript/compiledEval.cpp | 1 + 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Engine/source/console/console.h b/Engine/source/console/console.h index df921c694..8ac3f2f4c 100644 --- a/Engine/source/console/console.h +++ b/Engine/source/console/console.h @@ -245,7 +245,7 @@ public: if (type == ConsoleValueType::cvSTEntry) return s == StringTable->EmptyString() ? 0 : dAtoi(s); if (type == ConsoleValueType::cvString) - return dStrcmp(s, "") == 0 ? 0 : dAtoi(s); + return dStrcmp(s, "") == 0 ? 0 : dIsdigit(*s) ? dAtoi(s) : s == StringTable->EmptyString() ? 0 : 1; return dAtoi(getConsoleData()); } diff --git a/Engine/source/console/torquescript/CMDscan.cpp b/Engine/source/console/torquescript/CMDscan.cpp index 4497cb961..b533658f0 100644 --- a/Engine/source/console/torquescript/CMDscan.cpp +++ b/Engine/source/console/torquescript/CMDscan.cpp @@ -2827,6 +2827,18 @@ static int Sc_ScanString(int ret) if(!collapseEscape(CMDtext+1)) return -1; + const char* scannedStr = CMDtext + 1; + + if (dStrcmp(scannedStr, "true") == 0) { + CMDlval.i = MakeToken(1, yylineno); + return INTCONST; + } + + if (dStrcmp(scannedStr, "false") == 0) { + CMDlval.i = MakeToken(0, yylineno); + return INTCONST; + } + dsize_t bufferLen = dStrlen( CMDtext ); char* buffer = ( char* ) consoleAlloc( bufferLen ); dStrcpy( buffer, CMDtext + 1, bufferLen ); diff --git a/Engine/source/console/torquescript/CMDscan.l b/Engine/source/console/torquescript/CMDscan.l index aa3a72733..88246a3b5 100644 --- a/Engine/source/console/torquescript/CMDscan.l +++ b/Engine/source/console/torquescript/CMDscan.l @@ -377,6 +377,18 @@ static int Sc_ScanString(int ret) if(!collapseEscape(CMDtext+1)) return -1; + const char* scannedStr = CMDtext + 1; + + if (dStrcmp(scannedStr, "true") == 0) { + CMDlval.i = MakeToken(1, yylineno); + return INTCONST; + } + + if (dStrcmp(scannedStr, "false") == 0) { + CMDlval.i = MakeToken(0, yylineno); + return INTCONST; + } + dsize_t bufferLen = dStrlen( CMDtext ); char* buffer = ( char* ) consoleAlloc( bufferLen ); dStrcpy( buffer, CMDtext + 1, bufferLen ); diff --git a/Engine/source/console/torquescript/astNodes.cpp b/Engine/source/console/torquescript/astNodes.cpp index cc911477d..173f9ab53 100644 --- a/Engine/source/console/torquescript/astNodes.cpp +++ b/Engine/source/console/torquescript/astNodes.cpp @@ -200,7 +200,9 @@ U32 IfStmtNode::compileStmt(CodeStream& codeStream, U32 ip) U32 endifIp, elseIp; addBreakLine(codeStream); - if (testExpr->getPreferredType() == TypeReqUInt) + TypeReq testType = testExpr->getPreferredType(); + + if (testType == TypeReqUInt) { integer = true; } @@ -209,8 +211,16 @@ U32 IfStmtNode::compileStmt(CodeStream& codeStream, U32 ip) integer = false; } - ip = testExpr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); - codeStream.emit(integer ? OP_JMPIFNOT : OP_JMPIFFNOT); + if (testType == TypeReqString) + { + ip = testExpr->compile(codeStream, ip, TypeReqString); + codeStream.emit(OP_JMPIFNOT); + } + else + { + ip = testExpr->compile(codeStream, ip, integer ? TypeReqUInt : TypeReqFloat); + codeStream.emit(integer ? OP_JMPIFNOT : OP_JMPIFFNOT); + } if (elseBlock) { diff --git a/Engine/source/console/torquescript/compiledEval.cpp b/Engine/source/console/torquescript/compiledEval.cpp index 3d765c470..337c71354 100644 --- a/Engine/source/console/torquescript/compiledEval.cpp +++ b/Engine/source/console/torquescript/compiledEval.cpp @@ -1144,6 +1144,7 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi ip++; break; } + ip = code[ip]; break; case OP_JMPIFF: From d6a79e4f5ba0930cf1c5e49d0aaeec0526c429aa Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 16 Jun 2024 20:01:47 +0100 Subject: [PATCH 45/65] if statement treat "true" as a bool in getInt check (inside if statements for strings) no longer convert all "true" and "false" to ints --- Engine/source/console/console.h | 12 +++++++++++- Engine/source/console/torquescript/CMDscan.cpp | 12 ------------ Engine/source/console/torquescript/CMDscan.l | 12 ------------ 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/Engine/source/console/console.h b/Engine/source/console/console.h index 8ac3f2f4c..279f0dffc 100644 --- a/Engine/source/console/console.h +++ b/Engine/source/console/console.h @@ -245,7 +245,17 @@ public: if (type == ConsoleValueType::cvSTEntry) return s == StringTable->EmptyString() ? 0 : dAtoi(s); if (type == ConsoleValueType::cvString) - return dStrcmp(s, "") == 0 ? 0 : dIsdigit(*s) ? dAtoi(s) : s == StringTable->EmptyString() ? 0 : 1; + { + if (dStrcmp(s, "false") == 0) { + return 0; + } + else if (dStrcmp(s, "true") == 0) { + return 1; + } + + return dIsdigit(*s) ? dAtoi(s) : s == StringTable->EmptyString() ? 0 : 1; + } + return dAtoi(getConsoleData()); } diff --git a/Engine/source/console/torquescript/CMDscan.cpp b/Engine/source/console/torquescript/CMDscan.cpp index b533658f0..4497cb961 100644 --- a/Engine/source/console/torquescript/CMDscan.cpp +++ b/Engine/source/console/torquescript/CMDscan.cpp @@ -2827,18 +2827,6 @@ static int Sc_ScanString(int ret) if(!collapseEscape(CMDtext+1)) return -1; - const char* scannedStr = CMDtext + 1; - - if (dStrcmp(scannedStr, "true") == 0) { - CMDlval.i = MakeToken(1, yylineno); - return INTCONST; - } - - if (dStrcmp(scannedStr, "false") == 0) { - CMDlval.i = MakeToken(0, yylineno); - return INTCONST; - } - dsize_t bufferLen = dStrlen( CMDtext ); char* buffer = ( char* ) consoleAlloc( bufferLen ); dStrcpy( buffer, CMDtext + 1, bufferLen ); diff --git a/Engine/source/console/torquescript/CMDscan.l b/Engine/source/console/torquescript/CMDscan.l index 88246a3b5..aa3a72733 100644 --- a/Engine/source/console/torquescript/CMDscan.l +++ b/Engine/source/console/torquescript/CMDscan.l @@ -377,18 +377,6 @@ static int Sc_ScanString(int ret) if(!collapseEscape(CMDtext+1)) return -1; - const char* scannedStr = CMDtext + 1; - - if (dStrcmp(scannedStr, "true") == 0) { - CMDlval.i = MakeToken(1, yylineno); - return INTCONST; - } - - if (dStrcmp(scannedStr, "false") == 0) { - CMDlval.i = MakeToken(0, yylineno); - return INTCONST; - } - dsize_t bufferLen = dStrlen( CMDtext ); char* buffer = ( char* ) consoleAlloc( bufferLen ); dStrcpy( buffer, CMDtext + 1, bufferLen ); From d8411b4a58b94dbbec86323821c30ff7c7479eb9 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 16 Jun 2024 20:02:57 +0100 Subject: [PATCH 46/65] Update console.h case insensitive --- Engine/source/console/console.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/console/console.h b/Engine/source/console/console.h index 279f0dffc..07c57d1b5 100644 --- a/Engine/source/console/console.h +++ b/Engine/source/console/console.h @@ -246,10 +246,10 @@ public: return s == StringTable->EmptyString() ? 0 : dAtoi(s); if (type == ConsoleValueType::cvString) { - if (dStrcmp(s, "false") == 0) { + if (dStricmp(s, "false") == 0) { return 0; } - else if (dStrcmp(s, "true") == 0) { + else if (dStricmp(s, "true") == 0) { return 1; } From 0d4c335231ef67ef86ed41178660ca27b8c99040 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 16 Jun 2024 23:05:42 +0100 Subject: [PATCH 47/65] test working test without scanstring changes --- Engine/source/console/console.h | 11 +---------- Engine/source/console/torquescript/astNodes.cpp | 4 ++-- Engine/source/console/torquescript/compiledEval.cpp | 8 ++++++++ Engine/source/console/torquescript/compiler.h | 1 + 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Engine/source/console/console.h b/Engine/source/console/console.h index 07c57d1b5..b3163e5c5 100644 --- a/Engine/source/console/console.h +++ b/Engine/source/console/console.h @@ -245,16 +245,7 @@ public: if (type == ConsoleValueType::cvSTEntry) return s == StringTable->EmptyString() ? 0 : dAtoi(s); if (type == ConsoleValueType::cvString) - { - if (dStricmp(s, "false") == 0) { - return 0; - } - else if (dStricmp(s, "true") == 0) { - return 1; - } - - return dIsdigit(*s) ? dAtoi(s) : s == StringTable->EmptyString() ? 0 : 1; - } + return dStrcmp(s, "") == 0 ? 0 : dAtoi(s); return dAtoi(getConsoleData()); } diff --git a/Engine/source/console/torquescript/astNodes.cpp b/Engine/source/console/torquescript/astNodes.cpp index 173f9ab53..00e2d2d67 100644 --- a/Engine/source/console/torquescript/astNodes.cpp +++ b/Engine/source/console/torquescript/astNodes.cpp @@ -211,10 +211,10 @@ U32 IfStmtNode::compileStmt(CodeStream& codeStream, U32 ip) integer = false; } - if (testType == TypeReqString) + if (testType == TypeReqString || testType == TypeReqNone) { ip = testExpr->compile(codeStream, ip, TypeReqString); - codeStream.emit(OP_JMPIFNOT); + codeStream.emit(OP_JMPSTRING); } else { diff --git a/Engine/source/console/torquescript/compiledEval.cpp b/Engine/source/console/torquescript/compiledEval.cpp index 337c71354..73a972c96 100644 --- a/Engine/source/console/torquescript/compiledEval.cpp +++ b/Engine/source/console/torquescript/compiledEval.cpp @@ -1163,6 +1163,14 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi } ip = code[ip]; break; + case OP_JMPSTRING: + if (stack[_STK--].getBool()) + { + ip++; + break; + } + ip = code[ip]; + break; case OP_JMPIFNOT_NP: if (stack[_STK].getInt()) { diff --git a/Engine/source/console/torquescript/compiler.h b/Engine/source/console/torquescript/compiler.h index e1cfbbed9..8d271e7fb 100644 --- a/Engine/source/console/torquescript/compiler.h +++ b/Engine/source/console/torquescript/compiler.h @@ -64,6 +64,7 @@ namespace Compiler OP_JMPIFNOT, OP_JMPIFF, OP_JMPIF, + OP_JMPSTRING, OP_JMPIFNOT_NP, OP_JMPIF_NP, // 10 OP_JMP, From 54d0da6690cc1c803fdf00cc328eeb7952f8cef0 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 18 Jun 2024 15:10:24 +0100 Subject: [PATCH 48/65] Update stringFunctions.h changes to dAtob from az --- Engine/source/core/strings/stringFunctions.h | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Engine/source/core/strings/stringFunctions.h b/Engine/source/core/strings/stringFunctions.h index d0c91b734..d1556f7a6 100644 --- a/Engine/source/core/strings/stringFunctions.h +++ b/Engine/source/core/strings/stringFunctions.h @@ -259,9 +259,23 @@ extern S32 dStrcmp(const UTF16 *str1, const UTF16 *str2); extern S32 dStrnatcmp( const char* str1, const char* str2 ); extern S32 dStrnatcasecmp( const char* str1, const char* str2 ); -inline bool dAtob(const char *str) +inline bool dAtob(const char* str) { - return !dStricmp(str, "true") || dAtof(str); + if (str && str[0] != '\0') + { + if (dStricmp(str, "0") == 0) + return false; + if (dStricmp(str, "0.0") == 0) + return false; + if (dStricmp(str, "0.0f") == 0) + return false; + if (dStricmp(str, "null") == 0) + return false; + if (dStricmp(str, "false") == 0) + return false; + return true; + } + return false; } bool dStrEqual(const char* str1, const char* str2); From fed83cdb8f51048186e679e00648d86d1180024b Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 18 Jun 2024 15:15:25 +0100 Subject: [PATCH 49/65] naming change enum to OP_JMPIFNOTSTRING (same name as others doing similar for different types) place case with other ifnot statements --- .../source/console/torquescript/compiledEval.cpp | 16 ++++++++-------- Engine/source/console/torquescript/compiler.h | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Engine/source/console/torquescript/compiledEval.cpp b/Engine/source/console/torquescript/compiledEval.cpp index 73a972c96..94ddcab80 100644 --- a/Engine/source/console/torquescript/compiledEval.cpp +++ b/Engine/source/console/torquescript/compiledEval.cpp @@ -1145,6 +1145,14 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi break; } + ip = code[ip]; + break; + case OP_JMPNOTSTRING: + if (stack[_STK--].getBool()) + { + ip++; + break; + } ip = code[ip]; break; case OP_JMPIFF: @@ -1163,14 +1171,6 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi } ip = code[ip]; break; - case OP_JMPSTRING: - if (stack[_STK--].getBool()) - { - ip++; - break; - } - ip = code[ip]; - break; case OP_JMPIFNOT_NP: if (stack[_STK].getInt()) { diff --git a/Engine/source/console/torquescript/compiler.h b/Engine/source/console/torquescript/compiler.h index 8d271e7fb..74737c773 100644 --- a/Engine/source/console/torquescript/compiler.h +++ b/Engine/source/console/torquescript/compiler.h @@ -62,9 +62,9 @@ namespace Compiler OP_JMPIFFNOT, OP_JMPIFNOT, + OP_JMPNOTSTRING, OP_JMPIFF, OP_JMPIF, - OP_JMPSTRING, OP_JMPIFNOT_NP, OP_JMPIF_NP, // 10 OP_JMP, From b0181cc56a9a7158c34a7deb7a6b01d9441410fc Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 18 Jun 2024 15:23:52 +0100 Subject: [PATCH 50/65] Update astNodes.cpp missed naming --- Engine/source/console/torquescript/astNodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/console/torquescript/astNodes.cpp b/Engine/source/console/torquescript/astNodes.cpp index 00e2d2d67..17d96c961 100644 --- a/Engine/source/console/torquescript/astNodes.cpp +++ b/Engine/source/console/torquescript/astNodes.cpp @@ -214,7 +214,7 @@ U32 IfStmtNode::compileStmt(CodeStream& codeStream, U32 ip) if (testType == TypeReqString || testType == TypeReqNone) { ip = testExpr->compile(codeStream, ip, TypeReqString); - codeStream.emit(OP_JMPSTRING); + codeStream.emit(OP_JMPNOTSTRING); } else { From c9a1955b472c75bed45f9c9128fe7ac4d70aa755 Mon Sep 17 00:00:00 2001 From: Areloch Date: Fri, 28 Jun 2024 20:47:42 -0500 Subject: [PATCH 51/65] Removes old remaining refs to PauseMenu Updates and moves the escape keybind to be an actionMap that is activated/deactivated in the UI module when a client connection is processed to keep it within the module's functionality --- .../scripts/client/defaultKeybinds.tscript | 2 -- Templates/BaseGame/game/data/UI/UI.tscript | 13 +++++++++++-- .../BaseGame/game/data/UI/guis/GameMenu.tscript | 15 --------------- .../worldEditor/scripts/editor.keybinds.tscript | 5 ----- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/Templates/BaseGame/game/data/ExampleModule/scripts/client/defaultKeybinds.tscript b/Templates/BaseGame/game/data/ExampleModule/scripts/client/defaultKeybinds.tscript index 9a9a3ddd5..15cd1088e 100644 --- a/Templates/BaseGame/game/data/ExampleModule/scripts/client/defaultKeybinds.tscript +++ b/Templates/BaseGame/game/data/ExampleModule/scripts/client/defaultKeybinds.tscript @@ -29,8 +29,6 @@ addKeyRemap("Jump", "ExampleMoveMap", "gamepad", "jump", "Jump"); ExampleMoveMap.bind( keyboard, F2, showPlayerList ); ExampleMoveMap.bind(keyboard, "ctrl h", hideHUDs); ExampleMoveMap.bind(keyboard, "alt p", doScreenShotHudless); -ExampleMoveMap.bindCmd(keyboard, "escape", "", "Canvas.pushDialog(GameMenu);"); -ExampleMoveMap.bindCmd(gamepad, btn_start, "Canvas.pushDialog(GameMenu);", "" ); //------------------------------------------------------------------------------ // Movement Keys diff --git a/Templates/BaseGame/game/data/UI/UI.tscript b/Templates/BaseGame/game/data/UI/UI.tscript index 027451977..0588758d2 100644 --- a/Templates/BaseGame/game/data/UI/UI.tscript +++ b/Templates/BaseGame/game/data/UI/UI.tscript @@ -81,11 +81,20 @@ function UI::initClient(%this) if(isToolBuild()) %this.queueExec("./tools/creator.tscript"); + + //GameMenu actionmap + %this.queueExec("./scripts/defaultKeybinds.tscript"); } -function UI::onCreateClientConnection(%this){} +function UI::onCreateClientConnection(%this) +{ + GameMenuActionMap.push(); +} -function UI::onDestroyClientConnection(%this){} +function UI::onDestroyClientConnection(%this) +{ + GameMenuActionMap.pop(); +} function UI::registerGameMenus(%this, %menusArrayObj) { diff --git a/Templates/BaseGame/game/data/UI/guis/GameMenu.tscript b/Templates/BaseGame/game/data/UI/guis/GameMenu.tscript index 68373c5dd..117e7f184 100644 --- a/Templates/BaseGame/game/data/UI/guis/GameMenu.tscript +++ b/Templates/BaseGame/game/data/UI/guis/GameMenu.tscript @@ -107,21 +107,6 @@ function GameMenu::openGameMenu(%this, %menuName) %this.syncGui(); } -function openPauseMenuOptions() -{ - GameMenu.pushPage(OptionsMenu); -} - -function pauseMenuExitToMenu() -{ - MessageBoxOKCancel("Exit?", "Do you wish to exit to the Main Menu?", "escapeFromGame();", ""); -} - -function pauseMenuExitToDesktop() -{ - MessageBoxOKCancel("Exit?", "Do you wish to exit to the desktop?", "quit();", ""); -} - function GameMenuPrevMenu(%val) { if(%val) diff --git a/Templates/BaseGame/game/tools/worldEditor/scripts/editor.keybinds.tscript b/Templates/BaseGame/game/tools/worldEditor/scripts/editor.keybinds.tscript index 74c681bd5..097451e3d 100644 --- a/Templates/BaseGame/game/tools/worldEditor/scripts/editor.keybinds.tscript +++ b/Templates/BaseGame/game/tools/worldEditor/scripts/editor.keybinds.tscript @@ -25,11 +25,6 @@ if ( isObject( EditorMap ) ) new ActionMap(EditorMap); -//------------------------------------------------------------------------------ -// Non-remapable binds -//------------------------------------------------------------------------------ -EditorMap.bindCmd(keyboard, "escape", "", "Canvas.pushDialog(PauseMenu);"); - //------------------------------------------------------------------------------ // Movement Keys //------------------------------------------------------------------------------ From 6721a6b021f8b503cb739ee63763f3c09fe153c8 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 30 Jun 2024 14:35:57 -0500 Subject: [PATCH 52/65] update openal --- .../lib/openal-soft/.github/workflows/ci.yml | 46 +- .../openal-soft/.github/workflows/makemhr.yml | 2 +- Engine/lib/openal-soft/.gitignore | 1 + Engine/lib/openal-soft/CMakeLists.txt | 367 +- Engine/lib/openal-soft/LICENSE-pffft | 38 + Engine/lib/openal-soft/OpenALConfig.cmake.in | 2 +- Engine/lib/openal-soft/README.md | 27 + Engine/lib/openal-soft/al/auxeffectslot.cpp | 1015 ++- Engine/lib/openal-soft/al/auxeffectslot.h | 100 +- Engine/lib/openal-soft/al/buffer.cpp | 1833 ++-- Engine/lib/openal-soft/al/buffer.h | 34 +- Engine/lib/openal-soft/al/debug.cpp | 618 ++ Engine/lib/openal-soft/al/debug.h | 70 + Engine/lib/openal-soft/al/direct_defs.h | 127 + Engine/lib/openal-soft/al/eax/api.h | 59 +- Engine/lib/openal-soft/al/eax/call.cpp | 3 +- Engine/lib/openal-soft/al/eax/call.h | 34 +- Engine/lib/openal-soft/al/eax/effect.h | 325 +- Engine/lib/openal-soft/al/eax/exception.cpp | 47 +- Engine/lib/openal-soft/al/eax/exception.h | 15 +- Engine/lib/openal-soft/al/eax/fx_slot_index.h | 7 +- Engine/lib/openal-soft/al/eax/fx_slots.h | 14 +- Engine/lib/openal-soft/al/eax/globals.cpp | 21 - Engine/lib/openal-soft/al/eax/globals.h | 20 +- Engine/lib/openal-soft/al/eax/utils.cpp | 8 +- Engine/lib/openal-soft/al/eax/utils.h | 12 +- Engine/lib/openal-soft/al/eax/x_ram.h | 12 +- Engine/lib/openal-soft/al/effect.cpp | 735 +- Engine/lib/openal-soft/al/effect.h | 52 +- Engine/lib/openal-soft/al/effects/autowah.cpp | 156 +- Engine/lib/openal-soft/al/effects/chorus.cpp | 296 +- .../lib/openal-soft/al/effects/compressor.cpp | 79 +- .../openal-soft/al/effects/convolution.cpp | 178 +- .../lib/openal-soft/al/effects/dedicated.cpp | 94 +- .../lib/openal-soft/al/effects/distortion.cpp | 160 +- Engine/lib/openal-soft/al/effects/echo.cpp | 160 +- Engine/lib/openal-soft/al/effects/effects.cpp | 6 - Engine/lib/openal-soft/al/effects/effects.h | 96 +- .../lib/openal-soft/al/effects/equalizer.cpp | 250 +- .../lib/openal-soft/al/effects/fshifter.cpp | 167 +- .../lib/openal-soft/al/effects/modulator.cpp | 177 +- Engine/lib/openal-soft/al/effects/null.cpp | 166 +- .../lib/openal-soft/al/effects/pshifter.cpp | 106 +- Engine/lib/openal-soft/al/effects/reverb.cpp | 995 +-- .../lib/openal-soft/al/effects/vmorpher.cpp | 248 +- Engine/lib/openal-soft/al/error.cpp | 113 +- Engine/lib/openal-soft/al/error.h | 27 + Engine/lib/openal-soft/al/event.cpp | 203 +- Engine/lib/openal-soft/al/extension.cpp | 52 +- Engine/lib/openal-soft/al/filter.cpp | 948 +- Engine/lib/openal-soft/al/filter.h | 76 +- Engine/lib/openal-soft/al/listener.cpp | 466 +- Engine/lib/openal-soft/al/listener.h | 4 +- Engine/lib/openal-soft/al/source.cpp | 3772 ++++---- Engine/lib/openal-soft/al/source.h | 195 +- Engine/lib/openal-soft/al/state.cpp | 1098 +-- Engine/lib/openal-soft/alc/alc.cpp | 2421 ++--- Engine/lib/openal-soft/alc/alconfig.cpp | 399 +- Engine/lib/openal-soft/alc/alconfig.h | 21 +- Engine/lib/openal-soft/alc/alu.cpp | 1124 +-- Engine/lib/openal-soft/alc/alu.h | 10 +- Engine/lib/openal-soft/alc/backends/alsa.cpp | 324 +- Engine/lib/openal-soft/alc/backends/alsa.h | 10 +- Engine/lib/openal-soft/alc/backends/base.cpp | 64 +- Engine/lib/openal-soft/alc/backends/base.h | 63 +- .../openal-soft/alc/backends/coreaudio.cpp | 270 +- .../lib/openal-soft/alc/backends/coreaudio.h | 12 +- .../lib/openal-soft/alc/backends/dsound.cpp | 203 +- Engine/lib/openal-soft/alc/backends/dsound.h | 10 +- Engine/lib/openal-soft/alc/backends/jack.cpp | 294 +- Engine/lib/openal-soft/alc/backends/jack.h | 10 +- .../lib/openal-soft/alc/backends/loopback.cpp | 10 +- .../lib/openal-soft/alc/backends/loopback.h | 10 +- Engine/lib/openal-soft/alc/backends/null.cpp | 36 +- Engine/lib/openal-soft/alc/backends/null.h | 10 +- Engine/lib/openal-soft/alc/backends/oboe.cpp | 79 +- Engine/lib/openal-soft/alc/backends/oboe.h | 10 +- .../lib/openal-soft/alc/backends/opensl.cpp | 159 +- Engine/lib/openal-soft/alc/backends/opensl.h | 10 +- Engine/lib/openal-soft/alc/backends/oss.cpp | 247 +- Engine/lib/openal-soft/alc/backends/oss.h | 10 +- .../lib/openal-soft/alc/backends/pipewire.cpp | 922 +- .../lib/openal-soft/alc/backends/pipewire.h | 14 +- .../openal-soft/alc/backends/portaudio.cpp | 278 +- .../lib/openal-soft/alc/backends/portaudio.h | 10 +- .../openal-soft/alc/backends/pulseaudio.cpp | 497 +- .../lib/openal-soft/alc/backends/pulseaudio.h | 18 +- Engine/lib/openal-soft/alc/backends/sdl2.cpp | 64 +- Engine/lib/openal-soft/alc/backends/sdl2.h | 10 +- Engine/lib/openal-soft/alc/backends/sndio.cpp | 231 +- Engine/lib/openal-soft/alc/backends/sndio.h | 10 +- .../lib/openal-soft/alc/backends/solaris.cpp | 63 +- Engine/lib/openal-soft/alc/backends/solaris.h | 10 +- .../lib/openal-soft/alc/backends/wasapi.cpp | 2065 +++-- Engine/lib/openal-soft/alc/backends/wasapi.h | 12 +- Engine/lib/openal-soft/alc/backends/wave.cpp | 168 +- Engine/lib/openal-soft/alc/backends/wave.h | 10 +- Engine/lib/openal-soft/alc/backends/winmm.cpp | 165 +- Engine/lib/openal-soft/alc/backends/winmm.h | 10 +- Engine/lib/openal-soft/alc/context.cpp | 352 +- Engine/lib/openal-soft/alc/context.h | 152 +- Engine/lib/openal-soft/alc/device.cpp | 26 +- Engine/lib/openal-soft/alc/device.h | 106 +- .../lib/openal-soft/alc/effects/autowah.cpp | 98 +- Engine/lib/openal-soft/alc/effects/base.h | 36 +- Engine/lib/openal-soft/alc/effects/chorus.cpp | 210 +- .../openal-soft/alc/effects/compressor.cpp | 145 +- .../openal-soft/alc/effects/convolution.cpp | 543 +- .../lib/openal-soft/alc/effects/dedicated.cpp | 59 +- .../openal-soft/alc/effects/distortion.cpp | 64 +- Engine/lib/openal-soft/alc/effects/echo.cpp | 77 +- .../lib/openal-soft/alc/effects/equalizer.cpp | 59 +- .../lib/openal-soft/alc/effects/fshifter.cpp | 74 +- .../lib/openal-soft/alc/effects/modulator.cpp | 175 +- Engine/lib/openal-soft/alc/effects/null.cpp | 7 +- .../lib/openal-soft/alc/effects/pshifter.cpp | 106 +- Engine/lib/openal-soft/alc/effects/reverb.cpp | 1328 +-- .../lib/openal-soft/alc/effects/vmorpher.cpp | 172 +- Engine/lib/openal-soft/alc/events.cpp | 95 + Engine/lib/openal-soft/alc/events.h | 50 + Engine/lib/openal-soft/alc/export_list.h | 917 ++ Engine/lib/openal-soft/alc/inprogext.h | 91 +- Engine/lib/openal-soft/alc/panning.cpp | 567 +- Engine/lib/openal-soft/alsoftrc.sample | 48 +- Engine/lib/openal-soft/appveyor.yml | 4 +- Engine/lib/openal-soft/cmake/FindOpenSL.cmake | 12 +- Engine/lib/openal-soft/cmake/FindSndIO.cmake | 31 + .../lib/openal-soft/cmake/FindSoundIO.cmake | 32 - Engine/lib/openal-soft/common/alassert.cpp | 38 + Engine/lib/openal-soft/common/alassert.h | 24 + Engine/lib/openal-soft/common/albit.h | 15 + Engine/lib/openal-soft/common/albyte.h | 17 - Engine/lib/openal-soft/common/alcomplex.cpp | 180 +- Engine/lib/openal-soft/common/alcomplex.h | 17 +- Engine/lib/openal-soft/common/aldeque.h | 16 - Engine/lib/openal-soft/common/alfstream.cpp | 26 - Engine/lib/openal-soft/common/alfstream.h | 45 - Engine/lib/openal-soft/common/almalloc.cpp | 61 - Engine/lib/openal-soft/common/almalloc.h | 298 +- Engine/lib/openal-soft/common/alnumbers.h | 26 +- Engine/lib/openal-soft/common/alnumeric.h | 147 +- Engine/lib/openal-soft/common/aloptional.h | 353 - .../common/{threads.cpp => alsem.cpp} | 82 +- Engine/lib/openal-soft/common/alsem.h | 43 + Engine/lib/openal-soft/common/alspan.h | 490 +- Engine/lib/openal-soft/common/alstring.cpp | 69 +- Engine/lib/openal-soft/common/alstring.h | 33 +- .../lib/openal-soft/common/althrd_setname.cpp | 77 + .../lib/openal-soft/common/althrd_setname.h | 6 + Engine/lib/openal-soft/common/althreads.h | 143 + Engine/lib/openal-soft/common/atomic.h | 87 +- Engine/lib/openal-soft/common/comptr.h | 93 +- Engine/lib/openal-soft/common/dynload.cpp | 4 +- Engine/lib/openal-soft/common/flexarray.h | 139 + Engine/lib/openal-soft/common/intrusive_ptr.h | 31 +- Engine/lib/openal-soft/common/opthelpers.h | 5 +- Engine/lib/openal-soft/common/pffft.cpp | 2308 +++++ Engine/lib/openal-soft/common/pffft.h | 212 + Engine/lib/openal-soft/common/phase_shifter.h | 244 +- .../common/polyphase_resampler.cpp | 158 +- .../openal-soft/common/polyphase_resampler.h | 6 +- Engine/lib/openal-soft/common/ringbuffer.cpp | 216 +- Engine/lib/openal-soft/common/ringbuffer.h | 152 +- Engine/lib/openal-soft/common/strutils.cpp | 39 +- Engine/lib/openal-soft/common/strutils.h | 14 +- Engine/lib/openal-soft/common/threads.h | 48 - Engine/lib/openal-soft/common/vecmat.h | 119 +- Engine/lib/openal-soft/common/vector.h | 3 +- Engine/lib/openal-soft/common/win_main_utf8.h | 13 +- Engine/lib/openal-soft/config.h.in | 22 +- Engine/lib/openal-soft/core/ambdec.cpp | 56 +- Engine/lib/openal-soft/core/ambdec.h | 19 +- Engine/lib/openal-soft/core/ambidefs.cpp | 236 +- Engine/lib/openal-soft/core/ambidefs.h | 243 +- Engine/lib/openal-soft/core/async_event.h | 94 +- Engine/lib/openal-soft/core/bformatdec.cpp | 125 +- Engine/lib/openal-soft/core/bformatdec.h | 44 +- Engine/lib/openal-soft/core/bs2b.cpp | 148 +- Engine/lib/openal-soft/core/bs2b.h | 86 +- Engine/lib/openal-soft/core/bsinc_tables.cpp | 234 +- Engine/lib/openal-soft/core/bsinc_tables.h | 9 +- .../lib/openal-soft/core/buffer_storage.cpp | 76 - Engine/lib/openal-soft/core/buffer_storage.h | 62 +- Engine/lib/openal-soft/core/bufferline.h | 2 +- Engine/lib/openal-soft/core/context.cpp | 155 +- Engine/lib/openal-soft/core/context.h | 62 +- Engine/lib/openal-soft/core/converter.cpp | 283 +- Engine/lib/openal-soft/core/converter.h | 17 +- Engine/lib/openal-soft/core/cpu_caps.cpp | 5 +- Engine/lib/openal-soft/core/cpu_caps.h | 5 +- Engine/lib/openal-soft/core/cubic_defs.h | 6 +- Engine/lib/openal-soft/core/cubic_tables.cpp | 154 +- Engine/lib/openal-soft/core/cubic_tables.h | 36 +- Engine/lib/openal-soft/core/dbus_wrap.cpp | 22 +- Engine/lib/openal-soft/core/dbus_wrap.h | 11 +- Engine/lib/openal-soft/core/devformat.cpp | 2 + Engine/lib/openal-soft/core/devformat.h | 9 +- Engine/lib/openal-soft/core/device.cpp | 11 +- Engine/lib/openal-soft/core/device.h | 186 +- Engine/lib/openal-soft/core/effects/base.h | 261 +- Engine/lib/openal-soft/core/effectslot.cpp | 10 +- Engine/lib/openal-soft/core/effectslot.h | 29 +- Engine/lib/openal-soft/core/except.cpp | 6 +- Engine/lib/openal-soft/core/except.h | 19 +- .../lib/openal-soft/core/filters/biquad.cpp | 13 +- Engine/lib/openal-soft/core/filters/biquad.h | 11 +- Engine/lib/openal-soft/core/filters/nfc.cpp | 78 +- Engine/lib/openal-soft/core/filters/nfc.h | 35 +- .../lib/openal-soft/core/filters/splitter.cpp | 18 +- .../lib/openal-soft/core/filters/splitter.h | 6 +- Engine/lib/openal-soft/core/fmt_traits.cpp | 8 +- Engine/lib/openal-soft/core/fmt_traits.h | 64 +- Engine/lib/openal-soft/core/fpu_ctrl.cpp | 82 +- Engine/lib/openal-soft/core/fpu_ctrl.h | 23 +- Engine/lib/openal-soft/core/front_stablizer.h | 1 + Engine/lib/openal-soft/core/helpers.cpp | 534 +- Engine/lib/openal-soft/core/helpers.h | 22 +- Engine/lib/openal-soft/core/hrtf.cpp | 702 +- Engine/lib/openal-soft/core/hrtf.h | 40 +- Engine/lib/openal-soft/core/logging.cpp | 113 +- Engine/lib/openal-soft/core/logging.h | 43 +- Engine/lib/openal-soft/core/mastering.cpp | 292 +- Engine/lib/openal-soft/core/mastering.h | 32 +- Engine/lib/openal-soft/core/mixer.cpp | 15 +- Engine/lib/openal-soft/core/mixer.h | 28 +- Engine/lib/openal-soft/core/mixer/defs.h | 66 +- Engine/lib/openal-soft/core/mixer/hrtfbase.h | 79 +- Engine/lib/openal-soft/core/mixer/mixer_c.cpp | 282 +- .../lib/openal-soft/core/mixer/mixer_neon.cpp | 454 +- .../lib/openal-soft/core/mixer/mixer_sse.cpp | 335 +- .../lib/openal-soft/core/mixer/mixer_sse2.cpp | 164 +- .../openal-soft/core/mixer/mixer_sse41.cpp | 164 +- .../lib/openal-soft/core/resampler_limits.h | 4 +- Engine/lib/openal-soft/core/rtkit.cpp | 24 +- .../lib/openal-soft/core/storage_formats.cpp | 85 + Engine/lib/openal-soft/core/storage_formats.h | 54 + Engine/lib/openal-soft/core/uhjfilter.cpp | 529 +- Engine/lib/openal-soft/core/uhjfilter.h | 131 +- Engine/lib/openal-soft/core/uiddefs.cpp | 12 +- Engine/lib/openal-soft/core/voice.cpp | 738 +- Engine/lib/openal-soft/core/voice.h | 98 +- Engine/lib/openal-soft/core/voice_change.h | 4 - Engine/lib/openal-soft/docs/ambisonics.txt | 4 +- Engine/lib/openal-soft/docs/env-vars.txt | 9 + Engine/lib/openal-soft/examples/alconvolve.c | 73 +- Engine/lib/openal-soft/examples/aldirect.cpp | 472 + Engine/lib/openal-soft/examples/alffplay.cpp | 441 +- Engine/lib/openal-soft/examples/alhrtf.c | 4 +- Engine/lib/openal-soft/examples/allatency.c | 4 +- Engine/lib/openal-soft/examples/alloopback.c | 3 +- .../lib/openal-soft/examples/almultireverb.c | 14 +- Engine/lib/openal-soft/examples/alplay.c | 4 +- Engine/lib/openal-soft/examples/alrecord.c | 2 +- Engine/lib/openal-soft/examples/alreverb.c | 20 +- Engine/lib/openal-soft/examples/alstream.c | 41 +- .../lib/openal-soft/examples/alstreamcb.cpp | 117 +- Engine/lib/openal-soft/examples/altonegen.c | 77 +- .../openal-soft/examples/common/alhelpers.h | 51 +- Engine/lib/openal-soft/include/AL/al.h | 313 +- Engine/lib/openal-soft/include/AL/alc.h | 101 +- Engine/lib/openal-soft/include/AL/alext.h | 608 +- .../lib/openal-soft/include/AL/efx-presets.h | 2 + Engine/lib/openal-soft/include/AL/efx.h | 134 +- Engine/lib/openal-soft/router/al.cpp | 21 +- Engine/lib/openal-soft/router/alc.cpp | 557 +- Engine/lib/openal-soft/router/router.cpp | 519 +- Engine/lib/openal-soft/router/router.h | 99 +- Engine/lib/openal-soft/tests/CMakeLists.txt | 24 + Engine/lib/openal-soft/tests/example.t.cpp | 16 + Engine/lib/openal-soft/utils/CIAIR.def | 7780 ++++++++--------- .../utils/alsoft-config/CMakeLists.txt | 3 +- .../utils/alsoft-config/mainwindow.cpp | 738 +- .../utils/alsoft-config/mainwindow.h | 39 +- Engine/lib/openal-soft/utils/getopt.c | 137 - Engine/lib/openal-soft/utils/getopt.h | 26 - .../lib/openal-soft/utils/makemhr/loaddef.cpp | 923 +- .../lib/openal-soft/utils/makemhr/loaddef.h | 7 +- .../openal-soft/utils/makemhr/loadsofa.cpp | 172 +- .../lib/openal-soft/utils/makemhr/loadsofa.h | 4 +- .../lib/openal-soft/utils/makemhr/makemhr.cpp | 767 +- .../lib/openal-soft/utils/makemhr/makemhr.h | 67 +- Engine/lib/openal-soft/utils/openal-info.c | 89 +- Engine/lib/openal-soft/utils/sofa-info.cpp | 61 +- Engine/lib/openal-soft/utils/sofa-support.cpp | 14 +- Engine/lib/openal-soft/utils/sofa-support.h | 4 +- Engine/lib/openal-soft/utils/uhjdecoder.cpp | 191 +- Engine/lib/openal-soft/utils/uhjencoder.cpp | 258 +- 287 files changed, 33851 insertions(+), 27325 deletions(-) create mode 100644 Engine/lib/openal-soft/LICENSE-pffft create mode 100644 Engine/lib/openal-soft/al/debug.cpp create mode 100644 Engine/lib/openal-soft/al/debug.h create mode 100644 Engine/lib/openal-soft/al/direct_defs.h delete mode 100644 Engine/lib/openal-soft/al/eax/globals.cpp create mode 100644 Engine/lib/openal-soft/al/error.h create mode 100644 Engine/lib/openal-soft/alc/events.cpp create mode 100644 Engine/lib/openal-soft/alc/events.h create mode 100644 Engine/lib/openal-soft/alc/export_list.h create mode 100644 Engine/lib/openal-soft/cmake/FindSndIO.cmake delete mode 100644 Engine/lib/openal-soft/cmake/FindSoundIO.cmake create mode 100644 Engine/lib/openal-soft/common/alassert.cpp create mode 100644 Engine/lib/openal-soft/common/alassert.h delete mode 100644 Engine/lib/openal-soft/common/albyte.h delete mode 100644 Engine/lib/openal-soft/common/aldeque.h delete mode 100644 Engine/lib/openal-soft/common/alfstream.cpp delete mode 100644 Engine/lib/openal-soft/common/alfstream.h delete mode 100644 Engine/lib/openal-soft/common/almalloc.cpp delete mode 100644 Engine/lib/openal-soft/common/aloptional.h rename Engine/lib/openal-soft/common/{threads.cpp => alsem.cpp} (58%) create mode 100644 Engine/lib/openal-soft/common/alsem.h create mode 100644 Engine/lib/openal-soft/common/althrd_setname.cpp create mode 100644 Engine/lib/openal-soft/common/althrd_setname.h create mode 100644 Engine/lib/openal-soft/common/althreads.h create mode 100644 Engine/lib/openal-soft/common/flexarray.h create mode 100644 Engine/lib/openal-soft/common/pffft.cpp create mode 100644 Engine/lib/openal-soft/common/pffft.h delete mode 100644 Engine/lib/openal-soft/common/threads.h create mode 100644 Engine/lib/openal-soft/core/storage_formats.cpp create mode 100644 Engine/lib/openal-soft/core/storage_formats.h create mode 100644 Engine/lib/openal-soft/examples/aldirect.cpp create mode 100644 Engine/lib/openal-soft/tests/CMakeLists.txt create mode 100644 Engine/lib/openal-soft/tests/example.t.cpp delete mode 100644 Engine/lib/openal-soft/utils/getopt.c delete mode 100644 Engine/lib/openal-soft/utils/getopt.h diff --git a/Engine/lib/openal-soft/.github/workflows/ci.yml b/Engine/lib/openal-soft/.github/workflows/ci.yml index 1a94c7609..b1c3b413b 100644 --- a/Engine/lib/openal-soft/.github/workflows/ci.yml +++ b/Engine/lib/openal-soft/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI -on: [push] +on: [push, pull_request] jobs: build: @@ -14,6 +14,7 @@ jobs: name: "Win32-Release", os: windows-latest, cmake_opts: "-A Win32 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ @@ -24,6 +25,7 @@ jobs: name: "Win32-Debug", os: windows-latest, cmake_opts: "-A Win32 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ @@ -34,6 +36,7 @@ jobs: name: "Win64-Release", os: windows-latest, cmake_opts: "-A x64 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ @@ -44,16 +47,42 @@ jobs: name: "Win64-Debug", os: windows-latest, cmake_opts: "-A x64 \ + -DALSOFT_TESTS=ON \ -DALSOFT_BUILD_ROUTER=ON \ -DALSOFT_REQUIRE_WINMM=ON \ -DALSOFT_REQUIRE_DSOUND=ON \ -DALSOFT_REQUIRE_WASAPI=ON", build_type: "Debug" } + - { + name: "Win64-UWP", + os: windows-latest, + cmake_opts: "-A x64 \ + -DALSOFT_TESTS=OFF \ + -DCMAKE_SYSTEM_NAME=WindowsStore \ + \"-DCMAKE_SYSTEM_VERSION=10.0\" \ + -DALSOFT_BUILD_ROUTER=ON \ + -DALSOFT_REQUIRE_WASAPI=ON", + build_type: "Release" + } - { name: "macOS-Release", os: macos-latest, - cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON", + cmake_opts: "-DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_TESTS=ON", + build_type: "Release" + } + - { + name: "iOS-Release", + os: macos-latest, + cmake_opts: "-GXcode \ + -DCMAKE_SYSTEM_NAME=iOS \ + -DALSOFT_REQUIRE_COREAUDIO=ON \ + -DALSOFT_UTILS=OFF \ + -DALSOFT_EXAMPLES=OFF \ + -DALSOFT_TESTS=OFF \ + -DALSOFT_INSTALL=OFF \ + \"-DCMAKE_OSX_ARCHITECTURES=arm64\"", build_type: "Release" } - { @@ -65,7 +94,8 @@ jobs: -DALSOFT_REQUIRE_PORTAUDIO=ON \ -DALSOFT_REQUIRE_PULSEAUDIO=ON \ -DALSOFT_REQUIRE_JACK=ON \ - -DALSOFT_REQUIRE_PIPEWIRE=ON", + -DALSOFT_REQUIRE_PIPEWIRE=ON \ + -DALSOFT_TESTS=ON", deps_cmdline: "sudo apt update && sudo apt-get install -qq \ libpulse-dev \ portaudio19-dev \ @@ -78,7 +108,7 @@ jobs: } steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Install Dependencies shell: bash @@ -97,6 +127,12 @@ jobs: run: | cmake --build build --config ${{matrix.config.build_type}} + - name: Test + shell: bash + run: | + cd build + ctest + - name: Create Archive if: ${{ matrix.config.os == 'windows-latest' }} shell: bash @@ -109,7 +145,7 @@ jobs: - name: Upload Archive # Upload package as an artifact of this workflow. - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v3.1.2 if: ${{ matrix.config.os == 'windows-latest' }} with: name: soft_oal-${{matrix.config.name}} diff --git a/Engine/lib/openal-soft/.github/workflows/makemhr.yml b/Engine/lib/openal-soft/.github/workflows/makemhr.yml index 7bde284c1..654861500 100644 --- a/Engine/lib/openal-soft/.github/workflows/makemhr.yml +++ b/Engine/lib/openal-soft/.github/workflows/makemhr.yml @@ -55,7 +55,7 @@ jobs: copy "libmysofa/windows/third-party/zlib-1.2.11/bin/zlib.dll" "Artifacts/zlib.dll" - name: Upload makemhr artifact - uses: actions/upload-artifact@v3.1.1 + uses: actions/upload-artifact@v3.1.2 with: name: makemhr path: "Artifacts/" diff --git a/Engine/lib/openal-soft/.gitignore b/Engine/lib/openal-soft/.gitignore index 4a8212b53..688980a54 100644 --- a/Engine/lib/openal-soft/.gitignore +++ b/Engine/lib/openal-soft/.gitignore @@ -1,6 +1,7 @@ build*/ winbuild win64build +.vs/ ## kdevelop *.kdev4 diff --git a/Engine/lib/openal-soft/CMakeLists.txt b/Engine/lib/openal-soft/CMakeLists.txt index 55644b045..e8c0607f9 100644 --- a/Engine/lib/openal-soft/CMakeLists.txt +++ b/Engine/lib/openal-soft/CMakeLists.txt @@ -1,6 +1,7 @@ # CMake build file list for OpenAL -cmake_minimum_required(VERSION 3.0.2) +cmake_minimum_required(VERSION 3.13) +enable_testing() if(APPLE) # The workaround for try_compile failing with code signing @@ -28,11 +29,10 @@ if(CMAKE_SYSTEM_NAME STREQUAL "iOS") FORCE) endif() endif() +elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + set(ALSOFT_UWP TRUE) endif() -set(CMAKE_C_VISIBILITY_PRESET hidden) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) - if(COMMAND CMAKE_POLICY) cmake_policy(SET CMP0003 NEW) cmake_policy(SET CMP0005 NEW) @@ -76,8 +76,8 @@ if(NOT CMAKE_DEBUG_POSTFIX) endif() set(DEFAULT_TARGET_PROPS - # Require C++14. - CXX_STANDARD 14 + # Require C++17. + CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE # Prefer C11, but support C99 and earlier when possible. C_STANDARD 11) @@ -109,6 +109,7 @@ option(ALSOFT_UTILS "Build utility programs" ON) option(ALSOFT_NO_CONFIG_UTIL "Disable building the alsoft-config utility" OFF) option(ALSOFT_EXAMPLES "Build example programs" ON) +option(ALSOFT_TESTS "Build test programs" OFF) option(ALSOFT_INSTALL "Install main library" ON) option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" ON) @@ -148,6 +149,8 @@ set(CPP_DEFS ) # C pre-processor, not C++ set(INC_PATHS ) set(C_FLAGS ) set(LINKER_FLAGS ) +set(LINKER_FLAGS_DEBUG ) +set(LINKER_FLAGS_RELEASE ) set(EXTRA_LIBS ) if(WIN32) @@ -165,7 +168,7 @@ elseif(APPLE) endif() -# QNX's gcc do not uses /usr/include and /usr/lib pathes by default +# QNX's gcc do not uses /usr/include and /usr/lib paths by default if("${CMAKE_C_PLATFORM_ID}" STREQUAL "QNX") set(INC_PATHS ${INC_PATHS} /usr/include) set(LINKER_FLAGS ${LINKER_FLAGS} -L/usr/lib) @@ -186,29 +189,18 @@ set(LIB_VERSION_NUM ${LIB_MAJOR_VERSION},${LIB_MINOR_VERSION},${LIB_REVISION},0) set(EXPORT_DECL "") -if(NOT WIN32) - # Check if _POSIX_C_SOURCE and _XOPEN_SOURCE needs to be set for POSIX functions - check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_DEFAULT) - if(NOT HAVE_POSIX_MEMALIGN_DEFAULT) - set(OLD_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -D_POSIX_C_SOURCE=200112L -D_XOPEN_SOURCE=600") - check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN_POSIX) - if(NOT HAVE_POSIX_MEMALIGN_POSIX) - set(CMAKE_REQUIRED_FLAGS ${OLD_REQUIRED_FLAGS}) - else() - set(CPP_DEFS ${CPP_DEFS} _POSIX_C_SOURCE=200112L _XOPEN_SOURCE=600) - endif() - endif() - unset(OLD_REQUIRED_FLAGS) -endif() - -# C99 has restrict, but C++ does not, so we can only utilize __restrict. -check_cxx_source_compiles("int *__restrict foo; -int main() { return 0; }" HAVE___RESTRICT) -if(HAVE___RESTRICT) - set(CPP_DEFS ${CPP_DEFS} RESTRICT=__restrict) -else() - set(CPP_DEFS ${CPP_DEFS} "RESTRICT=") +# Some systems erroneously require the __STDC_FORMAT_MACROS macro to be defined +# to get the fixed-width integer type formatter macros. +check_cxx_source_compiles("#include +#include +int main() +{ + int64_t i64{}; + std::printf(\"%\" PRId64, i64); +}" +HAVE_STDC_FORMAT_MACROS) +if(NOT HAVE_STDC_FORMAT_MACROS) + set(CPP_DEFS ${CPP_DEFS} __STDC_FORMAT_MACROS) endif() # Some systems may need libatomic for atomic functions to work @@ -278,6 +270,11 @@ else() endif() endif() + check_cxx_compiler_flag(-Wno-interference-size HAVE_WNO_INTERFERENCE_SIZE) + if(HAVE_WNO_INTERFERENCE_SIZE) + set(C_FLAGS ${C_FLAGS} $<$:-Wno-interference-size>) + endif() + if(ALSOFT_WERROR) set(C_FLAGS ${C_FLAGS} -Werror) endif() @@ -312,7 +309,7 @@ else() option(ALSOFT_STATIC_STDCXX "Static link libstdc++" OFF) if(ALSOFT_STATIC_STDCXX) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state") + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-static-libstdc++") check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBSTDCXX_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -320,14 +317,14 @@ else() if(NOT HAVE_STATIC_LIBSTDCXX_SWITCH) message(FATAL_ERROR "Cannot static link libstdc++") endif() - set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,--pop-state") + set(LINKER_FLAGS ${LINKER_FLAGS} "-static-libstdc++") endif() if(WIN32) option(ALSOFT_STATIC_WINPTHREAD "Static link libwinpthread" OFF) if(ALSOFT_STATIC_WINPTHREAD) set(OLD_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state") + set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state") check_cxx_source_compiles("int main() { }" HAVE_STATIC_LIBWINPTHREAD_SWITCH) set(CMAKE_REQUIRED_LIBRARIES ${OLD_REQUIRED_LIBRARIES}) unset(OLD_REQUIRED_LIBRARIES) @@ -335,38 +332,38 @@ else() if(NOT HAVE_STATIC_LIBWINPTHREAD_SWITCH) message(FATAL_ERROR "Cannot static link libwinpthread") endif() - set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lwinpthread,--pop-state") + set(LINKER_FLAGS ${LINKER_FLAGS} "-Wl,--push-state,-Bstatic,-lstdc++,-lwinpthread,--pop-state") endif() endif() endif() # Set visibility/export options if available -if(WIN32) - if(NOT LIBTYPE STREQUAL "STATIC") +if(NOT LIBTYPE STREQUAL "STATIC") + if(WIN32) set(EXPORT_DECL "__declspec(dllexport)") - endif() -else() - set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") - # Yes GCC, really don't accept visibility modes you don't support - set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") - - check_c_source_compiles("int foo() __attribute__((visibility(\"protected\"))); - int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY) - if(HAVE_GCC_PROTECTED_VISIBILITY) - if(NOT LIBTYPE STREQUAL "STATIC") - set(EXPORT_DECL "__attribute__((visibility(\"protected\")))") - endif() else() - check_c_source_compiles("int foo() __attribute__((visibility(\"default\"))); - int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY) - if(HAVE_GCC_DEFAULT_VISIBILITY) - if(NOT LIBTYPE STREQUAL "STATIC") + set(OLD_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + # Yes GCC, really don't accept visibility modes you don't support + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS} -Wattributes -Werror") + + check_c_source_compiles("int foo() __attribute__((visibility(\"protected\"))); + int main() {return 0;}" HAVE_GCC_PROTECTED_VISIBILITY) + if(HAVE_GCC_PROTECTED_VISIBILITY) + set(EXPORT_DECL "__attribute__((visibility(\"protected\")))") + else() + check_c_source_compiles("int foo() __attribute__((visibility(\"default\"))); + int main() {return 0;}" HAVE_GCC_DEFAULT_VISIBILITY) + if(HAVE_GCC_DEFAULT_VISIBILITY) set(EXPORT_DECL "__attribute__((visibility(\"default\")))") endif() endif() - endif() + if(HAVE_GCC_PROTECTED_VISIBILITY OR HAVE_GCC_DEFAULT_VISIBILITY) + set(CMAKE_C_VISIBILITY_PRESET hidden) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) + endif() - set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${OLD_REQUIRED_FLAGS}") + endif() endif() @@ -403,7 +400,7 @@ if(ALSOFT_CPUEXT_SSE AND HAVE_XMMINTRIN_H) set(HAVE_SSE 1) endif() if(ALSOFT_REQUIRE_SSE AND NOT HAVE_SSE) - message(FATAL_ERROR "Failed to enabled required SSE CPU extensions") + message(FATAL_ERROR "Failed to enable required SSE CPU extensions") endif() option(ALSOFT_CPUEXT_SSE2 "Enable SSE2 support" ON) @@ -448,10 +445,11 @@ if(ALSOFT_CPUEXT_NEON AND HAVE_ARM_NEON_H) endif() endif() if(ALSOFT_REQUIRE_NEON AND NOT HAVE_NEON) - message(FATAL_ERROR "Failed to enabled required ARM NEON CPU extensions") + message(FATAL_ERROR "Failed to enable required ARM NEON CPU extensions") endif() +set(ALSOFT_FORCE_ALIGN ) set(SSE_FLAGS ) set(FPMATH_SET "0") if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) @@ -476,6 +474,7 @@ if(CMAKE_SIZEOF_VOID_P MATCHES "4" AND HAVE_SSE2) # OSs don't guarantee this on 32-bit, so externally-callable # functions need to ensure an aligned stack. set(EXPORT_DECL "${EXPORT_DECL}__attribute__((force_align_arg_pointer))") + set(ALSOFT_FORCE_ALIGN "__attribute__((force_align_arg_pointer))") endif() endif() endif() @@ -493,13 +492,9 @@ if(HAVE_SSE2) endif() -check_include_file(malloc.h HAVE_MALLOC_H) check_include_file(cpuid.h HAVE_CPUID_H) check_include_file(intrin.h HAVE_INTRIN_H) check_include_file(guiddef.h HAVE_GUIDDEF_H) -if(NOT HAVE_GUIDDEF_H) - check_include_file(initguid.h HAVE_INITGUID_H) -endif() # Some systems need libm for some math functions to work set(MATH_LIB ) @@ -517,7 +512,7 @@ if(HAVE_LIBRT) set(RT_LIB rt) endif() -# Check for the dlopen API (for dynamicly loading backend libs) +# Check for the dlopen API (for dynamically loading backend libs) if(ALSOFT_DLOPEN) check_include_file(dlfcn.h HAVE_DLFCN_H) check_library_exists(dl dlopen "" HAVE_LIBDL) @@ -546,8 +541,6 @@ if(HAVE_INTRIN_H) }" HAVE_CPUID_INTRINSIC) endif() -check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN) -check_symbol_exists(_aligned_malloc malloc.h HAVE__ALIGNED_MALLOC) check_symbol_exists(proc_pidpath libproc.h HAVE_PROC_PIDPATH) if(NOT WIN32) @@ -582,34 +575,36 @@ if(NOT WIN32) endif() endif() -check_symbol_exists(getopt unistd.h HAVE_GETOPT) - # Common sources used by both the OpenAL implementation library, the OpenAL # router, and certain tools and examples. set(COMMON_OBJS + common/alassert.cpp + common/alassert.h common/albit.h - common/albyte.h common/alcomplex.cpp common/alcomplex.h - common/aldeque.h - common/alfstream.cpp - common/alfstream.h - common/almalloc.cpp common/almalloc.h common/alnumbers.h common/alnumeric.h - common/aloptional.h + common/alsem.cpp + common/alsem.h common/alspan.h common/alstring.cpp common/alstring.h + common/althrd_setname.cpp + common/althrd_setname.h + common/althreads.h common/altraits.h common/atomic.h common/comptr.h common/dynload.cpp common/dynload.h + common/flexarray.h common/intrusive_ptr.h common/opthelpers.h + common/pffft.cpp + common/pffft.h common/phase_shifter.h common/polyphase_resampler.cpp common/polyphase_resampler.h @@ -618,8 +613,6 @@ set(COMMON_OBJS common/ringbuffer.h common/strutils.cpp common/strutils.h - common/threads.cpp - common/threads.h common/vecmat.h common/vector.h) @@ -680,6 +673,8 @@ set(CORE_OBJS core/mixer.cpp core/mixer.h core/resampler_limits.h + core/storage_formats.cpp + core/storage_formats.h core/uhjfilter.cpp core/uhjfilter.h core/uiddefs.cpp @@ -726,7 +721,7 @@ if(NOT WIN32) endif() endif() if(ALSOFT_REQUIRE_RTKIT AND NOT HAVE_RTKIT) - message(FATAL_ERROR "Failed to enabled required RTKit support") + message(FATAL_ERROR "Failed to enable required RTKit support") endif() # Default mixers, always available @@ -742,6 +737,9 @@ set(OPENAL_OBJS al/auxeffectslot.h al/buffer.cpp al/buffer.h + al/debug.cpp + al/debug.h + al/direct_defs.h al/effect.cpp al/effect.h al/effects/autowah.cpp @@ -761,6 +759,7 @@ set(OPENAL_OBJS al/effects/reverb.cpp al/effects/vmorpher.cpp al/error.cpp + al/error.h al/event.cpp al/event.h al/extension.cpp @@ -798,6 +797,9 @@ set(ALC_OBJS alc/effects/pshifter.cpp alc/effects/reverb.cpp alc/effects/vmorpher.cpp + alc/events.cpp + alc/events.h + alc/export_list.h alc/inprogext.h alc/panning.cpp) @@ -815,7 +817,6 @@ if(ALSOFT_EAX) al/eax/fx_slot_index.h al/eax/fx_slots.cpp al/eax/fx_slots.h - al/eax/globals.cpp al/eax/globals.h al/eax/utils.cpp al/eax/utils.h @@ -899,7 +900,7 @@ if(ALSOFT_BACKEND_PIPEWIRE AND PkgConfig_FOUND) endif() endif() if(ALSOFT_REQUIRE_PIPEWIRE AND NOT HAVE_PIPEWIRE) - message(FATAL_ERROR "Failed to enabled required PipeWire backend") + message(FATAL_ERROR "Failed to enable required PipeWire backend") endif() # Check PulseAudio backend @@ -916,7 +917,7 @@ if(ALSOFT_BACKEND_PULSEAUDIO) endif() endif() if(ALSOFT_REQUIRE_PULSEAUDIO AND NOT HAVE_PULSEAUDIO) - message(FATAL_ERROR "Failed to enabled required PulseAudio backend") + message(FATAL_ERROR "Failed to enable required PulseAudio backend") endif() if(NOT WIN32) @@ -963,66 +964,72 @@ if(NOT WIN32) endif() endif() - # Check SndIO backend - option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + # Check SndIO backend (disabled by default on non-BSDs) + if(BSD) + option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" ON) + else() + option(ALSOFT_BACKEND_SNDIO "Enable SndIO backend" OFF) + endif() option(ALSOFT_REQUIRE_SNDIO "Require SndIO backend" OFF) if(ALSOFT_BACKEND_SNDIO) - find_package(SoundIO) - if(SOUNDIO_FOUND) + find_package(SndIO) + if(SNDIO_FOUND) set(HAVE_SNDIO 1) set(BACKENDS "${BACKENDS} SndIO (linked),") set(ALC_OBJS ${ALC_OBJS} alc/backends/sndio.cpp alc/backends/sndio.h) - set(EXTRA_LIBS ${SOUNDIO_LIBRARIES} ${EXTRA_LIBS}) - set(INC_PATHS ${INC_PATHS} ${SOUNDIO_INCLUDE_DIRS}) + set(EXTRA_LIBS ${SNDIO_LIBRARIES} ${EXTRA_LIBS}) + set(INC_PATHS ${INC_PATHS} ${SNDIO_INCLUDE_DIRS}) endif() endif() endif() if(ALSOFT_REQUIRE_ALSA AND NOT HAVE_ALSA) - message(FATAL_ERROR "Failed to enabled required ALSA backend") + message(FATAL_ERROR "Failed to enable required ALSA backend") endif() if(ALSOFT_REQUIRE_OSS AND NOT HAVE_OSS) - message(FATAL_ERROR "Failed to enabled required OSS backend") + message(FATAL_ERROR "Failed to enable required OSS backend") endif() if(ALSOFT_REQUIRE_SOLARIS AND NOT HAVE_SOLARIS) - message(FATAL_ERROR "Failed to enabled required Solaris backend") + message(FATAL_ERROR "Failed to enable required Solaris backend") endif() if(ALSOFT_REQUIRE_SNDIO AND NOT HAVE_SNDIO) - message(FATAL_ERROR "Failed to enabled required SndIO backend") + message(FATAL_ERROR "Failed to enable required SndIO backend") endif() # Check Windows-only backends if(WIN32) - # Check MMSystem backend - option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) - option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) - if(ALSOFT_BACKEND_WINMM) - set(HAVE_WINMM 1) - set(BACKENDS "${BACKENDS} WinMM,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h) - # There doesn't seem to be good way to search for winmm.lib for MSVC. - # find_library doesn't find it without being told to look in a specific - # place in the WindowsSDK, but it links anyway. If there ends up being - # Windows targets without this, another means to detect it is needed. - set(EXTRA_LIBS winmm ${EXTRA_LIBS}) - endif() - - # Check DSound backend - option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) - option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) - if(ALSOFT_BACKEND_DSOUND) - check_include_file(dsound.h HAVE_DSOUND_H) - if(DXSDK_DIR) - find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h" - PATHS "${DXSDK_DIR}" PATH_SUFFIXES include - DOC "The DirectSound include directory") + if (NOT ALSOFT_UWP) + # Check MMSystem backend + option(ALSOFT_BACKEND_WINMM "Enable Windows Multimedia backend" ON) + option(ALSOFT_REQUIRE_WINMM "Require Windows Multimedia backend" OFF) + if(ALSOFT_BACKEND_WINMM) + set(HAVE_WINMM 1) + set(BACKENDS "${BACKENDS} WinMM,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/winmm.cpp alc/backends/winmm.h) + # There doesn't seem to be good way to search for winmm.lib for MSVC. + # find_library doesn't find it without being told to look in a specific + # place in the WindowsSDK, but it links anyway. If there ends up being + # Windows targets without this, another means to detect it is needed. + set(EXTRA_LIBS winmm ${EXTRA_LIBS}) endif() - if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR) - set(HAVE_DSOUND 1) - set(BACKENDS "${BACKENDS} DirectSound,") - set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) - if(NOT HAVE_DSOUND_H) - set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) + # Check DSound backend + option(ALSOFT_BACKEND_DSOUND "Enable DirectSound backend" ON) + option(ALSOFT_REQUIRE_DSOUND "Require DirectSound backend" OFF) + if(ALSOFT_BACKEND_DSOUND) + check_include_file(dsound.h HAVE_DSOUND_H) + if(DXSDK_DIR) + find_path(DSOUND_INCLUDE_DIR NAMES "dsound.h" + PATHS "${DXSDK_DIR}" PATH_SUFFIXES include + DOC "The DirectSound include directory") + endif() + if(HAVE_DSOUND_H OR DSOUND_INCLUDE_DIR) + set(HAVE_DSOUND 1) + set(BACKENDS "${BACKENDS} DirectSound,") + set(ALC_OBJS ${ALC_OBJS} alc/backends/dsound.cpp alc/backends/dsound.h) + + if(NOT HAVE_DSOUND_H) + set(INC_PATHS ${INC_PATHS} ${DSOUND_INCLUDE_DIR}) + endif() endif() endif() endif() @@ -1040,13 +1047,13 @@ if(WIN32) endif() endif() if(ALSOFT_REQUIRE_WINMM AND NOT HAVE_WINMM) - message(FATAL_ERROR "Failed to enabled required WinMM backend") + message(FATAL_ERROR "Failed to enable required WinMM backend") endif() if(ALSOFT_REQUIRE_DSOUND AND NOT HAVE_DSOUND) - message(FATAL_ERROR "Failed to enabled required DSound backend") + message(FATAL_ERROR "Failed to enable required DSound backend") endif() if(ALSOFT_REQUIRE_WASAPI AND NOT HAVE_WASAPI) - message(FATAL_ERROR "Failed to enabled required WASAPI backend") + message(FATAL_ERROR "Failed to enable required WASAPI backend") endif() # Check JACK backend @@ -1063,7 +1070,7 @@ if(ALSOFT_BACKEND_JACK) endif() endif() if(ALSOFT_REQUIRE_JACK AND NOT HAVE_JACK) - message(FATAL_ERROR "Failed to enabled required JACK backend") + message(FATAL_ERROR "Failed to enable required JACK backend") endif() # Check CoreAudio backend @@ -1098,7 +1105,7 @@ if(ALSOFT_BACKEND_COREAUDIO) endif() endif() if(ALSOFT_REQUIRE_COREAUDIO AND NOT HAVE_COREAUDIO) - message(FATAL_ERROR "Failed to enabled required CoreAudio backend") + message(FATAL_ERROR "Failed to enable required CoreAudio backend") endif() # Check for Oboe (Android) backend @@ -1130,7 +1137,7 @@ if(ALSOFT_BACKEND_OBOE) endif() endif() if(ALSOFT_REQUIRE_OBOE AND NOT HAVE_OBOE) - message(FATAL_ERROR "Failed to enabled required Oboe backend") + message(FATAL_ERROR "Failed to enable required Oboe backend") endif() # Check for OpenSL (Android) backend @@ -1142,11 +1149,12 @@ if(ALSOFT_BACKEND_OPENSL) set(HAVE_OPENSL 1) set(ALC_OBJS ${ALC_OBJS} alc/backends/opensl.cpp alc/backends/opensl.h) set(BACKENDS "${BACKENDS} OpenSL,") - set(EXTRA_LIBS "OpenSL::OpenSLES" ${EXTRA_LIBS}) + set(EXTRA_LIBS ${OPENSL_LIBRARIES} ${EXTRA_LIBS}) + set(INC_PATHS ${INC_PATHS} ${OPENSL_INCLUDE_DIRS}) endif() endif() if(ALSOFT_REQUIRE_OPENSL AND NOT HAVE_OPENSL) - message(FATAL_ERROR "Failed to enabled required OpenSL backend") + message(FATAL_ERROR "Failed to enable required OpenSL backend") endif() # Check PortAudio backend @@ -1163,7 +1171,7 @@ if(ALSOFT_BACKEND_PORTAUDIO) endif() endif() if(ALSOFT_REQUIRE_PORTAUDIO AND NOT HAVE_PORTAUDIO) - message(FATAL_ERROR "Failed to enabled required PortAudio backend") + message(FATAL_ERROR "Failed to enable required PortAudio backend") endif() # Check for SDL2 backend @@ -1181,7 +1189,7 @@ if(ALSOFT_BACKEND_SDL2) endif() endif() if(ALSOFT_REQUIRE_SDL2 AND NOT SDL2_FOUND) - message(FATAL_ERROR "Failed to enabled required SDL2 backend") + message(FATAL_ERROR "Failed to enable required SDL2 backend") endif() # Optionally enable the Wave Writer backend @@ -1309,11 +1317,12 @@ configure_file( @ONLY) -add_library(common STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) -target_include_directories(common PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include) -target_compile_definitions(common PRIVATE ${CPP_DEFS}) -target_compile_options(common PRIVATE ${C_FLAGS}) -set_target_properties(common PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) +add_library(alcommon STATIC EXCLUDE_FROM_ALL ${COMMON_OBJS}) +target_include_directories(alcommon PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/include + PUBLIC ${OpenAL_SOURCE_DIR}/common) +target_compile_definitions(alcommon PRIVATE ${CPP_DEFS}) +target_compile_options(alcommon PRIVATE ${C_FLAGS}) +set_target_properties(alcommon PROPERTIES ${DEFAULT_TARGET_PROPS} POSITION_INDEPENDENT_CODE TRUE) unset(HAS_ROUTER) @@ -1348,7 +1357,7 @@ else() PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(OpenAL PRIVATE ${C_FLAGS}) - target_link_libraries(OpenAL PRIVATE common ${LINKER_FLAGS}) + target_link_libraries(OpenAL PRIVATE alcommon ${LINKER_FLAGS}) target_include_directories(OpenAL PUBLIC $ @@ -1379,13 +1388,31 @@ else() if(WIN32) set_target_properties(${IMPL_TARGET} PROPERTIES PREFIX "") endif() - target_link_libraries(${IMPL_TARGET} PRIVATE common ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + target_link_libraries(${IMPL_TARGET} PRIVATE alcommon ${LINKER_FLAGS} ${EXTRA_LIBS} ${MATH_LIB}) + + if(ALSOFT_UWP) + set(ALSOFT_CPPWINRT_VERSION "2.0.230706.1" CACHE STRING "The soft-oal default cppwinrt version") + + find_program(NUGET_EXE NAMES nuget) + if(NOT NUGET_EXE) + message("NUGET.EXE not found.") + message(FATAL_ERROR "Please install this executable, and run CMake again.") + endif() + + exec_program(${NUGET_EXE} + ARGS install "Microsoft.Windows.CppWinRT" -Version ${ALSOFT_CPPWINRT_VERSION} -ExcludeVersion -OutputDirectory "\"${CMAKE_BINARY_DIR}/packages\"") + + set_target_properties(${IMPL_TARGET} PROPERTIES + VS_PROJECT_IMPORT ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.props + ) + target_link_libraries(${IMPL_TARGET} PRIVATE ${CMAKE_BINARY_DIR}/packages/Microsoft.Windows.CppWinRT/build/native/Microsoft.Windows.CppWinRT.targets) + endif() if(NOT WIN32 AND NOT APPLE) # FIXME: This doesn't put a dependency on the version script. Changing # the version script will not cause a relink as it should. - set_property(TARGET ${IMPL_TARGET} APPEND_STRING PROPERTY - LINK_FLAGS " -Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version") + target_link_options(${IMPL_TARGET} PRIVATE + "-Wl,--version-script=${OpenAL_SOURCE_DIR}/libopenal.version") endif() if(APPLE AND ALSOFT_OSX_FRAMEWORK) @@ -1443,8 +1470,8 @@ set_target_properties(${IMPL_TARGET} PROPERTIES ${DEFAULT_TARGET_PROPS} SOVERSION ${LIB_MAJOR_VERSION} ) target_compile_definitions(${IMPL_TARGET} - PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" - ${CPP_DEFS}) + PRIVATE AL_BUILD_LIBRARY AL_ALEXT_PROTOTYPES $<$:ALSOFT_EAX> + "ALC_API=${EXPORT_DECL}" "AL_API=${EXPORT_DECL}" ${CPP_DEFS}) target_compile_options(${IMPL_TARGET} PRIVATE ${C_FLAGS}) if(TARGET build_version) @@ -1462,8 +1489,7 @@ if(WIN32 AND MINGW AND ALSOFT_BUILD_IMPORT_LIB AND NOT LIBTYPE STREQUAL "STATIC" message(STATUS "WARNING: Cannot find dlltool, disabling .def/.lib generation") endif() else() - set_property(TARGET OpenAL APPEND_STRING PROPERTY - LINK_FLAGS " -Wl,--output-def,OpenAL32.def") + target_link_options(OpenAL PRIVATE "-Wl,--output-def,${PROJECT_BINARY_DIR}/OpenAL32.def") add_custom_command(TARGET OpenAL POST_BUILD COMMAND "${SED_EXECUTABLE}" -i -e "s/ @[^ ]*//" OpenAL32.def COMMAND "${CMAKE_DLLTOOL}" -d OpenAL32.def -l OpenAL32.lib -D OpenAL32.dll @@ -1494,7 +1520,10 @@ if(FPMATH_SET) message(STATUS "Building with SSE${FPMATH_SET} codegen") message(STATUS "") endif() - +if(ALSOFT_UWP) + message(STATUS "Building with UWP support") + message(STATUS "") +endif() if(ALSOFT_EAX) message(STATUS "Building with legacy EAX extension support") message(STATUS "") @@ -1580,7 +1609,7 @@ if(ALSOFT_UTILS) target_include_directories(uhjdecoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjdecoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjdecoder PUBLIC common + target_link_libraries(uhjdecoder PUBLIC alcommon PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) set_target_properties(uhjdecoder PROPERTIES ${DEFAULT_TARGET_PROPS}) @@ -1589,7 +1618,7 @@ if(ALSOFT_UTILS) target_include_directories(uhjencoder PRIVATE ${OpenAL_BINARY_DIR} ${OpenAL_SOURCE_DIR}/common) target_compile_options(uhjencoder PRIVATE ${C_FLAGS}) - target_link_libraries(uhjencoder PUBLIC common + target_link_libraries(uhjencoder PUBLIC alcommon PRIVATE ${LINKER_FLAGS} SndFile::SndFile ${UNICODE_FLAG}) set_target_properties(uhjencoder PROPERTIES ${DEFAULT_TARGET_PROPS}) endif() @@ -1602,7 +1631,7 @@ if(ALSOFT_UTILS) target_compile_definitions(sofa-support PRIVATE ${CPP_DEFS}) target_include_directories(sofa-support PUBLIC ${OpenAL_SOURCE_DIR}/common) target_compile_options(sofa-support PRIVATE ${C_FLAGS}) - target_link_libraries(sofa-support PUBLIC common MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) + target_link_libraries(sofa-support PUBLIC alcommon MySOFA::MySOFA PRIVATE ${LINKER_FLAGS}) set_target_properties(sofa-support PROPERTIES ${DEFAULT_TARGET_PROPS}) set(MAKEMHR_SRCS @@ -1612,9 +1641,6 @@ if(ALSOFT_UTILS) utils/makemhr/loadsofa.h utils/makemhr/makemhr.cpp utils/makemhr/makemhr.h) - if(NOT HAVE_GETOPT) - set(MAKEMHR_SRCS ${MAKEMHR_SRCS} utils/getopt.c utils/getopt.h) - endif() add_executable(makemhr ${MAKEMHR_SRCS}) target_compile_definitions(makemhr PRIVATE ${CPP_DEFS}) target_include_directories(makemhr @@ -1644,22 +1670,22 @@ endif() # Add a static library with common functions used by multiple example targets -add_library(ex-common STATIC EXCLUDE_FROM_ALL +add_library(al-excommon STATIC EXCLUDE_FROM_ALL examples/common/alhelpers.c examples/common/alhelpers.h) -target_compile_definitions(ex-common PUBLIC ${CPP_DEFS}) -target_include_directories(ex-common PUBLIC ${OpenAL_SOURCE_DIR}/common) -target_compile_options(ex-common PUBLIC ${C_FLAGS}) -target_link_libraries(ex-common PUBLIC OpenAL PRIVATE ${RT_LIB}) -set_target_properties(ex-common PROPERTIES ${DEFAULT_TARGET_PROPS}) +target_compile_definitions(al-excommon PUBLIC ${CPP_DEFS}) +target_include_directories(al-excommon PUBLIC ${OpenAL_SOURCE_DIR}/common) +target_compile_options(al-excommon PUBLIC ${C_FLAGS}) +target_link_libraries(al-excommon PUBLIC OpenAL PRIVATE ${RT_LIB}) +set_target_properties(al-excommon PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_EXAMPLES) add_executable(altonegen examples/altonegen.c) - target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} ex-common ${UNICODE_FLAG}) + target_link_libraries(altonegen PRIVATE ${LINKER_FLAGS} ${MATH_LIB} al-excommon ${UNICODE_FLAG}) set_target_properties(altonegen PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alrecord examples/alrecord.c) - target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} ex-common ${UNICODE_FLAG}) + target_link_libraries(alrecord PRIVATE ${LINKER_FLAGS} al-excommon ${UNICODE_FLAG}) set_target_properties(alrecord PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) @@ -1670,48 +1696,53 @@ if(ALSOFT_EXAMPLES) if(SNDFILE_FOUND) add_executable(alplay examples/alplay.c) - target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alplay PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alplay PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alstream examples/alstream.c) - target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alstream PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alstream PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alreverb examples/alreverb.c) - target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alreverb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alreverb PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(almultireverb examples/almultireverb.c) target_link_libraries(almultireverb - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) set_target_properties(almultireverb PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(allatency examples/allatency.c) - target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(allatency PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(allatency PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alhrtf examples/alhrtf.c) target_link_libraries(alhrtf - PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common ${MATH_LIB} ${UNICODE_FLAG}) + PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${MATH_LIB} ${UNICODE_FLAG}) set_target_properties(alhrtf PROPERTIES ${DEFAULT_TARGET_PROPS}) add_executable(alstreamcb examples/alstreamcb.cpp) - target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile ex-common + target_link_libraries(alstreamcb PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) set_target_properties(alstreamcb PROPERTIES ${DEFAULT_TARGET_PROPS}) - add_executable(alconvolve examples/alconvolve.c) - target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} common SndFile::SndFile ex-common + add_executable(aldirect examples/aldirect.cpp) + target_link_libraries(aldirect PRIVATE ${LINKER_FLAGS} SndFile::SndFile al-excommon ${UNICODE_FLAG}) + set_target_properties(aldirect PROPERTIES ${DEFAULT_TARGET_PROPS}) + + add_executable(alconvolve examples/alconvolve.c) + target_link_libraries(alconvolve PRIVATE ${LINKER_FLAGS} alcommon SndFile::SndFile + al-excommon ${UNICODE_FLAG}) set_target_properties(alconvolve PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) set(EXTRA_INSTALLS ${EXTRA_INSTALLS} alplay alstream alreverb almultireverb allatency - alhrtf) + alhrtf aldirect) endif() message(STATUS "Building SndFile example programs") @@ -1720,7 +1751,7 @@ if(ALSOFT_EXAMPLES) if(SDL2_FOUND) add_executable(alloopback examples/alloopback.c) target_link_libraries(alloopback - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ex-common ${MATH_LIB}) + PRIVATE ${LINKER_FLAGS} SDL2::SDL2 al-excommon ${MATH_LIB}) set_target_properties(alloopback PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) @@ -1757,7 +1788,7 @@ if(ALSOFT_EXAMPLES) add_executable(alffplay examples/alffplay.cpp) target_include_directories(alffplay PRIVATE ${FFMPEG_INCLUDE_DIRS}) target_link_libraries(alffplay - PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} ex-common) + PRIVATE ${LINKER_FLAGS} SDL2::SDL2 ${FFMPEG_LIBRARIES} al-excommon) set_target_properties(alffplay PROPERTIES ${DEFAULT_TARGET_PROPS}) if(ALSOFT_INSTALL_EXAMPLES) @@ -1769,6 +1800,10 @@ if(ALSOFT_EXAMPLES) message(STATUS "") endif() +if (ALSOFT_TESTS) +add_subdirectory(tests) +endif() + if(EXTRA_INSTALLS) install(TARGETS ${EXTRA_INSTALLS} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} diff --git a/Engine/lib/openal-soft/LICENSE-pffft b/Engine/lib/openal-soft/LICENSE-pffft new file mode 100644 index 000000000..5f1a8493f --- /dev/null +++ b/Engine/lib/openal-soft/LICENSE-pffft @@ -0,0 +1,38 @@ +A modified PFFFT is included, with the following license. + +Copyright (c) 2023 Christopher Robinson + +Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + +Copyright (c) 2004 the University Corporation for Atmospheric +Research ("UCAR"). All rights reserved. Developed by NCAR's +Computational and Information Systems Laboratory, UCAR, +www.cisl.ucar.edu. + +Redistribution and use of the Software in source and binary forms, +with or without modification, is permitted provided that the +following conditions are met: + +- Neither the names of NCAR's Computational and Information Systems +Laboratory, the University Corporation for Atmospheric Research, +nor the names of its sponsors or contributors may be used to +endorse or promote products derived from this Software without +specific prior written permission. + +- Redistributions of source code must retain the above copyright +notices, this list of conditions, and the disclaimer below. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions, and the disclaimer below in the +documentation and/or other materials provided with the +distribution. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/Engine/lib/openal-soft/OpenALConfig.cmake.in b/Engine/lib/openal-soft/OpenALConfig.cmake.in index 128c1a4e0..9704d3c49 100644 --- a/Engine/lib/openal-soft/OpenALConfig.cmake.in +++ b/Engine/lib/openal-soft/OpenALConfig.cmake.in @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1) +cmake_minimum_required(VERSION 3.1...3.18) include("${CMAKE_CURRENT_LIST_DIR}/OpenALTargets.cmake") diff --git a/Engine/lib/openal-soft/README.md b/Engine/lib/openal-soft/README.md index 50b7bfb49..dac53e71e 100644 --- a/Engine/lib/openal-soft/README.md +++ b/Engine/lib/openal-soft/README.md @@ -64,6 +64,33 @@ as application-agnostic behavior of the library. See alsoftrc.sample for available settings. +Language Bindings +----------------- + +As a C API, OpenAL Soft can be used directly by any language that can use +functions with C linkage. For languages that can't directly use C-style +headers, bindings may be developed to allow code written in that language to +call into the library. Some bindings for some languages are listed here. + +C# Bindings: +* [OpenTK](https://opentk.net/) includes low-level C# bindings for the OpenAL +API, including some extensions. It also includes utility libraries for math and +linear algebra, which can be useful for 3D calculations. + +Java Bindings: +* [JOAL](https://jogamp.org/joal/www/), part of the JogAmp project, includes +Java bindings for the OpenAL API, usable with OpenAL Soft. It also includes a +higher level Sound3D Toolkit API and utility functions to make easier use of +OpenAL features and capabilities. + +Python Bindings: +* [PyOpenAL](https://pypi.org/project/PyOpenAL/). Also includes methods to play +wave files and, with PyOgg, also Vorbis, Opus, and FLAC. + +Other bindings for these and other languages also exist. This list will grow as +more bindings are found. + + Acknowledgements ---------------- diff --git a/Engine/lib/openal-soft/al/auxeffectslot.cpp b/Engine/lib/openal-soft/al/auxeffectslot.cpp index 285da1d43..243f8fcbf 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.cpp +++ b/Engine/lib/openal-soft/al/auxeffectslot.cpp @@ -23,69 +23,82 @@ #include "auxeffectslot.h" #include -#include +#include #include +#include #include #include #include #include -#include +#include +#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" +#include "AL/alext.h" #include "AL/efx.h" #include "albit.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/device.h" +#include "alc/effects/base.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" #include "alspan.h" +#include "atomic.h" #include "buffer.h" -#include "core/except.h" +#include "core/buffer_storage.h" +#include "core/device.h" #include "core/fpu_ctrl.h" #include "core/logging.h" +#include "direct_defs.h" #include "effect.h" +#include "error.h" +#include "flexarray.h" #include "opthelpers.h" +#ifdef ALSOFT_EAX +#include "eax/api.h" +#include "eax/call.h" +#include "eax/effect.h" +#include "eax/fx_slot_index.h" +#include "eax/utils.h" +#endif + namespace { -struct FactoryItem { - EffectSlotType Type; - EffectStateFactory* (&GetFactory)(void); -}; -constexpr FactoryItem FactoryList[] = { - { EffectSlotType::None, NullStateFactory_getFactory }, - { EffectSlotType::EAXReverb, ReverbStateFactory_getFactory }, - { EffectSlotType::Reverb, StdReverbStateFactory_getFactory }, - { EffectSlotType::Autowah, AutowahStateFactory_getFactory }, - { EffectSlotType::Chorus, ChorusStateFactory_getFactory }, - { EffectSlotType::Compressor, CompressorStateFactory_getFactory }, - { EffectSlotType::Distortion, DistortionStateFactory_getFactory }, - { EffectSlotType::Echo, EchoStateFactory_getFactory }, - { EffectSlotType::Equalizer, EqualizerStateFactory_getFactory }, - { EffectSlotType::Flanger, FlangerStateFactory_getFactory }, - { EffectSlotType::FrequencyShifter, FshifterStateFactory_getFactory }, - { EffectSlotType::RingModulator, ModulatorStateFactory_getFactory }, - { EffectSlotType::PitchShifter, PshifterStateFactory_getFactory }, - { EffectSlotType::VocalMorpher, VmorpherStateFactory_getFactory }, - { EffectSlotType::DedicatedDialog, DedicatedStateFactory_getFactory }, - { EffectSlotType::DedicatedLFE, DedicatedStateFactory_getFactory }, - { EffectSlotType::Convolution, ConvolutionStateFactory_getFactory }, -}; +using SubListAllocator = al::allocator>; EffectStateFactory *getFactoryByType(EffectSlotType type) { - auto iter = std::find_if(std::begin(FactoryList), std::end(FactoryList), - [type](const FactoryItem &item) noexcept -> bool - { return item.Type == type; }); - return (iter != std::end(FactoryList)) ? iter->GetFactory() : nullptr; + switch(type) + { + case EffectSlotType::None: return NullStateFactory_getFactory(); + case EffectSlotType::Reverb: return ReverbStateFactory_getFactory(); + case EffectSlotType::Chorus: return ChorusStateFactory_getFactory(); + case EffectSlotType::Autowah: return AutowahStateFactory_getFactory(); + case EffectSlotType::Compressor: return CompressorStateFactory_getFactory(); + case EffectSlotType::Convolution: return ConvolutionStateFactory_getFactory(); + case EffectSlotType::Dedicated: return DedicatedStateFactory_getFactory(); + case EffectSlotType::Distortion: return DistortionStateFactory_getFactory(); + case EffectSlotType::Echo: return EchoStateFactory_getFactory(); + case EffectSlotType::Equalizer: return EqualizerStateFactory_getFactory(); + case EffectSlotType::Flanger: return ChorusStateFactory_getFactory(); + case EffectSlotType::FrequencyShifter: return FshifterStateFactory_getFactory(); + case EffectSlotType::RingModulator: return ModulatorStateFactory_getFactory(); + case EffectSlotType::PitchShifter: return PshifterStateFactory_getFactory(); + case EffectSlotType::VocalMorpher: return VmorpherStateFactory_getFactory(); + } + return nullptr; } -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +auto LookupEffectSlot(ALCcontext *context, ALuint id) noexcept -> ALeffectslot* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -95,10 +108,10 @@ inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.EffectSlots + slidx; + return al::to_address(sublist.EffectSlots->begin() + slidx); } -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept +inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -108,10 +121,10 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) noexcept EffectSubList &sublist = device->EffectList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Effects + slidx; + return al::to_address(sublist.Effects->begin() + slidx); } -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept +inline auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -121,7 +134,7 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept BufferSubList &sublist = device->BufferList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; + return al::to_address(sublist.Buffers->begin() + slidx); } @@ -129,44 +142,44 @@ void AddActiveEffectSlots(const al::span auxslots, ALCcontext *co { if(auxslots.empty()) return; EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_acquire)}; - size_t newcount{curarray->size() + auxslots.size()}; + if((curarray->size()>>1) > std::numeric_limits::max()-auxslots.size()) + throw std::runtime_error{"Too many active effect slots"}; - /* Insert the new effect slots into the head of the array, followed by the - * existing ones. + size_t newcount{(curarray->size()>>1) + auxslots.size()}; + if(newcount > std::numeric_limits::max()>>1) + throw std::runtime_error{"Too many active effect slots"}; + + /* Insert the new effect slots into the head of the new array, followed by + * the existing ones. */ - EffectSlotArray *newarray = EffectSlot::CreatePtrArray(newcount); - auto slotiter = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), - [](ALeffectslot *auxslot) noexcept { return auxslot->mSlot; }); - std::copy(curarray->begin(), curarray->end(), slotiter); + auto newarray = EffectSlot::CreatePtrArray(newcount<<1); + auto new_end = std::transform(auxslots.begin(), auxslots.end(), newarray->begin(), + std::mem_fn(&ALeffectslot::mSlot)); + new_end = std::copy_n(curarray->begin(), curarray->size()>>1, new_end); /* Remove any duplicates (first instance of each will be kept). */ - auto last = newarray->end(); for(auto start=newarray->begin()+1;;) { - last = std::remove(start, last, *(start-1)); - if(start == last) break; + new_end = std::remove(start, new_end, *(start-1)); + if(start == new_end) break; ++start; } - newcount = static_cast(std::distance(newarray->begin(), last)); + newcount = static_cast(std::distance(newarray->begin(), new_end)); /* Reallocate newarray if the new size ended up smaller from duplicate * removal. */ - if(newcount < newarray->size()) UNLIKELY + if(newcount < newarray->size()>>1) UNLIKELY { - curarray = newarray; - newarray = EffectSlot::CreatePtrArray(newcount); - std::copy_n(curarray->begin(), newcount, newarray->begin()); - delete curarray; - curarray = nullptr; + auto oldarray = std::move(newarray); + newarray = EffectSlot::CreatePtrArray(newcount<<1); + new_end = std::copy_n(oldarray->begin(), newcount, newarray->begin()); } - std::uninitialized_fill_n(newarray->end(), newcount, nullptr); + std::fill(new_end, newarray->end(), nullptr); - curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - context->mDevice->waitForMix(); - - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; + auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), + std::memory_order_acq_rel); + std::ignore = context->mDevice->waitForMix(); } void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext *context) @@ -177,9 +190,9 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext /* Don't shrink the allocated array size since we don't know how many (if * any) of the effect slots to remove are in the array. */ - EffectSlotArray *newarray = EffectSlot::CreatePtrArray(curarray->size()); + auto newarray = EffectSlot::CreatePtrArray(curarray->size()); - auto new_end = std::copy(curarray->begin(), curarray->end(), newarray->begin()); + auto new_end = std::copy_n(curarray->begin(), curarray->size()>>1, newarray->begin()); /* Remove elements from newarray that match any ID in slotids. */ for(const ALeffectslot *auxslot : auxslots) { @@ -190,26 +203,21 @@ void RemoveActiveEffectSlots(const al::span auxslots, ALCcontext /* Reallocate with the new size. */ auto newsize = static_cast(std::distance(newarray->begin(), new_end)); - if(newsize != newarray->size()) LIKELY + if(newsize < newarray->size()>>1) LIKELY { - curarray = newarray; - newarray = EffectSlot::CreatePtrArray(newsize); - std::copy_n(curarray->begin(), newsize, newarray->begin()); - - delete curarray; - curarray = nullptr; + auto oldarray = std::move(newarray); + newarray = EffectSlot::CreatePtrArray(newsize<<1); + new_end = std::copy_n(oldarray->begin(), newsize, newarray->begin()); } - std::uninitialized_fill_n(newarray->end(), newsize, nullptr); + std::fill(new_end, newarray->end(), nullptr); - curarray = context->mActiveAuxSlots.exchange(newarray, std::memory_order_acq_rel); - context->mDevice->waitForMix(); - - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; + auto oldarray = context->mActiveAuxSlots.exchange(std::move(newarray), + std::memory_order_acq_rel); + std::ignore = context->mDevice->waitForMix(); } -EffectSlotType EffectSlotTypeFromEnum(ALenum type) +constexpr auto EffectSlotTypeFromEnum(ALenum type) noexcept -> EffectSlotType { switch(type) { @@ -226,19 +234,19 @@ EffectSlotType EffectSlotTypeFromEnum(ALenum type) case AL_EFFECT_AUTOWAH: return EffectSlotType::Autowah; case AL_EFFECT_COMPRESSOR: return EffectSlotType::Compressor; case AL_EFFECT_EQUALIZER: return EffectSlotType::Equalizer; - case AL_EFFECT_EAXREVERB: return EffectSlotType::EAXReverb; - case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::DedicatedLFE; - case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::DedicatedDialog; - case AL_EFFECT_CONVOLUTION_REVERB_SOFT: return EffectSlotType::Convolution; + case AL_EFFECT_EAXREVERB: return EffectSlotType::Reverb; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return EffectSlotType::Dedicated; + case AL_EFFECT_DEDICATED_DIALOGUE: return EffectSlotType::Dedicated; + case AL_EFFECT_CONVOLUTION_SOFT: return EffectSlotType::Convolution; } ERR("Unhandled effect enum: 0x%04x\n", type); return EffectSlotType::None; } -bool EnsureEffectSlots(ALCcontext *context, size_t needed) -{ +auto EnsureEffectSlots(ALCcontext *context, size_t needed) noexcept -> bool +try { size_t count{std::accumulate(context->mEffectSlotList.cbegin(), - context->mEffectSlotList.cend(), size_t{0}, + context->mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -247,20 +255,17 @@ bool EnsureEffectSlots(ALCcontext *context, size_t needed) if(context->mEffectSlotList.size() >= 1<<25) UNLIKELY return false; - context->mEffectSlotList.emplace_back(); - auto sublist = context->mEffectSlotList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->EffectSlots = static_cast( - al_calloc(alignof(ALeffectslot), sizeof(ALeffectslot)*64)); - if(!sublist->EffectSlots) UNLIKELY - { - context->mEffectSlotList.pop_back(); - return false; - } - count += 64; + EffectSlotSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.EffectSlots = SubListAllocator{}.allocate(1); + context->mEffectSlotList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} ALeffectslot *AllocEffectSlot(ALCcontext *context) { @@ -271,7 +276,8 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALeffectslot *slot{al::construct_at(sublist->EffectSlots + slidx, context)}; + ALeffectslot *slot{al::construct_at(al::to_address(sublist->EffectSlots->begin() + slidx), + context)}; aluInitEffectPanning(slot->mSlot, context); /* Add 1 to avoid ID 0. */ @@ -285,11 +291,13 @@ ALeffectslot *AllocEffectSlot(ALCcontext *context) void FreeEffectSlot(ALCcontext *context, ALeffectslot *slot) { + context->mEffectSlotNames.erase(slot->id); + const ALuint id{slot->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(slot); + std::destroy_at(slot); context->mEffectSlotList[lidx].FreeMask |= 1_u64 << slidx; context->mNumEffectSlots--; @@ -309,299 +317,213 @@ inline void UpdateProps(ALeffectslot *slot, ALCcontext *context) } // namespace -AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d effect slots", n); +AL_API DECL_FUNC2(void, alGenAuxiliaryEffectSlots, ALsizei,n, ALuint*,effectslots) +FORCE_ALIGN void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, + ALuint *effectslots) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d effect slots", n}; if(n <= 0) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; ALCdevice *device{context->mALDevice.get()}; - if(static_cast(n) > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) - { - context->setError(AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", - device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n); - return; - } - if(!EnsureEffectSlots(context.get(), static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, - (n==1) ? "" : "s"); - return; - } - if(n == 1) - { - ALeffectslot *slot{AllocEffectSlot(context.get())}; - effectslots[0] = slot->id; + const al::span eids{effectslots, static_cast(n)}; + if(eids.size() > device->AuxiliaryEffectSlotMax-context->mNumEffectSlots) + throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u effect slot limit (%u + %d)", + device->AuxiliaryEffectSlotMax, context->mNumEffectSlots, n}; + + if(!EnsureEffectSlots(context, eids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effectslot%s", n, + (n == 1) ? "" : "s"}; + + std::vector slots; + try { + if(eids.size() == 1) + { + /* Special handling for the easy and normal case. */ + eids[0] = AllocEffectSlot(context)->id; + } + else + { + slots.reserve(eids.size()); + std::generate_n(std::back_inserter(slots), eids.size(), + [context]{ return AllocEffectSlot(context); }); + + std::transform(slots.cbegin(), slots.cend(), eids.begin(), + [](ALeffectslot *slot) -> ALuint { return slot->id; }); + } } - else - { - al::vector ids; - ALsizei count{n}; - ids.reserve(static_cast(count)); - do { - ALeffectslot *slot{AllocEffectSlot(context.get())}; - ids.emplace_back(slot->id); - } while(--count); - std::copy(ids.cbegin(), ids.cend(), effectslots); + catch(std::exception& e) { + ERR("Exception allocating effectslot %zu of %d: %s\n", slots.size()+1, n, e.what()); + auto delete_effectslot = [context](ALeffectslot *slot) -> void + { FreeEffectSlot(context, slot); }; + std::for_each(slots.begin(), slots.end(), delete_effectslot); + throw al::context_error{AL_INVALID_OPERATION, "Exception allocating %d effectslots: %s", n, + e.what()}; } } -END_API_FUNC - -AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} +AL_API DECL_FUNC2(void, alDeleteAuxiliaryEffectSlots, ALsizei,n, const ALuint*,effectslots) +FORCE_ALIGN void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, + const ALuint *effectslots) noexcept +try { if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d effect slots", n); + throw al::context_error{AL_INVALID_VALUE, "Deleting %d effect slots", n}; if(n <= 0) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; if(n == 1) { - ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[0])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[0]); - return; - } - if(ReadRef(slot->ref) != 0) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - effectslots[0]); - return; - } - RemoveActiveEffectSlots({&slot, 1u}, context.get()); - FreeEffectSlot(context.get(), slot); + ALeffectslot *slot{LookupEffectSlot(context, *effectslots)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", *effectslots}; + if(slot->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", + *effectslots}; + + RemoveActiveEffectSlots({&slot, 1u}, context); + FreeEffectSlot(context, slot); } else { - auto slots = al::vector(static_cast(n)); - for(size_t i{0};i < slots.size();++i) + const al::span eids{effectslots, static_cast(n)}; + std::vector slots; + slots.reserve(eids.size()); + + auto lookupslot = [context](const ALuint eid) -> ALeffectslot* { - ALeffectslot *slot{LookupEffectSlot(context.get(), effectslots[i])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslots[i]); - return; - } - if(ReadRef(slot->ref) != 0) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use effect slot %u", - effectslots[i]); - return; - } - slots[i] = slot; - } - /* Remove any duplicates. */ - auto slots_end = slots.end(); - for(auto start=slots.begin()+1;start != slots_end;++start) - { - slots_end = std::remove(start, slots_end, *(start-1)); - if(start == slots_end) break; - } - slots.erase(slots_end, slots.end()); + ALeffectslot *slot{LookupEffectSlot(context, eid)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", eid}; + if(slot->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use effect slot %u", + eid}; + return slot; + }; + std::transform(eids.cbegin(), eids.cend(), std::back_inserter(slots), lookupslot); /* All effectslots are valid, remove and delete them */ - RemoveActiveEffectSlots(slots, context.get()); - for(ALeffectslot *slot : slots) - FreeEffectSlot(context.get(), slot); + RemoveActiveEffectSlots(slots, context); + + auto delete_effectslot = [context](const ALuint eid) -> void + { + if(ALeffectslot *slot{LookupEffectSlot(context, eid)}) + FreeEffectSlot(context, slot); + }; + std::for_each(eids.begin(), eids.end(), delete_effectslot); } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsAuxiliaryEffectSlot, ALuint,effectslot) +FORCE_ALIGN ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, + ALuint effectslot) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - std::lock_guard _{context->mEffectSlotLock}; - if(LookupEffectSlot(context.get(), effectslot) != nullptr) - return AL_TRUE; - } + std::lock_guard slotlock{context->mEffectSlotLock}; + if(LookupEffectSlot(context, effectslot) != nullptr) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid) -START_API_FUNC +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlaySOFT not supported"); +} + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei, const ALuint*) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotPlayvSOFT not supported"); +} + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopSOFT not supported"); +} + +AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei, const ALuint*) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alAuxiliaryEffectSlotStopvSOFT not supported"); +} + + +AL_API DECL_FUNC3(void, alAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, + ALenum param, ALint value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; + + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); - return; - } - if(slot->mState == SlotState::Playing) - return; - - slot->mPropsDirty = false; - slot->updateProps(context.get()); - - AddActiveEffectSlots({&slot, 1}, context.get()); - slot->mState = SlotState::Playing; -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d effect slots", n); - if(n <= 0) UNLIKELY return; - - auto slots = al::vector(static_cast(n)); - std::lock_guard _{context->mEffectSlotLock}; - for(size_t i{0};i < slots.size();++i) - { - ALeffectslot *slot{LookupEffectSlot(context.get(), slotids[i])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); - return; - } - - if(slot->mState != SlotState::Playing) - { - slot->mPropsDirty = false; - slot->updateProps(context.get()); - } - slots[i] = slot; - }; - - AddActiveEffectSlots(slots, context.get()); - for(auto slot : slots) - slot->mState = SlotState::Playing; -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot{LookupEffectSlot(context.get(), slotid)}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotid); - return; - } - - RemoveActiveEffectSlots({&slot, 1}, context.get()); - slot->mState = SlotState::Stopped; -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Stopping %d effect slots", n); - if(n <= 0) UNLIKELY return; - - auto slots = al::vector(static_cast(n)); - std::lock_guard _{context->mEffectSlotLock}; - for(size_t i{0};i < slots.size();++i) - { - ALeffectslot *slot{LookupEffectSlot(context.get(), slotids[i])}; - if(!slot) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", slotids[i]); - return; - } - - slots[i] = slot; - }; - - RemoveActiveEffectSlots(slots, context.get()); - for(auto slot : slots) - slot->mState = SlotState::Stopped; -} -END_API_FUNC - - -AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; ALeffectslot *target{}; - ALCdevice *device{}; ALenum err{}; switch(param) { case AL_EFFECTSLOT_EFFECT: - device = context->mALDevice.get(); - { - std::lock_guard ___{device->EffectLock}; + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *effect{value ? LookupEffect(device, static_cast(value)) : nullptr}; if(effect) - err = slot->initEffect(effect->type, effect->Props, context.get()); + err = slot->initEffect(effect->id, effect->type, effect->Props, context); else { if(value != 0) - return context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", value); - err = slot->initEffect(AL_EFFECT_NULL, EffectProps{}, context.get()); + throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %u", value}; + err = slot->initEffect(0, AL_EFFECT_NULL, EffectProps{}, context); } } - if(err != AL_NO_ERROR) UNLIKELY - { - context->setError(err, "Effect initialization failed"); - return; - } + if(err != AL_NO_ERROR) + throw al::context_error{err, "Effect initialization failed"}; + if(slot->mState == SlotState::Initial) UNLIKELY { slot->mPropsDirty = false; - slot->updateProps(context.get()); + slot->updateProps(context); - AddActiveEffectSlots({&slot, 1}, context.get()); + AddActiveEffectSlots({&slot, 1}, context); slot->mState = SlotState::Playing; return; } - break; + UpdateProps(slot, context); + return; case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: if(!(value == AL_TRUE || value == AL_FALSE)) - return context->setError(AL_INVALID_VALUE, - "Effect slot auxiliary send auto out of range"); - if(slot->AuxSendAuto == !!value) UNLIKELY - return; - slot->AuxSendAuto = !!value; - break; + throw al::context_error{AL_INVALID_VALUE, + "Effect slot auxiliary send auto out of range"}; + if(!(slot->AuxSendAuto == !!value)) LIKELY + { + slot->AuxSendAuto = !!value; + UpdateProps(slot, context); + } + return; case AL_EFFECTSLOT_TARGET_SOFT: - target = LookupEffectSlot(context.get(), static_cast(value)); + target = LookupEffectSlot(context, static_cast(value)); if(value && !target) - return context->setError(AL_INVALID_VALUE, "Invalid effect slot target ID"); + throw al::context_error{AL_INVALID_VALUE, "Invalid effect slot target ID"}; if(slot->Target == target) UNLIKELY return; if(target) @@ -610,9 +532,9 @@ START_API_FUNC while(checker && checker != slot) checker = checker->Target; if(checker) - return context->setError(AL_INVALID_OPERATION, + throw al::context_error{AL_INVALID_OPERATION, "Setting target of effect slot ID %u to %u creates circular chain", slot->id, - target->id); + target->id}; } if(ALeffectslot *oldtarget{slot->Target}) @@ -623,39 +545,77 @@ START_API_FUNC if(target) IncrementRef(target->ref); DecrementRef(oldtarget->ref); slot->Target = target; - slot->updateProps(context.get()); + slot->updateProps(context); return; } if(target) IncrementRef(target->ref); slot->Target = target; - break; + UpdateProps(slot, context); + return; case AL_BUFFER: - device = context->mALDevice.get(); - - if(slot->mState == SlotState::Playing) - return context->setError(AL_INVALID_OPERATION, - "Setting buffer on playing effect slot %u", slot->id); - if(ALbuffer *buffer{slot->Buffer}) { - if(buffer->id == static_cast(value)) UNLIKELY + if(buffer->id == static_cast(value)) return; } - else if(value == 0) UNLIKELY + else if(value == 0) return; + if(slot->mState == SlotState::Playing) { - std::lock_guard ___{device->BufferLock}; + EffectStateFactory *factory{getFactoryByType(slot->Effect.Type)}; + assert(factory); + al::intrusive_ptr state{factory->create()}; + + ALCdevice *device{context->mALDevice.get()}; + auto bufferlock = std::unique_lock{device->BufferLock}; ALbuffer *buffer{}; if(value) { buffer = LookupBuffer(device, static_cast(value)); - if(!buffer) return context->setError(AL_INVALID_VALUE, "Invalid buffer ID"); + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; if(buffer->mCallback) - return context->setError(AL_INVALID_OPERATION, - "Callback buffer not valid for effects"); + throw al::context_error{AL_INVALID_OPERATION, + "Callback buffer not valid for effects"}; + + IncrementRef(buffer->ref); + } + + /* Stop the effect slot from processing while we switch buffers. */ + RemoveActiveEffectSlots({&slot, 1}, context); + + if(ALbuffer *oldbuffer{slot->Buffer}) + DecrementRef(oldbuffer->ref); + slot->Buffer = buffer; + bufferlock.unlock(); + + state->mOutTarget = device->Dry.Buffer; + { + FPUCtl mixer_mode{}; + state->deviceUpdate(device, buffer); + } + slot->Effect.State = std::move(state); + + slot->mPropsDirty = false; + slot->updateProps(context); + AddActiveEffectSlots({&slot, 1}, context); + } + else + { + ALCdevice *device{context->mALDevice.get()}; + auto bufferlock = std::unique_lock{device->BufferLock}; + ALbuffer *buffer{}; + if(value) + { + buffer = LookupBuffer(device, static_cast(value)); + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %u", value}; + if(buffer->mCallback) + throw al::context_error{AL_INVALID_OPERATION, + "Callback buffer not valid for effects"}; IncrementRef(buffer->ref); } @@ -663,27 +623,29 @@ START_API_FUNC if(ALbuffer *oldbuffer{slot->Buffer}) DecrementRef(oldbuffer->ref); slot->Buffer = buffer; + bufferlock.unlock(); FPUCtl mixer_mode{}; auto *state = slot->Effect.State.get(); state->deviceUpdate(device, buffer); + slot->mPropsDirty = true; } - break; + return; case AL_EFFECTSLOT_STATE_SOFT: - return context->setError(AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"); - - default: - return context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", - param); + throw al::context_error{AL_INVALID_OPERATION, "AL_EFFECTSLOT_STATE_SOFT is read-only"}; } - UpdateProps(slot, context.get()); -} -END_API_FUNC -AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *values) -START_API_FUNC -{ + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, + ALenum param, const ALint *values) noexcept +try { switch(param) { case AL_EFFECTSLOT_EFFECT: @@ -691,129 +653,130 @@ START_API_FUNC case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_STATE_SOFT: case AL_BUFFER: - alAuxiliaryEffectSloti(effectslot, param, values[0]); + alAuxiliaryEffectSlotiDirect(context, effectslot, param, *values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { - default: - return context->setError(AL_INVALID_ENUM, - "Invalid effect slot integer-vector property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, + ALenum param, ALfloat value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { case AL_EFFECTSLOT_GAIN: if(!(value >= 0.0f && value <= 1.0f)) - return context->setError(AL_INVALID_VALUE, "Effect slot gain out of range"); - if(slot->Gain == value) UNLIKELY - return; - slot->Gain = value; - break; - - default: - return context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", - param); - } - UpdateProps(slot, context.get()); -} -END_API_FUNC - -AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_GAIN: - alAuxiliaryEffectSlotf(effectslot, param, values[0]); + throw al::context_error{AL_INVALID_VALUE, "Effect slot gain out of range"}; + if(!(slot->Gain == value)) LIKELY + { + slot->Gain = value; + UpdateProps(slot, context); + } return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); - - switch(param) - { - default: - return context->setError(AL_INVALID_ENUM, - "Invalid effect slot float-vector property 0x%04x", param); - } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC +AL_API DECL_FUNC3(void, alAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, + ALenum param, const ALfloat *values) noexcept +try { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alAuxiliaryEffectSlotfDirect(context, effectslot, param, *values); + return; + } -AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSloti, ALuint,effectslot, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALint *value) noexcept +try { + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; + + switch(param) + { + case AL_EFFECTSLOT_EFFECT: + *value = static_cast(slot->EffectId); + return; + case AL_EFFECTSLOT_AUXILIARY_SEND_AUTO: *value = slot->AuxSendAuto ? AL_TRUE : AL_FALSE; - break; + return; case AL_EFFECTSLOT_TARGET_SOFT: if(auto *target = slot->Target) *value = static_cast(target->id); else *value = 0; - break; + return; case AL_EFFECTSLOT_STATE_SOFT: *value = static_cast(slot->mState); - break; + return; case AL_BUFFER: if(auto *buffer = slot->Buffer) *value = static_cast(buffer->id); else *value = 0; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param); + return; } -} -END_API_FUNC -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *values) -START_API_FUNC -{ + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotiv, ALuint,effectslot, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALint *values) noexcept +try { switch(param) { case AL_EFFECTSLOT_EFFECT: @@ -821,76 +784,72 @@ START_API_FUNC case AL_EFFECTSLOT_TARGET_SOFT: case AL_EFFECTSLOT_STATE_SOFT: case AL_BUFFER: - alGetAuxiliaryEffectSloti(effectslot, param, values); + alGetAuxiliaryEffectSlotiDirect(context, effectslot, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot = LookupEffectSlot(context, effectslot); + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", - param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot integer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotf, ALuint,effectslot, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALfloat *value) noexcept +try { + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { case AL_EFFECTSLOT_GAIN: *value = slot->Gain; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECTSLOT_GAIN: - alGetAuxiliaryEffectSlotf(effectslot, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} - std::lock_guard _{context->mEffectSlotLock}; - ALeffectslot *slot = LookupEffectSlot(context.get(), effectslot); - if(!slot) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot); +AL_API DECL_FUNC3(void, alGetAuxiliaryEffectSlotfv, ALuint,effectslot, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, + ALuint effectslot, ALenum param, ALfloat *values) noexcept +try { + switch(param) + { + case AL_EFFECTSLOT_GAIN: + alGetAuxiliaryEffectSlotfDirect(context, effectslot, param, values); + return; + } + + std::lock_guard slotlock{context->mEffectSlotLock}; + ALeffectslot *slot{LookupEffectSlot(context, effectslot)}; + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", effectslot}; switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", - param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid effect slot float-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC ALeffectslot::ALeffectslot(ALCcontext *context) @@ -915,18 +874,14 @@ ALeffectslot::~ALeffectslot() DecrementRef(Buffer->ref); Buffer = nullptr; - if(EffectSlotProps *props{mSlot->Update.exchange(nullptr)}) - { - TRACE("Freed unapplied AuxiliaryEffectSlot update %p\n", - decltype(std::declval()){props}); - delete props; - } + if(auto *slot = mSlot->Update.exchange(nullptr, std::memory_order_relaxed)) + slot->State = nullptr; mSlot->mEffectState = nullptr; mSlot->InUse = false; } -ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProps, +ALenum ALeffectslot::initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, ALCcontext *context) { EffectSlotType newtype{EffectSlotTypeFromEnum(effectType)}; @@ -941,7 +896,6 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp al::intrusive_ptr state{factory->create()}; ALCdevice *device{context->mALDevice.get()}; - std::unique_lock statelock{device->StateLock}; state->mOutTarget = device->Dry.Buffer; { FPUCtl mixer_mode{}; @@ -955,9 +909,10 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp } else if(newtype != EffectSlotType::None) Effect.Props = effectProps; + EffectId = effectId; /* Remove state references from old effect slot property updates. */ - EffectSlotProps *props{context->mFreeEffectslotProps.load()}; + EffectSlotProps *props{context->mFreeEffectSlotProps.load()}; while(props) { props->State = nullptr; @@ -967,20 +922,20 @@ ALenum ALeffectslot::initEffect(ALenum effectType, const EffectProps &effectProp return AL_NO_ERROR; } -void ALeffectslot::updateProps(ALCcontext *context) +void ALeffectslot::updateProps(ALCcontext *context) const { /* Get an unused property container, or allocate a new one as needed. */ - EffectSlotProps *props{context->mFreeEffectslotProps.load(std::memory_order_relaxed)}; + EffectSlotProps *props{context->mFreeEffectSlotProps.load(std::memory_order_acquire)}; if(!props) - props = new EffectSlotProps{}; - else { - EffectSlotProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->mFreeEffectslotProps.compare_exchange_weak(props, next, - std::memory_order_seq_cst, std::memory_order_acquire) == 0); + context->allocEffectSlotProps(); + props = context->mFreeEffectSlotProps.load(std::memory_order_acquire); } + EffectSlotProps *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(!context->mFreeEffectSlotProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire)); /* Copy in current property values. */ props->Gain = Gain; @@ -999,39 +954,53 @@ void ALeffectslot::updateProps(ALCcontext *context) * freelist. */ props->State = nullptr; - AtomicReplaceHead(context->mFreeEffectslotProps, props); + AtomicReplaceHead(context->mFreeEffectSlotProps, props); } } +void ALeffectslot::SetName(ALCcontext* context, ALuint id, std::string_view name) +{ + std::lock_guard slotlock{context->mEffectSlotLock}; + + auto slot = LookupEffectSlot(context, id); + if(!slot) + throw al::context_error{AL_INVALID_NAME, "Invalid effect slot ID %u", id}; + + context->mEffectSlotNames.insert_or_assign(id, name); +} + void UpdateAllEffectSlotProps(ALCcontext *context) { - std::lock_guard _{context->mEffectSlotLock}; + std::lock_guard slotlock{context->mEffectSlotLock}; for(auto &sublist : context->mEffectSlotList) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; + const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); - ALeffectslot *slot{sublist.EffectSlots + idx}; + auto &slot = (*sublist.EffectSlots)[idx]; - if(slot->mState != SlotState::Stopped && std::exchange(slot->mPropsDirty, false)) - slot->updateProps(context); + if(std::exchange(slot.mPropsDirty, false)) + slot.updateProps(context); } } } EffectSlotSubList::~EffectSlotSubList() { + if(!EffectSlots) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(EffectSlots+idx); + std::destroy_at(al::to_address(EffectSlots->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(EffectSlots); + SubListAllocator{}.deallocate(EffectSlots, 1); EffectSlots = nullptr; } @@ -1182,7 +1151,7 @@ void ALeffectslot::eax_fx_slot_set_defaults() eax_df_ = EaxDirtyFlags{}; } -void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const +void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) { switch(call.get_property_id()) { @@ -1206,7 +1175,7 @@ void ALeffectslot::eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) } } -void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const +void ALeffectslot::eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) { switch(call.get_property_id()) { @@ -1272,7 +1241,7 @@ void ALeffectslot::eax_fx_slot_load_effect(int version, ALenum altype) void ALeffectslot::eax_fx_slot_set_volume() { - const auto volume = clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); + const auto volume = std::clamp(eax_.lVolume, EAXFXSLOT_MINVOLUME, EAXFXSLOT_MAXVOLUME); const auto gain = level_mb_to_gain(static_cast(volume)); eax_set_efx_slot_gain(gain); } @@ -1316,15 +1285,12 @@ void ALeffectslot::eax5_fx_slot_set_all(const EaxCall& call) bool ALeffectslot::eax_fx_slot_should_update_sources() const noexcept { - const auto dirty_bits = + static constexpr auto dirty_bits = eax_occlusion_dirty_bit | eax_occlusion_lf_ratio_dirty_bit | eax_flags_dirty_bit; - if((eax_df_ & dirty_bits) != EaxDirtyFlags{}) - return true; - - return false; + return (eax_df_ & dirty_bits) != EaxDirtyFlags{}; } // Returns `true` if all sources should be updated, or `false` otherwise. @@ -1470,7 +1436,8 @@ void ALeffectslot::eax_set_efx_slot_effect(EaxEffect &effect) { #define EAX_PREFIX "[EAX_SET_EFFECT_SLOT_EFFECT] " - const auto error = initEffect(effect.al_effect_type_, effect.al_effect_props_, eax_al_context_); + const auto error = initEffect(0, effect.al_effect_type_, effect.al_effect_props_, + eax_al_context_); if(error != AL_NO_ERROR) { ERR(EAX_PREFIX "%s\n", "Failed to initialize an effect."); @@ -1509,7 +1476,7 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) if(gain < 0.0f || gain > 1.0f) ERR(EAX_PREFIX "Gain out of range (%f)\n", gain); - Gain = clampf(gain, 0.0f, 1.0f); + Gain = std::clamp(gain, 0.0f, 1.0f); mPropsDirty = true; #undef EAX_PREFIX @@ -1517,7 +1484,6 @@ void ALeffectslot::eax_set_efx_slot_gain(ALfloat gain) void ALeffectslot::EaxDeleter::operator()(ALeffectslot* effect_slot) { - assert(effect_slot); eax_delete_al_effect_slot(*effect_slot->eax_al_context_, *effect_slot); } @@ -1525,7 +1491,7 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context) { #define EAX_PREFIX "[EAX_MAKE_EFFECT_SLOT] " - std::unique_lock effect_slot_lock{context.mEffectSlotLock}; + std::lock_guard slotlock{context.mEffectSlotLock}; auto& device = *context.mALDevice; if(context.mNumEffectSlots == device.AuxiliaryEffectSlotMax) { @@ -1547,9 +1513,10 @@ void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot) { #define EAX_PREFIX "[EAX_DELETE_EFFECT_SLOT] " - std::lock_guard effect_slot_lock{context.mEffectSlotLock}; + std::lock_guard slotlock{context.mEffectSlotLock}; - if(ReadRef(effect_slot.ref) != 0) { + if(effect_slot.ref.load(std::memory_order_relaxed) != 0) + { ERR(EAX_PREFIX "Deleting in-use effect slot %u.\n", effect_slot.id); return; } diff --git a/Engine/lib/openal-soft/al/auxeffectslot.h b/Engine/lib/openal-soft/al/auxeffectslot.h index 3e9a2a4e5..5f9913a02 100644 --- a/Engine/lib/openal-soft/al/auxeffectslot.h +++ b/Engine/lib/openal-soft/al/auxeffectslot.h @@ -1,23 +1,24 @@ #ifndef AL_AUXEFFECTSLOT_H #define AL_AUXEFFECTSLOT_H +#include #include -#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" -#include "AL/efx.h" -#include "alc/device.h" -#include "alc/effects/base.h" #include "almalloc.h" -#include "atomic.h" +#include "alnumeric.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX #include +#include "eax/api.h" #include "eax/call.h" #include "eax/effect.h" #include "eax/exception.h" @@ -26,8 +27,6 @@ #endif // ALSOFT_EAX struct ALbuffer; -struct ALeffect; -struct WetBuffer; #ifdef ALSOFT_EAX class EaxFxSlotException : public EaxException { @@ -38,30 +37,30 @@ public: }; #endif // ALSOFT_EAX -enum class SlotState : ALenum { - Initial = AL_INITIAL, - Playing = AL_PLAYING, - Stopped = AL_STOPPED, +enum class SlotState : bool { + Initial, Playing, }; struct ALeffectslot { + ALuint EffectId{}; float Gain{1.0f}; bool AuxSendAuto{true}; ALeffectslot *Target{nullptr}; ALbuffer *Buffer{nullptr}; - struct { + struct EffectData { EffectSlotType Type{EffectSlotType::None}; EffectProps Props{}; al::intrusive_ptr State; - } Effect; + }; + EffectData Effect; bool mPropsDirty{true}; SlotState mState{SlotState::Initial}; - RefCount ref{0u}; + std::atomic ref{0u}; EffectSlot *mSlot{nullptr}; @@ -73,23 +72,23 @@ struct ALeffectslot { ALeffectslot& operator=(const ALeffectslot&) = delete; ~ALeffectslot(); - ALenum initEffect(ALenum effectType, const EffectProps &effectProps, ALCcontext *context); - void updateProps(ALCcontext *context); + ALenum initEffect(ALuint effectId, ALenum effectType, const EffectProps &effectProps, + ALCcontext *context); + void updateProps(ALCcontext *context) const; - /* This can be new'd for the context's default effect slot. */ - DEF_NEWDEL(ALeffectslot) + static void SetName(ALCcontext *context, ALuint id, std::string_view name); #ifdef ALSOFT_EAX public: void eax_initialize(ALCcontext& al_context, EaxFxSlotIndexValue index); - EaxFxSlotIndexValue eax_get_index() const noexcept { return eax_fx_slot_index_; } - const EAX50FXSLOTPROPERTIES& eax_get_eax_fx_slot() const noexcept + [[nodiscard]] auto eax_get_index() const noexcept -> EaxFxSlotIndexValue { return eax_fx_slot_index_; } + [[nodiscard]] auto eax_get_eax_fx_slot() const noexcept -> const EAX50FXSLOTPROPERTIES& { return eax_; } // Returns `true` if all sources should be updated, or `false` otherwise. - bool eax_dispatch(const EaxCall& call) + [[nodiscard]] auto eax_dispatch(const EaxCall& call) -> bool { return call.is_get() ? eax_get(call) : eax_set(call); } void eax_commit(); @@ -193,6 +192,17 @@ private: } }; + struct Eax5FlagsValidator { + void operator()(unsigned long ulFlags) const + { + EaxRangeValidator{}( + "Flags", + ulFlags, + 0UL, + ~EAX50FXSLOTFLAGS_RESERVED); + } + }; + struct Eax5OcclusionValidator { void operator()(long lOcclusion) const { @@ -215,21 +225,13 @@ private: } }; - struct Eax5FlagsValidator { - void operator()(unsigned long ulFlags) const - { - EaxRangeValidator{}( - "Flags", - ulFlags, - 0UL, - ~EAX50FXSLOTFLAGS_RESERVED); - } - }; - struct Eax5AllValidator { void operator()(const EAX50FXSLOTPROPERTIES& all) const { - Eax4AllValidator{}(static_cast(all)); + Eax4GuidLoadEffectValidator{}(all.guidLoadEffect); + Eax4VolumeValidator{}(all.lVolume); + Eax4LockValidator{}(all.lLock); + Eax5FlagsValidator{}(all.ulFlags); Eax5OcclusionValidator{}(all.lOcclusion); Eax5OcclusionLfRatioValidator{}(all.flOcclusionLFRatio); } @@ -277,14 +279,14 @@ private: dst = src; } - constexpr bool eax4_fx_slot_is_legacy() const noexcept + [[nodiscard]] constexpr auto eax4_fx_slot_is_legacy() const noexcept -> bool { return eax_fx_slot_index_ < 2; } void eax4_fx_slot_ensure_unlocked() const; - static ALenum eax_get_efx_effect_type(const GUID& guid); - const GUID& eax_get_eax_default_effect_guid() const noexcept; - long eax_get_eax_default_lock() const noexcept; + [[nodiscard]] static auto eax_get_efx_effect_type(const GUID& guid) -> ALenum; + [[nodiscard]] auto eax_get_eax_default_effect_guid() const noexcept -> const GUID&; + [[nodiscard]] auto eax_get_eax_default_lock() const noexcept -> long; void eax4_fx_slot_set_defaults(Eax4Props& props) noexcept; void eax5_fx_slot_set_defaults(Eax5Props& props) noexcept; @@ -293,8 +295,8 @@ private: void eax_fx_slot_set_current_defaults(); void eax_fx_slot_set_defaults(); - void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props) const; - void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props) const; + static void eax4_fx_slot_get(const EaxCall& call, const Eax4Props& props); + static void eax5_fx_slot_get(const EaxCall& call, const Eax5Props& props); void eax_fx_slot_get(const EaxCall& call) const; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax_get(const EaxCall& call); @@ -307,7 +309,7 @@ private: void eax4_fx_slot_set_all(const EaxCall& call); void eax5_fx_slot_set_all(const EaxCall& call); - bool eax_fx_slot_should_update_sources() const noexcept; + [[nodiscard]] auto eax_fx_slot_should_update_sources() const noexcept -> bool; // Returns `true` if all sources should be updated, or `false` otherwise. bool eax4_fx_slot_set(const EaxCall& call); @@ -365,4 +367,20 @@ EaxAlEffectSlotUPtr eax_create_al_effect_slot(ALCcontext& context); void eax_delete_al_effect_slot(ALCcontext& context, ALeffectslot& effect_slot); #endif // ALSOFT_EAX +struct EffectSlotSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> EffectSlots{nullptr}; + + EffectSlotSubList() noexcept = default; + EffectSlotSubList(const EffectSlotSubList&) = delete; + EffectSlotSubList(EffectSlotSubList&& rhs) noexcept + : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} + { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } + ~EffectSlotSubList(); + + EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; + EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/buffer.cpp b/Engine/lib/openal-soft/al/buffer.cpp index ee5065968..4812e415a 100644 --- a/Engine/lib/openal-soft/al/buffer.cpp +++ b/Engine/lib/openal-soft/al/buffer.cpp @@ -26,37 +26,43 @@ #include #include #include +#include #include -#include #include #include #include #include #include -#include #include +#include #include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "albit.h" -#include "albyte.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" -#include "atomic.h" -#include "core/except.h" -#include "core/logging.h" +#include "alspan.h" +#include "core/device.h" +#include "core/resampler_limits.h" #include "core/voice.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #ifdef ALSOFT_EAX +#include + #include "eax/globals.h" #include "eax/x_ram.h" #endif // ALSOFT_EAX @@ -64,16 +70,18 @@ namespace { -al::optional AmbiLayoutFromEnum(ALenum layout) +using SubListAllocator = al::allocator>; + +constexpr auto AmbiLayoutFromEnum(ALenum layout) noexcept -> std::optional { switch(layout) { case AL_FUMA_SOFT: return AmbiLayout::FuMa; case AL_ACN_SOFT: return AmbiLayout::ACN; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromAmbiLayout(AmbiLayout layout) +constexpr auto EnumFromAmbiLayout(AmbiLayout layout) -> ALenum { switch(layout) { @@ -83,7 +91,7 @@ ALenum EnumFromAmbiLayout(AmbiLayout layout) throw std::runtime_error{"Invalid AmbiLayout: "+std::to_string(int(layout))}; } -al::optional AmbiScalingFromEnum(ALenum scale) +constexpr auto AmbiScalingFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) { @@ -91,9 +99,9 @@ al::optional AmbiScalingFromEnum(ALenum scale) case AL_SN3D_SOFT: return AmbiScaling::SN3D; case AL_N3D_SOFT: return AmbiScaling::N3D; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromAmbiScaling(AmbiScaling scale) +constexpr auto EnumFromAmbiScaling(AmbiScaling scale) -> ALenum { switch(scale) { @@ -106,7 +114,7 @@ ALenum EnumFromAmbiScaling(AmbiScaling scale) } #ifdef ALSOFT_EAX -al::optional EaxStorageFromEnum(ALenum scale) +constexpr auto EaxStorageFromEnum(ALenum scale) noexcept -> std::optional { switch(scale) { @@ -114,9 +122,9 @@ al::optional EaxStorageFromEnum(ALenum scale) case AL_STORAGE_ACCESSIBLE: return EaxStorage::Accessible; case AL_STORAGE_HARDWARE: return EaxStorage::Hardware; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromEaxStorage(EaxStorage storage) +constexpr auto EnumFromEaxStorage(EaxStorage storage) -> ALenum { switch(storage) { @@ -152,7 +160,7 @@ void eax_x_ram_apply(ALCdevice &device, ALbuffer &buffer) noexcept } } -void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) +void eax_x_ram_clear(ALCdevice& al_device, ALbuffer& al_buffer) noexcept { if(al_buffer.eax_x_ram_is_hardware) al_device.eax_x_ram_free_size += al_buffer.OriginalSize; @@ -168,9 +176,9 @@ constexpr ALbitfieldSOFT INVALID_MAP_FLAGS{~unsigned(AL_MAP_READ_BIT_SOFT | AL_M AL_MAP_PERSISTENT_BIT_SOFT)}; -bool EnsureBuffers(ALCdevice *device, size_t needed) -{ - size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), size_t{0}, +auto EnsureBuffers(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->BufferList.cbegin(), device->BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -179,21 +187,19 @@ bool EnsureBuffers(ALCdevice *device, size_t needed) if(device->BufferList.size() >= 1<<25) UNLIKELY return false; - device->BufferList.emplace_back(); - auto sublist = device->BufferList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Buffers = static_cast(al_calloc(alignof(ALbuffer), sizeof(ALbuffer)*64)); - if(!sublist->Buffers) UNLIKELY - { - device->BufferList.pop_back(); - return false; - } - count += 64; + BufferSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Buffers = SubListAllocator{}.allocate(1); + device->BufferList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALbuffer *AllocBuffer(ALCdevice *device) +ALbuffer *AllocBuffer(ALCdevice *device) noexcept { auto sublist = std::find_if(device->BufferList.begin(), device->BufferList.end(), [](const BufferSubList &entry) noexcept -> bool @@ -202,7 +208,7 @@ ALbuffer *AllocBuffer(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALbuffer *buffer{al::construct_at(sublist->Buffers + slidx)}; + ALbuffer *buffer{al::construct_at(al::to_address(sublist->Buffers->begin() + slidx))}; /* Add 1 to avoid buffer ID 0. */ buffer->id = ((lidx<<6) | slidx) + 1; @@ -218,16 +224,18 @@ void FreeBuffer(ALCdevice *device, ALbuffer *buffer) eax_x_ram_clear(*device, *buffer); #endif // ALSOFT_EAX + device->mBufferNames.erase(buffer->id); + const ALuint id{buffer->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(buffer); + std::destroy_at(buffer); device->BufferList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) +auto LookupBuffer(ALCdevice *device, ALuint id) noexcept -> ALbuffer* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -237,11 +245,11 @@ inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) BufferSubList &sublist = device->BufferList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; + return al::to_address(sublist.Buffers->begin() + slidx); } -ALuint SanitizeAlignment(FmtType type, ALuint align) +constexpr auto SanitizeAlignment(FmtType type, ALuint align) noexcept -> ALuint { if(align == 0) { @@ -276,19 +284,19 @@ ALuint SanitizeAlignment(FmtType type, ALuint align) /** Loads the specified data into the buffer, using the specified format. */ -void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, - const FmtChannels DstChannels, const FmtType DstType, const al::byte *SrcData, +void LoadData(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, ALuint size, + const FmtChannels DstChannels, const FmtType DstType, const std::byte *SrcData, ALbitfieldSOFT access) { - if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id); + if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", + ALBuf->id}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; - if(align < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)); + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromFormat(DstType)}; const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -296,12 +304,12 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, if((access&AL_PRESERVE_DATA_BIT_SOFT)) { /* Can only preserve data with the same format and alignment. */ - if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched format"); - if(ALBuf->mBlockAlign != align) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched alignment"); - if(ALBuf->mAmbiOrder != ambiorder) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Preserving data of mismatched order"); + if(ALBuf->mChannels != DstChannels || ALBuf->mType != DstType) + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched format"}; + if(ALBuf->mBlockAlign != align) + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched alignment"}; + if(ALBuf->mAmbiOrder != ambiorder) + throw al::context_error{AL_INVALID_VALUE, "Preserving data of mismatched order"}; } /* Convert the size in bytes to blocks using the unpack block alignment. */ @@ -310,18 +318,18 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; - if((size%BlockSize) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + if((size%BlockSize) != 0) + throw al::context_error{AL_INVALID_VALUE, "Data size %d is not a multiple of frame size %d (%d unpack alignment)", - size, BlockSize, align); + size, BlockSize, align}; const ALuint blocks{size / BlockSize}; - if(blocks > std::numeric_limits::max()/align) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align); - if(blocks > std::numeric_limits::max()/BlockSize) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize); + if(blocks > std::numeric_limits::max()/align) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + if(blocks > std::numeric_limits::max()/BlockSize) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; const size_t newsize{static_cast(blocks) * BlockSize}; @@ -330,8 +338,8 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, { ALCdevice &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, size)) - return context->setError(AL_OUT_OF_MEMORY, - "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size); + throw al::context_error{AL_OUT_OF_MEMORY, + "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, size}; } #endif @@ -343,10 +351,10 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, */ if(newsize != ALBuf->mDataStorage.size()) { - auto newdata = al::vector(newsize, al::byte{}); + auto newdata = decltype(ALBuf->mDataStorage)(newsize, std::byte{}); if((access&AL_PRESERVE_DATA_BIT_SOFT)) { - const size_t tocopy{minz(newdata.size(), ALBuf->mDataStorage.size())}; + const size_t tocopy{std::min(newdata.size(), ALBuf->mDataStorage.size())}; std::copy_n(ALBuf->mDataStorage.begin(), tocopy, newdata.begin()); } newdata.swap(ALBuf->mDataStorage); @@ -383,19 +391,23 @@ void LoadData(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ALuint size, } /** Prepares the buffer to use the specified callback, using the specified format. */ -void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, +void PrepareCallback(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, const FmtChannels DstChannels, const FmtType DstType, ALBUFFERCALLBACKTYPESOFT callback, void *userptr) { - if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u", - ALBuf->id); + if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Modifying callback for in-use buffer %u", + ALBuf->id}; const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromFormat(DstType)}; + const ALuint BlockSize{ChannelsFromFmt(DstChannels, ambiorder) * ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : @@ -436,18 +448,18 @@ void PrepareCallback(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, } /** Prepares the buffer to use caller-specified storage. */ -void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, - const FmtChannels DstChannels, const FmtType DstType, al::byte *sdata, const ALuint sdatalen) +void PrepareUserPtr(ALCcontext *context [[maybe_unused]], ALbuffer *ALBuf, ALsizei freq, + const FmtChannels DstChannels, const FmtType DstType, std::byte *sdata, const ALuint sdatalen) { - if(ReadRef(ALBuf->ref) != 0 || ALBuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", - ALBuf->id); + if(ALBuf->ref.load(std::memory_order_relaxed) != 0 || ALBuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Modifying storage for in-use buffer %u", + ALBuf->id}; const ALuint unpackalign{ALBuf->UnpackAlign}; const ALuint align{SanitizeAlignment(DstType, unpackalign)}; - if(align < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", - unpackalign, NameFromFormat(DstType)); + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u for %s samples", + unpackalign, NameFromFormat(DstType)}; auto get_type_alignment = [](const FmtType type) noexcept -> ALuint { @@ -458,6 +470,7 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, { case FmtUByte: return alignof(ALubyte); case FmtShort: return alignof(ALshort); + case FmtInt: return alignof(ALint); case FmtFloat: return alignof(ALfloat); case FmtDouble: return alignof(ALdouble); case FmtMulaw: return alignof(ALubyte); @@ -469,8 +482,8 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, }; const auto typealign = get_type_alignment(DstType); if((reinterpret_cast(sdata) & (typealign-1)) != 0) - return context->setError(AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)", - static_cast(sdata), NameFromFormat(DstType), typealign); + throw al::context_error{AL_INVALID_VALUE, "Pointer %p is misaligned for %s samples (%u)", + static_cast(sdata), NameFromFormat(DstType), typealign}; const ALuint ambiorder{IsBFormat(DstChannels) ? ALBuf->UnpackAmbiOrder : (IsUHJ(DstChannels) ? 1 : 0)}; @@ -481,32 +494,32 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, ((DstType == FmtIMA4) ? (align-1)/2 + 4 : (DstType == FmtMSADPCM) ? (align-2)/2 + 7 : (align * BytesFromFmt(DstType)))}; - if((sdatalen%BlockSize) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + if((sdatalen%BlockSize) != 0) + throw al::context_error{AL_INVALID_VALUE, "Data size %u is not a multiple of frame size %u (%u unpack alignment)", - sdatalen, BlockSize, align); + sdatalen, BlockSize, align}; const ALuint blocks{sdatalen / BlockSize}; - if(blocks > std::numeric_limits::max()/align) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d blocks x %d samples per block", blocks, align); - if(blocks > std::numeric_limits::max()/BlockSize) UNLIKELY - return context->setError(AL_OUT_OF_MEMORY, - "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize); + if(blocks > std::numeric_limits::max()/align) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d blocks x %d samples per block", blocks, align}; + if(blocks > std::numeric_limits::max()/BlockSize) + throw al::context_error{AL_OUT_OF_MEMORY, + "Buffer size overflow, %d frames x %d bytes per frame", blocks, BlockSize}; #ifdef ALSOFT_EAX if(ALBuf->eax_x_ram_mode == EaxStorage::Hardware) { ALCdevice &device = *context->mALDevice; if(!eax_x_ram_check_availability(device, *ALBuf, sdatalen)) - return context->setError(AL_OUT_OF_MEMORY, + throw al::context_error{AL_OUT_OF_MEMORY, "Out of X-RAM memory (avail: %u, needed: %u)", device.eax_x_ram_free_size, - sdatalen); + sdatalen}; } #endif decltype(ALBuf->mDataStorage){}.swap(ALBuf->mDataStorage); - ALBuf->mData = {static_cast(sdata), sdatalen}; + ALBuf->mData = {static_cast(sdata), sdatalen}; #ifdef ALSOFT_EAX eax_x_ram_clear(*context->mALDevice, *ALBuf); @@ -536,412 +549,381 @@ void PrepareUserPtr(ALCcontext *context, ALbuffer *ALBuf, ALsizei freq, struct DecompResult { FmtChannels channels; FmtType type; }; -al::optional DecomposeUserFormat(ALenum format) +auto DecomposeUserFormat(ALenum format) noexcept -> std::optional { struct FormatMap { ALenum format; - FmtChannels channels; - FmtType type; + DecompResult result; }; - static const std::array UserFmtList{{ - { AL_FORMAT_MONO8, FmtMono, FmtUByte }, - { AL_FORMAT_MONO16, FmtMono, FmtShort }, - { AL_FORMAT_MONO_FLOAT32, FmtMono, FmtFloat }, - { AL_FORMAT_MONO_DOUBLE_EXT, FmtMono, FmtDouble }, - { AL_FORMAT_MONO_IMA4, FmtMono, FmtIMA4 }, - { AL_FORMAT_MONO_MSADPCM_SOFT, FmtMono, FmtMSADPCM }, - { AL_FORMAT_MONO_MULAW, FmtMono, FmtMulaw }, - { AL_FORMAT_MONO_ALAW_EXT, FmtMono, FmtAlaw }, + static constexpr std::array UserFmtList{ + FormatMap{AL_FORMAT_MONO8, {FmtMono, FmtUByte} }, + FormatMap{AL_FORMAT_MONO16, {FmtMono, FmtShort} }, + FormatMap{AL_FORMAT_MONO_I32, {FmtMono, FmtInt} }, + FormatMap{AL_FORMAT_MONO_FLOAT32, {FmtMono, FmtFloat} }, + FormatMap{AL_FORMAT_MONO_DOUBLE_EXT, {FmtMono, FmtDouble} }, + FormatMap{AL_FORMAT_MONO_IMA4, {FmtMono, FmtIMA4} }, + FormatMap{AL_FORMAT_MONO_MSADPCM_SOFT, {FmtMono, FmtMSADPCM}}, + FormatMap{AL_FORMAT_MONO_MULAW, {FmtMono, FmtMulaw} }, + FormatMap{AL_FORMAT_MONO_ALAW_EXT, {FmtMono, FmtAlaw} }, - { AL_FORMAT_STEREO8, FmtStereo, FmtUByte }, - { AL_FORMAT_STEREO16, FmtStereo, FmtShort }, - { AL_FORMAT_STEREO_FLOAT32, FmtStereo, FmtFloat }, - { AL_FORMAT_STEREO_DOUBLE_EXT, FmtStereo, FmtDouble }, - { AL_FORMAT_STEREO_IMA4, FmtStereo, FmtIMA4 }, - { AL_FORMAT_STEREO_MSADPCM_SOFT, FmtStereo, FmtMSADPCM }, - { AL_FORMAT_STEREO_MULAW, FmtStereo, FmtMulaw }, - { AL_FORMAT_STEREO_ALAW_EXT, FmtStereo, FmtAlaw }, + FormatMap{AL_FORMAT_STEREO8, {FmtStereo, FmtUByte} }, + FormatMap{AL_FORMAT_STEREO16, {FmtStereo, FmtShort} }, + FormatMap{AL_FORMAT_STEREO_I32, {FmtStereo, FmtInt} }, + FormatMap{AL_FORMAT_STEREO_FLOAT32, {FmtStereo, FmtFloat} }, + FormatMap{AL_FORMAT_STEREO_DOUBLE_EXT, {FmtStereo, FmtDouble} }, + FormatMap{AL_FORMAT_STEREO_IMA4, {FmtStereo, FmtIMA4} }, + FormatMap{AL_FORMAT_STEREO_MSADPCM_SOFT, {FmtStereo, FmtMSADPCM}}, + FormatMap{AL_FORMAT_STEREO_MULAW, {FmtStereo, FmtMulaw} }, + FormatMap{AL_FORMAT_STEREO_ALAW_EXT, {FmtStereo, FmtAlaw} }, - { AL_FORMAT_REAR8, FmtRear, FmtUByte }, - { AL_FORMAT_REAR16, FmtRear, FmtShort }, - { AL_FORMAT_REAR32, FmtRear, FmtFloat }, - { AL_FORMAT_REAR_MULAW, FmtRear, FmtMulaw }, + FormatMap{AL_FORMAT_REAR8, {FmtRear, FmtUByte}}, + FormatMap{AL_FORMAT_REAR16, {FmtRear, FmtShort}}, + FormatMap{AL_FORMAT_REAR32, {FmtRear, FmtFloat}}, + FormatMap{AL_FORMAT_REAR_I32, {FmtRear, FmtInt} }, + FormatMap{AL_FORMAT_REAR_FLOAT32, {FmtRear, FmtFloat}}, + FormatMap{AL_FORMAT_REAR_MULAW, {FmtRear, FmtMulaw}}, - { AL_FORMAT_QUAD8_LOKI, FmtQuad, FmtUByte }, - { AL_FORMAT_QUAD16_LOKI, FmtQuad, FmtShort }, + FormatMap{AL_FORMAT_QUAD8_LOKI, {FmtQuad, FmtUByte}}, + FormatMap{AL_FORMAT_QUAD16_LOKI, {FmtQuad, FmtShort}}, - { AL_FORMAT_QUAD8, FmtQuad, FmtUByte }, - { AL_FORMAT_QUAD16, FmtQuad, FmtShort }, - { AL_FORMAT_QUAD32, FmtQuad, FmtFloat }, - { AL_FORMAT_QUAD_MULAW, FmtQuad, FmtMulaw }, + FormatMap{AL_FORMAT_QUAD8, {FmtQuad, FmtUByte}}, + FormatMap{AL_FORMAT_QUAD16, {FmtQuad, FmtShort}}, + FormatMap{AL_FORMAT_QUAD32, {FmtQuad, FmtFloat}}, + FormatMap{AL_FORMAT_QUAD_I32, {FmtQuad, FmtInt} }, + FormatMap{AL_FORMAT_QUAD_FLOAT32, {FmtQuad, FmtFloat}}, + FormatMap{AL_FORMAT_QUAD_MULAW, {FmtQuad, FmtMulaw}}, - { AL_FORMAT_51CHN8, FmtX51, FmtUByte }, - { AL_FORMAT_51CHN16, FmtX51, FmtShort }, - { AL_FORMAT_51CHN32, FmtX51, FmtFloat }, - { AL_FORMAT_51CHN_MULAW, FmtX51, FmtMulaw }, + FormatMap{AL_FORMAT_51CHN8, {FmtX51, FmtUByte}}, + FormatMap{AL_FORMAT_51CHN16, {FmtX51, FmtShort}}, + FormatMap{AL_FORMAT_51CHN32, {FmtX51, FmtFloat}}, + FormatMap{AL_FORMAT_51CHN_I32, {FmtX51, FmtInt} }, + FormatMap{AL_FORMAT_51CHN_FLOAT32, {FmtX51, FmtFloat}}, + FormatMap{AL_FORMAT_51CHN_MULAW, {FmtX51, FmtMulaw}}, - { AL_FORMAT_61CHN8, FmtX61, FmtUByte }, - { AL_FORMAT_61CHN16, FmtX61, FmtShort }, - { AL_FORMAT_61CHN32, FmtX61, FmtFloat }, - { AL_FORMAT_61CHN_MULAW, FmtX61, FmtMulaw }, + FormatMap{AL_FORMAT_61CHN8, {FmtX61, FmtUByte}}, + FormatMap{AL_FORMAT_61CHN16, {FmtX61, FmtShort}}, + FormatMap{AL_FORMAT_61CHN32, {FmtX61, FmtFloat}}, + FormatMap{AL_FORMAT_61CHN_I32, {FmtX61, FmtInt} }, + FormatMap{AL_FORMAT_61CHN_FLOAT32, {FmtX61, FmtFloat}}, + FormatMap{AL_FORMAT_61CHN_MULAW, {FmtX61, FmtMulaw}}, - { AL_FORMAT_71CHN8, FmtX71, FmtUByte }, - { AL_FORMAT_71CHN16, FmtX71, FmtShort }, - { AL_FORMAT_71CHN32, FmtX71, FmtFloat }, - { AL_FORMAT_71CHN_MULAW, FmtX71, FmtMulaw }, + FormatMap{AL_FORMAT_71CHN8, {FmtX71, FmtUByte}}, + FormatMap{AL_FORMAT_71CHN16, {FmtX71, FmtShort}}, + FormatMap{AL_FORMAT_71CHN32, {FmtX71, FmtFloat}}, + FormatMap{AL_FORMAT_71CHN_I32, {FmtX71, FmtInt} }, + FormatMap{AL_FORMAT_71CHN_FLOAT32, {FmtX71, FmtFloat}}, + FormatMap{AL_FORMAT_71CHN_MULAW, {FmtX71, FmtMulaw}}, - { AL_FORMAT_BFORMAT2D_8, FmtBFormat2D, FmtUByte }, - { AL_FORMAT_BFORMAT2D_16, FmtBFormat2D, FmtShort }, - { AL_FORMAT_BFORMAT2D_FLOAT32, FmtBFormat2D, FmtFloat }, - { AL_FORMAT_BFORMAT2D_MULAW, FmtBFormat2D, FmtMulaw }, + FormatMap{AL_FORMAT_BFORMAT2D_8, {FmtBFormat2D, FmtUByte}}, + FormatMap{AL_FORMAT_BFORMAT2D_16, {FmtBFormat2D, FmtShort}}, + FormatMap{AL_FORMAT_BFORMAT2D_FLOAT32, {FmtBFormat2D, FmtFloat}}, + FormatMap{AL_FORMAT_BFORMAT2D_MULAW, {FmtBFormat2D, FmtMulaw}}, - { AL_FORMAT_BFORMAT3D_8, FmtBFormat3D, FmtUByte }, - { AL_FORMAT_BFORMAT3D_16, FmtBFormat3D, FmtShort }, - { AL_FORMAT_BFORMAT3D_FLOAT32, FmtBFormat3D, FmtFloat }, - { AL_FORMAT_BFORMAT3D_MULAW, FmtBFormat3D, FmtMulaw }, + FormatMap{AL_FORMAT_BFORMAT3D_8, {FmtBFormat3D, FmtUByte}}, + FormatMap{AL_FORMAT_BFORMAT3D_16, {FmtBFormat3D, FmtShort}}, + FormatMap{AL_FORMAT_BFORMAT3D_FLOAT32, {FmtBFormat3D, FmtFloat}}, + FormatMap{AL_FORMAT_BFORMAT3D_MULAW, {FmtBFormat3D, FmtMulaw}}, - { AL_FORMAT_UHJ2CHN8_SOFT, FmtUHJ2, FmtUByte }, - { AL_FORMAT_UHJ2CHN16_SOFT, FmtUHJ2, FmtShort }, - { AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, FmtUHJ2, FmtFloat }, - { AL_FORMAT_UHJ2CHN_MULAW_SOFT, FmtUHJ2, FmtMulaw }, - { AL_FORMAT_UHJ2CHN_ALAW_SOFT, FmtUHJ2, FmtAlaw }, - { AL_FORMAT_UHJ2CHN_IMA4_SOFT, FmtUHJ2, FmtIMA4 }, - { AL_FORMAT_UHJ2CHN_MSADPCM_SOFT, FmtUHJ2, FmtMSADPCM }, + FormatMap{AL_FORMAT_UHJ2CHN8_SOFT, {FmtUHJ2, FmtUByte} }, + FormatMap{AL_FORMAT_UHJ2CHN16_SOFT, {FmtUHJ2, FmtShort} }, + FormatMap{AL_FORMAT_UHJ2CHN_I32_SOFT, {FmtUHJ2, FmtInt} }, + FormatMap{AL_FORMAT_UHJ2CHN_FLOAT32_SOFT, {FmtUHJ2, FmtFloat} }, + FormatMap{AL_FORMAT_UHJ2CHN_MULAW_SOFT, {FmtUHJ2, FmtMulaw} }, + FormatMap{AL_FORMAT_UHJ2CHN_ALAW_SOFT, {FmtUHJ2, FmtAlaw} }, + FormatMap{AL_FORMAT_UHJ2CHN_IMA4_SOFT, {FmtUHJ2, FmtIMA4} }, + FormatMap{AL_FORMAT_UHJ2CHN_MSADPCM_SOFT, {FmtUHJ2, FmtMSADPCM}}, - { AL_FORMAT_UHJ3CHN8_SOFT, FmtUHJ3, FmtUByte }, - { AL_FORMAT_UHJ3CHN16_SOFT, FmtUHJ3, FmtShort }, - { AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, FmtUHJ3, FmtFloat }, - { AL_FORMAT_UHJ3CHN_MULAW_SOFT, FmtUHJ3, FmtMulaw }, - { AL_FORMAT_UHJ3CHN_ALAW_SOFT, FmtUHJ3, FmtAlaw }, + FormatMap{AL_FORMAT_UHJ3CHN8_SOFT, {FmtUHJ3, FmtUByte}}, + FormatMap{AL_FORMAT_UHJ3CHN16_SOFT, {FmtUHJ3, FmtShort}}, + FormatMap{AL_FORMAT_UHJ3CHN_I32_SOFT, {FmtUHJ3, FmtInt} }, + FormatMap{AL_FORMAT_UHJ3CHN_FLOAT32_SOFT, {FmtUHJ3, FmtFloat}}, + FormatMap{AL_FORMAT_UHJ3CHN_MULAW_SOFT, {FmtUHJ3, FmtMulaw}}, + FormatMap{AL_FORMAT_UHJ3CHN_ALAW_SOFT, {FmtUHJ3, FmtAlaw} }, - { AL_FORMAT_UHJ4CHN8_SOFT, FmtUHJ4, FmtUByte }, - { AL_FORMAT_UHJ4CHN16_SOFT, FmtUHJ4, FmtShort }, - { AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, FmtUHJ4, FmtFloat }, - { AL_FORMAT_UHJ4CHN_MULAW_SOFT, FmtUHJ4, FmtMulaw }, - { AL_FORMAT_UHJ4CHN_ALAW_SOFT, FmtUHJ4, FmtAlaw }, - }}; + FormatMap{AL_FORMAT_UHJ4CHN8_SOFT, {FmtUHJ4, FmtUByte}}, + FormatMap{AL_FORMAT_UHJ4CHN16_SOFT, {FmtUHJ4, FmtShort}}, + FormatMap{AL_FORMAT_UHJ4CHN_I32_SOFT, {FmtUHJ4, FmtInt} }, + FormatMap{AL_FORMAT_UHJ4CHN_FLOAT32_SOFT, {FmtUHJ4, FmtFloat}}, + FormatMap{AL_FORMAT_UHJ4CHN_MULAW_SOFT, {FmtUHJ4, FmtMulaw}}, + FormatMap{AL_FORMAT_UHJ4CHN_ALAW_SOFT, {FmtUHJ4, FmtAlaw} }, + }; - for(const auto &fmt : UserFmtList) - { - if(fmt.format == format) - return al::make_optional({fmt.channels, fmt.type}); - } - return al::nullopt; + auto iter = std::find_if(UserFmtList.cbegin(), UserFmtList.cend(), + [format](const FormatMap &fmt) noexcept { return fmt.format == format; }); + if(iter != UserFmtList.cend()) + return iter->result; + return std::nullopt; } } // namespace -AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d buffers", n); +AL_API DECL_FUNC2(void, alGenBuffers, ALsizei,n, ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d buffers", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(!EnsureBuffers(device, static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, (n==1)?"":"s"); - return; - } + std::lock_guard buflock{device->BufferLock}; - if(n == 1) LIKELY - { - /* Special handling for the easy and normal case. */ - ALbuffer *buffer{AllocBuffer(device)}; - buffers[0] = buffer->id; - } - else - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector ids; - ids.reserve(static_cast(n)); - do { - ALbuffer *buffer{AllocBuffer(device)}; - ids.emplace_back(buffer->id); - } while(--n); - std::copy(ids.begin(), ids.end(), buffers); - } + const al::span bids{buffers, static_cast(n)}; + if(!EnsureBuffers(device, bids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d buffer%s", n, + (n == 1) ? "" : "s"}; + + std::generate(bids.begin(), bids.end(), [device]{ return AllocBuffer(device)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d buffers", n); +AL_API DECL_FUNC2(void, alDeleteBuffers, ALsizei,n, const ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, + const ALuint *buffers) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d buffers", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; /* First try to find any buffers that are invalid or in-use. */ - auto validate_buffer = [device, &context](const ALuint bid) -> bool + auto validate_buffer = [device](const ALuint bid) { - if(!bid) return true; + if(!bid) return; ALbuffer *ALBuf{LookupBuffer(device, bid)}; - if(!ALBuf) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", bid); - return false; - } - if(ReadRef(ALBuf->ref) != 0) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid); - return false; - } - return true; + if(!ALBuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bid}; + if(ALBuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, "Deleting in-use buffer %u", bid}; }; - const ALuint *buffers_end = buffers + n; - auto invbuf = std::find_if_not(buffers, buffers_end, validate_buffer); - if(invbuf != buffers_end) UNLIKELY return; + + const al::span bids{buffers, static_cast(n)}; + std::for_each(bids.begin(), bids.end(), validate_buffer); /* All good. Delete non-0 buffer IDs. */ auto delete_buffer = [device](const ALuint bid) -> void { - ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; - if(buffer) FreeBuffer(device, buffer); + if(ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}) + FreeBuffer(device, buffer); }; - std::for_each(buffers, buffers_end, delete_buffer); + std::for_each(bids.begin(), bids.end(), delete_buffer); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsBuffer, ALuint,buffer) +FORCE_ALIGN ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(!buffer || LookupBuffer(device, buffer)) - return AL_TRUE; - } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + if(!buffer || LookupBuffer(device, buffer)) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) -START_API_FUNC -{ alBufferStorageSOFT(buffer, format, data, size, freq, 0); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) -START_API_FUNC +AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) noexcept { - ContextRef context{GetContextRef()}; + auto context = GetContextRef(); if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(size < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); - else if(freq < 1) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if((flags&INVALID_STORAGE_MASK) != 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid storage flags 0x%x", - flags&INVALID_STORAGE_MASK); - else if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Declaring persistently mapped storage without read or write access"); - else - { - auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); - else - { - LoadData(context.get(), albuf, freq, static_cast(size), usrfmt->channels, - usrfmt->type, static_cast(data), flags); - } - } + alBufferStorageDirectSOFT(context.get(), buffer, format, data, size, freq, 0); } -END_API_FUNC -void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, - ALsizei freq) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +FORCE_ALIGN void AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq) noexcept +{ alBufferStorageDirectSOFT(context, buffer, format, data, size, freq, 0); } +AL_API DECL_FUNCEXT6(void, alBufferStorage,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,size, ALsizei,freq, ALbitfieldSOFT,flags) +FORCE_ALIGN void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - if(size < 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Negative storage size %d", size); - if(freq < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(size < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + if(freq < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + if((flags&INVALID_STORAGE_MASK) != 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid storage flags 0x%x", + flags&INVALID_STORAGE_MASK}; + if((flags&AL_MAP_PERSISTENT_BIT_SOFT) && !(flags&MAP_READ_WRITE_FLAGS)) + throw al::context_error{AL_INVALID_VALUE, + "Declaring persistently mapped storage without read or write access"}; auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - return context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; - PrepareUserPtr(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, - static_cast(data), static_cast(size)); + LoadData(context, albuf, freq, static_cast(size), usrfmt->channels, usrfmt->type, + static_cast(data), flags); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; +FORCE_ALIGN DECL_FUNC5(void, alBufferDataStatic, ALuint,buffer, ALenum,format, ALvoid*,data, ALsizei,size, ALsizei,freq) +FORCE_ALIGN void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, const ALuint buffer, + ALenum format, ALvoid *data, ALsizei size, ALsizei freq) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if((access&INVALID_MAP_FLAGS) != 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid map flags 0x%x", access&INVALID_MAP_FLAGS); - else if(!(access&MAP_READ_WRITE_FLAGS)) UNLIKELY - context->setError(AL_INVALID_VALUE, "Mapping buffer %u without read or write access", - buffer); - else - { - ALbitfieldSOFT unavailable = (albuf->Access^access) & access; - if(ReadRef(albuf->ref) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_OPERATION, - "Mapping in-use buffer %u without persistent mapping", buffer); - else if(albuf->MappedAccess != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer); - else if((unavailable&AL_MAP_READ_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u for reading without read access", buffer); - else if((unavailable&AL_MAP_WRITE_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u for writing without write access", buffer); - else if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_VALUE, - "Mapping buffer %u persistently without persistent access", buffer); - else if(offset < 0 || length <= 0 - || static_cast(offset) >= albuf->OriginalSize - || static_cast(length) > albuf->OriginalSize - static_cast(offset)) - UNLIKELY - context->setError(AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", - offset, length, buffer); - else - { - void *retval{albuf->mData.data() + offset}; - albuf->MappedAccess = access; - albuf->MappedOffset = offset; - albuf->MappedSize = length; - return retval; - } - } + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(size < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative storage size %d", size}; + if(freq < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + auto usrfmt = DecomposeUserFormat(format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + + PrepareUserPtr(context, albuf, freq, usrfmt->channels, usrfmt->type, + static_cast(data), static_cast(size)); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT4(void*, alMapBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length, ALbitfieldSOFT,access) +FORCE_ALIGN void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, + ALsizei offset, ALsizei length, ALbitfieldSOFT access) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if((access&INVALID_MAP_FLAGS) != 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid map flags 0x%x", + access&INVALID_MAP_FLAGS}; + if(!(access&MAP_READ_WRITE_FLAGS)) + throw al::context_error{AL_INVALID_VALUE, "Mapping buffer %u without read or write access", + buffer}; + + const ALbitfieldSOFT unavailable{(albuf->Access^access) & access}; + if(albuf->ref.load(std::memory_order_relaxed) != 0 && !(access&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Mapping in-use buffer %u without persistent mapping", buffer}; + if(albuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Mapping already-mapped buffer %u", buffer}; + if((unavailable&AL_MAP_READ_BIT_SOFT)) + throw al::context_error{AL_INVALID_VALUE, + "Mapping buffer %u for reading without read access", buffer}; + if((unavailable&AL_MAP_WRITE_BIT_SOFT)) + throw al::context_error{AL_INVALID_VALUE, + "Mapping buffer %u for writing without write access", buffer}; + if((unavailable&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_VALUE, + "Mapping buffer %u persistently without persistent access", buffer}; + if(offset < 0 || length <= 0 || static_cast(offset) >= albuf->OriginalSize + || static_cast(length) > albuf->OriginalSize - static_cast(offset)) + throw al::context_error{AL_INVALID_VALUE, "Mapping invalid range %d+%d for buffer %u", + offset, length, buffer}; + + void *retval{albuf->mData.data() + offset}; + albuf->MappedAccess = access; + albuf->MappedOffset = offset; + albuf->MappedSize = length; + return retval; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); return nullptr; } -END_API_FUNC - -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT1(void, alUnmapBuffer,SOFT, ALuint,buffer) +FORCE_ALIGN void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(albuf->MappedAccess == 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer); - else - { - albuf->MappedAccess = 0; - albuf->MappedOffset = 0; - albuf->MappedSize = 0; - } + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(albuf->MappedAccess == 0) + throw al::context_error{AL_INVALID_OPERATION, "Unmapping unmapped buffer %u", buffer}; + + albuf->MappedAccess = 0; + albuf->MappedOffset = 0; + albuf->MappedSize = 0; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT3(void, alFlushMappedBuffer,SOFT, ALuint,buffer, ALsizei,offset, ALsizei,length) +FORCE_ALIGN void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, + ALsizei offset, ALsizei length) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Flushing buffer %u while not mapped for writing", - buffer); - else if(offset < albuf->MappedOffset || length <= 0 + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!(albuf->MappedAccess&AL_MAP_WRITE_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Flushing buffer %u while not mapped for writing", buffer}; + if(offset < albuf->MappedOffset || length <= 0 || offset >= albuf->MappedOffset+albuf->MappedSize - || length > albuf->MappedOffset+albuf->MappedSize-offset) UNLIKELY - context->setError(AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", offset, - length, buffer); - else - { - /* FIXME: Need to use some method of double-buffering for the mixer and - * app to hold separate memory, which can be safely transfered - * asynchronously. Currently we just say the app shouldn't write where - * OpenAL's reading, and hope for the best... - */ - std::atomic_thread_fence(std::memory_order_seq_cst); - } + || length > albuf->MappedOffset+albuf->MappedSize-offset) + throw al::context_error{AL_INVALID_VALUE, "Flushing invalid range %d+%d on buffer %u", + offset, length, buffer}; + + /* FIXME: Need to use some method of double-buffering for the mixer and app + * to hold separate memory, which can be safely transferred asynchronously. + * Currently we just say the app shouldn't write where OpenAL's reading, + * and hope for the best... + */ + std::atomic_thread_fence(std::memory_order_seq_cst); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alBufferSubData,SOFT, ALuint,buffer, ALenum,format, const ALvoid*,data, ALsizei,offset, ALsizei,length) +FORCE_ALIGN void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - return context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; const ALuint unpack_align{albuf->UnpackAlign}; const ALuint align{SanitizeAlignment(usrfmt->type, unpack_align)}; - if(align < 1) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align); - if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) UNLIKELY - return context->setError(AL_INVALID_ENUM, "Unpacking data with mismatched format"); - if(align != albuf->mBlockAlign) UNLIKELY - return context->setError(AL_INVALID_VALUE, + if(align < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack alignment %u", unpack_align}; + if(usrfmt->channels != albuf->mChannels || usrfmt->type != albuf->mType) + throw al::context_error{AL_INVALID_ENUM, "Unpacking data with mismatched format"}; + if(align != albuf->mBlockAlign) + throw al::context_error{AL_INVALID_VALUE, "Unpacking data with alignment %u does not match original alignment %u", align, - albuf->mBlockAlign); - if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) UNLIKELY - return context->setError(AL_INVALID_VALUE, - "Unpacking data with mismatched ambisonic order"); - if(albuf->MappedAccess != 0) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", - buffer); + albuf->mBlockAlign}; + if(albuf->isBFormat() && albuf->UnpackAmbiOrder != albuf->mAmbiOrder) + throw al::context_error{AL_INVALID_VALUE, + "Unpacking data with mismatched ambisonic order"}; + if(albuf->MappedAccess != 0) + throw al::context_error{AL_INVALID_OPERATION, "Unpacking data into mapped buffer %u", + buffer}; const ALuint num_chans{albuf->channelsFromFmt()}; const ALuint byte_align{ @@ -951,431 +933,383 @@ START_API_FUNC if(offset < 0 || length < 0 || static_cast(offset) > albuf->OriginalSize || static_cast(length) > albuf->OriginalSize-static_cast(offset)) - UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", - offset, length, buffer); - if((static_cast(offset)%byte_align) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + throw al::context_error{AL_INVALID_VALUE, "Invalid data sub-range %d+%d on buffer %u", + offset, length, buffer}; + if((static_cast(offset)%byte_align) != 0) + throw al::context_error{AL_INVALID_VALUE, "Sub-range offset %d is not a multiple of frame size %d (%d unpack alignment)", - offset, byte_align, align); - if((static_cast(length)%byte_align) != 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, + offset, byte_align, align}; + if((static_cast(length)%byte_align) != 0) + throw al::context_error{AL_INVALID_VALUE, "Sub-range length %d is not a multiple of frame size %d (%d unpack alignment)", - length, byte_align, align); + length, byte_align, align}; - assert(al::to_underlying(usrfmt->type) == al::to_underlying(albuf->mType)); - memcpy(albuf->mData.data()+offset, data, static_cast(length)); + std::memcpy(albuf->mData.data()+offset, data, static_cast(length)); } -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, - ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, - const ALvoid* /*data*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, - ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, - ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); -} -END_API_FUNC - -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - - context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); - return AL_FALSE; -} -END_API_FUNC -AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat /*value*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alBufferf, ALuint,buffer, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat value [[maybe_unused]]) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, - ALfloat /*value1*/, ALfloat /*value2*/, ALfloat /*value3*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alBuffer3f, ALuint,buffer, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat value1 [[maybe_unused]], ALfloat value2 [[maybe_unused]], + ALfloat value3 [[maybe_unused]]) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alBufferfv, ALuint,buffer, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, + const ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alBufferi, ALuint,buffer, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - if(value < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack block alignment %d", value); - else - albuf->UnpackAlign = static_cast(value); - break; + if(value < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack block alignment %d", value}; + albuf->UnpackAlign = static_cast(value); + return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: - if(value < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid pack block alignment %d", value); - else - albuf->PackAlign = static_cast(value); - break; + if(value < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid pack block alignment %d", value}; + albuf->PackAlign = static_cast(value); + return; case AL_AMBISONIC_LAYOUT_SOFT: - if(ReadRef(albuf->ref) != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic layout", - buffer); - else if(const auto layout = AmbiLayoutFromEnum(value)) - albuf->mAmbiLayout = layout.value(); - else UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value); - break; - - case AL_AMBISONIC_SCALING_SOFT: - if(ReadRef(albuf->ref) != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's ambisonic scaling", - buffer); - else if(const auto scaling = AmbiScalingFromEnum(value)) - albuf->mAmbiScaling = scaling.value(); - else UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value); - break; - - case AL_UNPACK_AMBISONIC_ORDER_SOFT: - if(value < 1 || value > 14) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value); - else - albuf->UnpackAmbiOrder = static_cast(value); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, - ALint /*value1*/, ALint /*value2*/, ALint /*value3*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) -START_API_FUNC -{ - if(values) - { - switch(param) + if(albuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying in-use buffer %u's ambisonic layout", buffer}; + if(const auto layout = AmbiLayoutFromEnum(value)) { - case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: - case AL_PACK_BLOCK_ALIGNMENT_SOFT: - case AL_AMBISONIC_LAYOUT_SOFT: - case AL_AMBISONIC_SCALING_SOFT: - case AL_UNPACK_AMBISONIC_ORDER_SOFT: - alBufferi(buffer, param, values[0]); + albuf->mAmbiLayout = layout.value(); return; } + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic layout %04x", value}; + + case AL_AMBISONIC_SCALING_SOFT: + if(albuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying in-use buffer %u's ambisonic scaling", buffer}; + if(const auto scaling = AmbiScalingFromEnum(value)) + { + albuf->mAmbiScaling = scaling.value(); + return; + } + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic scaling %04x", value}; + + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + if(value < 1 || value > 14) + throw al::context_error{AL_INVALID_VALUE, "Invalid unpack ambisonic order %d", value}; + albuf->UnpackAmbiOrder = static_cast(value); + return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC5(void, alBuffer3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) +FORCE_ALIGN void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint value1 [[maybe_unused]], ALint value2 [[maybe_unused]], ALint value3 [[maybe_unused]]) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alBufferiv, ALuint,buffer, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, + const ALint *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: + case AL_PACK_BLOCK_ALIGNMENT_SOFT: + case AL_AMBISONIC_LAYOUT_SOFT: + case AL_AMBISONIC_SCALING_SOFT: + case AL_UNPACK_AMBISONIC_ORDER_SOFT: + alBufferiDirect(context, buffer, param, *values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + + switch(param) { case AL_LOOP_POINTS_SOFT: - if(ReadRef(albuf->ref) != 0) UNLIKELY - context->setError(AL_INVALID_OPERATION, "Modifying in-use buffer %u's loop points", - buffer); - else if(values[0] < 0 || values[0] >= values[1] - || static_cast(values[1]) > albuf->mSampleLen) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid loop point range %d -> %d on buffer %u", - values[0], values[1], buffer); - else - { - albuf->mLoopStart = static_cast(values[0]); - albuf->mLoopEnd = static_cast(values[1]); - } - break; + auto vals = al::span{values, 2_uz}; + if(albuf->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying in-use buffer %u's loop points", buffer}; + if(vals[0] < 0 || vals[0] >= vals[1] || static_cast(vals[1]) > albuf->mSampleLen) + throw al::context_error{AL_INVALID_VALUE, + "Invalid loop point range %d -> %d on buffer %u", vals[0], vals[1], buffer}; - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param); + albuf->mLoopStart = static_cast(vals[0]); + albuf->mLoopEnd = static_cast(vals[1]); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alGetBufferf, ALuint,buffer, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; + std::lock_guard buflock{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_SEC_LENGTH_SOFT: *value = (albuf->mSampleRate < 1) ? 0.0f : (static_cast(albuf->mSampleLen) / static_cast(albuf->mSampleRate)); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value1 || !value2 || !value3) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) -START_API_FUNC -{ - switch(param) - { - case AL_SEC_LENGTH_SOFT: - alGetBufferf(buffer, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param); - } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC +AL_API DECL_FUNC5(void, alGetBuffer3f, ALuint,buffer, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) +FORCE_ALIGN void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; -AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-float property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetBufferfv, ALuint,buffer, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALfloat *values) noexcept +try { + switch(param) + { + case AL_SEC_LENGTH_SOFT: + alGetBufferfDirect(context, buffer, param, values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer float-vector property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +AL_API DECL_FUNC3(void, alGetBufferi, ALuint,buffer, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint *value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_FREQUENCY: *value = static_cast(albuf->mSampleRate); - break; + return; case AL_BITS: *value = (albuf->mType == FmtIMA4 || albuf->mType == FmtMSADPCM) ? 4 : static_cast(albuf->bytesFromFmt() * 8); - break; + return; case AL_CHANNELS: *value = static_cast(albuf->channelsFromFmt()); - break; + return; case AL_SIZE: *value = albuf->mCallback ? 0 : static_cast(albuf->mData.size()); - break; + return; case AL_BYTE_LENGTH_SOFT: *value = static_cast(albuf->mSampleLen / albuf->mBlockAlign * albuf->blockSizeFromFmt()); - break; + return; case AL_SAMPLE_LENGTH_SOFT: *value = static_cast(albuf->mSampleLen); - break; + return; case AL_UNPACK_BLOCK_ALIGNMENT_SOFT: *value = static_cast(albuf->UnpackAlign); - break; + return; case AL_PACK_BLOCK_ALIGNMENT_SOFT: *value = static_cast(albuf->PackAlign); - break; + return; case AL_AMBISONIC_LAYOUT_SOFT: *value = EnumFromAmbiLayout(albuf->mAmbiLayout); - break; + return; case AL_AMBISONIC_SCALING_SOFT: *value = EnumFromAmbiScaling(albuf->mAmbiScaling); - break; + return; case AL_UNPACK_AMBISONIC_ORDER_SOFT: *value = static_cast(albuf->UnpackAmbiOrder); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alGetBuffer3i, ALuint,buffer, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) +FORCE_ALIGN void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint *value1, ALint *value2, ALint *value3) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value1 || !value2 || !value3) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param); - } -} -END_API_FUNC + std::lock_guard buflock{device->BufferLock}; -AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) -START_API_FUNC -{ + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-integer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetBufferiv, ALuint,buffer, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, + ALint *values) noexcept +try { switch(param) { case AL_FREQUENCY: @@ -1390,252 +1324,305 @@ START_API_FUNC case AL_AMBISONIC_LAYOUT_SOFT: case AL_AMBISONIC_SCALING_SOFT: case AL_UNPACK_AMBISONIC_ORDER_SOFT: - alGetBufferi(buffer, param, values); + alGetBufferiDirect(context, buffer, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_LOOP_POINTS_SOFT: - values[0] = static_cast(albuf->mLoopStart); - values[1] = static_cast(albuf->mLoopEnd); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", param); + auto vals = al::span{values, 2_uz}; + vals[0] = static_cast(albuf->mLoopStart); + vals[1] = static_cast(albuf->mLoopEnd); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer integer-vector property 0x%04x", + param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, - ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(freq < 1) UNLIKELY - context->setError(AL_INVALID_VALUE, "Invalid sample rate %d", freq); - else if(callback == nullptr) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL callback"); - else - { - auto usrfmt = DecomposeUserFormat(format); - if(!usrfmt) UNLIKELY - context->setError(AL_INVALID_ENUM, "Invalid format 0x%04x", format); - else - PrepareCallback(context.get(), albuf, freq, usrfmt->channels, usrfmt->type, callback, - userptr); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alBufferCallback,SOFT, ALuint,buffer, ALenum,format, ALsizei,freq, ALBUFFERCALLBACKTYPESOFT,callback, ALvoid*,userptr) +FORCE_ALIGN void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - ALbuffer *albuf = LookupBuffer(device, buffer); - if(!albuf) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(freq < 1) + throw al::context_error{AL_INVALID_VALUE, "Invalid sample rate %d", freq}; + if(callback == nullptr) + throw al::context_error{AL_INVALID_VALUE, "NULL callback"}; + + auto usrfmt = DecomposeUserFormat(format); + if(!usrfmt) + throw al::context_error{AL_INVALID_ENUM, "Invalid format 0x%04x", format}; + + PrepareCallback(context, albuf, freq, usrfmt->channels, usrfmt->type, callback, userptr); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetBufferPtr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value) +FORCE_ALIGN void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum param, ALvoid **value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + ALbuffer *albuf{LookupBuffer(device, buffer)}; + if(!albuf) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: *value = reinterpret_cast(albuf->mCallback); - break; + return; case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: *value = albuf->mUserData; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param); + return; } + + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alGetBuffer3Ptr,SOFT, ALuint,buffer, ALenum,param, ALvoid**,value1, ALvoid**,value2, ALvoid**,value3) +FORCE_ALIGN void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!value1 || !value2 || !value3) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param); - } -} -END_API_FUNC + std::lock_guard buflock{device->BufferLock}; -AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **values) -START_API_FUNC -{ + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer 3-pointer property 0x%04x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetBufferPtrv,SOFT, ALuint,buffer, ALenum,param, ALvoid**,values) +FORCE_ALIGN void AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, + ALenum param, ALvoid **values) noexcept +try { switch(param) { case AL_BUFFER_CALLBACK_FUNCTION_SOFT: case AL_BUFFER_CALLBACK_USER_PARAM_SOFT: - alGetBufferPtrSOFT(buffer, param, values); + alGetBufferPtrDirectSOFT(context, buffer, param, values); return; } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + if(LookupBuffer(device, buffer) == nullptr) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) + { + } + throw al::context_error{AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint /*buffer*/, ALuint /*samplerate*/, + ALenum /*internalformat*/, ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, + const ALvoid* /*data*/) noexcept +{ ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->BufferLock}; - if(LookupBuffer(device, buffer) == nullptr) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid buffer ID %u", buffer); - else if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid buffer pointer-vector property 0x%04x", param); - } + context->setError(AL_INVALID_OPERATION, "alBufferSamplesSOFT not supported"); +} + +AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, + ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, const ALvoid* /*data*/) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alBufferSubSamplesSOFT not supported"); +} + +AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint /*buffer*/, ALsizei /*offset*/, + ALsizei /*samples*/, ALenum /*channels*/, ALenum /*type*/, ALvoid* /*data*/) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return; + + context->setError(AL_INVALID_OPERATION, "alGetBufferSamplesSOFT not supported"); +} + +AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum /*format*/) noexcept +{ + ContextRef context{GetContextRef()}; + if(!context) UNLIKELY return AL_FALSE; + + context->setError(AL_INVALID_OPERATION, "alIsBufferFormatSupportedSOFT not supported"); + return AL_FALSE; +} + + +void ALbuffer::SetName(ALCcontext *context, ALuint id, std::string_view name) +{ + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + + auto buffer = LookupBuffer(device, id); + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", id}; + + device->mBufferNames.insert_or_assign(id, name); } -END_API_FUNC BufferSubList::~BufferSubList() { + if(!Buffers) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Buffers+idx); + std::destroy_at(al::to_address(Buffers->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Buffers); + SubListAllocator{}.deallocate(Buffers, 1); Buffers = nullptr; } #ifdef ALSOFT_EAX -FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint* buffers, ALint value) -START_API_FUNC -{ -#define EAX_PREFIX "[EAXSetBufferMode] " - - const auto context = ContextRef{GetContextRef()}; - if(!context) - { - ERR(EAX_PREFIX "%s\n", "No current context."); - return ALC_FALSE; - } - +FORCE_ALIGN DECL_FUNC3(ALboolean, EAXSetBufferMode, ALsizei,n, const ALuint*,buffers, ALint,value) +FORCE_ALIGN ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, + const ALuint *buffers, ALint value) noexcept +try { if(!eax_g_is_enabled) - { - context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); - return ALC_FALSE; - } + throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled"}; const auto storage = EaxStorageFromEnum(value); if(!storage) - { - context->setError(AL_INVALID_ENUM, EAX_PREFIX "Unsupported X-RAM mode 0x%x", value); - return ALC_FALSE; - } + throw al::context_error{AL_INVALID_ENUM, "Unsupported X-RAM mode 0x%x", value}; if(n == 0) - return ALC_TRUE; + return AL_TRUE; if(n < 0) - { - context->setError(AL_INVALID_VALUE, EAX_PREFIX "Buffer count %d out of range", n); - return ALC_FALSE; - } - + throw al::context_error{AL_INVALID_VALUE, "Buffer count %d out of range", n}; if(!buffers) - { - context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Null AL buffers"); - return ALC_FALSE; - } + throw al::context_error{AL_INVALID_VALUE, "Null AL buffers"}; auto device = context->mALDevice.get(); - std::lock_guard device_lock{device->BufferLock}; - size_t total_needed{0}; + std::lock_guard devlock{device->BufferLock}; - // Validate the buffers. - // - for(auto i = 0;i < n;++i) + /* Special-case setting a single buffer, to avoid extraneous allocations. */ + if(n == 1) { - const auto bufid = buffers[i]; + const auto bufid = *buffers; if(bufid == AL_NONE) - continue; + return AL_TRUE; const auto buffer = LookupBuffer(device, bufid); - if(!buffer) UNLIKELY - { - ERR(EAX_PREFIX "Invalid buffer ID %u.\n", bufid); - return ALC_FALSE; - } + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; /* TODO: Is the store location allowed to change for in-use buffers, or * only when not set/queued on a source? */ - if(*storage == EaxStorage::Hardware && !buffer->eax_x_ram_is_hardware) + if(*storage == EaxStorage::Hardware) { - /* FIXME: This doesn't account for duplicate buffers. When the same - * buffer ID is specified multiple times in the provided list, it - * counts each instance as more memory that needs to fit in X-RAM. - */ - if(std::numeric_limits::max()-buffer->OriginalSize < total_needed) UNLIKELY - { - context->setError(AL_OUT_OF_MEMORY, EAX_PREFIX "Size overflow (%u + %zu)\n", - buffer->OriginalSize, total_needed); - return ALC_FALSE; - } - total_needed += buffer->OriginalSize; + if(!buffer->eax_x_ram_is_hardware + && buffer->OriginalSize > device->eax_x_ram_free_size) + throw al::context_error{AL_OUT_OF_MEMORY, + "Out of X-RAM memory (need: %u, avail: %u)", buffer->OriginalSize, + device->eax_x_ram_free_size}; + + eax_x_ram_apply(*device, *buffer); } - } - if(total_needed > device->eax_x_ram_free_size) - { - context->setError(AL_OUT_OF_MEMORY,EAX_PREFIX "Out of X-RAM memory (need: %zu, avail: %u)", - total_needed, device->eax_x_ram_free_size); - return ALC_FALSE; + else + eax_x_ram_clear(*device, *buffer); + buffer->eax_x_ram_mode = *storage; + return AL_TRUE; } - // Update the mode. - // - for(auto i = 0;i < n;++i) + /* Validate the buffers. */ + std::unordered_set buflist; + for(const ALuint bufid : al::span{buffers, static_cast(n)}) { - const auto bufid = buffers[i]; if(bufid == AL_NONE) continue; const auto buffer = LookupBuffer(device, bufid); - assert(buffer); + if(!buffer) + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", bufid}; + /* TODO: Is the store location allowed to change for in-use buffers, or + * only when not set/queued on a source? + */ + + buflist.emplace(buffer); + } + + if(*storage == EaxStorage::Hardware) + { + size_t total_needed{0}; + for(ALbuffer *buffer : buflist) + { + if(!buffer->eax_x_ram_is_hardware) + { + if(std::numeric_limits::max() - buffer->OriginalSize < total_needed) + throw al::context_error{AL_OUT_OF_MEMORY, "Size overflow (%u + %zu)", + buffer->OriginalSize, total_needed}; + + total_needed += buffer->OriginalSize; + } + } + if(total_needed > device->eax_x_ram_free_size) + throw al::context_error{AL_OUT_OF_MEMORY, "Out of X-RAM memory (need: %zu, avail: %u)", + total_needed, device->eax_x_ram_free_size}; + } + + /* Update the mode. */ + for(ALbuffer *buffer : buflist) + { if(*storage == EaxStorage::Hardware) eax_x_ram_apply(*device, *buffer); else @@ -1644,49 +1631,35 @@ START_API_FUNC } return AL_TRUE; - -#undef EAX_PREFIX } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "[EAXSetBufferMode] %s", e.what()); + return AL_FALSE; +} -FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint* pReserved) -START_API_FUNC -{ -#define EAX_PREFIX "[EAXGetBufferMode] " - - const auto context = ContextRef{GetContextRef()}; - if(!context) - { - ERR(EAX_PREFIX "%s\n", "No current context."); - return AL_NONE; - } +FORCE_ALIGN DECL_FUNC2(ALenum, EAXGetBufferMode, ALuint,buffer, ALint*,pReserved) +FORCE_ALIGN ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, + ALint *pReserved) noexcept +try { if(!eax_g_is_enabled) - { - context->setError(AL_INVALID_OPERATION, EAX_PREFIX "%s", "EAX not enabled."); - return AL_NONE; - } + throw al::context_error{AL_INVALID_OPERATION, "EAX not enabled."}; if(pReserved) - { - context->setError(AL_INVALID_VALUE, EAX_PREFIX "%s", "Non-null reserved parameter"); - return AL_NONE; - } + throw al::context_error{AL_INVALID_VALUE, "Non-null reserved parameter"}; auto device = context->mALDevice.get(); - std::lock_guard device_lock{device->BufferLock}; + std::lock_guard devlock{device->BufferLock}; const auto al_buffer = LookupBuffer(device, buffer); if(!al_buffer) - { - context->setError(AL_INVALID_NAME, EAX_PREFIX "Invalid buffer ID %u", buffer); - return AL_NONE; - } + throw al::context_error{AL_INVALID_NAME, "Invalid buffer ID %u", buffer}; return EnumFromEaxStorage(al_buffer->eax_x_ram_mode); - -#undef EAX_PREFIX } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "[EAXGetBufferMode] %s", e.what()); + return AL_NONE; +} #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/buffer.h b/Engine/lib/openal-soft/al/buffer.h index 64ebe1f32..f7661ec88 100644 --- a/Engine/lib/openal-soft/al/buffer.h +++ b/Engine/lib/openal-soft/al/buffer.h @@ -1,20 +1,23 @@ #ifndef AL_BUFFER_H #define AL_BUFFER_H +#include #include +#include +#include +#include +#include #include "AL/al.h" +#include "AL/alc.h" -#include "albyte.h" #include "alc/inprogext.h" #include "almalloc.h" -#include "atomic.h" +#include "alnumeric.h" #include "core/buffer_storage.h" #include "vector.h" #ifdef ALSOFT_EAX -#include "eax/x_ram.h" - enum class EaxStorage : uint8_t { Automatic, Accessible, @@ -26,7 +29,7 @@ enum class EaxStorage : uint8_t { struct ALbuffer : public BufferStorage { ALbitfieldSOFT Access{0u}; - al::vector mDataStorage; + al::vector mDataStorage; ALuint OriginalSize{0}; @@ -42,12 +45,14 @@ struct ALbuffer : public BufferStorage { ALuint mLoopEnd{0u}; /* Number of times buffer was attached to a source (deletion can only occur when 0) */ - RefCount ref{0u}; + std::atomic ref{0u}; /* Self ID */ ALuint id{0}; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC #ifdef ALSOFT_EAX EaxStorage eax_x_ram_mode{EaxStorage::Automatic}; @@ -55,4 +60,19 @@ struct ALbuffer : public BufferStorage { #endif // ALSOFT_EAX }; +struct BufferSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Buffers{nullptr}; + + BufferSubList() noexcept = default; + BufferSubList(const BufferSubList&) = delete; + BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} + { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } + ~BufferSubList(); + + BufferSubList& operator=(const BufferSubList&) = delete; + BufferSubList& operator=(BufferSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/debug.cpp b/Engine/lib/openal-soft/al/debug.cpp new file mode 100644 index 000000000..e91dc29c2 --- /dev/null +++ b/Engine/lib/openal-soft/al/debug.cpp @@ -0,0 +1,618 @@ +#include "config.h" + +#include "debug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alc/context.h" +#include "alc/device.h" +#include "alc/inprogext.h" +#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "auxeffectslot.h" +#include "buffer.h" +#include "core/logging.h" +#include "core/voice.h" +#include "direct_defs.h" +#include "effect.h" +#include "error.h" +#include "filter.h" +#include "intrusive_ptr.h" +#include "opthelpers.h" +#include "source.h" + + +/* Declared here to prevent compilers from thinking it should be inlined, which + * GCC warns about increasing code size. + */ +DebugGroup::~DebugGroup() = default; + +namespace { + +static_assert(DebugSeverityBase+DebugSeverityCount <= 32, "Too many debug bits"); + +template +constexpr auto make_array_sequence(std::integer_sequence) +{ return std::array{Vals...}; } + +template +constexpr auto make_array_sequence() +{ return make_array_sequence(std::make_integer_sequence{}); } + + +constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional +{ + switch(source) + { + case AL_DEBUG_SOURCE_API_EXT: return DebugSource::API; + case AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT: return DebugSource::System; + case AL_DEBUG_SOURCE_THIRD_PARTY_EXT: return DebugSource::ThirdParty; + case AL_DEBUG_SOURCE_APPLICATION_EXT: return DebugSource::Application; + case AL_DEBUG_SOURCE_OTHER_EXT: return DebugSource::Other; + } + return std::nullopt; +} + +constexpr auto GetDebugType(ALenum type) noexcept -> std::optional +{ + switch(type) + { + case AL_DEBUG_TYPE_ERROR_EXT: return DebugType::Error; + case AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT: return DebugType::DeprecatedBehavior; + case AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT: return DebugType::UndefinedBehavior; + case AL_DEBUG_TYPE_PORTABILITY_EXT: return DebugType::Portability; + case AL_DEBUG_TYPE_PERFORMANCE_EXT: return DebugType::Performance; + case AL_DEBUG_TYPE_MARKER_EXT: return DebugType::Marker; + case AL_DEBUG_TYPE_PUSH_GROUP_EXT: return DebugType::PushGroup; + case AL_DEBUG_TYPE_POP_GROUP_EXT: return DebugType::PopGroup; + case AL_DEBUG_TYPE_OTHER_EXT: return DebugType::Other; + } + return std::nullopt; +} + +constexpr auto GetDebugSeverity(ALenum severity) noexcept -> std::optional +{ + switch(severity) + { + case AL_DEBUG_SEVERITY_HIGH_EXT: return DebugSeverity::High; + case AL_DEBUG_SEVERITY_MEDIUM_EXT: return DebugSeverity::Medium; + case AL_DEBUG_SEVERITY_LOW_EXT: return DebugSeverity::Low; + case AL_DEBUG_SEVERITY_NOTIFICATION_EXT: return DebugSeverity::Notification; + } + return std::nullopt; +} + + +constexpr auto GetDebugSourceEnum(DebugSource source) -> ALenum +{ + switch(source) + { + case DebugSource::API: return AL_DEBUG_SOURCE_API_EXT; + case DebugSource::System: return AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT; + case DebugSource::ThirdParty: return AL_DEBUG_SOURCE_THIRD_PARTY_EXT; + case DebugSource::Application: return AL_DEBUG_SOURCE_APPLICATION_EXT; + case DebugSource::Other: return AL_DEBUG_SOURCE_OTHER_EXT; + } + throw std::runtime_error{"Unexpected debug source value "+std::to_string(al::to_underlying(source))}; +} + +constexpr auto GetDebugTypeEnum(DebugType type) -> ALenum +{ + switch(type) + { + case DebugType::Error: return AL_DEBUG_TYPE_ERROR_EXT; + case DebugType::DeprecatedBehavior: return AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT; + case DebugType::UndefinedBehavior: return AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT; + case DebugType::Portability: return AL_DEBUG_TYPE_PORTABILITY_EXT; + case DebugType::Performance: return AL_DEBUG_TYPE_PERFORMANCE_EXT; + case DebugType::Marker: return AL_DEBUG_TYPE_MARKER_EXT; + case DebugType::PushGroup: return AL_DEBUG_TYPE_PUSH_GROUP_EXT; + case DebugType::PopGroup: return AL_DEBUG_TYPE_POP_GROUP_EXT; + case DebugType::Other: return AL_DEBUG_TYPE_OTHER_EXT; + } + throw std::runtime_error{"Unexpected debug type value "+std::to_string(al::to_underlying(type))}; +} + +constexpr auto GetDebugSeverityEnum(DebugSeverity severity) -> ALenum +{ + switch(severity) + { + case DebugSeverity::High: return AL_DEBUG_SEVERITY_HIGH_EXT; + case DebugSeverity::Medium: return AL_DEBUG_SEVERITY_MEDIUM_EXT; + case DebugSeverity::Low: return AL_DEBUG_SEVERITY_LOW_EXT; + case DebugSeverity::Notification: return AL_DEBUG_SEVERITY_NOTIFICATION_EXT; + } + throw std::runtime_error{"Unexpected debug severity value "+std::to_string(al::to_underlying(severity))}; +} + + +constexpr auto GetDebugSourceName(DebugSource source) noexcept -> const char* +{ + switch(source) + { + case DebugSource::API: return "API"; + case DebugSource::System: return "Audio System"; + case DebugSource::ThirdParty: return "Third Party"; + case DebugSource::Application: return "Application"; + case DebugSource::Other: return "Other"; + } + return ""; +} + +constexpr auto GetDebugTypeName(DebugType type) noexcept -> const char* +{ + switch(type) + { + case DebugType::Error: return "Error"; + case DebugType::DeprecatedBehavior: return "Deprecated Behavior"; + case DebugType::UndefinedBehavior: return "Undefined Behavior"; + case DebugType::Portability: return "Portability"; + case DebugType::Performance: return "Performance"; + case DebugType::Marker: return "Marker"; + case DebugType::PushGroup: return "Push Group"; + case DebugType::PopGroup: return "Pop Group"; + case DebugType::Other: return "Other"; + } + return ""; +} + +constexpr auto GetDebugSeverityName(DebugSeverity severity) noexcept -> const char* +{ + switch(severity) + { + case DebugSeverity::High: return "High"; + case DebugSeverity::Medium: return "Medium"; + case DebugSeverity::Low: return "Low"; + case DebugSeverity::Notification: return "Notification"; + } + return ""; +} + +} // namespace + + +void ALCcontext::sendDebugMessage(std::unique_lock &debuglock, DebugSource source, + DebugType type, ALuint id, DebugSeverity severity, std::string_view message) +{ + if(!mDebugEnabled.load(std::memory_order_relaxed)) UNLIKELY + return; + + if(message.length() >= MaxDebugMessageLength) UNLIKELY + { + ERR("Debug message too long (%zu >= %d):\n-> %.*s\n", message.length(), + MaxDebugMessageLength, al::sizei(message), message.data()); + return; + } + + DebugGroup &debug = mDebugGroups.back(); + + const uint64_t idfilter{(1_u64 << (DebugSourceBase+al::to_underlying(source))) + | (1_u64 << (DebugTypeBase+al::to_underlying(type))) + | (uint64_t{id} << 32)}; + auto iditer = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), idfilter); + if(iditer != debug.mIdFilters.cend() && *iditer == idfilter) + return; + + const uint filter{(1u << (DebugSourceBase+al::to_underlying(source))) + | (1u << (DebugTypeBase+al::to_underlying(type))) + | (1u << (DebugSeverityBase+al::to_underlying(severity)))}; + auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); + if(iter != debug.mFilters.cend() && *iter == filter) + return; + + if(mDebugCb) + { + auto callback = mDebugCb; + auto param = mDebugParam; + debuglock.unlock(); + callback(GetDebugSourceEnum(source), GetDebugTypeEnum(type), id, + GetDebugSeverityEnum(severity), static_cast(message.length()), message.data(), + param); + } + else + { + if(mDebugLog.size() < MaxDebugLoggedMessages) + mDebugLog.emplace_back(source, type, id, severity, message); + else UNLIKELY + ERR("Debug message log overflow. Lost message:\n" + " Source: %s\n" + " Type: %s\n" + " ID: %u\n" + " Severity: %s\n" + " Message: \"%.*s\"\n", + GetDebugSourceName(source), GetDebugTypeName(type), id, + GetDebugSeverityName(severity), al::sizei(message), message.data()); + } +} + + +FORCE_ALIGN DECL_FUNCEXT2(void, alDebugMessageCallback,EXT, ALDEBUGPROCEXT,callback, void*,userParam) +FORCE_ALIGN void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, + ALDEBUGPROCEXT callback, void *userParam) noexcept +{ + std::lock_guard debuglock{context->mDebugCbLock}; + context->mDebugCb = callback; + context->mDebugParam = userParam; +} + + +FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageInsert,EXT, ALenum,source, ALenum,type, ALuint,id, ALenum,severity, ALsizei,length, const ALchar*,message) +FORCE_ALIGN void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, + ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) noexcept +try { + if(!context->mContextFlags.test(ContextFlags::DebugBit)) + return; + + if(!message) + throw al::context_error{AL_INVALID_VALUE, "Null message pointer"}; + + auto msgview = (length < 0) ? std::string_view{message} + : std::string_view{message, static_cast(length)}; + if(msgview.size() >= MaxDebugMessageLength) + throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", + msgview.size(), MaxDebugMessageLength}; + + auto dsource = GetDebugSource(source); + if(!dsource) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) + throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + + auto dtype = GetDebugType(type); + if(!dtype) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + + auto dseverity = GetDebugSeverity(severity); + if(!dseverity) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + + context->debugMessage(*dsource, *dtype, id, *dseverity, msgview); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +FORCE_ALIGN DECL_FUNCEXT6(void, alDebugMessageControl,EXT, ALenum,source, ALenum,type, ALenum,severity, ALsizei,count, const ALuint*,ids, ALboolean,enable) +FORCE_ALIGN void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, + ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) noexcept +try { + if(count > 0) + { + if(!ids) + throw al::context_error{AL_INVALID_VALUE, "IDs is null with non-0 count"}; + if(source == AL_DONT_CARE_EXT) + throw al::context_error{AL_INVALID_OPERATION, + "Debug source cannot be AL_DONT_CARE_EXT with IDs"}; + if(type == AL_DONT_CARE_EXT) + throw al::context_error{AL_INVALID_OPERATION, + "Debug type cannot be AL_DONT_CARE_EXT with IDs"}; + if(severity != AL_DONT_CARE_EXT) + throw al::context_error{AL_INVALID_OPERATION, + "Debug severity must be AL_DONT_CARE_EXT with IDs"}; + } + + if(enable != AL_TRUE && enable != AL_FALSE) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug enable %d", enable}; + + static constexpr size_t ElemCount{DebugSourceCount + DebugTypeCount + DebugSeverityCount}; + static constexpr auto Values = make_array_sequence(); + + auto srcIndices = al::span{Values}.subspan(DebugSourceBase,DebugSourceCount); + if(source != AL_DONT_CARE_EXT) + { + auto dsource = GetDebugSource(source); + if(!dsource) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + srcIndices = srcIndices.subspan(al::to_underlying(*dsource), 1); + } + + auto typeIndices = al::span{Values}.subspan(DebugTypeBase,DebugTypeCount); + if(type != AL_DONT_CARE_EXT) + { + auto dtype = GetDebugType(type); + if(!dtype) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug type 0x%04x", type}; + typeIndices = typeIndices.subspan(al::to_underlying(*dtype), 1); + } + + auto svrIndices = al::span{Values}.subspan(DebugSeverityBase,DebugSeverityCount); + if(severity != AL_DONT_CARE_EXT) + { + auto dseverity = GetDebugSeverity(severity); + if(!dseverity) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug severity 0x%04x", severity}; + svrIndices = svrIndices.subspan(al::to_underlying(*dseverity), 1); + } + + std::lock_guard debuglock{context->mDebugCbLock}; + DebugGroup &debug = context->mDebugGroups.back(); + if(count > 0) + { + const uint filterbase{(1u<(count)}) + { + const uint64_t filter{filterbase | (uint64_t{id} << 32)}; + + auto iter = std::lower_bound(debug.mIdFilters.cbegin(), debug.mIdFilters.cend(), + filter); + if(!enable && (iter == debug.mIdFilters.cend() || *iter != filter)) + debug.mIdFilters.insert(iter, filter); + else if(enable && iter != debug.mIdFilters.cend() && *iter == filter) + debug.mIdFilters.erase(iter); + } + } + else + { + auto apply_filter = [enable,&debug](const uint filter) + { + auto iter = std::lower_bound(debug.mFilters.cbegin(), debug.mFilters.cend(), filter); + if(!enable && (iter == debug.mFilters.cend() || *iter != filter)) + debug.mFilters.insert(iter, filter); + else if(enable && iter != debug.mFilters.cend() && *iter == filter) + debug.mFilters.erase(iter); + }; + auto apply_severity = [apply_filter,svrIndices](const uint filter) + { + std::for_each(svrIndices.cbegin(), svrIndices.cend(), + [apply_filter,filter](const uint idx){ apply_filter(filter | (1<setError(e.errorCode(), "%s", e.what()); +} + + +FORCE_ALIGN DECL_FUNCEXT4(void, alPushDebugGroup,EXT, ALenum,source, ALuint,id, ALsizei,length, const ALchar*,message) +FORCE_ALIGN void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, + ALuint id, ALsizei length, const ALchar *message) noexcept +try { + if(length < 0) + { + size_t newlen{std::strlen(message)}; + if(newlen >= MaxDebugMessageLength) + throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%zu >= %d)", newlen, + MaxDebugMessageLength}; + length = static_cast(newlen); + } + else if(length >= MaxDebugMessageLength) + throw al::context_error{AL_INVALID_VALUE, "Debug message too long (%d >= %d)", length, + MaxDebugMessageLength}; + + auto dsource = GetDebugSource(source); + if(!dsource) + throw al::context_error{AL_INVALID_ENUM, "Invalid debug source 0x%04x", source}; + if(*dsource != DebugSource::ThirdParty && *dsource != DebugSource::Application) + throw al::context_error{AL_INVALID_ENUM, "Debug source 0x%04x not allowed", source}; + + std::unique_lock debuglock{context->mDebugCbLock}; + if(context->mDebugGroups.size() >= MaxDebugGroupDepth) + throw al::context_error{AL_STACK_OVERFLOW_EXT, "Pushing too many debug groups"}; + + context->mDebugGroups.emplace_back(*dsource, id, + std::string_view{message, static_cast(length)}); + auto &oldback = *(context->mDebugGroups.end()-2); + auto &newback = context->mDebugGroups.back(); + + newback.mFilters = oldback.mFilters; + newback.mIdFilters = oldback.mIdFilters; + + if(context->mContextFlags.test(ContextFlags::DebugBit)) + context->sendDebugMessage(debuglock, newback.mSource, DebugType::PushGroup, newback.mId, + DebugSeverity::Notification, newback.mMessage); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +FORCE_ALIGN DECL_FUNCEXT(void, alPopDebugGroup,EXT) +FORCE_ALIGN void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) noexcept +try { + std::unique_lock debuglock{context->mDebugCbLock}; + if(context->mDebugGroups.size() <= 1) + throw al::context_error{AL_STACK_UNDERFLOW_EXT, + "Attempting to pop the default debug group"}; + + DebugGroup &debug = context->mDebugGroups.back(); + const auto source = debug.mSource; + const auto id = debug.mId; + std::string message{std::move(debug.mMessage)}; + + context->mDebugGroups.pop_back(); + if(context->mContextFlags.test(ContextFlags::DebugBit)) + context->sendDebugMessage(debuglock, source, DebugType::PopGroup, id, + DebugSeverity::Notification, message); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +FORCE_ALIGN DECL_FUNCEXT8(ALuint, alGetDebugMessageLog,EXT, ALuint,count, ALsizei,logBufSize, ALenum*,sources, ALenum*,types, ALuint*,ids, ALenum*,severities, ALsizei*,lengths, ALchar*,logBuf) +FORCE_ALIGN ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, + ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, + ALsizei *lengths, ALchar *logBuf) noexcept +try { + if(logBufSize < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative debug log buffer size"}; + + auto sourcesOut = al::span{sources, sources ? count : 0u}; + auto typesOut = al::span{types, types ? count : 0u}; + auto idsOut = al::span{ids, ids ? count : 0u}; + auto severitiesOut = al::span{severities, severities ? count : 0u}; + auto lengthsOut = al::span{lengths, lengths ? count : 0u}; + auto logOut = al::span{logBuf, logBuf ? static_cast(logBufSize) : 0u}; + + std::lock_guard debuglock{context->mDebugCbLock}; + for(ALuint i{0};i < count;++i) + { + if(context->mDebugLog.empty()) + return i; + + auto &entry = context->mDebugLog.front(); + const size_t tocopy{entry.mMessage.size() + 1}; + if(logOut.data() != nullptr) + { + if(logOut.size() < tocopy) + return i; + auto oiter = std::copy(entry.mMessage.cbegin(), entry.mMessage.cend(), logOut.begin()); + *oiter = '\0'; + logOut = {oiter+1, logOut.end()}; + } + + if(!sourcesOut.empty()) + { + sourcesOut.front() = GetDebugSourceEnum(entry.mSource); + sourcesOut = sourcesOut.subspan<1>(); + } + if(!typesOut.empty()) + { + typesOut.front() = GetDebugTypeEnum(entry.mType); + typesOut = typesOut.subspan<1>(); + } + if(!idsOut.empty()) + { + idsOut.front() = entry.mId; + idsOut = idsOut.subspan<1>(); + } + if(!severitiesOut.empty()) + { + severitiesOut.front() = GetDebugSeverityEnum(entry.mSeverity); + severitiesOut = severitiesOut.subspan<1>(); + } + if(!lengthsOut.empty()) + { + lengthsOut.front() = static_cast(tocopy); + lengthsOut = lengthsOut.subspan<1>(); + } + + context->mDebugLog.pop_front(); + } + + return count; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); + return 0; +} + +FORCE_ALIGN DECL_FUNCEXT4(void, alObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,length, const ALchar*,label) +FORCE_ALIGN void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, + ALuint name, ALsizei length, const ALchar *label) noexcept +try { + if(!label && length != 0) + throw al::context_error{AL_INVALID_VALUE, "Null label pointer"}; + + auto objname = (length < 0) ? std::string_view{label} + : std::string_view{label, static_cast(length)}; + if(objname.size() >= MaxObjectLabelLength) + throw al::context_error{AL_INVALID_VALUE, "Object label length too long (%zu >= %d)", + objname.size(), MaxObjectLabelLength}; + + switch(identifier) + { + case AL_SOURCE_EXT: ALsource::SetName(context, name, objname); return; + case AL_BUFFER: ALbuffer::SetName(context, name, objname); return; + case AL_FILTER_EXT: ALfilter::SetName(context, name, objname); return; + case AL_EFFECT_EXT: ALeffect::SetName(context, name, objname); return; + case AL_AUXILIARY_EFFECT_SLOT_EXT: ALeffectslot::SetName(context, name, objname); return; + } + + throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +FORCE_ALIGN DECL_FUNCEXT5(void, alGetObjectLabel,EXT, ALenum,identifier, ALuint,name, ALsizei,bufSize, ALsizei*,length, ALchar*,label) +FORCE_ALIGN void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, + ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) noexcept +try { + if(bufSize < 0) + throw al::context_error{AL_INVALID_VALUE, "Negative label bufSize"}; + + if(!label && !length) + throw al::context_error{AL_INVALID_VALUE, "Null length and label"}; + if(label && bufSize == 0) + throw al::context_error{AL_INVALID_VALUE, "Zero label bufSize"}; + + const auto labelOut = al::span{label, label ? static_cast(bufSize) : 0u}; + auto copy_name = [name,length,labelOut](std::unordered_map &names) + { + std::string_view objname; + + auto iter = names.find(name); + if(iter != names.end()) + objname = iter->second; + + if(labelOut.empty()) + *length = static_cast(objname.size()); + else + { + const size_t tocopy{std::min(objname.size(), labelOut.size()-1)}; + auto oiter = std::copy_n(objname.cbegin(), tocopy, labelOut.begin()); + *oiter = '\0'; + if(length) + *length = static_cast(tocopy); + } + }; + + if(identifier == AL_SOURCE_EXT) + { + std::lock_guard srclock{context->mSourceLock}; + copy_name(context->mSourceNames); + } + else if(identifier == AL_BUFFER) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard buflock{device->BufferLock}; + copy_name(device->mBufferNames); + } + else if(identifier == AL_FILTER_EXT) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + copy_name(device->mFilterNames); + } + else if(identifier == AL_EFFECT_EXT) + { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + copy_name(device->mEffectNames); + } + else if(identifier == AL_AUXILIARY_EFFECT_SLOT_EXT) + { + std::lock_guard slotlock{context->mEffectSlotLock}; + copy_name(context->mEffectSlotNames); + } + else + throw al::context_error{AL_INVALID_ENUM, "Invalid name identifier 0x%04x", identifier}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} diff --git a/Engine/lib/openal-soft/al/debug.h b/Engine/lib/openal-soft/al/debug.h new file mode 100644 index 000000000..d1792adb4 --- /dev/null +++ b/Engine/lib/openal-soft/al/debug.h @@ -0,0 +1,70 @@ +#ifndef AL_DEBUG_H +#define AL_DEBUG_H + +#include +#include +#include +#include + +using uint = unsigned int; + + +/* Somewhat arbitrary. Avoid letting it get out of control if the app enables + * logging but never reads it. + */ +inline constexpr std::uint8_t MaxDebugLoggedMessages{64}; +inline constexpr std::uint16_t MaxDebugMessageLength{1024}; +inline constexpr std::uint8_t MaxDebugGroupDepth{64}; +inline constexpr std::uint16_t MaxObjectLabelLength{1024}; + + +inline constexpr uint DebugSourceBase{0}; +enum class DebugSource : std::uint8_t { + API = 0, + System, + ThirdParty, + Application, + Other, +}; +inline constexpr uint DebugSourceCount{5}; + +inline constexpr uint DebugTypeBase{DebugSourceBase + DebugSourceCount}; +enum class DebugType : std::uint8_t { + Error = 0, + DeprecatedBehavior, + UndefinedBehavior, + Portability, + Performance, + Marker, + PushGroup, + PopGroup, + Other, +}; +inline constexpr uint DebugTypeCount{9}; + +inline constexpr uint DebugSeverityBase{DebugTypeBase + DebugTypeCount}; +enum class DebugSeverity : std::uint8_t { + High = 0, + Medium, + Low, + Notification, +}; +inline constexpr uint DebugSeverityCount{4}; + +struct DebugGroup { + const uint mId; + const DebugSource mSource; + std::string mMessage; + std::vector mFilters; + std::vector mIdFilters; + + template + DebugGroup(DebugSource source, uint id, T&& message) + : mId{id}, mSource{source}, mMessage{std::forward(message)} + { } + DebugGroup(const DebugGroup&) = default; + DebugGroup(DebugGroup&&) = default; + ~DebugGroup(); +}; + +#endif /* AL_DEBUG_H */ diff --git a/Engine/lib/openal-soft/al/direct_defs.h b/Engine/lib/openal-soft/al/direct_defs.h new file mode 100644 index 000000000..4119182f0 --- /dev/null +++ b/Engine/lib/openal-soft/al/direct_defs.h @@ -0,0 +1,127 @@ +#ifndef AL_DIRECT_DEFS_H +#define AL_DIRECT_DEFS_H + +namespace detail_ { + +template +constexpr T DefaultVal() noexcept { return T{}; } + +template<> +constexpr void DefaultVal() noexcept { } + +} // namespace detail_ + +#define DECL_FUNC(R, Name) \ +auto AL_APIENTRY Name() noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get()); \ +} + +#define DECL_FUNC1(R, Name, T1,n1) \ +auto AL_APIENTRY Name(T1 n1) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1); \ +} + +#define DECL_FUNC2(R, Name, T1,n1, T2,n2) \ +auto AL_APIENTRY Name(T1 n1, T2 n2) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2); \ +} + +#define DECL_FUNC3(R, Name, T1,n1, T2,n2, T3,n3) \ +auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2, n3); \ +} + +#define DECL_FUNC4(R, Name, T1,n1, T2,n2, T3,n3, T4,n4) \ +auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2, n3, n4); \ +} + +#define DECL_FUNC5(R, Name, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \ +auto AL_APIENTRY Name(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct(context.get(), n1, n2, n3, n4, n5); \ +} + + +#define DECL_FUNCEXT(R, Name,Ext) \ +auto AL_APIENTRY Name##Ext() noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get()); \ +} + +#define DECL_FUNCEXT1(R, Name,Ext, T1,n1) \ +auto AL_APIENTRY Name##Ext(T1 n1) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1); \ +} + +#define DECL_FUNCEXT2(R, Name,Ext, T1,n1, T2,n2) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2); \ +} + +#define DECL_FUNCEXT3(R, Name,Ext, T1,n1, T2,n2, T3,n3) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3); \ +} + +#define DECL_FUNCEXT4(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4); \ +} + +#define DECL_FUNCEXT5(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5); \ +} + +#define DECL_FUNCEXT6(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6); \ +} + +#define DECL_FUNCEXT8(R, Name,Ext, T1,n1, T2,n2, T3,n3, T4,n4, T5,n5, T6,n6, T7,n7, T8,n8) \ +auto AL_APIENTRY Name##Ext(T1 n1, T2 n2, T3 n3, T4 n4, T5 n5, T6 n6, T7 n7, T8 n8) noexcept -> R \ +{ \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return detail_::DefaultVal(); \ + return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6, n7, n8); \ +} + +#endif /* AL_DIRECT_DEFS_H */ diff --git a/Engine/lib/openal-soft/al/eax/api.h b/Engine/lib/openal-soft/al/eax/api.h index d254da1f7..3e621d30f 100644 --- a/Engine/lib/openal-soft/al/eax/api.h +++ b/Engine/lib/openal-soft/al/eax/api.h @@ -10,34 +10,39 @@ // +#include #include #include #include - -#include +#include +#ifdef _WIN32 +#include +#endif #include "AL/al.h" -#ifndef GUID_DEFINED -#define GUID_DEFINED -typedef struct _GUID { +#ifndef _WIN32 +using GUID = struct _GUID { /* NOLINT(*-reserved-identifier) */ std::uint32_t Data1; std::uint16_t Data2; std::uint16_t Data3; - std::uint8_t Data4[8]; -} GUID; + std::array Data4; +}; -#ifndef _SYS_GUID_OPERATOR_EQ_ -#define _SYS_GUID_OPERATOR_EQ_ inline bool operator==(const GUID& lhs, const GUID& rhs) noexcept { return std::memcmp(&lhs, &rhs, sizeof(GUID)) == 0; } inline bool operator!=(const GUID& lhs, const GUID& rhs) noexcept { return !(lhs == rhs); } -#endif // _SYS_GUID_OPERATOR_EQ_ -#endif // GUID_DEFINED +#endif // _WIN32 +#define DECL_EQOP(T, ...) \ +[[nodiscard]] auto get_members() const noexcept { return std::forward_as_tuple(__VA_ARGS__); } \ +[[nodiscard]] friend bool operator==(const T &lhs, const T &rhs) noexcept \ +{ return lhs.get_members() == rhs.get_members(); } \ +[[nodiscard]] friend bool operator!=(const T &lhs, const T &rhs) noexcept \ +{ return !(lhs == rhs); } extern const GUID DSPROPSETID_EAX_ReverbProperties; @@ -276,11 +281,15 @@ struct EAXVECTOR { float x; float y; float z; + [[nodiscard]] + auto get_members() const noexcept { return std::forward_as_tuple(x, y, z); } }; // EAXVECTOR +[[nodiscard]] inline bool operator==(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept -{ return std::memcmp(&lhs, &rhs, sizeof(EAXVECTOR)) == 0; } +{ return lhs.get_members() == rhs.get_members(); } +[[nodiscard]] inline bool operator!=(const EAXVECTOR& lhs, const EAXVECTOR& rhs) noexcept { return !(lhs == rhs); } @@ -361,6 +370,7 @@ constexpr auto EAXCONTEXT_MINMACROFXFACTOR = 0.0F; constexpr auto EAXCONTEXT_MAXMACROFXFACTOR = 1.0F; constexpr auto EAXCONTEXT_DEFAULTMACROFXFACTOR = 0.0F; +constexpr auto EAXCONTEXT_DEFAULTLASTERROR = EAX_OK; extern const GUID EAXPROPERTYID_EAX40_FXSlot0; extern const GUID EAXPROPERTYID_EAX50_FXSlot0; @@ -613,7 +623,7 @@ struct EAX30SOURCEPROPERTIES { float flOcclusionLFRatio; // occlusion low-frequency level re. main control float flOcclusionRoomRatio; // relative occlusion control for room effect float flOcclusionDirectRatio; // relative occlusion control for direct path - long lExclusion; // main exlusion control (attenuation at high frequencies) + long lExclusion; // main exclusion control (attenuation at high frequencies) float flExclusionLFRatio; // exclusion low-frequency level re. main control long lOutsideVolumeHF; // outside sound cone level at high frequencies float flDopplerFactor; // like DS3D flDopplerFactor but per source @@ -653,11 +663,11 @@ struct EAXSPEAKERLEVELPROPERTIES { }; // EAXSPEAKERLEVELPROPERTIES struct EAX40ACTIVEFXSLOTS { - GUID guidActiveFXSlots[EAX40_MAX_ACTIVE_FXSLOTS]; + std::array guidActiveFXSlots; }; // EAX40ACTIVEFXSLOTS struct EAX50ACTIVEFXSLOTS { - GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS]; + std::array guidActiveFXSlots; }; // EAX50ACTIVEFXSLOTS // Use this structure for EAXSOURCE_OBSTRUCTIONPARAMETERS property. @@ -836,6 +846,11 @@ struct EAXREVERBPROPERTIES { float flLFReference; // reference low frequency float flRoomRolloffFactor; // like DS3D flRolloffFactor but for room effect unsigned long ulFlags; // modifies the behavior of properties + DECL_EQOP(EAXREVERBPROPERTIES, ulEnvironment, flEnvironmentSize, flEnvironmentDiffusion, lRoom, + lRoomHF, lRoomLF, flDecayTime, flDecayHFRatio, flDecayLFRatio, lReflections, + flReflectionsDelay, vReflectionsPan, lReverb, flReverbDelay, vReverbPan, flEchoTime, + flEchoDepth, flModulationTime, flModulationDepth, flAirAbsorptionHF, flHFReference, + flLFReference, flRoomRolloffFactor, ulFlags) }; // EAXREVERBPROPERTIES @@ -965,6 +980,7 @@ enum EAXAGCCOMPRESSOR_PROPERTY : unsigned int { struct EAXAGCCOMPRESSORPROPERTIES { unsigned long ulOnOff; // Switch Compressor on or off + DECL_EQOP(EAXAGCCOMPRESSORPROPERTIES, ulOnOff) }; // EAXAGCCOMPRESSORPROPERTIES @@ -991,6 +1007,7 @@ struct EAXAUTOWAHPROPERTIES { float flReleaseTime; // Release time (seconds) long lResonance; // Resonance (mB) long lPeakLevel; // Peak level (mB) + DECL_EQOP(EAXAUTOWAHPROPERTIES, flAttackTime, flReleaseTime, lResonance, lPeakLevel) }; // EAXAUTOWAHPROPERTIES @@ -1038,6 +1055,7 @@ struct EAXCHORUSPROPERTIES { float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (-1 to 1) float flDelay; // Delay (seconds) + DECL_EQOP(EAXCHORUSPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXCHORUSPROPERTIES @@ -1086,6 +1104,7 @@ struct EAXDISTORTIONPROPERTIES { float flLowPassCutOff; // Controls the cut-off of the filter pre-distortion (Hz) float flEQCenter; // Controls the center frequency of the EQ post-distortion (Hz) float flEQBandwidth; // Controls the bandwidth of the EQ post-distortion (Hz) + DECL_EQOP(EAXDISTORTIONPROPERTIES, flEdge, lGain, flLowPassCutOff, flEQCenter, flEQBandwidth) }; // EAXDISTORTIONPROPERTIES @@ -1130,6 +1149,7 @@ struct EAXECHOPROPERTIES { float flDamping; // Controls a low-pass filter that dampens the echoes (0 to 1) float flFeedback; // Controls the duration of echo repetition (0 to 1) float flSpread; // Controls the left-right spread of the echoes + DECL_EQOP(EAXECHOPROPERTIES, flDelay, flLRDelay, flDamping, flFeedback, flSpread) }; // EAXECHOPROPERTIES @@ -1184,6 +1204,8 @@ struct EAXEQUALIZERPROPERTIES { float flMid2Width; // (octaves) long lHighGain; // (mB) float flHighCutOff; // (Hz) + DECL_EQOP(EAXEQUALIZERPROPERTIES, lLowGain, flLowCutOff, lMid1Gain, flMid1Center, flMid1Width, + lMid2Gain, flMid2Center, flMid2Width, lHighGain, flHighCutOff) }; // EAXEQUALIZERPROPERTIES @@ -1255,6 +1277,7 @@ struct EAXFLANGERPROPERTIES { float flDepth; // Depth (0 to 1) float flFeedback; // Feedback (0 to 1) float flDelay; // Delay (seconds) + DECL_EQOP(EAXFLANGERPROPERTIES, ulWaveform, lPhase, flRate, flDepth, flFeedback, flDelay) }; // EAXFLANGERPROPERTIES @@ -1305,6 +1328,7 @@ struct EAXFREQUENCYSHIFTERPROPERTIES { float flFrequency; // (Hz) unsigned long ulLeftDirection; // see enum above unsigned long ulRightDirection; // see enum above + DECL_EQOP(EAXFREQUENCYSHIFTERPROPERTIES, flFrequency, ulLeftDirection, ulRightDirection) }; // EAXFREQUENCYSHIFTERPROPERTIES @@ -1383,6 +1407,8 @@ struct EAXVOCALMORPHERPROPERTIES { long lPhonemeBCoarseTuning; // (semitones) unsigned long ulWaveform; // Waveform selector - see enum above float flRate; // (Hz) + DECL_EQOP(EAXVOCALMORPHERPROPERTIES, ulPhonemeA, lPhonemeACoarseTuning, ulPhonemeB, + lPhonemeBCoarseTuning, ulWaveform, flRate) }; // EAXVOCALMORPHERPROPERTIES @@ -1425,6 +1451,7 @@ enum EAXPITCHSHIFTER_PROPERTY : unsigned int { struct EAXPITCHSHIFTERPROPERTIES { long lCoarseTune; // Amount of pitch shift (semitones) long lFineTune; // Amount of pitch shift (cents) + DECL_EQOP(EAXPITCHSHIFTERPROPERTIES, lCoarseTune, lFineTune) }; // EAXPITCHSHIFTERPROPERTIES @@ -1460,6 +1487,7 @@ struct EAXRINGMODULATORPROPERTIES { float flFrequency; // Frequency of modulation (Hz) float flHighPassCutOff; // Cut-off frequency of high-pass filter (Hz) unsigned long ulWaveform; // Waveform selector - see enum above + DECL_EQOP(EAXRINGMODULATORPROPERTIES, flFrequency, flHighPassCutOff, ulWaveform) }; // EAXRINGMODULATORPROPERTIES @@ -1490,4 +1518,5 @@ using LPEAXGET = ALenum(AL_APIENTRY*)( ALvoid* property_buffer, ALuint property_size); +#undef DECL_EQOP #endif // !EAX_API_INCLUDED diff --git a/Engine/lib/openal-soft/al/eax/call.cpp b/Engine/lib/openal-soft/al/eax/call.cpp index 689d5cf1d..013a39928 100644 --- a/Engine/lib/openal-soft/al/eax/call.cpp +++ b/Engine/lib/openal-soft/al/eax/call.cpp @@ -22,8 +22,7 @@ EaxCall::EaxCall( ALuint property_source_id, ALvoid* property_buffer, ALuint property_size) - : mCallType{type}, mVersion{0}, mPropertySetId{EaxCallPropertySetId::none} - , mIsDeferred{(property_id & deferred_flag) != 0} + : mCallType{type}, mIsDeferred{(property_id & deferred_flag) != 0} , mPropertyId{property_id & ~deferred_flag}, mPropertySourceId{property_source_id} , mPropertyBuffer{property_buffer}, mPropertyBufferSize{property_size} { diff --git a/Engine/lib/openal-soft/al/eax/call.h b/Engine/lib/openal-soft/al/eax/call.h index 5ec33b0fa..72f96bbe4 100644 --- a/Engine/lib/openal-soft/al/eax/call.h +++ b/Engine/lib/openal-soft/al/eax/call.h @@ -31,16 +31,16 @@ public: ALvoid* property_buffer, ALuint property_size); - bool is_get() const noexcept { return mCallType == EaxCallType::get; } - bool is_deferred() const noexcept { return mIsDeferred; } - int get_version() const noexcept { return mVersion; } - EaxCallPropertySetId get_property_set_id() const noexcept { return mPropertySetId; } - ALuint get_property_id() const noexcept { return mPropertyId; } - ALuint get_property_al_name() const noexcept { return mPropertySourceId; } - EaxFxSlotIndex get_fx_slot_index() const noexcept { return mFxSlotIndex; } + [[nodiscard]] auto is_get() const noexcept -> bool { return mCallType == EaxCallType::get; } + [[nodiscard]] auto is_deferred() const noexcept -> bool { return mIsDeferred; } + [[nodiscard]] auto get_version() const noexcept -> int { return mVersion; } + [[nodiscard]] auto get_property_set_id() const noexcept -> EaxCallPropertySetId { return mPropertySetId; } + [[nodiscard]] auto get_property_id() const noexcept -> ALuint { return mPropertyId; } + [[nodiscard]] auto get_property_al_name() const noexcept -> ALuint { return mPropertySourceId; } + [[nodiscard]] auto get_fx_slot_index() const noexcept -> EaxFxSlotIndex { return mFxSlotIndex; } template - TValue& get_value() const + [[nodiscard]] auto get_value() const -> TValue& { if(mPropertyBufferSize < sizeof(TValue)) fail_too_small(); @@ -49,32 +49,32 @@ public: } template - al::span get_values(size_t max_count) const + [[nodiscard]] auto get_values(size_t max_count) const -> al::span { if(max_count == 0 || mPropertyBufferSize < sizeof(TValue)) fail_too_small(); - const auto count = minz(mPropertyBufferSize / sizeof(TValue), max_count); - return al::as_span(static_cast(mPropertyBuffer), count); + const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count); + return {static_cast(mPropertyBuffer), count}; } template - al::span get_values() const + [[nodiscard]] auto get_values() const -> al::span { - return get_values(~size_t{}); + return get_values(~0_uz); } template - void set_value(const TValue& value) const + auto set_value(const TValue& value) const -> void { get_value() = value; } private: const EaxCallType mCallType; - int mVersion; - EaxFxSlotIndex mFxSlotIndex; - EaxCallPropertySetId mPropertySetId; + int mVersion{}; + EaxFxSlotIndex mFxSlotIndex{}; + EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none}; bool mIsDeferred; const ALuint mPropertyId; diff --git a/Engine/lib/openal-soft/al/eax/effect.h b/Engine/lib/openal-soft/al/eax/effect.h index a0b4e71b2..9b1f8bd35 100644 --- a/Engine/lib/openal-soft/al/eax/effect.h +++ b/Engine/lib/openal-soft/al/eax/effect.h @@ -4,60 +4,56 @@ #include #include +#include #include "alnumeric.h" #include "AL/al.h" +#include "AL/alext.h" #include "core/effects/base.h" #include "call.h" -struct EaxEffectErrorMessages -{ +struct EaxEffectErrorMessages { static constexpr auto unknown_property_id() noexcept { return "Unknown property id."; } static constexpr auto unknown_version() noexcept { return "Unknown version."; } }; // EaxEffectErrorMessages -/* TODO: Use std::variant (C++17). */ -enum class EaxEffectType { - None, Reverb, Chorus, Autowah, Compressor, Distortion, Echo, Equalizer, Flanger, - FrequencyShifter, Modulator, PitchShifter, VocalMorpher -}; -struct EaxEffectProps { - EaxEffectType mType; - union { - EAXREVERBPROPERTIES mReverb; - EAXCHORUSPROPERTIES mChorus; - EAXAUTOWAHPROPERTIES mAutowah; - EAXAGCCOMPRESSORPROPERTIES mCompressor; - EAXDISTORTIONPROPERTIES mDistortion; - EAXECHOPROPERTIES mEcho; - EAXEQUALIZERPROPERTIES mEqualizer; - EAXFLANGERPROPERTIES mFlanger; - EAXFREQUENCYSHIFTERPROPERTIES mFrequencyShifter; - EAXRINGMODULATORPROPERTIES mModulator; - EAXPITCHSHIFTERPROPERTIES mPitchShifter; - EAXVOCALMORPHERPROPERTIES mVocalMorpher; - }; -}; +using EaxEffectProps = std::variant; + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; constexpr ALenum EnumFromEaxEffectType(const EaxEffectProps &props) { - switch(props.mType) - { - case EaxEffectType::None: break; - case EaxEffectType::Reverb: return AL_EFFECT_EAXREVERB; - case EaxEffectType::Chorus: return AL_EFFECT_CHORUS; - case EaxEffectType::Autowah: return AL_EFFECT_AUTOWAH; - case EaxEffectType::Compressor: return AL_EFFECT_COMPRESSOR; - case EaxEffectType::Distortion: return AL_EFFECT_DISTORTION; - case EaxEffectType::Echo: return AL_EFFECT_ECHO; - case EaxEffectType::Equalizer: return AL_EFFECT_EQUALIZER; - case EaxEffectType::Flanger: return AL_EFFECT_FLANGER; - case EaxEffectType::FrequencyShifter: return AL_EFFECT_FREQUENCY_SHIFTER; - case EaxEffectType::Modulator: return AL_EFFECT_RING_MODULATOR; - case EaxEffectType::PitchShifter: return AL_EFFECT_PITCH_SHIFTER; - case EaxEffectType::VocalMorpher: return AL_EFFECT_VOCAL_MORPHER; - } - return AL_EFFECT_NULL; + return std::visit(overloaded{ + [](const std::monostate&) noexcept { return AL_EFFECT_NULL; }, + [](const EAXREVERBPROPERTIES&) noexcept { return AL_EFFECT_EAXREVERB; }, + [](const EAXCHORUSPROPERTIES&) noexcept { return AL_EFFECT_CHORUS; }, + [](const EAXAUTOWAHPROPERTIES&) noexcept { return AL_EFFECT_AUTOWAH; }, + [](const EAXAGCCOMPRESSORPROPERTIES&) noexcept { return AL_EFFECT_COMPRESSOR; }, + [](const EAXDISTORTIONPROPERTIES&) noexcept { return AL_EFFECT_DISTORTION; }, + [](const EAXECHOPROPERTIES&) noexcept { return AL_EFFECT_ECHO; }, + [](const EAXEQUALIZERPROPERTIES&) noexcept { return AL_EFFECT_EQUALIZER; }, + [](const EAXFLANGERPROPERTIES&) noexcept { return AL_EFFECT_FLANGER; }, + [](const EAXFREQUENCYSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_FREQUENCY_SHIFTER; }, + [](const EAXRINGMODULATORPROPERTIES&) noexcept { return AL_EFFECT_RING_MODULATOR; }, + [](const EAXPITCHSHIFTERPROPERTIES&) noexcept { return AL_EFFECT_PITCH_SHIFTER; }, + [](const EAXVOCALMORPHERPROPERTIES&) noexcept { return AL_EFFECT_VOCAL_MORPHER; } + }, props); } struct EaxReverbCommitter { @@ -105,7 +101,6 @@ struct EaxReverbCommitter { bool commit(const EAX_REVERBPROPERTIES &props); bool commit(const EAX20LISTENERPROPERTIES &props); bool commit(const EAXREVERBPROPERTIES &props); - bool commit(const EaxEffectProps &props); static void SetDefaults(EAX_REVERBPROPERTIES &props); static void SetDefaults(EAX20LISTENERPROPERTIES &props); @@ -115,16 +110,13 @@ struct EaxReverbCommitter { static void Get(const EaxCall &call, const EAX_REVERBPROPERTIES &props); static void Get(const EaxCall &call, const EAX20LISTENERPROPERTIES &props); static void Get(const EaxCall &call, const EAXREVERBPROPERTIES &props); - static void Get(const EaxCall &call, const EaxEffectProps &props); static void Set(const EaxCall &call, EAX_REVERBPROPERTIES &props); static void Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props); static void Set(const EaxCall &call, EAXREVERBPROPERTIES &props); - static void Set(const EaxCall &call, EaxEffectProps &props); - static void translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept; - static void translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept; - static void translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept; + static void translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; + static void translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept; }; template @@ -149,51 +141,137 @@ struct EaxCommitter { [[noreturn]] static void fail(const char *message); [[noreturn]] static void fail_unknown_property_id() { fail(EaxEffectErrorMessages::unknown_property_id()); } - - bool commit(const EaxEffectProps &props); - - static void SetDefaults(EaxEffectProps &props); - static void Get(const EaxCall &call, const EaxEffectProps &props); - static void Set(const EaxCall &call, EaxEffectProps &props); }; struct EaxAutowahCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXAUTOWAHPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props); + static void Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props); }; struct EaxChorusCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXCHORUSPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props); + static void Set(const EaxCall &call, EAXCHORUSPROPERTIES &props); }; struct EaxCompressorCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXAGCCOMPRESSORPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props); + static void Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props); }; struct EaxDistortionCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXDISTORTIONPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props); + static void Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props); }; struct EaxEchoCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXECHOPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXECHOPROPERTIES &props); + static void Set(const EaxCall &call, EAXECHOPROPERTIES &props); }; struct EaxEqualizerCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXEQUALIZERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props); + static void Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props); }; struct EaxFlangerCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXFLANGERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props); + static void Set(const EaxCall &call, EAXFLANGERPROPERTIES &props); }; struct EaxFrequencyShifterCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXFREQUENCYSHIFTERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props); + static void Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props); }; struct EaxModulatorCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXRINGMODULATORPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props); + static void Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props); }; struct EaxPitchShifterCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXPITCHSHIFTERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props); + static void Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props); }; struct EaxVocalMorpherCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const EAXVOCALMORPHERPROPERTIES &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props); + static void Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props); }; struct EaxNullCommitter : public EaxCommitter { using EaxCommitter::EaxCommitter; + + bool commit(const std::monostate &props); + + static void SetDefaults(EaxEffectProps &props); + static void Get(const EaxCall &call, const std::monostate &props); + static void Set(const EaxCall &call, std::monostate &props); }; +template +struct CommitterFromProps { }; + +template<> struct CommitterFromProps { using type = EaxNullCommitter; }; +template<> struct CommitterFromProps { using type = EaxReverbCommitter; }; +template<> struct CommitterFromProps { using type = EaxChorusCommitter; }; +template<> struct CommitterFromProps { using type = EaxCompressorCommitter; }; +template<> struct CommitterFromProps { using type = EaxAutowahCommitter; }; +template<> struct CommitterFromProps { using type = EaxDistortionCommitter; }; +template<> struct CommitterFromProps { using type = EaxEchoCommitter; }; +template<> struct CommitterFromProps { using type = EaxEqualizerCommitter; }; +template<> struct CommitterFromProps { using type = EaxFlangerCommitter; }; +template<> struct CommitterFromProps { using type = EaxFrequencyShifterCommitter; }; +template<> struct CommitterFromProps { using type = EaxModulatorCommitter; }; +template<> struct CommitterFromProps { using type = EaxPitchShifterCommitter; }; +template<> struct CommitterFromProps { using type = EaxVocalMorpherCommitter; }; + +template +using CommitterFor = typename CommitterFromProps>>::type; + class EaxEffect { public: @@ -238,51 +316,39 @@ public: State4 state5_{}; - template - void call_set_defaults(Args&& ...args) - { return T::SetDefaults(std::forward(args)...); } - - void call_set_defaults(const ALenum altype, EaxEffectProps &props) + static void call_set_defaults(const ALenum altype, EaxEffectProps &props) { - if(altype == AL_EFFECT_EAXREVERB) - return call_set_defaults(props); - if(altype == AL_EFFECT_CHORUS) - return call_set_defaults(props); - if(altype == AL_EFFECT_AUTOWAH) - return call_set_defaults(props); - if(altype == AL_EFFECT_COMPRESSOR) - return call_set_defaults(props); - if(altype == AL_EFFECT_DISTORTION) - return call_set_defaults(props); - if(altype == AL_EFFECT_ECHO) - return call_set_defaults(props); - if(altype == AL_EFFECT_EQUALIZER) - return call_set_defaults(props); - if(altype == AL_EFFECT_FLANGER) - return call_set_defaults(props); - if(altype == AL_EFFECT_FREQUENCY_SHIFTER) - return call_set_defaults(props); - if(altype == AL_EFFECT_RING_MODULATOR) - return call_set_defaults(props); - if(altype == AL_EFFECT_PITCH_SHIFTER) - return call_set_defaults(props); - if(altype == AL_EFFECT_VOCAL_MORPHER) - return call_set_defaults(props); - return call_set_defaults(props); + switch(altype) + { + case AL_EFFECT_EAXREVERB: return EaxReverbCommitter::SetDefaults(props); + case AL_EFFECT_CHORUS: return EaxChorusCommitter::SetDefaults(props); + case AL_EFFECT_AUTOWAH: return EaxAutowahCommitter::SetDefaults(props); + case AL_EFFECT_COMPRESSOR: return EaxCompressorCommitter::SetDefaults(props); + case AL_EFFECT_DISTORTION: return EaxDistortionCommitter::SetDefaults(props); + case AL_EFFECT_ECHO: return EaxEchoCommitter::SetDefaults(props); + case AL_EFFECT_EQUALIZER: return EaxEqualizerCommitter::SetDefaults(props); + case AL_EFFECT_FLANGER: return EaxFlangerCommitter::SetDefaults(props); + case AL_EFFECT_FREQUENCY_SHIFTER: return EaxFrequencyShifterCommitter::SetDefaults(props); + case AL_EFFECT_RING_MODULATOR: return EaxModulatorCommitter::SetDefaults(props); + case AL_EFFECT_PITCH_SHIFTER: return EaxPitchShifterCommitter::SetDefaults(props); + case AL_EFFECT_VOCAL_MORPHER: return EaxVocalMorpherCommitter::SetDefaults(props); + case AL_EFFECT_NULL: break; + } + return EaxNullCommitter::SetDefaults(props); } template void init() { - call_set_defaults(state1_.d); + EaxReverbCommitter::SetDefaults(state1_.d); state1_.i = state1_.d; - call_set_defaults(state2_.d); + EaxReverbCommitter::SetDefaults(state2_.d); state2_.i = state2_.d; - call_set_defaults(state3_.d); + EaxReverbCommitter::SetDefaults(state3_.d); state3_.i = state3_.d; - call_set_defaults(state4_.d); + T::SetDefaults(state4_.d); state4_.i = state4_.d; - call_set_defaults(state5_.d); + T::SetDefaults(state5_.d); state5_.i = state5_.d; } @@ -290,9 +356,9 @@ public: { switch(eax_version) { - case 1: call_set_defaults(state1_.d); break; - case 2: call_set_defaults(state2_.d); break; - case 3: call_set_defaults(state3_.d); break; + case 1: EaxReverbCommitter::SetDefaults(state1_.d); break; + case 2: EaxReverbCommitter::SetDefaults(state2_.d); break; + case 3: EaxReverbCommitter::SetDefaults(state3_.d); break; case 4: call_set_defaults(altype, state4_.d); break; case 5: call_set_defaults(altype, state5_.d); break; } @@ -300,47 +366,20 @@ public: } -#define EAXCALL(T, Callable, ...) \ - if(T == EaxEffectType::Reverb) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Chorus) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Autowah) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Compressor) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Distortion) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Echo) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Equalizer) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Flanger) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::FrequencyShifter) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::Modulator) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::PitchShifter) \ - return Callable(__VA_ARGS__); \ - if(T == EaxEffectType::VocalMorpher) \ - return Callable(__VA_ARGS__); \ - return Callable(__VA_ARGS__) - - template - static void call_set(Args&& ...args) - { return T::Set(std::forward(args)...); } - static void call_set(const EaxCall &call, EaxEffectProps &props) - { EAXCALL(props.mType, call_set, call, props); } + { + return std::visit([&](auto &arg) + { return CommitterFor::Set(call, arg); }, + props); + } void set(const EaxCall &call) { switch(call.get_version()) { - case 1: call_set(call, state1_.d); break; - case 2: call_set(call, state2_.d); break; - case 3: call_set(call, state3_.d); break; + case 1: EaxReverbCommitter::Set(call, state1_.d); break; + case 2: EaxReverbCommitter::Set(call, state2_.d); break; + case 3: EaxReverbCommitter::Set(call, state3_.d); break; case 4: call_set(call, state4_.d); break; case 5: call_set(call, state5_.d); break; } @@ -348,32 +387,32 @@ public: } - template - static void call_get(Args&& ...args) - { return T::Get(std::forward(args)...); } - static void call_get(const EaxCall &call, const EaxEffectProps &props) - { EAXCALL(props.mType, call_get, call, props); } + { + return std::visit([&](auto &arg) + { return CommitterFor::Get(call, arg); }, + props); + } - void get(const EaxCall &call) + void get(const EaxCall &call) const { switch(call.get_version()) { - case 1: call_get(call, state1_.d); break; - case 2: call_get(call, state2_.d); break; - case 3: call_get(call, state3_.d); break; + case 1: EaxReverbCommitter::Get(call, state1_.d); break; + case 2: EaxReverbCommitter::Get(call, state2_.d); break; + case 3: EaxReverbCommitter::Get(call, state3_.d); break; case 4: call_get(call, state4_.d); break; case 5: call_get(call, state5_.d); break; } } - template - bool call_commit(Args&& ...args) - { return T{props_, al_effect_props_}.commit(std::forward(args)...); } - bool call_commit(const EaxEffectProps &props) - { EAXCALL(props.mType, call_commit, props); } + { + return std::visit([&](auto &arg) + { return CommitterFor{props_, al_effect_props_}.commit(arg); }, + props); + } bool commit(int eax_version) { @@ -388,15 +427,15 @@ public: { case 1: state1_.i = state1_.d; - ret |= call_commit(state1_.d); + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d); break; case 2: state2_.i = state2_.d; - ret |= call_commit(state2_.d); + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d); break; case 3: state3_.i = state3_.d; - ret |= call_commit(state3_.d); + ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d); break; case 4: state4_.i = state4_.d; diff --git a/Engine/lib/openal-soft/al/eax/exception.cpp b/Engine/lib/openal-soft/al/eax/exception.cpp index 435e74426..e4945d88d 100644 --- a/Engine/lib/openal-soft/al/eax/exception.cpp +++ b/Engine/lib/openal-soft/al/eax/exception.cpp @@ -6,54 +6,27 @@ #include -EaxException::EaxException(const char *context, const char *message) +EaxException::EaxException(std::string_view context, std::string_view message) : std::runtime_error{make_message(context, message)} { } EaxException::~EaxException() = default; -std::string EaxException::make_message(const char *context, const char *message) +std::string EaxException::make_message(std::string_view context, std::string_view message) { - const auto context_size = (context ? std::string::traits_type::length(context) : 0); - const auto has_contex = (context_size > 0); - - const auto message_size = (message ? std::string::traits_type::length(message) : 0); - const auto has_message = (message_size > 0); - - if (!has_contex && !has_message) - { - return std::string{}; - } - - static constexpr char left_prefix[] = "["; - const auto left_prefix_size = std::string::traits_type::length(left_prefix); - - static constexpr char right_prefix[] = "] "; - const auto right_prefix_size = std::string::traits_type::length(right_prefix); - - const auto what_size = - ( - has_contex ? - left_prefix_size + context_size + right_prefix_size : - 0) + - message_size + - 1; - auto what = std::string{}; - what.reserve(what_size); + if(context.empty() && message.empty()) + return what; - if (has_contex) + what.reserve((!context.empty() ? context.size() + 3 : 0) + message.length() + 1); + if(!context.empty()) { - what.append(left_prefix, left_prefix_size); - what.append(context, context_size); - what.append(right_prefix, right_prefix_size); - } - - if (has_message) - { - what.append(message, message_size); + what += "["; + what += context; + what += "] "; } + what += message; return what; } diff --git a/Engine/lib/openal-soft/al/eax/exception.h b/Engine/lib/openal-soft/al/eax/exception.h index 3ae88cdc6..64cf7c49e 100644 --- a/Engine/lib/openal-soft/al/eax/exception.h +++ b/Engine/lib/openal-soft/al/eax/exception.h @@ -1,18 +1,23 @@ #ifndef EAX_EXCEPTION_INCLUDED #define EAX_EXCEPTION_INCLUDED - #include #include +#include class EaxException : public std::runtime_error { - static std::string make_message(const char *context, const char *message); + static std::string make_message(std::string_view context, std::string_view message); public: - EaxException(const char *context, const char *message); + EaxException() = delete; + EaxException(const EaxException&) = default; + EaxException(EaxException&&) = default; + EaxException(std::string_view context, std::string_view message); ~EaxException() override; -}; // EaxException + auto operator=(const EaxException&) -> EaxException& = default; + auto operator=(EaxException&&) -> EaxException& = default; +}; -#endif // !EAX_EXCEPTION_INCLUDED +#endif /* EAX_EXCEPTION_INCLUDED */ diff --git a/Engine/lib/openal-soft/al/eax/fx_slot_index.h b/Engine/lib/openal-soft/al/eax/fx_slot_index.h index 63dba0372..9f350d9b1 100644 --- a/Engine/lib/openal-soft/al/eax/fx_slot_index.h +++ b/Engine/lib/openal-soft/al/eax/fx_slot_index.h @@ -3,17 +3,16 @@ #include +#include -#include "aloptional.h" #include "api.h" using EaxFxSlotIndexValue = std::size_t; -class EaxFxSlotIndex : public al::optional -{ +class EaxFxSlotIndex : public std::optional { public: - using al::optional::optional; + using std::optional::optional; EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; } EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; } diff --git a/Engine/lib/openal-soft/al/eax/fx_slots.h b/Engine/lib/openal-soft/al/eax/fx_slots.h index 18b2d3ad4..d2d90b246 100644 --- a/Engine/lib/openal-soft/al/eax/fx_slots.h +++ b/Engine/lib/openal-soft/al/eax/fx_slots.h @@ -6,13 +6,10 @@ #include "al/auxeffectslot.h" -#include "api.h" -#include "call.h" #include "fx_slot_index.h" -class EaxFxSlots -{ +class EaxFxSlots { public: void initialize(ALCcontext& al_context); @@ -25,11 +22,9 @@ public: } - const ALeffectslot& get( - EaxFxSlotIndex index) const; + [[nodiscard]] auto get(EaxFxSlotIndex index) const -> const ALeffectslot&; - ALeffectslot& get( - EaxFxSlotIndex index); + [[nodiscard]] auto get(EaxFxSlotIndex index) -> ALeffectslot&; private: using Items = std::array; @@ -39,8 +34,7 @@ private: [[noreturn]] - static void fail( - const char* message); + static void fail(const char* message); void initialize_fx_slots(ALCcontext& al_context); }; // EaxFxSlots diff --git a/Engine/lib/openal-soft/al/eax/globals.cpp b/Engine/lib/openal-soft/al/eax/globals.cpp deleted file mode 100644 index 80e9dbfe5..000000000 --- a/Engine/lib/openal-soft/al/eax/globals.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "config.h" - -#include "globals.h" - - -bool eax_g_is_enabled = true; - - -const char eax1_ext_name[] = "EAX"; -const char eax2_ext_name[] = "EAX2.0"; -const char eax3_ext_name[] = "EAX3.0"; -const char eax4_ext_name[] = "EAX4.0"; -const char eax5_ext_name[] = "EAX5.0"; - -const char eax_x_ram_ext_name[] = "EAX-RAM"; - -const char eax_eax_set_func_name[] = "EAXSet"; -const char eax_eax_get_func_name[] = "EAXGet"; - -const char eax_eax_set_buffer_mode_func_name[] = "EAXSetBufferMode"; -const char eax_eax_get_buffer_mode_func_name[] = "EAXGetBufferMode"; diff --git a/Engine/lib/openal-soft/al/eax/globals.h b/Engine/lib/openal-soft/al/eax/globals.h index 1b4d63b85..6f3e9078f 100644 --- a/Engine/lib/openal-soft/al/eax/globals.h +++ b/Engine/lib/openal-soft/al/eax/globals.h @@ -1,22 +1,6 @@ #ifndef EAX_GLOBALS_INCLUDED #define EAX_GLOBALS_INCLUDED +inline bool eax_g_is_enabled{true}; -extern bool eax_g_is_enabled; - - -extern const char eax1_ext_name[]; -extern const char eax2_ext_name[]; -extern const char eax3_ext_name[]; -extern const char eax4_ext_name[]; -extern const char eax5_ext_name[]; - -extern const char eax_x_ram_ext_name[]; - -extern const char eax_eax_set_func_name[]; -extern const char eax_eax_get_func_name[]; - -extern const char eax_eax_set_buffer_mode_func_name[]; -extern const char eax_eax_get_buffer_mode_func_name[]; - -#endif // !EAX_GLOBALS_INCLUDED +#endif /* EAX_GLOBALS_INCLUDED */ diff --git a/Engine/lib/openal-soft/al/eax/utils.cpp b/Engine/lib/openal-soft/al/eax/utils.cpp index b3ed6ca14..871ab764b 100644 --- a/Engine/lib/openal-soft/al/eax/utils.cpp +++ b/Engine/lib/openal-soft/al/eax/utils.cpp @@ -5,10 +5,11 @@ #include #include +#include "alstring.h" #include "core/logging.h" -void eax_log_exception(const char *message) noexcept +void eax_log_exception(std::string_view message) noexcept { const auto exception_ptr = std::current_exception(); assert(exception_ptr); @@ -17,10 +18,9 @@ void eax_log_exception(const char *message) noexcept std::rethrow_exception(exception_ptr); } catch(const std::exception& ex) { - const auto ex_message = ex.what(); - ERR("%s %s\n", message ? message : "", ex_message); + ERR("%.*s %s\n", al::sizei(message), message.data(), ex.what()); } catch(...) { - ERR("%s %s\n", message ? message : "", "Generic exception."); + ERR("%.*s %s\n", al::sizei(message), message.data(), "Generic exception."); } } diff --git a/Engine/lib/openal-soft/al/eax/utils.h b/Engine/lib/openal-soft/al/eax/utils.h index 8ff75a18f..8e0f975f1 100644 --- a/Engine/lib/openal-soft/al/eax/utils.h +++ b/Engine/lib/openal-soft/al/eax/utils.h @@ -4,8 +4,11 @@ #include #include #include +#include #include +#include "opthelpers.h" + using EaxDirtyFlags = unsigned int; struct EaxAlLowPassParam { @@ -13,16 +16,13 @@ struct EaxAlLowPassParam { float gain_hf; }; -void eax_log_exception(const char *message) noexcept; +void eax_log_exception(std::string_view message) noexcept; template -void eax_validate_range( - const char* value_name, - const TValue& value, - const TValue& min_value, +void eax_validate_range(std::string_view value_name, const TValue& value, const TValue& min_value, const TValue& max_value) { - if (value >= min_value && value <= max_value) + if(value >= min_value && value <= max_value) LIKELY return; const auto message = diff --git a/Engine/lib/openal-soft/al/eax/x_ram.h b/Engine/lib/openal-soft/al/eax/x_ram.h index 438b99163..3616550d2 100644 --- a/Engine/lib/openal-soft/al/eax/x_ram.h +++ b/Engine/lib/openal-soft/al/eax/x_ram.h @@ -24,15 +24,7 @@ constexpr auto AL_STORAGE_AUTOMATIC_NAME = "AL_STORAGE_AUTOMATIC"; constexpr auto AL_STORAGE_HARDWARE_NAME = "AL_STORAGE_HARDWARE"; constexpr auto AL_STORAGE_ACCESSIBLE_NAME = "AL_STORAGE_ACCESSIBLE"; - -ALboolean AL_APIENTRY EAXSetBufferMode( - ALsizei n, - const ALuint* buffers, - ALint value); - -ALenum AL_APIENTRY EAXGetBufferMode( - ALuint buffer, - ALint* pReserved); - +ALboolean AL_APIENTRY EAXSetBufferMode(ALsizei n, const ALuint *buffers, ALint value) noexcept; +ALenum AL_APIENTRY EAXGetBufferMode(ALuint buffer, ALint *pReserved) noexcept; #endif // !EAX_X_RAM_INCLUDED diff --git a/Engine/lib/openal-soft/al/effect.cpp b/Engine/lib/openal-soft/al/effect.cpp index bde89912a..6509d755d 100644 --- a/Engine/lib/openal-soft/al/effect.cpp +++ b/Engine/lib/openal-soft/al/effect.cpp @@ -28,9 +28,13 @@ #include #include #include -#include #include +#include +#include +#include #include +#include +#include #include "AL/al.h" #include "AL/alc.h" @@ -38,26 +42,23 @@ #include "AL/efx-presets.h" #include "AL/efx.h" +#include "al/effects/effects.h" #include "albit.h" #include "alc/context.h" #include "alc/device.h" -#include "alc/effects/base.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" +#include "alspan.h" #include "alstring.h" -#include "core/except.h" #include "core/logging.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" -#ifdef ALSOFT_EAX -#include -#include "eax/exception.h" -#endif // ALSOFT_EAX - -const EffectList gEffectList[16]{ +const std::array gEffectList{{ { "eaxreverb", EAXREVERB_EFFECT, AL_EFFECT_EAXREVERB }, { "reverb", REVERB_EFFECT, AL_EFFECT_REVERB }, { "autowah", AUTOWAH_EFFECT, AL_EFFECT_AUTOWAH }, @@ -73,95 +74,74 @@ const EffectList gEffectList[16]{ { "vmorpher", VMORPHER_EFFECT, AL_EFFECT_VOCAL_MORPHER }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT }, { "dedicated", DEDICATED_EFFECT, AL_EFFECT_DEDICATED_DIALOGUE }, - { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_REVERB_SOFT }, -}; + { "convolution", CONVOLUTION_EFFECT, AL_EFFECT_CONVOLUTION_SOFT }, +}}; -bool DisabledEffects[MAX_EFFECTS]; - - -effect_exception::effect_exception(ALenum code, const char *msg, ...) : mErrorCode{code} -{ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); -} -effect_exception::~effect_exception() = default; namespace { -struct EffectPropsItem { - ALenum Type; - const EffectProps &DefaultProps; - const EffectVtable &Vtable; -}; -constexpr EffectPropsItem EffectPropsList[] = { - { AL_EFFECT_NULL, NullEffectProps, NullEffectVtable }, - { AL_EFFECT_EAXREVERB, ReverbEffectProps, ReverbEffectVtable }, - { AL_EFFECT_REVERB, StdReverbEffectProps, StdReverbEffectVtable }, - { AL_EFFECT_AUTOWAH, AutowahEffectProps, AutowahEffectVtable }, - { AL_EFFECT_CHORUS, ChorusEffectProps, ChorusEffectVtable }, - { AL_EFFECT_COMPRESSOR, CompressorEffectProps, CompressorEffectVtable }, - { AL_EFFECT_DISTORTION, DistortionEffectProps, DistortionEffectVtable }, - { AL_EFFECT_ECHO, EchoEffectProps, EchoEffectVtable }, - { AL_EFFECT_EQUALIZER, EqualizerEffectProps, EqualizerEffectVtable }, - { AL_EFFECT_FLANGER, FlangerEffectProps, FlangerEffectVtable }, - { AL_EFFECT_FREQUENCY_SHIFTER, FshifterEffectProps, FshifterEffectVtable }, - { AL_EFFECT_RING_MODULATOR, ModulatorEffectProps, ModulatorEffectVtable }, - { AL_EFFECT_PITCH_SHIFTER, PshifterEffectProps, PshifterEffectVtable }, - { AL_EFFECT_VOCAL_MORPHER, VmorpherEffectProps, VmorpherEffectVtable }, - { AL_EFFECT_DEDICATED_DIALOGUE, DedicatedEffectProps, DedicatedEffectVtable }, - { AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT, DedicatedEffectProps, DedicatedEffectVtable }, - { AL_EFFECT_CONVOLUTION_REVERB_SOFT, ConvolutionEffectProps, ConvolutionEffectVtable }, -}; +using SubListAllocator = al::allocator>; - -void ALeffect_setParami(ALeffect *effect, ALenum param, int value) -{ effect->vtab->setParami(&effect->Props, param, value); } -void ALeffect_setParamiv(ALeffect *effect, ALenum param, const int *values) -{ effect->vtab->setParamiv(&effect->Props, param, values); } -void ALeffect_setParamf(ALeffect *effect, ALenum param, float value) -{ effect->vtab->setParamf(&effect->Props, param, value); } -void ALeffect_setParamfv(ALeffect *effect, ALenum param, const float *values) -{ effect->vtab->setParamfv(&effect->Props, param, values); } - -void ALeffect_getParami(const ALeffect *effect, ALenum param, int *value) -{ effect->vtab->getParami(&effect->Props, param, value); } -void ALeffect_getParamiv(const ALeffect *effect, ALenum param, int *values) -{ effect->vtab->getParamiv(&effect->Props, param, values); } -void ALeffect_getParamf(const ALeffect *effect, ALenum param, float *value) -{ effect->vtab->getParamf(&effect->Props, param, value); } -void ALeffect_getParamfv(const ALeffect *effect, ALenum param, float *values) -{ effect->vtab->getParamfv(&effect->Props, param, values); } - - -const EffectPropsItem *getEffectPropsItemByType(ALenum type) +constexpr auto GetDefaultProps(ALenum type) noexcept -> const EffectProps& { - auto iter = std::find_if(std::begin(EffectPropsList), std::end(EffectPropsList), - [type](const EffectPropsItem &item) noexcept -> bool - { return item.Type == type; }); - return (iter != std::end(EffectPropsList)) ? al::to_address(iter) : nullptr; + switch(type) + { + case AL_EFFECT_NULL: return NullEffectProps; + case AL_EFFECT_EAXREVERB: return ReverbEffectProps; + case AL_EFFECT_REVERB: return StdReverbEffectProps; + case AL_EFFECT_AUTOWAH: return AutowahEffectProps; + case AL_EFFECT_CHORUS: return ChorusEffectProps; + case AL_EFFECT_COMPRESSOR: return CompressorEffectProps; + case AL_EFFECT_DISTORTION: return DistortionEffectProps; + case AL_EFFECT_ECHO: return EchoEffectProps; + case AL_EFFECT_EQUALIZER: return EqualizerEffectProps; + case AL_EFFECT_FLANGER: return FlangerEffectProps; + case AL_EFFECT_FREQUENCY_SHIFTER: return FshifterEffectProps; + case AL_EFFECT_RING_MODULATOR: return ModulatorEffectProps; + case AL_EFFECT_PITCH_SHIFTER: return PshifterEffectProps; + case AL_EFFECT_VOCAL_MORPHER: return VmorpherEffectProps; + case AL_EFFECT_DEDICATED_DIALOGUE: return DedicatedDialogEffectProps; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: return DedicatedLfeEffectProps; + case AL_EFFECT_CONVOLUTION_SOFT: return ConvolutionEffectProps; + } + return NullEffectProps; } -void InitEffectParams(ALeffect *effect, ALenum type) +void InitEffectParams(ALeffect *effect, ALenum type) noexcept { - const EffectPropsItem *item{getEffectPropsItemByType(type)}; - if(item) + switch(type) { - effect->Props = item->DefaultProps; - effect->vtab = &item->Vtable; - } - else - { - effect->Props = EffectProps{}; - effect->vtab = &NullEffectVtable; + case AL_EFFECT_NULL: effect->PropsVariant.emplace(); break; + case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace(); break; + case AL_EFFECT_REVERB: effect->PropsVariant.emplace(); break; + case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace(); break; + case AL_EFFECT_CHORUS: effect->PropsVariant.emplace(); break; + case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace(); break; + case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace(); break; + case AL_EFFECT_ECHO: effect->PropsVariant.emplace(); break; + case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_FLANGER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace(); break; + case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace(); break; + case AL_EFFECT_DEDICATED_DIALOGUE: + effect->PropsVariant.emplace(); + break; + case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT: + effect->PropsVariant.emplace(); + break; + case AL_EFFECT_CONVOLUTION_SOFT: + effect->PropsVariant.emplace(); + break; } + effect->Props = GetDefaultProps(type); effect->type = type; } -bool EnsureEffects(ALCdevice *device, size_t needed) -{ - size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), size_t{0}, +auto EnsureEffects(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->EffectList.cbegin(), device->EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -170,21 +150,19 @@ bool EnsureEffects(ALCdevice *device, size_t needed) if(device->EffectList.size() >= 1<<25) UNLIKELY return false; - device->EffectList.emplace_back(); - auto sublist = device->EffectList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Effects = static_cast(al_calloc(alignof(ALeffect), sizeof(ALeffect)*64)); - if(!sublist->Effects) UNLIKELY - { - device->EffectList.pop_back(); - return false; - } - count += 64; + EffectSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Effects = SubListAllocator{}.allocate(1); + device->EffectList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALeffect *AllocEffect(ALCdevice *device) +ALeffect *AllocEffect(ALCdevice *device) noexcept { auto sublist = std::find_if(device->EffectList.begin(), device->EffectList.end(), [](const EffectSubList &entry) noexcept -> bool @@ -193,7 +171,7 @@ ALeffect *AllocEffect(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALeffect *effect{al::construct_at(sublist->Effects + slidx)}; + ALeffect *effect{al::construct_at(al::to_address(sublist->Effects->begin() + slidx))}; InitEffectParams(effect, AL_EFFECT_NULL); /* Add 1 to avoid effect ID 0. */ @@ -206,16 +184,18 @@ ALeffect *AllocEffect(ALCdevice *device) void FreeEffect(ALCdevice *device, ALeffect *effect) { + device->mEffectNames.erase(effect->id); + const ALuint id{effect->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(effect); + std::destroy_at(effect); device->EffectList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) +inline auto LookupEffect(ALCdevice *device, ALuint id) noexcept -> ALeffect* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -225,320 +205,294 @@ inline ALeffect *LookupEffect(ALCdevice *device, ALuint id) EffectSubList &sublist = device->EffectList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Effects + slidx; + return al::to_address(sublist.Effects->begin() + slidx); } } // namespace -AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d effects", n); +AL_API DECL_FUNC2(void, alGenEffects, ALsizei,n, ALuint*,effects) +FORCE_ALIGN void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d effects", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; - if(!EnsureEffects(device, static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s"); - return; - } + std::lock_guard effectlock{device->EffectLock}; - if(n == 1) LIKELY - { - /* Special handling for the easy and normal case. */ - ALeffect *effect{AllocEffect(device)}; - effects[0] = effect->id; - } - else - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector ids; - ids.reserve(static_cast(n)); - do { - ALeffect *effect{AllocEffect(device)}; - ids.emplace_back(effect->id); - } while(--n); - std::copy(ids.cbegin(), ids.cend(), effects); - } + const al::span eids{effects, static_cast(n)}; + if(!EnsureEffects(device, eids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, + (n == 1) ? "" : "s"}; + + std::generate(eids.begin(), eids.end(), [device]{ return AllocEffect(device)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d effects", n); +AL_API DECL_FUNC2(void, alDeleteEffects, ALsizei,n, const ALuint*,effects) +FORCE_ALIGN void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, + const ALuint *effects) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d effects", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; /* First try to find any effects that are invalid. */ auto validate_effect = [device](const ALuint eid) -> bool { return !eid || LookupEffect(device, eid) != nullptr; }; - const ALuint *effects_end = effects + n; - auto inveffect = std::find_if_not(effects, effects_end, validate_effect); - if(inveffect != effects_end) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", *inveffect); - return; - } + const al::span eids{effects, static_cast(n)}; + auto inveffect = std::find_if_not(eids.begin(), eids.end(), validate_effect); + if(inveffect != eids.end()) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", *inveffect}; /* All good. Delete non-0 effect IDs. */ auto delete_effect = [device](ALuint eid) -> void { - ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}; - if(effect) FreeEffect(device, effect); + if(ALeffect *effect{eid ? LookupEffect(device, eid) : nullptr}) + FreeEffect(device, effect); }; - std::for_each(effects, effects_end, delete_effect); + std::for_each(eids.begin(), eids.end(), delete_effect); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsEffect, ALuint,effect) +FORCE_ALIGN ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; - if(!effect || LookupEffect(device, effect)) - return AL_TRUE; - } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + if(!effect || LookupEffect(device, effect)) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC - -AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alEffecti, ALuint,effect, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else if(param == AL_EFFECT_TYPE) + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + switch(param) { - bool isOk{value == AL_EFFECT_NULL}; - if(!isOk) + case AL_EFFECT_TYPE: + if(value != AL_EFFECT_NULL) { - for(const EffectList &effectitem : gEffectList) - { - if(value == effectitem.val && !DisabledEffects[effectitem.type]) - { - isOk = true; - break; - } - } + auto check_effect = [value](const EffectList &item) -> bool + { return value == item.val && !DisabledEffects.test(item.type); }; + if(!std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect)) + throw al::context_error{AL_INVALID_VALUE, "Effect type 0x%04x not supported", + value}; } - if(isOk) - InitEffectParams(aleffect, value); - else - context->setError(AL_INVALID_VALUE, "Effect type 0x%04x not supported", value); - } - else try - { - /* Call the appropriate handler */ - ALeffect_setParami(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECT_TYPE: - alEffecti(effect, param, values[0]); + InitEffectParams(aleffect, value); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) + { + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParami(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alEffectiv, ALuint,effect, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, + const ALint *values) noexcept +try { + switch(param) + { + case AL_EFFECT_TYPE: + alEffectiDirect(context, effect, param, *values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; + + ALeffect *aleffect{LookupEffect(device, effect)}; + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) + { + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParamiv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alEffectf, ALuint,effect, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamiv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParamf(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alEffectfv, ALuint,effect, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, + const ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_setParamf(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.SetParamfv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); } -END_API_FUNC - -AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; - - ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try - { - /* Call the appropriate handler */ - ALeffect_setParamfv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alGetEffecti, ALuint,effect, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else if(param == AL_EFFECT_TYPE) + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + switch(param) + { + case AL_EFFECT_TYPE: *value = aleffect->type; - else try - { - /* Call the appropriate handler */ - ALeffect_getParami(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *values) -START_API_FUNC -{ - switch(param) - { - case AL_EFFECT_TYPE: - alGetEffecti(effect, param, values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) + { + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParami(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetEffectiv, ALuint,effect, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, + ALint *values) noexcept +try { + switch(param) + { + case AL_EFFECT_TYPE: + alGetEffectiDirect(context, effect, param, values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamiv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParamiv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alGetEffectf, ALuint,effect, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat *value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,value](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamf(aleffect, param, value); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParamf(std::get(aleffect->Props), param, value); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alGetEffectfv, ALuint,effect, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, + ALfloat *values) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->EffectLock}; + std::lock_guard effectlock{device->EffectLock}; const ALeffect *aleffect{LookupEffect(device, effect)}; - if(!aleffect) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid effect ID %u", effect); - else try + if(!aleffect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", effect}; + + /* Call the appropriate handler */ + std::visit([aleffect,param,values](auto &arg) { - /* Call the appropriate handler */ - ALeffect_getParamfv(aleffect, param, values); - } - catch(effect_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + using Type = std::remove_cv_t>; + using PropType = typename Type::prop_type; + return arg.GetParamfv(std::get(aleffect->Props), param, values); + }, aleffect->PropsVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC void InitEffect(ALeffect *effect) @@ -546,26 +500,43 @@ void InitEffect(ALeffect *effect) InitEffectParams(effect, AL_EFFECT_NULL); } +void ALeffect::SetName(ALCcontext* context, ALuint id, std::string_view name) +{ + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard effectlock{device->EffectLock}; + + auto effect = LookupEffect(device, id); + if(!effect) + throw al::context_error{AL_INVALID_NAME, "Invalid effect ID %u", id}; + + device->mEffectNames.insert_or_assign(id, name); +} + + EffectSubList::~EffectSubList() { + if(!Effects) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Effects+idx); + std::destroy_at(al::to_address(Effects->begin()+idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Effects); + SubListAllocator{}.deallocate(Effects, 1); Effects = nullptr; } -#define DECL(x) { #x, EFX_REVERB_PRESET_##x } -static const struct { - const char name[32]; +struct EffectPreset { + const char name[32]; /* NOLINT(*-avoid-c-arrays) */ EFXEAXREVERBPROPERTIES props; -} reverblist[] = { +}; +#define DECL(x) EffectPreset{#x, EFX_REVERB_PRESET_##x} +static constexpr std::array reverblist{ DECL(GENERIC), DECL(PADDEDCELL), DECL(ROOM), @@ -695,61 +666,62 @@ static const struct { }; #undef DECL -void LoadReverbPreset(const char *name, ALeffect *effect) +void LoadReverbPreset(const std::string_view name, ALeffect *effect) { - if(al::strcasecmp(name, "NONE") == 0) + using namespace std::string_view_literals; + + if(al::case_compare(name, "NONE"sv) == 0) { InitEffectParams(effect, AL_EFFECT_NULL); TRACE("Loading reverb '%s'\n", "NONE"); return; } - if(!DisabledEffects[EAXREVERB_EFFECT]) + if(!DisabledEffects.test(EAXREVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_EAXREVERB); - else if(!DisabledEffects[REVERB_EFFECT]) + else if(!DisabledEffects.test(REVERB_EFFECT)) InitEffectParams(effect, AL_EFFECT_REVERB); else InitEffectParams(effect, AL_EFFECT_NULL); for(const auto &reverbitem : reverblist) { - const EFXEAXREVERBPROPERTIES *props; - - if(al::strcasecmp(name, reverbitem.name) != 0) + if(al::case_compare(name, std::data(reverbitem.name)) != 0) continue; - TRACE("Loading reverb '%s'\n", reverbitem.name); - props = &reverbitem.props; - effect->Props.Reverb.Density = props->flDensity; - effect->Props.Reverb.Diffusion = props->flDiffusion; - effect->Props.Reverb.Gain = props->flGain; - effect->Props.Reverb.GainHF = props->flGainHF; - effect->Props.Reverb.GainLF = props->flGainLF; - effect->Props.Reverb.DecayTime = props->flDecayTime; - effect->Props.Reverb.DecayHFRatio = props->flDecayHFRatio; - effect->Props.Reverb.DecayLFRatio = props->flDecayLFRatio; - effect->Props.Reverb.ReflectionsGain = props->flReflectionsGain; - effect->Props.Reverb.ReflectionsDelay = props->flReflectionsDelay; - effect->Props.Reverb.ReflectionsPan[0] = props->flReflectionsPan[0]; - effect->Props.Reverb.ReflectionsPan[1] = props->flReflectionsPan[1]; - effect->Props.Reverb.ReflectionsPan[2] = props->flReflectionsPan[2]; - effect->Props.Reverb.LateReverbGain = props->flLateReverbGain; - effect->Props.Reverb.LateReverbDelay = props->flLateReverbDelay; - effect->Props.Reverb.LateReverbPan[0] = props->flLateReverbPan[0]; - effect->Props.Reverb.LateReverbPan[1] = props->flLateReverbPan[1]; - effect->Props.Reverb.LateReverbPan[2] = props->flLateReverbPan[2]; - effect->Props.Reverb.EchoTime = props->flEchoTime; - effect->Props.Reverb.EchoDepth = props->flEchoDepth; - effect->Props.Reverb.ModulationTime = props->flModulationTime; - effect->Props.Reverb.ModulationDepth = props->flModulationDepth; - effect->Props.Reverb.AirAbsorptionGainHF = props->flAirAbsorptionGainHF; - effect->Props.Reverb.HFReference = props->flHFReference; - effect->Props.Reverb.LFReference = props->flLFReference; - effect->Props.Reverb.RoomRolloffFactor = props->flRoomRolloffFactor; - effect->Props.Reverb.DecayHFLimit = props->iDecayHFLimit ? AL_TRUE : AL_FALSE; + TRACE("Loading reverb '%s'\n", std::data(reverbitem.name)); + const auto &props = reverbitem.props; + auto &dst = std::get(effect->Props); + dst.Density = props.flDensity; + dst.Diffusion = props.flDiffusion; + dst.Gain = props.flGain; + dst.GainHF = props.flGainHF; + dst.GainLF = props.flGainLF; + dst.DecayTime = props.flDecayTime; + dst.DecayHFRatio = props.flDecayHFRatio; + dst.DecayLFRatio = props.flDecayLFRatio; + dst.ReflectionsGain = props.flReflectionsGain; + dst.ReflectionsDelay = props.flReflectionsDelay; + dst.ReflectionsPan[0] = props.flReflectionsPan[0]; + dst.ReflectionsPan[1] = props.flReflectionsPan[1]; + dst.ReflectionsPan[2] = props.flReflectionsPan[2]; + dst.LateReverbGain = props.flLateReverbGain; + dst.LateReverbDelay = props.flLateReverbDelay; + dst.LateReverbPan[0] = props.flLateReverbPan[0]; + dst.LateReverbPan[1] = props.flLateReverbPan[1]; + dst.LateReverbPan[2] = props.flLateReverbPan[2]; + dst.EchoTime = props.flEchoTime; + dst.EchoDepth = props.flEchoDepth; + dst.ModulationTime = props.flModulationTime; + dst.ModulationDepth = props.flModulationDepth; + dst.AirAbsorptionGainHF = props.flAirAbsorptionGainHF; + dst.HFReference = props.flHFReference; + dst.LFReference = props.flLFReference; + dst.RoomRolloffFactor = props.flRoomRolloffFactor; + dst.DecayHFLimit = props.iDecayHFLimit ? AL_TRUE : AL_FALSE; return; } - WARN("Reverb preset '%s' not found\n", name); + WARN("Reverb preset '%.*s' not found\n", al::sizei(name), name.data()); } bool IsValidEffectType(ALenum type) noexcept @@ -757,10 +729,7 @@ bool IsValidEffectType(ALenum type) noexcept if(type == AL_EFFECT_NULL) return true; - for(const auto &effect_item : gEffectList) - { - if(type == effect_item.val && !DisabledEffects[effect_item.type]) - return true; - } - return false; + auto check_effect = [type](const EffectList &item) noexcept -> bool + { return type == item.val && !DisabledEffects.test(item.type); }; + return std::any_of(gEffectList.cbegin(), gEffectList.cend(), check_effect); } diff --git a/Engine/lib/openal-soft/al/effect.h b/Engine/lib/openal-soft/al/effect.h index a1d433133..842566578 100644 --- a/Engine/lib/openal-soft/al/effect.h +++ b/Engine/lib/openal-soft/al/effect.h @@ -1,11 +1,20 @@ #ifndef AL_EFFECT_H #define AL_EFFECT_H +#include +#include +#include +#include +#include + #include "AL/al.h" +#include "AL/alc.h" #include "AL/efx.h" -#include "al/effects/effects.h" -#include "alc/effects/base.h" +#include "almalloc.h" +#include "alnumeric.h" +#include "core/effects/base.h" +#include "effects/effects.h" enum { @@ -27,36 +36,55 @@ enum { MAX_EFFECTS }; -extern bool DisabledEffects[MAX_EFFECTS]; - -extern float ReverbBoost; +inline std::bitset DisabledEffects; struct EffectList { - const char name[16]; - int type; + const char name[16]; /* NOLINT(*-avoid-c-arrays) */ + ALuint type; ALenum val; }; -extern const EffectList gEffectList[16]; +extern const std::array gEffectList; +using EffectHandlerVariant = std::variant; struct ALeffect { // Effect type (AL_EFFECT_NULL, ...) ALenum type{AL_EFFECT_NULL}; + EffectHandlerVariant PropsVariant; EffectProps Props{}; - const EffectVtable *vtab{nullptr}; - /* Self ID */ ALuint id{0u}; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC }; void InitEffect(ALeffect *effect); -void LoadReverbPreset(const char *name, ALeffect *effect); +void LoadReverbPreset(const std::string_view name, ALeffect *effect); bool IsValidEffectType(ALenum type) noexcept; +struct EffectSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Effects{nullptr}; /* 64 */ + + EffectSubList() noexcept = default; + EffectSubList(const EffectSubList&) = delete; + EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} + { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } + ~EffectSubList(); + + EffectSubList& operator=(const EffectSubList&) = delete; + EffectSubList& operator=(EffectSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/effects/autowah.cpp b/Engine/lib/openal-soft/al/effects/autowah.cpp index 129318f4e..fe52b8be4 100644 --- a/Engine/lib/openal-soft/al/effects/autowah.cpp +++ b/Engine/lib/openal-soft/al/effects/autowah.cpp @@ -13,6 +13,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,100 +21,87 @@ namespace { -void Autowah_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept +{ + AutowahProps props{}; + props.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; + props.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; + props.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; + props.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; + return props; +} + +} // namespace + +const EffectProps AutowahEffectProps{genDefaultProps()}; + +void AutowahEffectHandler::SetParami(AutowahProps&, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } +void AutowahEffectHandler::SetParamiv(AutowahProps&, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", + param}; +} + +void AutowahEffectHandler::SetParamf(AutowahProps &props, ALenum param, float val) { switch(param) { case AL_AUTOWAH_ATTACK_TIME: if(!(val >= AL_AUTOWAH_MIN_ATTACK_TIME && val <= AL_AUTOWAH_MAX_ATTACK_TIME)) throw effect_exception{AL_INVALID_VALUE, "Autowah attack time out of range"}; - props->Autowah.AttackTime = val; + props.AttackTime = val; break; case AL_AUTOWAH_RELEASE_TIME: if(!(val >= AL_AUTOWAH_MIN_RELEASE_TIME && val <= AL_AUTOWAH_MAX_RELEASE_TIME)) throw effect_exception{AL_INVALID_VALUE, "Autowah release time out of range"}; - props->Autowah.ReleaseTime = val; + props.ReleaseTime = val; break; case AL_AUTOWAH_RESONANCE: if(!(val >= AL_AUTOWAH_MIN_RESONANCE && val <= AL_AUTOWAH_MAX_RESONANCE)) throw effect_exception{AL_INVALID_VALUE, "Autowah resonance out of range"}; - props->Autowah.Resonance = val; + props.Resonance = val; break; case AL_AUTOWAH_PEAK_GAIN: if(!(val >= AL_AUTOWAH_MIN_PEAK_GAIN && val <= AL_AUTOWAH_MAX_PEAK_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Autowah peak gain out of range"}; - props->Autowah.PeakGain = val; + props.PeakGain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; } } -void Autowah_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Autowah_setParamf(props, param, vals[0]); } +void AutowahEffectHandler::SetParamfv(AutowahProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Autowah_setParami(EffectProps*, ALenum param, int) +void AutowahEffectHandler::GetParami(const AutowahProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void Autowah_setParamiv(EffectProps*, ALenum param, const int*) +void AutowahEffectHandler::GetParamiv(const AutowahProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", param}; } -void Autowah_getParamf(const EffectProps *props, ALenum param, float *val) +void AutowahEffectHandler::GetParamf(const AutowahProps &props, ALenum param, float *val) { switch(param) { - case AL_AUTOWAH_ATTACK_TIME: - *val = props->Autowah.AttackTime; - break; - - case AL_AUTOWAH_RELEASE_TIME: - *val = props->Autowah.ReleaseTime; - break; - - case AL_AUTOWAH_RESONANCE: - *val = props->Autowah.Resonance; - break; - - case AL_AUTOWAH_PEAK_GAIN: - *val = props->Autowah.PeakGain; - break; + case AL_AUTOWAH_ATTACK_TIME: *val = props.AttackTime; break; + case AL_AUTOWAH_RELEASE_TIME: *val = props.ReleaseTime; break; + case AL_AUTOWAH_RESONANCE: *val = props.Resonance; break; + case AL_AUTOWAH_PEAK_GAIN: *val = props.PeakGain; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid autowah float property 0x%04x", param}; } } -void Autowah_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Autowah_getParamf(props, param, vals); } - -void Autowah_getParami(const EffectProps*, ALenum param, int*) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer property 0x%04x", param}; } -void Autowah_getParamiv(const EffectProps*, ALenum param, int*) -{ - throw effect_exception{AL_INVALID_ENUM, "Invalid autowah integer vector property 0x%04x", - param}; -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Autowah.AttackTime = AL_AUTOWAH_DEFAULT_ATTACK_TIME; - props.Autowah.ReleaseTime = AL_AUTOWAH_DEFAULT_RELEASE_TIME; - props.Autowah.Resonance = AL_AUTOWAH_DEFAULT_RESONANCE; - props.Autowah.PeakGain = AL_AUTOWAH_DEFAULT_PEAK_GAIN; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Autowah); - -const EffectProps AutowahEffectProps{genDefaultProps()}; +void AutowahEffectHandler::GetParamfv(const AutowahProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } #ifdef ALSOFT_EAX namespace { @@ -189,62 +177,62 @@ template<> throw Exception{message}; } -template<> -bool AutowahCommitter::commit(const EaxEffectProps &props) +bool EaxAutowahCommitter::commit(const EAXAUTOWAHPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mAutowah.flAttackTime == props.mAutowah.flAttackTime - && mEaxProps.mAutowah.flReleaseTime == props.mAutowah.flReleaseTime - && mEaxProps.mAutowah.lResonance == props.mAutowah.lResonance - && mEaxProps.mAutowah.lPeakLevel == props.mAutowah.lPeakLevel) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Autowah.AttackTime = props.mAutowah.flAttackTime; - mAlProps.Autowah.ReleaseTime = props.mAutowah.flReleaseTime; - mAlProps.Autowah.Resonance = level_mb_to_gain(static_cast(props.mAutowah.lResonance)); - mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast(props.mAutowah.lPeakLevel)); + mAlProps = [&]{ + AutowahProps ret{}; + ret.AttackTime = props.flAttackTime; + ret.ReleaseTime = props.flReleaseTime; + ret.Resonance = level_mb_to_gain(static_cast(props.lResonance)); + ret.PeakGain = level_mb_to_gain(static_cast(props.lPeakLevel)); + return ret; + }(); return true; } -template<> -void AutowahCommitter::SetDefaults(EaxEffectProps &props) +void EaxAutowahCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Autowah; - props.mAutowah.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; - props.mAutowah.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; - props.mAutowah.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; - props.mAutowah.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + static constexpr EAXAUTOWAHPROPERTIES defprops{[] + { + EAXAUTOWAHPROPERTIES ret{}; + ret.flAttackTime = EAXAUTOWAH_DEFAULTATTACKTIME; + ret.flReleaseTime = EAXAUTOWAH_DEFAULTRELEASETIME; + ret.lResonance = EAXAUTOWAH_DEFAULTRESONANCE; + ret.lPeakLevel = EAXAUTOWAH_DEFAULTPEAKLEVEL; + return ret; + }()}; + props = defprops; } -template<> -void AutowahCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxAutowahCommitter::Get(const EaxCall &call, const EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; - case EAXAUTOWAH_ALLPARAMETERS: call.set_value(props.mAutowah); break; - case EAXAUTOWAH_ATTACKTIME: call.set_value(props.mAutowah.flAttackTime); break; - case EAXAUTOWAH_RELEASETIME: call.set_value(props.mAutowah.flReleaseTime); break; - case EAXAUTOWAH_RESONANCE: call.set_value(props.mAutowah.lResonance); break; - case EAXAUTOWAH_PEAKLEVEL: call.set_value(props.mAutowah.lPeakLevel); break; + case EAXAUTOWAH_ALLPARAMETERS: call.set_value(props); break; + case EAXAUTOWAH_ATTACKTIME: call.set_value(props.flAttackTime); break; + case EAXAUTOWAH_RELEASETIME: call.set_value(props.flReleaseTime); break; + case EAXAUTOWAH_RESONANCE: call.set_value(props.lResonance); break; + case EAXAUTOWAH_PEAKLEVEL: call.set_value(props.lPeakLevel); break; default: fail_unknown_property_id(); } } -template<> -void AutowahCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxAutowahCommitter::Set(const EaxCall &call, EAXAUTOWAHPROPERTIES &props) { switch(call.get_property_id()) { case EAXAUTOWAH_NONE: break; - case EAXAUTOWAH_ALLPARAMETERS: defer(call, props.mAutowah); break; - case EAXAUTOWAH_ATTACKTIME: defer(call, props.mAutowah.flAttackTime); break; - case EAXAUTOWAH_RELEASETIME: defer(call, props.mAutowah.flReleaseTime); break; - case EAXAUTOWAH_RESONANCE: defer(call, props.mAutowah.lResonance); break; - case EAXAUTOWAH_PEAKLEVEL: defer(call, props.mAutowah.lPeakLevel); break; + case EAXAUTOWAH_ALLPARAMETERS: defer(call, props); break; + case EAXAUTOWAH_ATTACKTIME: defer(call, props.flAttackTime); break; + case EAXAUTOWAH_RELEASETIME: defer(call, props.flReleaseTime); break; + case EAXAUTOWAH_RESONANCE: defer(call, props.lResonance); break; + case EAXAUTOWAH_PEAKLEVEL: defer(call, props.lPeakLevel); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/chorus.cpp b/Engine/lib/openal-soft/al/effects/chorus.cpp index 305259a41..5a5013ce4 100644 --- a/Engine/lib/openal-soft/al/effects/chorus.cpp +++ b/Engine/lib/openal-soft/al/effects/chorus.cpp @@ -1,19 +1,17 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" -#include "aloptional.h" -#include "core/logging.h" #include "effects.h" #ifdef ALSOFT_EAX #include -#include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -27,16 +25,16 @@ static_assert(FlangerMaxDelay >= AL_FLANGER_MAX_DELAY, "Flanger max delay too sm static_assert(AL_CHORUS_WAVEFORM_SINUSOID == AL_FLANGER_WAVEFORM_SINUSOID, "Chorus/Flanger waveform value mismatch"); static_assert(AL_CHORUS_WAVEFORM_TRIANGLE == AL_FLANGER_WAVEFORM_TRIANGLE, "Chorus/Flanger waveform value mismatch"); -inline al::optional WaveformFromEnum(ALenum type) +constexpr std::optional WaveformFromEnum(ALenum type) noexcept { switch(type) { case AL_CHORUS_WAVEFORM_SINUSOID: return ChorusWaveform::Sinusoid; case AL_CHORUS_WAVEFORM_TRIANGLE: return ChorusWaveform::Triangle; } - return al::nullopt; + return std::nullopt; } -inline ALenum EnumFromWaveform(ChorusWaveform type) +constexpr ALenum EnumFromWaveform(ChorusWaveform type) { switch(type) { @@ -46,13 +44,41 @@ inline ALenum EnumFromWaveform(ChorusWaveform type) throw std::runtime_error{"Invalid chorus waveform: "+std::to_string(static_cast(type))}; } -void Chorus_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultChorusProps() noexcept +{ + ChorusProps props{}; + props.Waveform = WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM).value(); + props.Phase = AL_CHORUS_DEFAULT_PHASE; + props.Rate = AL_CHORUS_DEFAULT_RATE; + props.Depth = AL_CHORUS_DEFAULT_DEPTH; + props.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; + props.Delay = AL_CHORUS_DEFAULT_DELAY; + return props; +} + +constexpr EffectProps genDefaultFlangerProps() noexcept +{ + ChorusProps props{}; + props.Waveform = WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM).value(); + props.Phase = AL_FLANGER_DEFAULT_PHASE; + props.Rate = AL_FLANGER_DEFAULT_RATE; + props.Depth = AL_FLANGER_DEFAULT_DEPTH; + props.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; + props.Delay = AL_FLANGER_DEFAULT_DELAY; + return props; +} + +} // namespace + +const EffectProps ChorusEffectProps{genDefaultChorusProps()}; + +void ChorusEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) { switch(param) { case AL_CHORUS_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) - props->Chorus.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid chorus waveform: 0x%04x", val}; break; @@ -60,115 +86,89 @@ void Chorus_setParami(EffectProps *props, ALenum param, int val) case AL_CHORUS_PHASE: if(!(val >= AL_CHORUS_MIN_PHASE && val <= AL_CHORUS_MAX_PHASE)) throw effect_exception{AL_INVALID_VALUE, "Chorus phase out of range: %d", val}; - props->Chorus.Phase = val; + props.Phase = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; } } -void Chorus_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Chorus_setParami(props, param, vals[0]); } -void Chorus_setParamf(EffectProps *props, ALenum param, float val) +void ChorusEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void ChorusEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) { switch(param) { case AL_CHORUS_RATE: if(!(val >= AL_CHORUS_MIN_RATE && val <= AL_CHORUS_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Chorus rate out of range: %f", val}; - props->Chorus.Rate = val; + props.Rate = val; break; case AL_CHORUS_DEPTH: if(!(val >= AL_CHORUS_MIN_DEPTH && val <= AL_CHORUS_MAX_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "Chorus depth out of range: %f", val}; - props->Chorus.Depth = val; + props.Depth = val; break; case AL_CHORUS_FEEDBACK: if(!(val >= AL_CHORUS_MIN_FEEDBACK && val <= AL_CHORUS_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Chorus feedback out of range: %f", val}; - props->Chorus.Feedback = val; + props.Feedback = val; break; case AL_CHORUS_DELAY: if(!(val >= AL_CHORUS_MIN_DELAY && val <= AL_CHORUS_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Chorus delay out of range: %f", val}; - props->Chorus.Delay = val; + props.Delay = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; } } -void Chorus_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Chorus_setParamf(props, param, vals[0]); } +void ChorusEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Chorus_getParami(const EffectProps *props, ALenum param, int *val) +void ChorusEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_CHORUS_WAVEFORM: - *val = EnumFromWaveform(props->Chorus.Waveform); - break; - - case AL_CHORUS_PHASE: - *val = props->Chorus.Phase; - break; + case AL_CHORUS_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + case AL_CHORUS_PHASE: *val = props.Phase; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus integer property 0x%04x", param}; } } -void Chorus_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Chorus_getParami(props, param, vals); } -void Chorus_getParamf(const EffectProps *props, ALenum param, float *val) +void ChorusEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void ChorusEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_CHORUS_RATE: - *val = props->Chorus.Rate; - break; - - case AL_CHORUS_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_CHORUS_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_CHORUS_DELAY: - *val = props->Chorus.Delay; - break; + case AL_CHORUS_RATE: *val = props.Rate; break; + case AL_CHORUS_DEPTH: *val = props.Depth; break; + case AL_CHORUS_FEEDBACK: *val = props.Feedback; break; + case AL_CHORUS_DELAY: *val = props.Delay; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid chorus float property 0x%04x", param}; } } -void Chorus_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Chorus_getParamf(props, param, vals); } - -EffectProps genDefaultChorusProps() noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = *WaveformFromEnum(AL_CHORUS_DEFAULT_WAVEFORM); - props.Chorus.Phase = AL_CHORUS_DEFAULT_PHASE; - props.Chorus.Rate = AL_CHORUS_DEFAULT_RATE; - props.Chorus.Depth = AL_CHORUS_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_CHORUS_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_CHORUS_DEFAULT_DELAY; - return props; -} +void ChorusEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -void Flanger_setParami(EffectProps *props, ALenum param, int val) +const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; + +void FlangerEffectHandler::SetParami(ChorusProps &props, ALenum param, int val) { switch(param) { case AL_FLANGER_WAVEFORM: if(auto formopt = WaveformFromEnum(val)) - props->Chorus.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid flanger waveform: 0x%04x", val}; break; @@ -176,127 +176,87 @@ void Flanger_setParami(EffectProps *props, ALenum param, int val) case AL_FLANGER_PHASE: if(!(val >= AL_FLANGER_MIN_PHASE && val <= AL_FLANGER_MAX_PHASE)) throw effect_exception{AL_INVALID_VALUE, "Flanger phase out of range: %d", val}; - props->Chorus.Phase = val; + props.Phase = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; } } -void Flanger_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Flanger_setParami(props, param, vals[0]); } -void Flanger_setParamf(EffectProps *props, ALenum param, float val) +void FlangerEffectHandler::SetParamiv(ChorusProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void FlangerEffectHandler::SetParamf(ChorusProps &props, ALenum param, float val) { switch(param) { case AL_FLANGER_RATE: if(!(val >= AL_FLANGER_MIN_RATE && val <= AL_FLANGER_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Flanger rate out of range: %f", val}; - props->Chorus.Rate = val; + props.Rate = val; break; case AL_FLANGER_DEPTH: if(!(val >= AL_FLANGER_MIN_DEPTH && val <= AL_FLANGER_MAX_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "Flanger depth out of range: %f", val}; - props->Chorus.Depth = val; + props.Depth = val; break; case AL_FLANGER_FEEDBACK: if(!(val >= AL_FLANGER_MIN_FEEDBACK && val <= AL_FLANGER_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Flanger feedback out of range: %f", val}; - props->Chorus.Feedback = val; + props.Feedback = val; break; case AL_FLANGER_DELAY: if(!(val >= AL_FLANGER_MIN_DELAY && val <= AL_FLANGER_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Flanger delay out of range: %f", val}; - props->Chorus.Delay = val; + props.Delay = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; } } -void Flanger_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Flanger_setParamf(props, param, vals[0]); } +void FlangerEffectHandler::SetParamfv(ChorusProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Flanger_getParami(const EffectProps *props, ALenum param, int *val) +void FlangerEffectHandler::GetParami(const ChorusProps &props, ALenum param, int *val) { switch(param) { - case AL_FLANGER_WAVEFORM: - *val = EnumFromWaveform(props->Chorus.Waveform); - break; - - case AL_FLANGER_PHASE: - *val = props->Chorus.Phase; - break; + case AL_FLANGER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + case AL_FLANGER_PHASE: *val = props.Phase; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger integer property 0x%04x", param}; } } -void Flanger_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Flanger_getParami(props, param, vals); } -void Flanger_getParamf(const EffectProps *props, ALenum param, float *val) +void FlangerEffectHandler::GetParamiv(const ChorusProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void FlangerEffectHandler::GetParamf(const ChorusProps &props, ALenum param, float *val) { switch(param) { - case AL_FLANGER_RATE: - *val = props->Chorus.Rate; - break; - - case AL_FLANGER_DEPTH: - *val = props->Chorus.Depth; - break; - - case AL_FLANGER_FEEDBACK: - *val = props->Chorus.Feedback; - break; - - case AL_FLANGER_DELAY: - *val = props->Chorus.Delay; - break; + case AL_FLANGER_RATE: *val = props.Rate; break; + case AL_FLANGER_DEPTH: *val = props.Depth; break; + case AL_FLANGER_FEEDBACK: *val = props.Feedback; break; + case AL_FLANGER_DELAY: *val = props.Delay; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid flanger float property 0x%04x", param}; } } -void Flanger_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Flanger_getParamf(props, param, vals); } - -EffectProps genDefaultFlangerProps() noexcept -{ - EffectProps props{}; - props.Chorus.Waveform = *WaveformFromEnum(AL_FLANGER_DEFAULT_WAVEFORM); - props.Chorus.Phase = AL_FLANGER_DEFAULT_PHASE; - props.Chorus.Rate = AL_FLANGER_DEFAULT_RATE; - props.Chorus.Depth = AL_FLANGER_DEFAULT_DEPTH; - props.Chorus.Feedback = AL_FLANGER_DEFAULT_FEEDBACK; - props.Chorus.Delay = AL_FLANGER_DEFAULT_DELAY; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Chorus); - -const EffectProps ChorusEffectProps{genDefaultChorusProps()}; - -DEFINE_ALEFFECT_VTABLE(Flanger); - -const EffectProps FlangerEffectProps{genDefaultFlangerProps()}; +void FlangerEffectHandler::GetParamfv(const ChorusProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } #ifdef ALSOFT_EAX namespace { struct EaxChorusTraits { - using Props = EAXCHORUSPROPERTIES; + using EaxProps = EAXCHORUSPROPERTIES; using Committer = EaxChorusCommitter; - static constexpr auto Field = &EaxEffectProps::mChorus; - static constexpr auto eax_effect_type() { return EaxEffectType::Chorus; } static constexpr auto efx_effect() { return AL_EFFECT_CHORUS; } static constexpr auto eax_none_param_id() { return EAXCHORUS_NONE; } @@ -359,11 +319,9 @@ struct EaxChorusTraits { }; // EaxChorusTraits struct EaxFlangerTraits { - using Props = EAXFLANGERPROPERTIES; + using EaxProps = EAXFLANGERPROPERTIES; using Committer = EaxFlangerCommitter; - static constexpr auto Field = &EaxEffectProps::mFlanger; - static constexpr auto eax_effect_type() { return EaxEffectType::Flanger; } static constexpr auto efx_effect() { return AL_EFFECT_FLANGER; } static constexpr auto eax_none_param_id() { return EAXFLANGER_NONE; } @@ -428,11 +386,10 @@ struct EaxFlangerTraits { template struct ChorusFlangerEffect { using Traits = TTraits; + using EaxProps = typename Traits::EaxProps; using Committer = typename Traits::Committer; using Exception = typename Committer::Exception; - static constexpr auto Field = Traits::Field; - struct WaveformValidator { void operator()(unsigned long ulWaveform) const { @@ -500,7 +457,7 @@ struct ChorusFlangerEffect { }; // DelayValidator struct AllValidator { - void operator()(const typename Traits::Props& all) const + void operator()(const EaxProps& all) const { WaveformValidator{}(all.ulWaveform); PhaseValidator{}(all.lPhase); @@ -514,8 +471,7 @@ struct ChorusFlangerEffect { public: static void SetDefaults(EaxEffectProps &props) { - auto&& all = props.*Field; - props.mType = Traits::eax_effect_type(); + auto&& all = props.emplace(); all.ulWaveform = Traits::eax_default_waveform(); all.lPhase = Traits::eax_default_phase(); all.flRate = Traits::eax_default_rate(); @@ -525,109 +481,83 @@ public: } - static void Get(const EaxCall &call, const EaxEffectProps &props) + static void Get(const EaxCall &call, const EaxProps &all) { - auto&& all = props.*Field; switch(call.get_property_id()) { case Traits::eax_none_param_id(): break; - case Traits::eax_allparameters_param_id(): call.template set_value(all); break; - case Traits::eax_waveform_param_id(): call.template set_value(all.ulWaveform); break; - case Traits::eax_phase_param_id(): call.template set_value(all.lPhase); break; - case Traits::eax_rate_param_id(): call.template set_value(all.flRate); break; - case Traits::eax_depth_param_id(): call.template set_value(all.flDepth); break; - case Traits::eax_feedback_param_id(): call.template set_value(all.flFeedback); break; - case Traits::eax_delay_param_id(): call.template set_value(all.flDelay); break; - default: Committer::fail_unknown_property_id(); } } - static void Set(const EaxCall &call, EaxEffectProps &props) + static void Set(const EaxCall &call, EaxProps &all) { - auto&& all = props.*Field; switch(call.get_property_id()) { case Traits::eax_none_param_id(): break; - case Traits::eax_allparameters_param_id(): Committer::template defer(call, all); break; - case Traits::eax_waveform_param_id(): Committer::template defer(call, all.ulWaveform); break; - case Traits::eax_phase_param_id(): Committer::template defer(call, all.lPhase); break; - case Traits::eax_rate_param_id(): Committer::template defer(call, all.flRate); break; - case Traits::eax_depth_param_id(): Committer::template defer(call, all.flDepth); break; - case Traits::eax_feedback_param_id(): Committer::template defer(call, all.flFeedback); break; - case Traits::eax_delay_param_id(): Committer::template defer(call, all.flDelay); break; - default: Committer::fail_unknown_property_id(); } } - static bool Commit(const EaxEffectProps &props, EaxEffectProps &props_, EffectProps &al_props_) + static bool Commit(const EaxProps &props, EaxEffectProps &props_, ChorusProps &al_props_) { - if(props.mType == props_.mType) - { - auto&& src = props_.*Field; - auto&& dst = props.*Field; - if(dst.ulWaveform == src.ulWaveform && dst.lPhase == src.lPhase - && dst.flRate == src.flRate && dst.flDepth == src.flDepth - && dst.flFeedback == src.flFeedback && dst.flDelay == src.flDelay) - return false; - } + if(auto *cur = std::get_if(&props_); cur && *cur == props) + return false; props_ = props; - auto&& dst = props.*Field; - al_props_.Chorus.Waveform = Traits::eax_waveform(dst.ulWaveform); - al_props_.Chorus.Phase = static_cast(dst.lPhase); - al_props_.Chorus.Rate = dst.flRate; - al_props_.Chorus.Depth = dst.flDepth; - al_props_.Chorus.Feedback = dst.flFeedback; - al_props_.Chorus.Delay = dst.flDelay; + al_props_.Waveform = Traits::eax_waveform(props.ulWaveform); + al_props_.Phase = static_cast(props.lPhase); + al_props_.Rate = props.flRate; + al_props_.Depth = props.flDepth; + al_props_.Feedback = props.flFeedback; + al_props_.Delay = props.flDelay; return true; } @@ -652,29 +582,25 @@ template<> throw Exception{message}; } -template<> -bool ChorusCommitter::commit(const EaxEffectProps &props) +bool EaxChorusCommitter::commit(const EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; - return Committer::Commit(props, mEaxProps, mAlProps); + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } -template<> -void ChorusCommitter::SetDefaults(EaxEffectProps &props) +void EaxChorusCommitter::SetDefaults(EaxEffectProps &props) { using Committer = ChorusFlangerEffect; Committer::SetDefaults(props); } -template<> -void ChorusCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxChorusCommitter::Get(const EaxCall &call, const EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Get(call, props); } -template<> -void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Set(call, props); @@ -693,29 +619,25 @@ template<> throw Exception{message}; } -template<> -bool FlangerCommitter::commit(const EaxEffectProps &props) +bool EaxFlangerCommitter::commit(const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; - return Committer::Commit(props, mEaxProps, mAlProps); + return Committer::Commit(props, mEaxProps, mAlProps.emplace()); } -template<> -void FlangerCommitter::SetDefaults(EaxEffectProps &props) +void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props) { using Committer = ChorusFlangerEffect; Committer::SetDefaults(props); } -template<> -void FlangerCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxFlangerCommitter::Get(const EaxCall &call, const EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Get(call, props); } -template<> -void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props) { using Committer = ChorusFlangerEffect; Committer::Set(call, props); diff --git a/Engine/lib/openal-soft/al/effects/compressor.cpp b/Engine/lib/openal-soft/al/effects/compressor.cpp index a4aa8e77f..8c9f5b79a 100644 --- a/Engine/lib/openal-soft/al/effects/compressor.cpp +++ b/Engine/lib/openal-soft/al/effects/compressor.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,14 +17,25 @@ namespace { -void Compressor_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + CompressorProps props{}; + props.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; + return props; +} + +} // namespace + +const EffectProps CompressorEffectProps{genDefaultProps()}; + +void CompressorEffectHandler::SetParami(CompressorProps &props, ALenum param, int val) { switch(param) { case AL_COMPRESSOR_ONOFF: if(!(val >= AL_COMPRESSOR_MIN_ONOFF && val <= AL_COMPRESSOR_MAX_ONOFF)) throw effect_exception{AL_INVALID_VALUE, "Compressor state out of range"}; - props->Compressor.OnOff = (val != AL_FALSE); + props.OnOff = (val != AL_FALSE); break; default: @@ -31,51 +43,36 @@ void Compressor_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Compressor_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Compressor_setParami(props, param, vals[0]); } -void Compressor_setParamf(EffectProps*, ALenum param, float) +void CompressorEffectHandler::SetParamiv(CompressorProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void CompressorEffectHandler::SetParamf(CompressorProps&, ALenum param, float) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void Compressor_setParamfv(EffectProps*, ALenum param, const float*) +void CompressorEffectHandler::SetParamfv(CompressorProps&, ALenum param, const float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param}; } -void Compressor_getParami(const EffectProps *props, ALenum param, int *val) +void CompressorEffectHandler::GetParami(const CompressorProps &props, ALenum param, int *val) { switch(param) { - case AL_COMPRESSOR_ONOFF: - *val = props->Compressor.OnOff; - break; - + case AL_COMPRESSOR_ONOFF: *val = props.OnOff; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid compressor integer property 0x%04x", param}; } } -void Compressor_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Compressor_getParami(props, param, vals); } -void Compressor_getParamf(const EffectProps*, ALenum param, float*) +void CompressorEffectHandler::GetParamiv(const CompressorProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void CompressorEffectHandler::GetParamf(const CompressorProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float property 0x%04x", param}; } -void Compressor_getParamfv(const EffectProps*, ALenum param, float*) +void CompressorEffectHandler::GetParamfv(const CompressorProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid compressor float-vector property 0x%04x", param}; } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Compressor.OnOff = AL_COMPRESSOR_DEFAULT_ONOFF; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Compressor); - -const EffectProps CompressorEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -115,46 +112,40 @@ template<> throw Exception{message}; } -template<> -bool CompressorCommitter::commit(const EaxEffectProps &props) +bool EaxCompressorCommitter::commit(const EAXAGCCOMPRESSORPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && props.mCompressor.ulOnOff == mEaxProps.mCompressor.ulOnOff) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; + mAlProps = CompressorProps{props.ulOnOff != 0}; - mAlProps.Compressor.OnOff = (props.mCompressor.ulOnOff != 0); return true; } -template<> -void CompressorCommitter::SetDefaults(EaxEffectProps &props) +void EaxCompressorCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Compressor; - props.mCompressor.ulOnOff = EAXAGCCOMPRESSOR_DEFAULTONOFF; + props = EAXAGCCOMPRESSORPROPERTIES{EAXAGCCOMPRESSOR_DEFAULTONOFF}; } -template<> -void CompressorCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxCompressorCommitter::Get(const EaxCall &call, const EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; - case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value(props.mCompressor); break; - case EAXAGCCOMPRESSOR_ONOFF: call.set_value(props.mCompressor.ulOnOff); break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value(props); break; + case EAXAGCCOMPRESSOR_ONOFF: call.set_value(props.ulOnOff); break; default: fail_unknown_property_id(); } } -template<> -void CompressorCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxCompressorCommitter::Set(const EaxCall &call, EAXAGCCOMPRESSORPROPERTIES &props) { switch(call.get_property_id()) { case EAXAGCCOMPRESSOR_NONE: break; - case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer(call, props.mCompressor); break; - case EAXAGCCOMPRESSOR_ONOFF: defer(call, props.mCompressor.ulOnOff); break; + case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer(call, props); break; + case EAXAGCCOMPRESSOR_ONOFF: defer(call, props.ulOnOff); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/convolution.cpp b/Engine/lib/openal-soft/al/effects/convolution.cpp index 8e850fd3e..618d5aac5 100644 --- a/Engine/lib/openal-soft/al/effects/convolution.cpp +++ b/Engine/lib/openal-soft/al/effects/convolution.cpp @@ -1,93 +1,117 @@ #include "config.h" -#include "AL/al.h" -#include "alc/inprogext.h" +#include +#include +#include -#include "alc/effects/base.h" +#include "AL/al.h" + +#include "alc/inprogext.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/effects/base.h" #include "effects.h" namespace { -void Convolution_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Convolution_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ - switch(param) - { - default: - Convolution_setParami(props, param, vals[0]); - } -} -void Convolution_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Convolution_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ - switch(param) - { - default: - Convolution_setParamf(props, param, vals[0]); - } -} - -void Convolution_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Convolution_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ - switch(param) - { - default: - Convolution_getParami(props, param, vals); - } -} -void Convolution_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Convolution_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ - switch(param) - { - default: - Convolution_getParamf(props, param, vals); - } -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; + ConvolutionProps props{}; + props.OrientAt = {0.0f, 0.0f, -1.0f}; + props.OrientUp = {0.0f, 1.0f, 0.0f}; return props; } } // namespace -DEFINE_ALEFFECT_VTABLE(Convolution); - const EffectProps ConvolutionEffectProps{genDefaultProps()}; + +void ConvolutionEffectHandler::SetParami(ConvolutionProps& /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::SetParamiv(ConvolutionProps &props, ALenum param, const int *vals) +{ + switch(param) + { + default: + SetParami(props, param, *vals); + } +} +void ConvolutionEffectHandler::SetParamf(ConvolutionProps& /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::SetParamfv(ConvolutionProps &props, ALenum param, const float *values) +{ + static constexpr auto finite_checker = [](float val) -> bool { return std::isfinite(val); }; + al::span vals; + switch(param) + { + case AL_CONVOLUTION_ORIENTATION_SOFT: + vals = {values, 6_uz}; + if(!std::all_of(vals.cbegin(), vals.cend(), finite_checker)) + throw effect_exception{AL_INVALID_VALUE, "Property 0x%04x value out of range", param}; + + std::copy_n(vals.cbegin(), props.OrientAt.size(), props.OrientAt.begin()); + std::copy_n(vals.cbegin()+3, props.OrientUp.size(), props.OrientUp.begin()); + break; + + default: + SetParamf(props, param, *values); + } +} + +void ConvolutionEffectHandler::GetParami(const ConvolutionProps& /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect integer property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::GetParamiv(const ConvolutionProps &props, ALenum param, int *vals) +{ + switch(param) + { + default: + GetParami(props, param, vals); + } +} +void ConvolutionEffectHandler::GetParamf(const ConvolutionProps& /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid convolution effect float property 0x%04x", + param}; + } +} +void ConvolutionEffectHandler::GetParamfv(const ConvolutionProps &props, ALenum param, float *values) +{ + al::span vals; + switch(param) + { + case AL_CONVOLUTION_ORIENTATION_SOFT: + vals = {values, 6_uz}; + std::copy(props.OrientAt.cbegin(), props.OrientAt.cend(), vals.begin()); + std::copy(props.OrientUp.cbegin(), props.OrientUp.cend(), vals.begin()+3); + break; + + default: + GetParamf(props, param, values); + } +} diff --git a/Engine/lib/openal-soft/al/effects/dedicated.cpp b/Engine/lib/openal-soft/al/effects/dedicated.cpp index db57003c2..f032a72ec 100644 --- a/Engine/lib/openal-soft/al/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/al/effects/dedicated.cpp @@ -12,61 +12,111 @@ namespace { -void Dedicated_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultDialogProps() noexcept +{ + DedicatedProps props{}; + props.Target = DedicatedProps::Dialog; + props.Gain = 1.0f; + return props; +} + +constexpr EffectProps genDefaultLfeProps() noexcept +{ + DedicatedProps props{}; + props.Target = DedicatedProps::Lfe; + props.Gain = 1.0f; + return props; +} + +} // namespace + +const EffectProps DedicatedDialogEffectProps{genDefaultDialogProps()}; + +void DedicatedDialogEffectHandler::SetParami(DedicatedProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void Dedicated_setParamiv(EffectProps*, ALenum param, const int*) +void DedicatedDialogEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param}; } -void Dedicated_setParamf(EffectProps *props, ALenum param, float val) +void DedicatedDialogEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: if(!(val >= 0.0f && std::isfinite(val))) throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; - props->Dedicated.Gain = val; + props.Gain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; } } -void Dedicated_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Dedicated_setParamf(props, param, vals[0]); } +void DedicatedDialogEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Dedicated_getParami(const EffectProps*, ALenum param, int*) +void DedicatedDialogEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } -void Dedicated_getParamiv(const EffectProps*, ALenum param, int*) +void DedicatedDialogEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", param}; } -void Dedicated_getParamf(const EffectProps *props, ALenum param, float *val) +void DedicatedDialogEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: *val = props.Gain; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void DedicatedDialogEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } + + +const EffectProps DedicatedLfeEffectProps{genDefaultLfeProps()}; + +void DedicatedLfeEffectHandler::SetParami(DedicatedProps&, ALenum param, int) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void DedicatedLfeEffectHandler::SetParamiv(DedicatedProps&, ALenum param, const int*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; +} +void DedicatedLfeEffectHandler::SetParamf(DedicatedProps &props, ALenum param, float val) { switch(param) { case AL_DEDICATED_GAIN: - *val = props->Dedicated.Gain; + if(!(val >= 0.0f && std::isfinite(val))) + throw effect_exception{AL_INVALID_VALUE, "Dedicated gain out of range"}; + props.Gain = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; } } -void Dedicated_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Dedicated_getParamf(props, param, vals); } +void DedicatedLfeEffectHandler::SetParamfv(DedicatedProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void DedicatedLfeEffectHandler::GetParami(const DedicatedProps&, ALenum param, int*) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer property 0x%04x", param}; } +void DedicatedLfeEffectHandler::GetParamiv(const DedicatedProps&, ALenum param, int*) { - EffectProps props{}; - props.Dedicated.Gain = 1.0f; - return props; + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated integer-vector property 0x%04x", + param}; } - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Dedicated); - -const EffectProps DedicatedEffectProps{genDefaultProps()}; +void DedicatedLfeEffectHandler::GetParamf(const DedicatedProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_DEDICATED_GAIN: *val = props.Gain; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid dedicated float property 0x%04x", param}; + } +} +void DedicatedLfeEffectHandler::GetParamfv(const DedicatedProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } diff --git a/Engine/lib/openal-soft/al/effects/distortion.cpp b/Engine/lib/openal-soft/al/effects/distortion.cpp index ee298ddf7..7d47fef2d 100644 --- a/Engine/lib/openal-soft/al/effects/distortion.cpp +++ b/Engine/lib/openal-soft/al/effects/distortion.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,108 +17,93 @@ namespace { -void Distortion_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + DistortionProps props{}; + props.Edge = AL_DISTORTION_DEFAULT_EDGE; + props.Gain = AL_DISTORTION_DEFAULT_GAIN; + props.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; + props.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; + props.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; + return props; +} + +} // namespace + +const EffectProps DistortionEffectProps{genDefaultProps()}; + +void DistortionEffectHandler::SetParami(DistortionProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void Distortion_setParamiv(EffectProps*, ALenum param, const int*) +void DistortionEffectHandler::SetParamiv(DistortionProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param}; } -void Distortion_setParamf(EffectProps *props, ALenum param, float val) +void DistortionEffectHandler::SetParamf(DistortionProps &props, ALenum param, float val) { switch(param) { case AL_DISTORTION_EDGE: if(!(val >= AL_DISTORTION_MIN_EDGE && val <= AL_DISTORTION_MAX_EDGE)) throw effect_exception{AL_INVALID_VALUE, "Distortion edge out of range"}; - props->Distortion.Edge = val; + props.Edge = val; break; case AL_DISTORTION_GAIN: if(!(val >= AL_DISTORTION_MIN_GAIN && val <= AL_DISTORTION_MAX_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Distortion gain out of range"}; - props->Distortion.Gain = val; + props.Gain = val; break; case AL_DISTORTION_LOWPASS_CUTOFF: if(!(val >= AL_DISTORTION_MIN_LOWPASS_CUTOFF && val <= AL_DISTORTION_MAX_LOWPASS_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Distortion low-pass cutoff out of range"}; - props->Distortion.LowpassCutoff = val; + props.LowpassCutoff = val; break; case AL_DISTORTION_EQCENTER: if(!(val >= AL_DISTORTION_MIN_EQCENTER && val <= AL_DISTORTION_MAX_EQCENTER)) throw effect_exception{AL_INVALID_VALUE, "Distortion EQ center out of range"}; - props->Distortion.EQCenter = val; + props.EQCenter = val; break; case AL_DISTORTION_EQBANDWIDTH: if(!(val >= AL_DISTORTION_MIN_EQBANDWIDTH && val <= AL_DISTORTION_MAX_EQBANDWIDTH)) throw effect_exception{AL_INVALID_VALUE, "Distortion EQ bandwidth out of range"}; - props->Distortion.EQBandwidth = val; + props.EQBandwidth = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; } } -void Distortion_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Distortion_setParamf(props, param, vals[0]); } +void DistortionEffectHandler::SetParamfv(DistortionProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Distortion_getParami(const EffectProps*, ALenum param, int*) +void DistortionEffectHandler::GetParami(const DistortionProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer property 0x%04x", param}; } -void Distortion_getParamiv(const EffectProps*, ALenum param, int*) +void DistortionEffectHandler::GetParamiv(const DistortionProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid distortion integer-vector property 0x%04x", param}; } -void Distortion_getParamf(const EffectProps *props, ALenum param, float *val) +void DistortionEffectHandler::GetParamf(const DistortionProps &props, ALenum param, float *val) { switch(param) { - case AL_DISTORTION_EDGE: - *val = props->Distortion.Edge; - break; - - case AL_DISTORTION_GAIN: - *val = props->Distortion.Gain; - break; - - case AL_DISTORTION_LOWPASS_CUTOFF: - *val = props->Distortion.LowpassCutoff; - break; - - case AL_DISTORTION_EQCENTER: - *val = props->Distortion.EQCenter; - break; - - case AL_DISTORTION_EQBANDWIDTH: - *val = props->Distortion.EQBandwidth; - break; + case AL_DISTORTION_EDGE: *val = props.Edge; break; + case AL_DISTORTION_GAIN: *val = props.Gain; break; + case AL_DISTORTION_LOWPASS_CUTOFF: *val = props.LowpassCutoff; break; + case AL_DISTORTION_EQCENTER: *val = props.EQCenter; break; + case AL_DISTORTION_EQBANDWIDTH: *val = props.EQBandwidth; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid distortion float property 0x%04x", param}; } } -void Distortion_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Distortion_getParamf(props, param, vals); } +void DistortionEffectHandler::GetParamfv(const DistortionProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Distortion.Edge = AL_DISTORTION_DEFAULT_EDGE; - props.Distortion.Gain = AL_DISTORTION_DEFAULT_GAIN; - props.Distortion.LowpassCutoff = AL_DISTORTION_DEFAULT_LOWPASS_CUTOFF; - props.Distortion.EQCenter = AL_DISTORTION_DEFAULT_EQCENTER; - props.Distortion.EQBandwidth = AL_DISTORTION_DEFAULT_EQBANDWIDTH; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Distortion); - -const EffectProps DistortionEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -204,66 +190,66 @@ template<> throw Exception{message}; } -template<> -bool DistortionCommitter::commit(const EaxEffectProps &props) +bool EaxDistortionCommitter::commit(const EAXDISTORTIONPROPERTIES &props) { - if(props.mType == mEaxProps.mType && mEaxProps.mDistortion.flEdge == props.mDistortion.flEdge - && mEaxProps.mDistortion.lGain == props.mDistortion.lGain - && mEaxProps.mDistortion.flLowPassCutOff == props.mDistortion.flLowPassCutOff - && mEaxProps.mDistortion.flEQCenter == props.mDistortion.flEQCenter - && mEaxProps.mDistortion.flEQBandwidth == props.mDistortion.flEQBandwidth) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Distortion.Edge = props.mDistortion.flEdge; - mAlProps.Distortion.Gain = level_mb_to_gain(static_cast(props.mDistortion.lGain)); - mAlProps.Distortion.LowpassCutoff = props.mDistortion.flLowPassCutOff; - mAlProps.Distortion.EQCenter = props.mDistortion.flEQCenter; - mAlProps.Distortion.EQBandwidth = props.mDistortion.flEdge; + mAlProps = [&]{ + DistortionProps ret{}; + ret.Edge = props.flEdge; + ret.Gain = level_mb_to_gain(static_cast(props.lGain)); + ret.LowpassCutoff = props.flLowPassCutOff; + ret.EQCenter = props.flEQCenter; + ret.EQBandwidth = props.flEdge; + return ret; + }(); return true; } -template<> -void DistortionCommitter::SetDefaults(EaxEffectProps &props) +void EaxDistortionCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Distortion; - props.mDistortion.flEdge = EAXDISTORTION_DEFAULTEDGE; - props.mDistortion.lGain = EAXDISTORTION_DEFAULTGAIN; - props.mDistortion.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; - props.mDistortion.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; - props.mDistortion.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + static constexpr EAXDISTORTIONPROPERTIES defprops{[] + { + EAXDISTORTIONPROPERTIES ret{}; + ret.flEdge = EAXDISTORTION_DEFAULTEDGE; + ret.lGain = EAXDISTORTION_DEFAULTGAIN; + ret.flLowPassCutOff = EAXDISTORTION_DEFAULTLOWPASSCUTOFF; + ret.flEQCenter = EAXDISTORTION_DEFAULTEQCENTER; + ret.flEQBandwidth = EAXDISTORTION_DEFAULTEQBANDWIDTH; + return ret; + }()}; + props = defprops; } -template<> -void DistortionCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxDistortionCommitter::Get(const EaxCall &call, const EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; - case EAXDISTORTION_ALLPARAMETERS: call.set_value(props.mDistortion); break; - case EAXDISTORTION_EDGE: call.set_value(props.mDistortion.flEdge); break; - case EAXDISTORTION_GAIN: call.set_value(props.mDistortion.lGain); break; - case EAXDISTORTION_LOWPASSCUTOFF: call.set_value(props.mDistortion.flLowPassCutOff); break; - case EAXDISTORTION_EQCENTER: call.set_value(props.mDistortion.flEQCenter); break; - case EAXDISTORTION_EQBANDWIDTH: call.set_value(props.mDistortion.flEQBandwidth); break; + case EAXDISTORTION_ALLPARAMETERS: call.set_value(props); break; + case EAXDISTORTION_EDGE: call.set_value(props.flEdge); break; + case EAXDISTORTION_GAIN: call.set_value(props.lGain); break; + case EAXDISTORTION_LOWPASSCUTOFF: call.set_value(props.flLowPassCutOff); break; + case EAXDISTORTION_EQCENTER: call.set_value(props.flEQCenter); break; + case EAXDISTORTION_EQBANDWIDTH: call.set_value(props.flEQBandwidth); break; default: fail_unknown_property_id(); } } -template<> -void DistortionCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxDistortionCommitter::Set(const EaxCall &call, EAXDISTORTIONPROPERTIES &props) { switch(call.get_property_id()) { case EAXDISTORTION_NONE: break; - case EAXDISTORTION_ALLPARAMETERS: defer(call, props.mDistortion); break; - case EAXDISTORTION_EDGE: defer(call, props.mDistortion.flEdge); break; - case EAXDISTORTION_GAIN: defer(call, props.mDistortion.lGain); break; - case EAXDISTORTION_LOWPASSCUTOFF: defer(call, props.mDistortion.flLowPassCutOff); break; - case EAXDISTORTION_EQCENTER: defer(call, props.mDistortion.flEQCenter); break; - case EAXDISTORTION_EQBANDWIDTH: defer(call, props.mDistortion.flEQBandwidth); break; + case EAXDISTORTION_ALLPARAMETERS: defer(call, props); break; + case EAXDISTORTION_EDGE: defer(call, props.flEdge); break; + case EAXDISTORTION_GAIN: defer(call, props.lGain); break; + case EAXDISTORTION_LOWPASSCUTOFF: defer(call, props.flLowPassCutOff); break; + case EAXDISTORTION_EQCENTER: defer(call, props.flEQCenter); break; + case EAXDISTORTION_EQBANDWIDTH: defer(call, props.flEQBandwidth); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/echo.cpp b/Engine/lib/openal-soft/al/effects/echo.cpp index 2eb37603d..96ed7be27 100644 --- a/Engine/lib/openal-soft/al/effects/echo.cpp +++ b/Engine/lib/openal-soft/al/effects/echo.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -19,102 +20,87 @@ namespace { static_assert(EchoMaxDelay >= AL_ECHO_MAX_DELAY, "Echo max delay too short"); static_assert(EchoMaxLRDelay >= AL_ECHO_MAX_LRDELAY, "Echo max left-right delay too short"); -void Echo_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + EchoProps props{}; + props.Delay = AL_ECHO_DEFAULT_DELAY; + props.LRDelay = AL_ECHO_DEFAULT_LRDELAY; + props.Damping = AL_ECHO_DEFAULT_DAMPING; + props.Feedback = AL_ECHO_DEFAULT_FEEDBACK; + props.Spread = AL_ECHO_DEFAULT_SPREAD; + return props; +} + +} // namespace + +const EffectProps EchoEffectProps{genDefaultProps()}; + +void EchoEffectHandler::SetParami(EchoProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void Echo_setParamiv(EffectProps*, ALenum param, const int*) +void EchoEffectHandler::SetParamiv(EchoProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void Echo_setParamf(EffectProps *props, ALenum param, float val) +void EchoEffectHandler::SetParamf(EchoProps &props, ALenum param, float val) { switch(param) { case AL_ECHO_DELAY: if(!(val >= AL_ECHO_MIN_DELAY && val <= AL_ECHO_MAX_DELAY)) throw effect_exception{AL_INVALID_VALUE, "Echo delay out of range"}; - props->Echo.Delay = val; + props.Delay = val; break; case AL_ECHO_LRDELAY: if(!(val >= AL_ECHO_MIN_LRDELAY && val <= AL_ECHO_MAX_LRDELAY)) throw effect_exception{AL_INVALID_VALUE, "Echo LR delay out of range"}; - props->Echo.LRDelay = val; + props.LRDelay = val; break; case AL_ECHO_DAMPING: if(!(val >= AL_ECHO_MIN_DAMPING && val <= AL_ECHO_MAX_DAMPING)) throw effect_exception{AL_INVALID_VALUE, "Echo damping out of range"}; - props->Echo.Damping = val; + props.Damping = val; break; case AL_ECHO_FEEDBACK: if(!(val >= AL_ECHO_MIN_FEEDBACK && val <= AL_ECHO_MAX_FEEDBACK)) throw effect_exception{AL_INVALID_VALUE, "Echo feedback out of range"}; - props->Echo.Feedback = val; + props.Feedback = val; break; case AL_ECHO_SPREAD: if(!(val >= AL_ECHO_MIN_SPREAD && val <= AL_ECHO_MAX_SPREAD)) throw effect_exception{AL_INVALID_VALUE, "Echo spread out of range"}; - props->Echo.Spread = val; + props.Spread = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; } } -void Echo_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Echo_setParamf(props, param, vals[0]); } +void EchoEffectHandler::SetParamfv(EchoProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Echo_getParami(const EffectProps*, ALenum param, int*) +void EchoEffectHandler::GetParami(const EchoProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer property 0x%04x", param}; } -void Echo_getParamiv(const EffectProps*, ALenum param, int*) +void EchoEffectHandler::GetParamiv(const EchoProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid echo integer-vector property 0x%04x", param}; } -void Echo_getParamf(const EffectProps *props, ALenum param, float *val) +void EchoEffectHandler::GetParamf(const EchoProps &props, ALenum param, float *val) { switch(param) { - case AL_ECHO_DELAY: - *val = props->Echo.Delay; - break; - - case AL_ECHO_LRDELAY: - *val = props->Echo.LRDelay; - break; - - case AL_ECHO_DAMPING: - *val = props->Echo.Damping; - break; - - case AL_ECHO_FEEDBACK: - *val = props->Echo.Feedback; - break; - - case AL_ECHO_SPREAD: - *val = props->Echo.Spread; - break; + case AL_ECHO_DELAY: *val = props.Delay; break; + case AL_ECHO_LRDELAY: *val = props.LRDelay; break; + case AL_ECHO_DAMPING: *val = props.Damping; break; + case AL_ECHO_FEEDBACK: *val = props.Feedback; break; + case AL_ECHO_SPREAD: *val = props.Spread; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid echo float property 0x%04x", param}; } } -void Echo_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Echo_getParamf(props, param, vals); } +void EchoEffectHandler::GetParamfv(const EchoProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Echo.Delay = AL_ECHO_DEFAULT_DELAY; - props.Echo.LRDelay = AL_ECHO_DEFAULT_LRDELAY; - props.Echo.Damping = AL_ECHO_DEFAULT_DAMPING; - props.Echo.Feedback = AL_ECHO_DEFAULT_FEEDBACK; - props.Echo.Spread = AL_ECHO_DEFAULT_SPREAD; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Echo); - -const EffectProps EchoEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -201,66 +187,66 @@ template<> throw Exception{message}; } -template<> -bool EchoCommitter::commit(const EaxEffectProps &props) +bool EaxEchoCommitter::commit(const EAXECHOPROPERTIES &props) { - if(props.mType == mEaxProps.mType && mEaxProps.mEcho.flDelay == props.mEcho.flDelay - && mEaxProps.mEcho.flLRDelay == props.mEcho.flLRDelay - && mEaxProps.mEcho.flDamping == props.mEcho.flDamping - && mEaxProps.mEcho.flFeedback == props.mEcho.flFeedback - && mEaxProps.mEcho.flSpread == props.mEcho.flSpread) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Echo.Delay = props.mEcho.flDelay; - mAlProps.Echo.LRDelay = props.mEcho.flLRDelay; - mAlProps.Echo.Damping = props.mEcho.flDamping; - mAlProps.Echo.Feedback = props.mEcho.flFeedback; - mAlProps.Echo.Spread = props.mEcho.flSpread; + mAlProps = [&]{ + EchoProps ret{}; + ret.Delay = props.flDelay; + ret.LRDelay = props.flLRDelay; + ret.Damping = props.flDamping; + ret.Feedback = props.flFeedback; + ret.Spread = props.flSpread; + return ret; + }(); return true; } -template<> -void EchoCommitter::SetDefaults(EaxEffectProps &props) +void EaxEchoCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Echo; - props.mEcho.flDelay = EAXECHO_DEFAULTDELAY; - props.mEcho.flLRDelay = EAXECHO_DEFAULTLRDELAY; - props.mEcho.flDamping = EAXECHO_DEFAULTDAMPING; - props.mEcho.flFeedback = EAXECHO_DEFAULTFEEDBACK; - props.mEcho.flSpread = EAXECHO_DEFAULTSPREAD; + static constexpr EAXECHOPROPERTIES defprops{[] + { + EAXECHOPROPERTIES ret{}; + ret.flDelay = EAXECHO_DEFAULTDELAY; + ret.flLRDelay = EAXECHO_DEFAULTLRDELAY; + ret.flDamping = EAXECHO_DEFAULTDAMPING; + ret.flFeedback = EAXECHO_DEFAULTFEEDBACK; + ret.flSpread = EAXECHO_DEFAULTSPREAD; + return ret; + }()}; + props = defprops; } -template<> -void EchoCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxEchoCommitter::Get(const EaxCall &call, const EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; - case EAXECHO_ALLPARAMETERS: call.set_value(props.mEcho); break; - case EAXECHO_DELAY: call.set_value(props.mEcho.flDelay); break; - case EAXECHO_LRDELAY: call.set_value(props.mEcho.flLRDelay); break; - case EAXECHO_DAMPING: call.set_value(props.mEcho.flDamping); break; - case EAXECHO_FEEDBACK: call.set_value(props.mEcho.flFeedback); break; - case EAXECHO_SPREAD: call.set_value(props.mEcho.flSpread); break; + case EAXECHO_ALLPARAMETERS: call.set_value(props); break; + case EAXECHO_DELAY: call.set_value(props.flDelay); break; + case EAXECHO_LRDELAY: call.set_value(props.flLRDelay); break; + case EAXECHO_DAMPING: call.set_value(props.flDamping); break; + case EAXECHO_FEEDBACK: call.set_value(props.flFeedback); break; + case EAXECHO_SPREAD: call.set_value(props.flSpread); break; default: fail_unknown_property_id(); } } -template<> -void EchoCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxEchoCommitter::Set(const EaxCall &call, EAXECHOPROPERTIES &props) { switch(call.get_property_id()) { case EAXECHO_NONE: break; - case EAXECHO_ALLPARAMETERS: defer(call, props.mEcho); break; - case EAXECHO_DELAY: defer(call, props.mEcho.flDelay); break; - case EAXECHO_LRDELAY: defer(call, props.mEcho.flLRDelay); break; - case EAXECHO_DAMPING: defer(call, props.mEcho.flDamping); break; - case EAXECHO_FEEDBACK: defer(call, props.mEcho.flFeedback); break; - case EAXECHO_SPREAD: defer(call, props.mEcho.flSpread); break; + case EAXECHO_ALLPARAMETERS: defer(call, props); break; + case EAXECHO_DELAY: defer(call, props.flDelay); break; + case EAXECHO_LRDELAY: defer(call, props.flLRDelay); break; + case EAXECHO_DAMPING: defer(call, props.flDamping); break; + case EAXECHO_FEEDBACK: defer(call, props.flFeedback); break; + case EAXECHO_SPREAD: defer(call, props.flSpread); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/effects.cpp b/Engine/lib/openal-soft/al/effects/effects.cpp index 4a67b5ffe..ace4e7066 100644 --- a/Engine/lib/openal-soft/al/effects/effects.cpp +++ b/Engine/lib/openal-soft/al/effects/effects.cpp @@ -1,9 +1,3 @@ #include "config.h" -#ifdef ALSOFT_EAX - -#include -#include "AL/efx.h" #include "effects.h" - -#endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/effects.h b/Engine/lib/openal-soft/al/effects/effects.h index 9d57dd820..3c4b8f93f 100644 --- a/Engine/lib/openal-soft/al/effects/effects.h +++ b/Engine/lib/openal-soft/al/effects/effects.h @@ -1,52 +1,47 @@ #ifndef AL_EFFECTS_EFFECTS_H #define AL_EFFECTS_EFFECTS_H +#include + #include "AL/al.h" -#include "core/except.h" +#include "al/error.h" +#include "core/effects/base.h" -#ifdef ALSOFT_EAX -#include "al/eax/effect.h" -#endif // ALSOFT_EAX - -union EffectProps; - - -class effect_exception final : public al::base_exception { - ALenum mErrorCode; - -public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - effect_exception(ALenum code, const char *msg, ...); - ~effect_exception() override; - - ALenum errorCode() const noexcept { return mErrorCode; } +#define DECL_HANDLER(N, T) \ +struct N { \ + using prop_type = T; \ + \ + static void SetParami(prop_type &props, ALenum param, int val); \ + static void SetParamiv(prop_type &props, ALenum param, const int *vals); \ + static void SetParamf(prop_type &props, ALenum param, float val); \ + static void SetParamfv(prop_type &props, ALenum param, const float *vals);\ + static void GetParami(const prop_type &props, ALenum param, int *val); \ + static void GetParamiv(const prop_type &props, ALenum param, int *vals); \ + static void GetParamf(const prop_type &props, ALenum param, float *val); \ + static void GetParamfv(const prop_type &props, ALenum param, float *vals);\ }; +DECL_HANDLER(NullEffectHandler, std::monostate) +DECL_HANDLER(ReverbEffectHandler, ReverbProps) +DECL_HANDLER(StdReverbEffectHandler, ReverbProps) +DECL_HANDLER(AutowahEffectHandler, AutowahProps) +DECL_HANDLER(ChorusEffectHandler, ChorusProps) +DECL_HANDLER(CompressorEffectHandler, CompressorProps) +DECL_HANDLER(DistortionEffectHandler, DistortionProps) +DECL_HANDLER(EchoEffectHandler, EchoProps) +DECL_HANDLER(EqualizerEffectHandler, EqualizerProps) +DECL_HANDLER(FlangerEffectHandler, ChorusProps) +DECL_HANDLER(FshifterEffectHandler, FshifterProps) +DECL_HANDLER(ModulatorEffectHandler, ModulatorProps) +DECL_HANDLER(PshifterEffectHandler, PshifterProps) +DECL_HANDLER(VmorpherEffectHandler, VmorpherProps) +DECL_HANDLER(DedicatedDialogEffectHandler, DedicatedProps) +DECL_HANDLER(DedicatedLfeEffectHandler, DedicatedProps) +DECL_HANDLER(ConvolutionEffectHandler, ConvolutionProps) +#undef DECL_HANDLER -struct EffectVtable { - void (*const setParami)(EffectProps *props, ALenum param, int val); - void (*const setParamiv)(EffectProps *props, ALenum param, const int *vals); - void (*const setParamf)(EffectProps *props, ALenum param, float val); - void (*const setParamfv)(EffectProps *props, ALenum param, const float *vals); - - void (*const getParami)(const EffectProps *props, ALenum param, int *val); - void (*const getParamiv)(const EffectProps *props, ALenum param, int *vals); - void (*const getParamf)(const EffectProps *props, ALenum param, float *val); - void (*const getParamfv)(const EffectProps *props, ALenum param, float *vals); -}; - -#define DEFINE_ALEFFECT_VTABLE(T) \ -const EffectVtable T##EffectVtable = { \ - T##_setParami, T##_setParamiv, \ - T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, \ - T##_getParamf, T##_getParamfv, \ -} +using effect_exception = al::context_error; /* Default properties for the given effect types. */ @@ -64,25 +59,8 @@ extern const EffectProps FshifterEffectProps; extern const EffectProps ModulatorEffectProps; extern const EffectProps PshifterEffectProps; extern const EffectProps VmorpherEffectProps; -extern const EffectProps DedicatedEffectProps; +extern const EffectProps DedicatedDialogEffectProps; +extern const EffectProps DedicatedLfeEffectProps; extern const EffectProps ConvolutionEffectProps; -/* Vtables to get/set properties for the given effect types. */ -extern const EffectVtable NullEffectVtable; -extern const EffectVtable ReverbEffectVtable; -extern const EffectVtable StdReverbEffectVtable; -extern const EffectVtable AutowahEffectVtable; -extern const EffectVtable ChorusEffectVtable; -extern const EffectVtable CompressorEffectVtable; -extern const EffectVtable DistortionEffectVtable; -extern const EffectVtable EchoEffectVtable; -extern const EffectVtable EqualizerEffectVtable; -extern const EffectVtable FlangerEffectVtable; -extern const EffectVtable FshifterEffectVtable; -extern const EffectVtable ModulatorEffectVtable; -extern const EffectVtable PshifterEffectVtable; -extern const EffectVtable VmorpherEffectVtable; -extern const EffectVtable DedicatedEffectVtable; -extern const EffectVtable ConvolutionEffectVtable; - #endif /* AL_EFFECTS_EFFECTS_H */ diff --git a/Engine/lib/openal-soft/al/effects/equalizer.cpp b/Engine/lib/openal-soft/al/effects/equalizer.cpp index 7dc703db6..ba7353078 100644 --- a/Engine/lib/openal-soft/al/effects/equalizer.cpp +++ b/Engine/lib/openal-soft/al/effects/equalizer.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,163 +17,133 @@ namespace { -void Equalizer_setParami(EffectProps*, ALenum param, int) +constexpr EffectProps genDefaultProps() noexcept +{ + EqualizerProps props{}; + props.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; + props.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; + props.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; + props.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; + props.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; + props.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; + props.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; + props.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; + props.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; + props.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; + return props; +} + +} // namespace + +const EffectProps EqualizerEffectProps{genDefaultProps()}; + +void EqualizerEffectHandler::SetParami(EqualizerProps&, ALenum param, int) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void Equalizer_setParamiv(EffectProps*, ALenum param, const int*) +void EqualizerEffectHandler::SetParamiv(EqualizerProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param}; } -void Equalizer_setParamf(EffectProps *props, ALenum param, float val) +void EqualizerEffectHandler::SetParamf(EqualizerProps &props, ALenum param, float val) { switch(param) { case AL_EQUALIZER_LOW_GAIN: if(!(val >= AL_EQUALIZER_MIN_LOW_GAIN && val <= AL_EQUALIZER_MAX_LOW_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band gain out of range"}; - props->Equalizer.LowGain = val; + props.LowGain = val; break; case AL_EQUALIZER_LOW_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_LOW_CUTOFF && val <= AL_EQUALIZER_MAX_LOW_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Equalizer low-band cutoff out of range"}; - props->Equalizer.LowCutoff = val; + props.LowCutoff = val; break; case AL_EQUALIZER_MID1_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID1_GAIN && val <= AL_EQUALIZER_MAX_MID1_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band gain out of range"}; - props->Equalizer.Mid1Gain = val; + props.Mid1Gain = val; break; case AL_EQUALIZER_MID1_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID1_CENTER && val <= AL_EQUALIZER_MAX_MID1_CENTER)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band center out of range"}; - props->Equalizer.Mid1Center = val; + props.Mid1Center = val; break; case AL_EQUALIZER_MID1_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID1_WIDTH && val <= AL_EQUALIZER_MAX_MID1_WIDTH)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid1-band width out of range"}; - props->Equalizer.Mid1Width = val; + props.Mid1Width = val; break; case AL_EQUALIZER_MID2_GAIN: if(!(val >= AL_EQUALIZER_MIN_MID2_GAIN && val <= AL_EQUALIZER_MAX_MID2_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band gain out of range"}; - props->Equalizer.Mid2Gain = val; + props.Mid2Gain = val; break; case AL_EQUALIZER_MID2_CENTER: if(!(val >= AL_EQUALIZER_MIN_MID2_CENTER && val <= AL_EQUALIZER_MAX_MID2_CENTER)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band center out of range"}; - props->Equalizer.Mid2Center = val; + props.Mid2Center = val; break; case AL_EQUALIZER_MID2_WIDTH: if(!(val >= AL_EQUALIZER_MIN_MID2_WIDTH && val <= AL_EQUALIZER_MAX_MID2_WIDTH)) throw effect_exception{AL_INVALID_VALUE, "Equalizer mid2-band width out of range"}; - props->Equalizer.Mid2Width = val; + props.Mid2Width = val; break; case AL_EQUALIZER_HIGH_GAIN: if(!(val >= AL_EQUALIZER_MIN_HIGH_GAIN && val <= AL_EQUALIZER_MAX_HIGH_GAIN)) throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band gain out of range"}; - props->Equalizer.HighGain = val; + props.HighGain = val; break; case AL_EQUALIZER_HIGH_CUTOFF: if(!(val >= AL_EQUALIZER_MIN_HIGH_CUTOFF && val <= AL_EQUALIZER_MAX_HIGH_CUTOFF)) throw effect_exception{AL_INVALID_VALUE, "Equalizer high-band cutoff out of range"}; - props->Equalizer.HighCutoff = val; + props.HighCutoff = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; } } -void Equalizer_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Equalizer_setParamf(props, param, vals[0]); } +void EqualizerEffectHandler::SetParamfv(EqualizerProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Equalizer_getParami(const EffectProps*, ALenum param, int*) +void EqualizerEffectHandler::GetParami(const EqualizerProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer property 0x%04x", param}; } -void Equalizer_getParamiv(const EffectProps*, ALenum param, int*) +void EqualizerEffectHandler::GetParamiv(const EqualizerProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer integer-vector property 0x%04x", param}; } -void Equalizer_getParamf(const EffectProps *props, ALenum param, float *val) +void EqualizerEffectHandler::GetParamf(const EqualizerProps &props, ALenum param, float *val) { switch(param) { - case AL_EQUALIZER_LOW_GAIN: - *val = props->Equalizer.LowGain; - break; - - case AL_EQUALIZER_LOW_CUTOFF: - *val = props->Equalizer.LowCutoff; - break; - - case AL_EQUALIZER_MID1_GAIN: - *val = props->Equalizer.Mid1Gain; - break; - - case AL_EQUALIZER_MID1_CENTER: - *val = props->Equalizer.Mid1Center; - break; - - case AL_EQUALIZER_MID1_WIDTH: - *val = props->Equalizer.Mid1Width; - break; - - case AL_EQUALIZER_MID2_GAIN: - *val = props->Equalizer.Mid2Gain; - break; - - case AL_EQUALIZER_MID2_CENTER: - *val = props->Equalizer.Mid2Center; - break; - - case AL_EQUALIZER_MID2_WIDTH: - *val = props->Equalizer.Mid2Width; - break; - - case AL_EQUALIZER_HIGH_GAIN: - *val = props->Equalizer.HighGain; - break; - - case AL_EQUALIZER_HIGH_CUTOFF: - *val = props->Equalizer.HighCutoff; - break; + case AL_EQUALIZER_LOW_GAIN: *val = props.LowGain; break; + case AL_EQUALIZER_LOW_CUTOFF: *val = props.LowCutoff; break; + case AL_EQUALIZER_MID1_GAIN: *val = props.Mid1Gain; break; + case AL_EQUALIZER_MID1_CENTER: *val = props.Mid1Center; break; + case AL_EQUALIZER_MID1_WIDTH: *val = props.Mid1Width; break; + case AL_EQUALIZER_MID2_GAIN: *val = props.Mid2Gain; break; + case AL_EQUALIZER_MID2_CENTER: *val = props.Mid2Center; break; + case AL_EQUALIZER_MID2_WIDTH: *val = props.Mid2Width; break; + case AL_EQUALIZER_HIGH_GAIN: *val = props.HighGain; break; + case AL_EQUALIZER_HIGH_CUTOFF: *val = props.HighCutoff; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid equalizer float property 0x%04x", param}; } } -void Equalizer_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Equalizer_getParamf(props, param, vals); } +void EqualizerEffectHandler::GetParamfv(const EqualizerProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Equalizer.LowCutoff = AL_EQUALIZER_DEFAULT_LOW_CUTOFF; - props.Equalizer.LowGain = AL_EQUALIZER_DEFAULT_LOW_GAIN; - props.Equalizer.Mid1Center = AL_EQUALIZER_DEFAULT_MID1_CENTER; - props.Equalizer.Mid1Gain = AL_EQUALIZER_DEFAULT_MID1_GAIN; - props.Equalizer.Mid1Width = AL_EQUALIZER_DEFAULT_MID1_WIDTH; - props.Equalizer.Mid2Center = AL_EQUALIZER_DEFAULT_MID2_CENTER; - props.Equalizer.Mid2Gain = AL_EQUALIZER_DEFAULT_MID2_GAIN; - props.Equalizer.Mid2Width = AL_EQUALIZER_DEFAULT_MID2_WIDTH; - props.Equalizer.HighCutoff = AL_EQUALIZER_DEFAULT_HIGH_CUTOFF; - props.Equalizer.HighGain = AL_EQUALIZER_DEFAULT_HIGH_GAIN; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Equalizer); - -const EffectProps EqualizerEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -319,91 +290,86 @@ template<> throw Exception{message}; } -template<> -bool EqualizerCommitter::commit(const EaxEffectProps &props) +bool EaxEqualizerCommitter::commit(const EAXEQUALIZERPROPERTIES &props) { - if(props.mType == mEaxProps.mType && mEaxProps.mEqualizer.lLowGain == props.mEqualizer.lLowGain - && mEaxProps.mEqualizer.flLowCutOff == props.mEqualizer.flLowCutOff - && mEaxProps.mEqualizer.lMid1Gain == props.mEqualizer.lMid1Gain - && mEaxProps.mEqualizer.flMid1Center == props.mEqualizer.flMid1Center - && mEaxProps.mEqualizer.flMid1Width == props.mEqualizer.flMid1Width - && mEaxProps.mEqualizer.lMid2Gain == props.mEqualizer.lMid2Gain - && mEaxProps.mEqualizer.flMid2Center == props.mEqualizer.flMid2Center - && mEaxProps.mEqualizer.flMid2Width == props.mEqualizer.flMid2Width - && mEaxProps.mEqualizer.lHighGain == props.mEqualizer.lHighGain - && mEaxProps.mEqualizer.flHighCutOff == props.mEqualizer.flHighCutOff) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast(props.mEqualizer.lLowGain)); - mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff; - mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast(props.mEqualizer.lMid1Gain)); - mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center; - mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width; - mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast(props.mEqualizer.lMid2Gain)); - mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center; - mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width; - mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast(props.mEqualizer.lHighGain)); - mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff; + mAlProps = [&]{ + EqualizerProps ret{}; + ret.LowGain = level_mb_to_gain(static_cast(props.lLowGain)); + ret.LowCutoff = props.flLowCutOff; + ret.Mid1Gain = level_mb_to_gain(static_cast(props.lMid1Gain)); + ret.Mid1Center = props.flMid1Center; + ret.Mid1Width = props.flMid1Width; + ret.Mid2Gain = level_mb_to_gain(static_cast(props.lMid2Gain)); + ret.Mid2Center = props.flMid2Center; + ret.Mid2Width = props.flMid2Width; + ret.HighGain = level_mb_to_gain(static_cast(props.lHighGain)); + ret.HighCutoff = props.flHighCutOff; + return ret; + }(); return true; } -template<> -void EqualizerCommitter::SetDefaults(EaxEffectProps &props) +void EaxEqualizerCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Equalizer; - props.mEqualizer.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; - props.mEqualizer.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; - props.mEqualizer.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; - props.mEqualizer.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; - props.mEqualizer.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; - props.mEqualizer.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; - props.mEqualizer.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; - props.mEqualizer.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; - props.mEqualizer.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; - props.mEqualizer.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + static constexpr EAXEQUALIZERPROPERTIES defprops{[] + { + EAXEQUALIZERPROPERTIES ret{}; + ret.lLowGain = EAXEQUALIZER_DEFAULTLOWGAIN; + ret.flLowCutOff = EAXEQUALIZER_DEFAULTLOWCUTOFF; + ret.lMid1Gain = EAXEQUALIZER_DEFAULTMID1GAIN; + ret.flMid1Center = EAXEQUALIZER_DEFAULTMID1CENTER; + ret.flMid1Width = EAXEQUALIZER_DEFAULTMID1WIDTH; + ret.lMid2Gain = EAXEQUALIZER_DEFAULTMID2GAIN; + ret.flMid2Center = EAXEQUALIZER_DEFAULTMID2CENTER; + ret.flMid2Width = EAXEQUALIZER_DEFAULTMID2WIDTH; + ret.lHighGain = EAXEQUALIZER_DEFAULTHIGHGAIN; + ret.flHighCutOff = EAXEQUALIZER_DEFAULTHIGHCUTOFF; + return ret; + }()}; + props = defprops; } -template<> -void EqualizerCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxEqualizerCommitter::Get(const EaxCall &call, const EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; - case EAXEQUALIZER_ALLPARAMETERS: call.set_value(props.mEqualizer); break; - case EAXEQUALIZER_LOWGAIN: call.set_value(props.mEqualizer.lLowGain); break; - case EAXEQUALIZER_LOWCUTOFF: call.set_value(props.mEqualizer.flLowCutOff); break; - case EAXEQUALIZER_MID1GAIN: call.set_value(props.mEqualizer.lMid1Gain); break; - case EAXEQUALIZER_MID1CENTER: call.set_value(props.mEqualizer.flMid1Center); break; - case EAXEQUALIZER_MID1WIDTH: call.set_value(props.mEqualizer.flMid1Width); break; - case EAXEQUALIZER_MID2GAIN: call.set_value(props.mEqualizer.lMid2Gain); break; - case EAXEQUALIZER_MID2CENTER: call.set_value(props.mEqualizer.flMid2Center); break; - case EAXEQUALIZER_MID2WIDTH: call.set_value(props.mEqualizer.flMid2Width); break; - case EAXEQUALIZER_HIGHGAIN: call.set_value(props.mEqualizer.lHighGain); break; - case EAXEQUALIZER_HIGHCUTOFF: call.set_value(props.mEqualizer.flHighCutOff); break; + case EAXEQUALIZER_ALLPARAMETERS: call.set_value(props); break; + case EAXEQUALIZER_LOWGAIN: call.set_value(props.lLowGain); break; + case EAXEQUALIZER_LOWCUTOFF: call.set_value(props.flLowCutOff); break; + case EAXEQUALIZER_MID1GAIN: call.set_value(props.lMid1Gain); break; + case EAXEQUALIZER_MID1CENTER: call.set_value(props.flMid1Center); break; + case EAXEQUALIZER_MID1WIDTH: call.set_value(props.flMid1Width); break; + case EAXEQUALIZER_MID2GAIN: call.set_value(props.lMid2Gain); break; + case EAXEQUALIZER_MID2CENTER: call.set_value(props.flMid2Center); break; + case EAXEQUALIZER_MID2WIDTH: call.set_value(props.flMid2Width); break; + case EAXEQUALIZER_HIGHGAIN: call.set_value(props.lHighGain); break; + case EAXEQUALIZER_HIGHCUTOFF: call.set_value(props.flHighCutOff); break; default: fail_unknown_property_id(); } } -template<> -void EqualizerCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxEqualizerCommitter::Set(const EaxCall &call, EAXEQUALIZERPROPERTIES &props) { switch(call.get_property_id()) { case EAXEQUALIZER_NONE: break; - case EAXEQUALIZER_ALLPARAMETERS: defer(call, props.mEqualizer); break; - case EAXEQUALIZER_LOWGAIN: defer(call, props.mEqualizer.lLowGain); break; - case EAXEQUALIZER_LOWCUTOFF: defer(call, props.mEqualizer.flLowCutOff); break; - case EAXEQUALIZER_MID1GAIN: defer(call, props.mEqualizer.lMid1Gain); break; - case EAXEQUALIZER_MID1CENTER: defer(call, props.mEqualizer.flMid1Center); break; - case EAXEQUALIZER_MID1WIDTH: defer(call, props.mEqualizer.flMid1Width); break; - case EAXEQUALIZER_MID2GAIN: defer(call, props.mEqualizer.lMid2Gain); break; - case EAXEQUALIZER_MID2CENTER: defer(call, props.mEqualizer.flMid2Center); break; - case EAXEQUALIZER_MID2WIDTH: defer(call, props.mEqualizer.flMid2Width); break; - case EAXEQUALIZER_HIGHGAIN: defer(call, props.mEqualizer.lHighGain); break; - case EAXEQUALIZER_HIGHCUTOFF: defer(call, props.mEqualizer.flHighCutOff); break; + case EAXEQUALIZER_ALLPARAMETERS: defer(call, props); break; + case EAXEQUALIZER_LOWGAIN: defer(call, props.lLowGain); break; + case EAXEQUALIZER_LOWCUTOFF: defer(call, props.flLowCutOff); break; + case EAXEQUALIZER_MID1GAIN: defer(call, props.lMid1Gain); break; + case EAXEQUALIZER_MID1CENTER: defer(call, props.flMid1Center); break; + case EAXEQUALIZER_MID1WIDTH: defer(call, props.flMid1Width); break; + case EAXEQUALIZER_MID2GAIN: defer(call, props.lMid2Gain); break; + case EAXEQUALIZER_MID2CENTER: defer(call, props.flMid2Center); break; + case EAXEQUALIZER_MID2WIDTH: defer(call, props.flMid2Width); break; + case EAXEQUALIZER_HIGHGAIN: defer(call, props.lHighGain); break; + case EAXEQUALIZER_HIGHCUTOFF: defer(call, props.flHighCutOff); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/fshifter.cpp b/Engine/lib/openal-soft/al/effects/fshifter.cpp index 949db2035..a23213952 100644 --- a/Engine/lib/openal-soft/al/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/fshifter.cpp @@ -1,18 +1,19 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/effects/base.h" -#include "aloptional.h" #include "effects.h" #ifdef ALSOFT_EAX #include #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,7 +21,7 @@ namespace { -al::optional DirectionFromEmum(ALenum value) +constexpr std::optional DirectionFromEmum(ALenum value) noexcept { switch(value) { @@ -28,9 +29,9 @@ al::optional DirectionFromEmum(ALenum value) case AL_FREQUENCY_SHIFTER_DIRECTION_UP: return FShifterDirection::Up; case AL_FREQUENCY_SHIFTER_DIRECTION_OFF: return FShifterDirection::Off; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromDirection(FShifterDirection dir) +constexpr ALenum EnumFromDirection(FShifterDirection dir) { switch(dir) { @@ -41,31 +42,26 @@ ALenum EnumFromDirection(FShifterDirection dir) throw std::runtime_error{"Invalid direction: "+std::to_string(static_cast(dir))}; } -void Fshifter_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - case AL_FREQUENCY_SHIFTER_FREQUENCY: - if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; - props->Fshifter.Frequency = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", - param}; - } + FshifterProps props{}; + props.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; + props.LeftDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION).value(); + props.RightDirection = DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION).value(); + return props; } -void Fshifter_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Fshifter_setParamf(props, param, vals[0]); } -void Fshifter_setParami(EffectProps *props, ALenum param, int val) +} // namespace + +const EffectProps FshifterEffectProps{genDefaultProps()}; + +void FshifterEffectHandler::SetParami(FshifterProps &props, ALenum param, int val) { switch(param) { case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) - props->Fshifter.LeftDirection = *diropt; + props.LeftDirection = *diropt; else throw effect_exception{AL_INVALID_VALUE, "Unsupported frequency shifter left direction: 0x%04x", val}; @@ -73,7 +69,7 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val) case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: if(auto diropt = DirectionFromEmum(val)) - props->Fshifter.RightDirection = *diropt; + props.RightDirection = *diropt; else throw effect_exception{AL_INVALID_VALUE, "Unsupported frequency shifter right direction: 0x%04x", val}; @@ -84,33 +80,17 @@ void Fshifter_setParami(EffectProps *props, ALenum param, int val) "Invalid frequency shifter integer property 0x%04x", param}; } } -void Fshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Fshifter_setParami(props, param, vals[0]); } +void FshifterEffectHandler::SetParamiv(FshifterProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Fshifter_getParami(const EffectProps *props, ALenum param, int *val) -{ - switch(param) - { - case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: - *val = EnumFromDirection(props->Fshifter.LeftDirection); - break; - case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: - *val = EnumFromDirection(props->Fshifter.RightDirection); - break; - default: - throw effect_exception{AL_INVALID_ENUM, - "Invalid frequency shifter integer property 0x%04x", param}; - } -} -void Fshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Fshifter_getParami(props, param, vals); } - -void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val) +void FshifterEffectHandler::SetParamf(FshifterProps &props, ALenum param, float val) { switch(param) { case AL_FREQUENCY_SHIFTER_FREQUENCY: - *val = props->Fshifter.Frequency; + if(!(val >= AL_FREQUENCY_SHIFTER_MIN_FREQUENCY && val <= AL_FREQUENCY_SHIFTER_MAX_FREQUENCY)) + throw effect_exception{AL_INVALID_VALUE, "Frequency shifter frequency out of range"}; + props.Frequency = val; break; default: @@ -118,23 +98,44 @@ void Fshifter_getParamf(const EffectProps *props, ALenum param, float *val) param}; } } -void Fshifter_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Fshifter_getParamf(props, param, vals); } +void FshifterEffectHandler::SetParamfv(FshifterProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void FshifterEffectHandler::GetParami(const FshifterProps &props, ALenum param, int *val) { - EffectProps props{}; - props.Fshifter.Frequency = AL_FREQUENCY_SHIFTER_DEFAULT_FREQUENCY; - props.Fshifter.LeftDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_LEFT_DIRECTION); - props.Fshifter.RightDirection = *DirectionFromEmum(AL_FREQUENCY_SHIFTER_DEFAULT_RIGHT_DIRECTION); - return props; + switch(param) + { + case AL_FREQUENCY_SHIFTER_LEFT_DIRECTION: + *val = EnumFromDirection(props.LeftDirection); + break; + case AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION: + *val = EnumFromDirection(props.RightDirection); + break; + + default: + throw effect_exception{AL_INVALID_ENUM, + "Invalid frequency shifter integer property 0x%04x", param}; + } } +void FshifterEffectHandler::GetParamiv(const FshifterProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } -} // namespace +void FshifterEffectHandler::GetParamf(const FshifterProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_FREQUENCY_SHIFTER_FREQUENCY: + *val = props.Frequency; + break; -DEFINE_ALEFFECT_VTABLE(Fshifter); + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid frequency shifter float property 0x%04x", + param}; + } +} +void FshifterEffectHandler::GetParamfv(const FshifterProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -const EffectProps FshifterEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -197,13 +198,9 @@ template<> throw Exception{message}; } -template<> -bool FrequencyShifterCommitter::commit(const EaxEffectProps &props) +bool EaxFrequencyShifterCommitter::commit(const EAXFREQUENCYSHIFTERPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mFrequencyShifter.flFrequency == props.mFrequencyShifter.flFrequency - && mEaxProps.mFrequencyShifter.ulLeftDirection == props.mFrequencyShifter.ulLeftDirection - && mEaxProps.mFrequencyShifter.ulRightDirection == props.mFrequencyShifter.ulRightDirection) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; @@ -217,46 +214,52 @@ bool FrequencyShifterCommitter::commit(const EaxEffectProps &props) return FShifterDirection::Off; }; - mAlProps.Fshifter.Frequency = props.mFrequencyShifter.flFrequency; - mAlProps.Fshifter.LeftDirection = get_direction(props.mFrequencyShifter.ulLeftDirection); - mAlProps.Fshifter.RightDirection = get_direction(props.mFrequencyShifter.ulRightDirection); + mAlProps = [&]{ + FshifterProps ret{}; + ret.Frequency = props.flFrequency; + ret.LeftDirection = get_direction(props.ulLeftDirection); + ret.RightDirection = get_direction(props.ulRightDirection); + return ret; + }(); return true; } -template<> -void FrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) +void EaxFrequencyShifterCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::FrequencyShifter; - props.mFrequencyShifter.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; - props.mFrequencyShifter.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; - props.mFrequencyShifter.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + static constexpr EAXFREQUENCYSHIFTERPROPERTIES defprops{[] + { + EAXFREQUENCYSHIFTERPROPERTIES ret{}; + ret.flFrequency = EAXFREQUENCYSHIFTER_DEFAULTFREQUENCY; + ret.ulLeftDirection = EAXFREQUENCYSHIFTER_DEFAULTLEFTDIRECTION; + ret.ulRightDirection = EAXFREQUENCYSHIFTER_DEFAULTRIGHTDIRECTION; + return ret; + }()}; + props = defprops; } -template<> -void FrequencyShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxFrequencyShifterCommitter::Get(const EaxCall &call, const EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; - case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value(props.mFrequencyShifter); break; - case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value(props.mFrequencyShifter.flFrequency); break; - case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value(props.mFrequencyShifter.ulLeftDirection); break; - case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value(props.mFrequencyShifter.ulRightDirection); break; + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value(props); break; + case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value(props.flFrequency); break; + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value(props.ulLeftDirection); break; + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value(props.ulRightDirection); break; default: fail_unknown_property_id(); } } -template<> -void FrequencyShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxFrequencyShifterCommitter::Set(const EaxCall &call, EAXFREQUENCYSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXFREQUENCYSHIFTER_NONE: break; - case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer(call, props.mFrequencyShifter); break; - case EAXFREQUENCYSHIFTER_FREQUENCY: defer(call, props.mFrequencyShifter.flFrequency); break; - case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer(call, props.mFrequencyShifter.ulLeftDirection); break; - case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer(call, props.mFrequencyShifter.ulRightDirection); break; + case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer(call, props); break; + case EAXFREQUENCYSHIFTER_FREQUENCY: defer(call, props.flFrequency); break; + case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer(call, props.ulLeftDirection); break; + case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer(call, props.ulRightDirection); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/modulator.cpp b/Engine/lib/openal-soft/al/effects/modulator.cpp index 5f37d08f3..856c85a97 100644 --- a/Engine/lib/openal-soft/al/effects/modulator.cpp +++ b/Engine/lib/openal-soft/al/effects/modulator.cpp @@ -1,18 +1,19 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" #include "alc/effects/base.h" -#include "aloptional.h" #include "effects.h" #ifdef ALSOFT_EAX #include #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,7 +21,7 @@ namespace { -al::optional WaveformFromEmum(ALenum value) +constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { @@ -28,9 +29,9 @@ al::optional WaveformFromEmum(ALenum value) case AL_RING_MODULATOR_SAWTOOTH: return ModulatorWaveform::Sawtooth; case AL_RING_MODULATOR_SQUARE: return ModulatorWaveform::Square; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromWaveform(ModulatorWaveform type) +constexpr ALenum EnumFromWaveform(ModulatorWaveform type) { switch(type) { @@ -42,40 +43,31 @@ ALenum EnumFromWaveform(ModulatorWaveform type) std::to_string(static_cast(type))}; } -void Modulator_setParamf(EffectProps *props, ALenum param, float val) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) - throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; - props->Modulator.Frequency = val; - break; - - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) - throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; - props->Modulator.HighPassCutoff = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; - } + ModulatorProps props{}; + props.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; + props.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; + props.Waveform = WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM).value(); + return props; } -void Modulator_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Modulator_setParamf(props, param, vals[0]); } -void Modulator_setParami(EffectProps *props, ALenum param, int val) + +} // namespace + +const EffectProps ModulatorEffectProps{genDefaultProps()}; + +void ModulatorEffectHandler::SetParami(ModulatorProps &props, ALenum param, int val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - Modulator_setParamf(props, param, static_cast(val)); + SetParamf(props, param, static_cast(val)); break; case AL_RING_MODULATOR_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) - props->Modulator.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Invalid modulator waveform: 0x%04x", val}; break; @@ -85,62 +77,61 @@ void Modulator_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Modulator_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Modulator_setParami(props, param, vals[0]); } +void ModulatorEffectHandler::SetParamiv(ModulatorProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Modulator_getParami(const EffectProps *props, ALenum param, int *val) +void ModulatorEffectHandler::SetParamf(ModulatorProps &props, ALenum param, float val) { switch(param) { case AL_RING_MODULATOR_FREQUENCY: - *val = static_cast(props->Modulator.Frequency); - break; - case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = static_cast(props->Modulator.HighPassCutoff); - break; - case AL_RING_MODULATOR_WAVEFORM: - *val = EnumFromWaveform(props->Modulator.Waveform); + if(!(val >= AL_RING_MODULATOR_MIN_FREQUENCY && val <= AL_RING_MODULATOR_MAX_FREQUENCY)) + throw effect_exception{AL_INVALID_VALUE, "Modulator frequency out of range: %f", val}; + props.Frequency = val; break; - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", - param}; - } -} -void Modulator_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Modulator_getParami(props, param, vals); } -void Modulator_getParamf(const EffectProps *props, ALenum param, float *val) -{ - switch(param) - { - case AL_RING_MODULATOR_FREQUENCY: - *val = props->Modulator.Frequency; - break; case AL_RING_MODULATOR_HIGHPASS_CUTOFF: - *val = props->Modulator.HighPassCutoff; + if(!(val >= AL_RING_MODULATOR_MIN_HIGHPASS_CUTOFF && val <= AL_RING_MODULATOR_MAX_HIGHPASS_CUTOFF)) + throw effect_exception{AL_INVALID_VALUE, "Modulator high-pass cutoff out of range: %f", val}; + props.HighPassCutoff = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; } } -void Modulator_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Modulator_getParamf(props, param, vals); } +void ModulatorEffectHandler::SetParamfv(ModulatorProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -EffectProps genDefaultProps() noexcept +void ModulatorEffectHandler::GetParami(const ModulatorProps &props, ALenum param, int *val) { - EffectProps props{}; - props.Modulator.Frequency = AL_RING_MODULATOR_DEFAULT_FREQUENCY; - props.Modulator.HighPassCutoff = AL_RING_MODULATOR_DEFAULT_HIGHPASS_CUTOFF; - props.Modulator.Waveform = *WaveformFromEmum(AL_RING_MODULATOR_DEFAULT_WAVEFORM); - return props; + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: *val = static_cast(props.Frequency); break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast(props.HighPassCutoff); break; + case AL_RING_MODULATOR_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator integer property 0x%04x", + param}; + } } +void ModulatorEffectHandler::GetParamiv(const ModulatorProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void ModulatorEffectHandler::GetParamf(const ModulatorProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_RING_MODULATOR_FREQUENCY: *val = props.Frequency; break; + case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = props.HighPassCutoff; break; -} // namespace + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid modulator float property 0x%04x", param}; + } +} +void ModulatorEffectHandler::GetParamfv(const ModulatorProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -DEFINE_ALEFFECT_VTABLE(Modulator); - -const EffectProps ModulatorEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -203,13 +194,9 @@ template<> throw Exception{message}; } -template<> -bool ModulatorCommitter::commit(const EaxEffectProps &props) +bool EaxModulatorCommitter::commit(const EAXRINGMODULATORPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mModulator.flFrequency == props.mModulator.flFrequency - && mEaxProps.mModulator.flHighPassCutOff == props.mModulator.flHighPassCutOff - && mEaxProps.mModulator.ulWaveform == props.mModulator.ulWaveform) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; @@ -225,46 +212,52 @@ bool ModulatorCommitter::commit(const EaxEffectProps &props) return ModulatorWaveform::Sinusoid; }; - mAlProps.Modulator.Frequency = props.mModulator.flFrequency; - mAlProps.Modulator.HighPassCutoff = props.mModulator.flHighPassCutOff; - mAlProps.Modulator.Waveform = get_waveform(props.mModulator.ulWaveform); + mAlProps = [&]{ + ModulatorProps ret{}; + ret.Frequency = props.flFrequency; + ret.HighPassCutoff = props.flHighPassCutOff; + ret.Waveform = get_waveform(props.ulWaveform); + return ret; + }(); return true; } -template<> -void ModulatorCommitter::SetDefaults(EaxEffectProps &props) +void EaxModulatorCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Modulator; - props.mModulator.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; - props.mModulator.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; - props.mModulator.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + static constexpr EAXRINGMODULATORPROPERTIES defprops{[] + { + EAXRINGMODULATORPROPERTIES ret{}; + ret.flFrequency = EAXRINGMODULATOR_DEFAULTFREQUENCY; + ret.flHighPassCutOff = EAXRINGMODULATOR_DEFAULTHIGHPASSCUTOFF; + ret.ulWaveform = EAXRINGMODULATOR_DEFAULTWAVEFORM; + return ret; + }()}; + props = defprops; } -template<> -void ModulatorCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxModulatorCommitter::Get(const EaxCall &call, const EAXRINGMODULATORPROPERTIES &props) { switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; - case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value(props.mModulator); break; - case EAXRINGMODULATOR_FREQUENCY: call.set_value(props.mModulator.flFrequency); break; - case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value(props.mModulator.flHighPassCutOff); break; - case EAXRINGMODULATOR_WAVEFORM: call.set_value(props.mModulator.ulWaveform); break; + case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value(props); break; + case EAXRINGMODULATOR_FREQUENCY: call.set_value(props.flFrequency); break; + case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value(props.flHighPassCutOff); break; + case EAXRINGMODULATOR_WAVEFORM: call.set_value(props.ulWaveform); break; default: fail_unknown_property_id(); } } -template<> -void ModulatorCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxModulatorCommitter::Set(const EaxCall &call, EAXRINGMODULATORPROPERTIES &props) { - switch (call.get_property_id()) + switch(call.get_property_id()) { case EAXRINGMODULATOR_NONE: break; - case EAXRINGMODULATOR_ALLPARAMETERS: defer(call, props.mModulator); break; - case EAXRINGMODULATOR_FREQUENCY: defer(call, props.mModulator.flFrequency); break; - case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer(call, props.mModulator.flHighPassCutOff); break; - case EAXRINGMODULATOR_WAVEFORM: defer(call, props.mModulator.ulWaveform); break; + case EAXRINGMODULATOR_ALLPARAMETERS: defer(call, props); break; + case EAXRINGMODULATOR_FREQUENCY: defer(call, props.flFrequency); break; + case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer(call, props.flHighPassCutOff); break; + case EAXRINGMODULATOR_WAVEFORM: defer(call, props.ulWaveform); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/null.cpp b/Engine/lib/openal-soft/al/effects/null.cpp index 0bbc183a3..2fa9cb6d4 100644 --- a/Engine/lib/openal-soft/al/effects/null.cpp +++ b/Engine/lib/openal-soft/al/effects/null.cpp @@ -8,94 +8,92 @@ #include "effects.h" #ifdef ALSOFT_EAX +#include "al/eax/effect.h" #include "al/eax/exception.h" #endif // ALSOFT_EAX namespace { -void Null_setParami(EffectProps* /*props*/, ALenum param, int /*val*/) +constexpr EffectProps genDefaultProps() noexcept { - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Null_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ - switch(param) - { - default: - Null_setParami(props, param, vals[0]); - } -} -void Null_setParamf(EffectProps* /*props*/, ALenum param, float /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Null_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ - switch(param) - { - default: - Null_setParamf(props, param, vals[0]); - } -} - -void Null_getParami(const EffectProps* /*props*/, ALenum param, int* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", - param}; - } -} -void Null_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ - switch(param) - { - default: - Null_getParami(props, param, vals); - } -} -void Null_getParamf(const EffectProps* /*props*/, ALenum param, float* /*val*/) -{ - switch(param) - { - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", - param}; - } -} -void Null_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ - switch(param) - { - default: - Null_getParamf(props, param, vals); - } -} - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - return props; + return std::monostate{}; } } // namespace -DEFINE_ALEFFECT_VTABLE(Null); - const EffectProps NullEffectProps{genDefaultProps()}; +void NullEffectHandler::SetParami(std::monostate& /*props*/, ALenum param, int /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void NullEffectHandler::SetParamiv(std::monostate &props, ALenum param, const int *vals) +{ + switch(param) + { + default: + SetParami(props, param, *vals); + } +} +void NullEffectHandler::SetParamf(std::monostate& /*props*/, ALenum param, float /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void NullEffectHandler::SetParamfv(std::monostate &props, ALenum param, const float *vals) +{ + switch(param) + { + default: + SetParamf(props, param, *vals); + } +} + +void NullEffectHandler::GetParami(const std::monostate& /*props*/, ALenum param, int* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect integer property 0x%04x", + param}; + } +} +void NullEffectHandler::GetParamiv(const std::monostate &props, ALenum param, int *vals) +{ + switch(param) + { + default: + GetParami(props, param, vals); + } +} +void NullEffectHandler::GetParamf(const std::monostate& /*props*/, ALenum param, float* /*val*/) +{ + switch(param) + { + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid null effect float property 0x%04x", + param}; + } +} +void NullEffectHandler::GetParamfv(const std::monostate &props, ALenum param, float *vals) +{ + switch(param) + { + default: + GetParamf(props, param, vals); + } +} + #ifdef ALSOFT_EAX namespace { @@ -117,30 +115,26 @@ template<> throw Exception{message}; } -template<> -bool NullCommitter::commit(const EaxEffectProps &props) +bool EaxNullCommitter::commit(const std::monostate &props) { - const bool ret{props.mType != mEaxProps.mType}; + const bool ret{std::holds_alternative(mEaxProps)}; mEaxProps = props; + mAlProps = std::monostate{}; return ret; } -template<> -void NullCommitter::SetDefaults(EaxEffectProps &props) +void EaxNullCommitter::SetDefaults(EaxEffectProps &props) { - props = EaxEffectProps{}; - props.mType = EaxEffectType::None; + props = std::monostate{}; } -template<> -void NullCommitter::Get(const EaxCall &call, const EaxEffectProps&) +void EaxNullCommitter::Get(const EaxCall &call, const std::monostate&) { if(call.get_property_id() != 0) fail_unknown_property_id(); } -template<> -void NullCommitter::Set(const EaxCall &call, EaxEffectProps&) +void EaxNullCommitter::Set(const EaxCall &call, std::monostate&) { if(call.get_property_id() != 0) fail_unknown_property_id(); diff --git a/Engine/lib/openal-soft/al/effects/pshifter.cpp b/Engine/lib/openal-soft/al/effects/pshifter.cpp index 634eb1866..c408eddce 100644 --- a/Engine/lib/openal-soft/al/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/al/effects/pshifter.cpp @@ -9,6 +9,7 @@ #ifdef ALSOFT_EAX #include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -16,28 +17,32 @@ namespace { -void Pshifter_setParamf(EffectProps*, ALenum param, float) -{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void Pshifter_setParamfv(EffectProps*, ALenum param, const float*) +constexpr EffectProps genDefaultProps() noexcept { - throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", - param}; + PshifterProps props{}; + props.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; + props.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; + return props; } -void Pshifter_setParami(EffectProps *props, ALenum param, int val) +} // namespace + +const EffectProps PshifterEffectProps{genDefaultProps()}; + +void PshifterEffectHandler::SetParami(PshifterProps &props, ALenum param, int val) { switch(param) { case AL_PITCH_SHIFTER_COARSE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_COARSE_TUNE && val <= AL_PITCH_SHIFTER_MAX_COARSE_TUNE)) throw effect_exception{AL_INVALID_VALUE, "Pitch shifter coarse tune out of range"}; - props->Pshifter.CoarseTune = val; + props.CoarseTune = val; break; case AL_PITCH_SHIFTER_FINE_TUNE: if(!(val >= AL_PITCH_SHIFTER_MIN_FINE_TUNE && val <= AL_PITCH_SHIFTER_MAX_FINE_TUNE)) throw effect_exception{AL_INVALID_VALUE, "Pitch shifter fine tune out of range"}; - props->Pshifter.FineTune = val; + props.FineTune = val; break; default: @@ -45,49 +50,40 @@ void Pshifter_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Pshifter_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Pshifter_setParami(props, param, vals[0]); } +void PshifterEffectHandler::SetParamiv(PshifterProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } -void Pshifter_getParami(const EffectProps *props, ALenum param, int *val) +void PshifterEffectHandler::SetParamf(PshifterProps&, ALenum param, float) +{ throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } +void PshifterEffectHandler::SetParamfv(PshifterProps&, ALenum param, const float*) +{ + throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float-vector property 0x%04x", + param}; +} + +void PshifterEffectHandler::GetParami(const PshifterProps &props, ALenum param, int *val) { switch(param) { - case AL_PITCH_SHIFTER_COARSE_TUNE: - *val = props->Pshifter.CoarseTune; - break; - case AL_PITCH_SHIFTER_FINE_TUNE: - *val = props->Pshifter.FineTune; - break; + case AL_PITCH_SHIFTER_COARSE_TUNE: *val = props.CoarseTune; break; + case AL_PITCH_SHIFTER_FINE_TUNE: *val = props.FineTune; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter integer property 0x%04x", param}; } } -void Pshifter_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Pshifter_getParami(props, param, vals); } +void PshifterEffectHandler::GetParamiv(const PshifterProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } -void Pshifter_getParamf(const EffectProps*, ALenum param, float*) +void PshifterEffectHandler::GetParamf(const PshifterProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float property 0x%04x", param}; } -void Pshifter_getParamfv(const EffectProps*, ALenum param, float*) +void PshifterEffectHandler::GetParamfv(const PshifterProps&, ALenum param, float*) { throw effect_exception{AL_INVALID_ENUM, "Invalid pitch shifter float vector-property 0x%04x", param}; } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Pshifter.CoarseTune = AL_PITCH_SHIFTER_DEFAULT_COARSE_TUNE; - props.Pshifter.FineTune = AL_PITCH_SHIFTER_DEFAULT_FINE_TUNE; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Pshifter); - -const EffectProps PshifterEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -138,52 +134,48 @@ template<> throw Exception{message}; } -template<> -bool PitchShifterCommitter::commit(const EaxEffectProps &props) +bool EaxPitchShifterCommitter::commit(const EAXPITCHSHIFTERPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mPitchShifter.lCoarseTune == props.mPitchShifter.lCoarseTune - && mEaxProps.mPitchShifter.lFineTune == props.mPitchShifter.lFineTune) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - - mAlProps.Pshifter.CoarseTune = static_cast(mEaxProps.mPitchShifter.lCoarseTune); - mAlProps.Pshifter.FineTune = static_cast(mEaxProps.mPitchShifter.lFineTune); + mAlProps = [&]{ + PshifterProps ret{}; + ret.CoarseTune = static_cast(props.lCoarseTune); + ret.FineTune = static_cast(props.lFineTune); + return ret; + }(); return true; } -template<> -void PitchShifterCommitter::SetDefaults(EaxEffectProps &props) +void EaxPitchShifterCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::PitchShifter; - props.mPitchShifter.lCoarseTune = EAXPITCHSHIFTER_DEFAULTCOARSETUNE; - props.mPitchShifter.lFineTune = EAXPITCHSHIFTER_DEFAULTFINETUNE; + props = EAXPITCHSHIFTERPROPERTIES{EAXPITCHSHIFTER_DEFAULTCOARSETUNE, + EAXPITCHSHIFTER_DEFAULTFINETUNE}; } -template<> -void PitchShifterCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxPitchShifterCommitter::Get(const EaxCall &call, const EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; - case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value(props.mPitchShifter); break; - case EAXPITCHSHIFTER_COARSETUNE: call.set_value(props.mPitchShifter.lCoarseTune); break; - case EAXPITCHSHIFTER_FINETUNE: call.set_value(props.mPitchShifter.lFineTune); break; + case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value(props); break; + case EAXPITCHSHIFTER_COARSETUNE: call.set_value(props.lCoarseTune); break; + case EAXPITCHSHIFTER_FINETUNE: call.set_value(props.lFineTune); break; default: fail_unknown_property_id(); } } -template<> -void PitchShifterCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxPitchShifterCommitter::Set(const EaxCall &call, EAXPITCHSHIFTERPROPERTIES &props) { switch(call.get_property_id()) { case EAXPITCHSHIFTER_NONE: break; - case EAXPITCHSHIFTER_ALLPARAMETERS: defer(call, props.mPitchShifter); break; - case EAXPITCHSHIFTER_COARSETUNE: defer(call, props.mPitchShifter.lCoarseTune); break; - case EAXPITCHSHIFTER_FINETUNE: defer(call, props.mPitchShifter.lFineTune); break; + case EAXPITCHSHIFTER_ALLPARAMETERS: defer(call, props); break; + case EAXPITCHSHIFTER_COARSETUNE: defer(call, props.lCoarseTune); break; + case EAXPITCHSHIFTER_FINETUNE: defer(call, props.lFineTune); break; default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/effects/reverb.cpp b/Engine/lib/openal-soft/al/effects/reverb.cpp index 440d7b4e5..7954e6179 100644 --- a/Engine/lib/openal-soft/al/effects/reverb.cpp +++ b/Engine/lib/openal-soft/al/effects/reverb.cpp @@ -1,18 +1,24 @@ #include "config.h" +#include +#include #include +#include #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" +#include "alnumeric.h" +#include "alspan.h" +#include "core/effects/base.h" #include "effects.h" #ifdef ALSOFT_EAX #include -#include "alnumeric.h" -#include "AL/efx-presets.h" +#include "al/eax/api.h" +#include "al/eax/call.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,14 +26,80 @@ namespace { -void Reverb_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + ReverbProps props{}; + props.Density = AL_EAXREVERB_DEFAULT_DENSITY; + props.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; + props.Gain = AL_EAXREVERB_DEFAULT_GAIN; + props.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; + props.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; + props.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; + props.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; + props.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; + props.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; + props.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; + props.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; + props.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; + props.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; + props.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; + props.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; + props.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; + props.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; + props.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; + props.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; + props.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; + props.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +constexpr EffectProps genDefaultStdProps() noexcept +{ + ReverbProps props{}; + props.Density = AL_REVERB_DEFAULT_DENSITY; + props.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; + props.Gain = AL_REVERB_DEFAULT_GAIN; + props.GainHF = AL_REVERB_DEFAULT_GAINHF; + props.GainLF = 1.0f; + props.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; + props.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; + props.DecayLFRatio = 1.0f; + props.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; + props.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; + props.ReflectionsPan = {0.0f, 0.0f, 0.0f}; + props.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; + props.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; + props.LateReverbPan = {0.0f, 0.0f, 0.0f}; + props.EchoTime = 0.25f; + props.EchoDepth = 0.0f; + props.ModulationTime = 0.25f; + props.ModulationDepth = 0.0f; + props.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; + props.HFReference = 5000.0f; + props.LFReference = 250.0f; + props.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; + props.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; + return props; +} + +} // namespace + +const EffectProps ReverbEffectProps{genDefaultProps()}; + +void ReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) { switch(param) { case AL_EAXREVERB_DECAY_HFLIMIT: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFLIMIT && val <= AL_EAXREVERB_MAX_DECAY_HFLIMIT)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; - props->Reverb.DecayHFLimit = val != AL_FALSE; + props.DecayHFLimit = val != AL_FALSE; break; default: @@ -35,167 +107,233 @@ void Reverb_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Reverb_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ Reverb_setParami(props, param, vals[0]); } -void Reverb_setParamf(EffectProps *props, ALenum param, float val) +void ReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void ReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) { switch(param) { case AL_EAXREVERB_DENSITY: if(!(val >= AL_EAXREVERB_MIN_DENSITY && val <= AL_EAXREVERB_MAX_DENSITY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; - props->Reverb.Density = val; + props.Density = val; break; case AL_EAXREVERB_DIFFUSION: if(!(val >= AL_EAXREVERB_MIN_DIFFUSION && val <= AL_EAXREVERB_MAX_DIFFUSION)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; - props->Reverb.Diffusion = val; + props.Diffusion = val; break; case AL_EAXREVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_GAIN && val <= AL_EAXREVERB_MAX_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; - props->Reverb.Gain = val; + props.Gain = val; break; case AL_EAXREVERB_GAINHF: if(!(val >= AL_EAXREVERB_MIN_GAINHF && val <= AL_EAXREVERB_MAX_GAINHF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; - props->Reverb.GainHF = val; + props.GainHF = val; break; case AL_EAXREVERB_GAINLF: if(!(val >= AL_EAXREVERB_MIN_GAINLF && val <= AL_EAXREVERB_MAX_GAINLF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainlf out of range"}; - props->Reverb.GainLF = val; + props.GainLF = val; break; case AL_EAXREVERB_DECAY_TIME: if(!(val >= AL_EAXREVERB_MIN_DECAY_TIME && val <= AL_EAXREVERB_MAX_DECAY_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; - props->Reverb.DecayTime = val; + props.DecayTime = val; break; case AL_EAXREVERB_DECAY_HFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_HFRATIO && val <= AL_EAXREVERB_MAX_DECAY_HFRATIO)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; - props->Reverb.DecayHFRatio = val; + props.DecayHFRatio = val; break; case AL_EAXREVERB_DECAY_LFRATIO: if(!(val >= AL_EAXREVERB_MIN_DECAY_LFRATIO && val <= AL_EAXREVERB_MAX_DECAY_LFRATIO)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay lfratio out of range"}; - props->Reverb.DecayLFRatio = val; + props.DecayLFRatio = val; break; case AL_EAXREVERB_REFLECTIONS_GAIN: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_GAIN && val <= AL_EAXREVERB_MAX_REFLECTIONS_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; - props->Reverb.ReflectionsGain = val; + props.ReflectionsGain = val; break; case AL_EAXREVERB_REFLECTIONS_DELAY: if(!(val >= AL_EAXREVERB_MIN_REFLECTIONS_DELAY && val <= AL_EAXREVERB_MAX_REFLECTIONS_DELAY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; - props->Reverb.ReflectionsDelay = val; + props.ReflectionsDelay = val; break; case AL_EAXREVERB_LATE_REVERB_GAIN: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_GAIN && val <= AL_EAXREVERB_MAX_LATE_REVERB_GAIN)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; - props->Reverb.LateReverbGain = val; + props.LateReverbGain = val; break; case AL_EAXREVERB_LATE_REVERB_DELAY: if(!(val >= AL_EAXREVERB_MIN_LATE_REVERB_DELAY && val <= AL_EAXREVERB_MAX_LATE_REVERB_DELAY)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; - props->Reverb.LateReverbDelay = val; + props.LateReverbDelay = val; break; case AL_EAXREVERB_ECHO_TIME: if(!(val >= AL_EAXREVERB_MIN_ECHO_TIME && val <= AL_EAXREVERB_MAX_ECHO_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo time out of range"}; - props->Reverb.EchoTime = val; + props.EchoTime = val; break; case AL_EAXREVERB_ECHO_DEPTH: if(!(val >= AL_EAXREVERB_MIN_ECHO_DEPTH && val <= AL_EAXREVERB_MAX_ECHO_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb echo depth out of range"}; - props->Reverb.EchoDepth = val; + props.EchoDepth = val; break; case AL_EAXREVERB_MODULATION_TIME: if(!(val >= AL_EAXREVERB_MIN_MODULATION_TIME && val <= AL_EAXREVERB_MAX_MODULATION_TIME)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation time out of range"}; - props->Reverb.ModulationTime = val; + props.ModulationTime = val; break; case AL_EAXREVERB_MODULATION_DEPTH: if(!(val >= AL_EAXREVERB_MIN_MODULATION_DEPTH && val <= AL_EAXREVERB_MAX_MODULATION_DEPTH)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb modulation depth out of range"}; - props->Reverb.ModulationDepth = val; + props.ModulationDepth = val; break; case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: if(!(val >= AL_EAXREVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_EAXREVERB_MAX_AIR_ABSORPTION_GAINHF)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; - props->Reverb.AirAbsorptionGainHF = val; + props.AirAbsorptionGainHF = val; break; case AL_EAXREVERB_HFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_HFREFERENCE && val <= AL_EAXREVERB_MAX_HFREFERENCE)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb hfreference out of range"}; - props->Reverb.HFReference = val; + props.HFReference = val; break; case AL_EAXREVERB_LFREFERENCE: if(!(val >= AL_EAXREVERB_MIN_LFREFERENCE && val <= AL_EAXREVERB_MAX_LFREFERENCE)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb lfreference out of range"}; - props->Reverb.LFReference = val; + props.LFReference = val; break; case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: if(!(val >= AL_EAXREVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_EAXREVERB_MAX_ROOM_ROLLOFF_FACTOR)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; - props->Reverb.RoomRolloffFactor = val; + props.RoomRolloffFactor = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void Reverb_setParamfv(EffectProps *props, ALenum param, const float *vals) +void ReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) { + static constexpr auto finite_checker = [](float f) -> bool { return std::isfinite(f); }; + al::span values; switch(param) { case AL_EAXREVERB_REFLECTIONS_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + values = {vals, 3_uz}; + if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections pan out of range"}; - props->Reverb.ReflectionsPan[0] = vals[0]; - props->Reverb.ReflectionsPan[1] = vals[1]; - props->Reverb.ReflectionsPan[2] = vals[2]; + std::copy(values.cbegin(), values.cend(), props.ReflectionsPan.begin()); break; case AL_EAXREVERB_LATE_REVERB_PAN: - if(!(std::isfinite(vals[0]) && std::isfinite(vals[1]) && std::isfinite(vals[2]))) + values = {vals, 3_uz}; + if(!std::all_of(values.cbegin(), values.cend(), finite_checker)) throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb pan out of range"}; - props->Reverb.LateReverbPan[0] = vals[0]; - props->Reverb.LateReverbPan[1] = vals[1]; - props->Reverb.LateReverbPan[2] = vals[2]; + std::copy(values.cbegin(), values.cend(), props.LateReverbPan.begin()); break; default: - Reverb_setParamf(props, param, vals[0]); + SetParamf(props, param, *vals); break; } } -void Reverb_getParami(const EffectProps *props, ALenum param, int *val) +void ReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_EAXREVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; + case AL_EAXREVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; + } +} +void ReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void ReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) +{ + switch(param) + { + case AL_EAXREVERB_DENSITY: *val = props.Density; break; + case AL_EAXREVERB_DIFFUSION: *val = props.Diffusion; break; + case AL_EAXREVERB_GAIN: *val = props.Gain; break; + case AL_EAXREVERB_GAINHF: *val = props.GainHF; break; + case AL_EAXREVERB_GAINLF: *val = props.GainLF; break; + case AL_EAXREVERB_DECAY_TIME: *val = props.DecayTime; break; + case AL_EAXREVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; + case AL_EAXREVERB_DECAY_LFRATIO: *val = props.DecayLFRatio; break; + case AL_EAXREVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; + case AL_EAXREVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; + case AL_EAXREVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; + case AL_EAXREVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; + case AL_EAXREVERB_ECHO_TIME: *val = props.EchoTime; break; + case AL_EAXREVERB_ECHO_DEPTH: *val = props.EchoDepth; break; + case AL_EAXREVERB_MODULATION_TIME: *val = props.ModulationTime; break; + case AL_EAXREVERB_MODULATION_DEPTH: *val = props.ModulationDepth; break; + case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; + case AL_EAXREVERB_HFREFERENCE: *val = props.HFReference; break; + case AL_EAXREVERB_LFREFERENCE: *val = props.LFReference; break; + case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; + + default: + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; + } +} +void ReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) +{ + al::span values; + switch(param) + { + case AL_EAXREVERB_REFLECTIONS_PAN: + values = {vals, 3_uz}; + std::copy(props.ReflectionsPan.cbegin(), props.ReflectionsPan.cend(), values.begin()); + break; + case AL_EAXREVERB_LATE_REVERB_PAN: + values = {vals, 3_uz}; + std::copy(props.LateReverbPan.cbegin(), props.LateReverbPan.cend(), values.begin()); + break; + + default: + GetParamf(props, param, vals); + break; + } +} + + +const EffectProps StdReverbEffectProps{genDefaultStdProps()}; + +void StdReverbEffectHandler::SetParami(ReverbProps &props, ALenum param, int val) +{ + switch(param) + { + case AL_REVERB_DECAY_HFLIMIT: + if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hflimit out of range"}; + props.DecayHFLimit = val != AL_FALSE; break; default: @@ -203,365 +341,127 @@ void Reverb_getParami(const EffectProps *props, ALenum param, int *val) param}; } } -void Reverb_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ Reverb_getParami(props, param, vals); } -void Reverb_getParamf(const EffectProps *props, ALenum param, float *val) +void StdReverbEffectHandler::SetParamiv(ReverbProps &props, ALenum param, const int *vals) +{ SetParami(props, param, *vals); } +void StdReverbEffectHandler::SetParamf(ReverbProps &props, ALenum param, float val) { switch(param) { - case AL_EAXREVERB_DENSITY: - *val = props->Reverb.Density; + case AL_REVERB_DENSITY: + if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb density out of range"}; + props.Density = val; break; - case AL_EAXREVERB_DIFFUSION: - *val = props->Reverb.Diffusion; + case AL_REVERB_DIFFUSION: + if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb diffusion out of range"}; + props.Diffusion = val; break; - case AL_EAXREVERB_GAIN: - *val = props->Reverb.Gain; + case AL_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gain out of range"}; + props.Gain = val; break; - case AL_EAXREVERB_GAINHF: - *val = props->Reverb.GainHF; + case AL_REVERB_GAINHF: + if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb gainhf out of range"}; + props.GainHF = val; break; - case AL_EAXREVERB_GAINLF: - *val = props->Reverb.GainLF; + case AL_REVERB_DECAY_TIME: + if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay time out of range"}; + props.DecayTime = val; break; - case AL_EAXREVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; + case AL_REVERB_DECAY_HFRATIO: + if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb decay hfratio out of range"}; + props.DecayHFRatio = val; break; - case AL_EAXREVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; + case AL_REVERB_REFLECTIONS_GAIN: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections gain out of range"}; + props.ReflectionsGain = val; break; - case AL_EAXREVERB_DECAY_LFRATIO: - *val = props->Reverb.DecayLFRatio; + case AL_REVERB_REFLECTIONS_DELAY: + if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb reflections delay out of range"}; + props.ReflectionsDelay = val; break; - case AL_EAXREVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; + case AL_REVERB_LATE_REVERB_GAIN: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb gain out of range"}; + props.LateReverbGain = val; break; - case AL_EAXREVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; + case AL_REVERB_LATE_REVERB_DELAY: + if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb late reverb delay out of range"}; + props.LateReverbDelay = val; break; - case AL_EAXREVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; + case AL_REVERB_AIR_ABSORPTION_GAINHF: + if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb air absorption gainhf out of range"}; + props.AirAbsorptionGainHF = val; break; - case AL_EAXREVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_EAXREVERB_ECHO_TIME: - *val = props->Reverb.EchoTime; - break; - - case AL_EAXREVERB_ECHO_DEPTH: - *val = props->Reverb.EchoDepth; - break; - - case AL_EAXREVERB_MODULATION_TIME: - *val = props->Reverb.ModulationTime; - break; - - case AL_EAXREVERB_MODULATION_DEPTH: - *val = props->Reverb.ModulationDepth; - break; - - case AL_EAXREVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_EAXREVERB_HFREFERENCE: - *val = props->Reverb.HFReference; - break; - - case AL_EAXREVERB_LFREFERENCE: - *val = props->Reverb.LFReference; - break; - - case AL_EAXREVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; + case AL_REVERB_ROOM_ROLLOFF_FACTOR: + if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) + throw effect_exception{AL_INVALID_VALUE, "EAX Reverb room rolloff factor out of range"}; + props.RoomRolloffFactor = val; break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void Reverb_getParamfv(const EffectProps *props, ALenum param, float *vals) +void StdReverbEffectHandler::SetParamfv(ReverbProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } + +void StdReverbEffectHandler::GetParami(const ReverbProps &props, ALenum param, int *val) { switch(param) { - case AL_EAXREVERB_REFLECTIONS_PAN: - vals[0] = props->Reverb.ReflectionsPan[0]; - vals[1] = props->Reverb.ReflectionsPan[1]; - vals[2] = props->Reverb.ReflectionsPan[2]; - break; - case AL_EAXREVERB_LATE_REVERB_PAN: - vals[0] = props->Reverb.LateReverbPan[0]; - vals[1] = props->Reverb.LateReverbPan[1]; - vals[2] = props->Reverb.LateReverbPan[2]; - break; - + case AL_REVERB_DECAY_HFLIMIT: *val = props.DecayHFLimit; break; default: - Reverb_getParamf(props, param, vals); - break; + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb integer property 0x%04x", + param}; } } - -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_EAXREVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_EAXREVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_EAXREVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_EAXREVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = AL_EAXREVERB_DEFAULT_GAINLF; - props.Reverb.DecayTime = AL_EAXREVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_EAXREVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = AL_EAXREVERB_DEFAULT_DECAY_LFRATIO; - props.Reverb.ReflectionsGain = AL_EAXREVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_EAXREVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[1] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.ReflectionsPan[2] = AL_EAXREVERB_DEFAULT_REFLECTIONS_PAN_XYZ; - props.Reverb.LateReverbGain = AL_EAXREVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_EAXREVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[1] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.LateReverbPan[2] = AL_EAXREVERB_DEFAULT_LATE_REVERB_PAN_XYZ; - props.Reverb.EchoTime = AL_EAXREVERB_DEFAULT_ECHO_TIME; - props.Reverb.EchoDepth = AL_EAXREVERB_DEFAULT_ECHO_DEPTH; - props.Reverb.ModulationTime = AL_EAXREVERB_DEFAULT_MODULATION_TIME; - props.Reverb.ModulationDepth = AL_EAXREVERB_DEFAULT_MODULATION_DEPTH; - props.Reverb.AirAbsorptionGainHF = AL_EAXREVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = AL_EAXREVERB_DEFAULT_HFREFERENCE; - props.Reverb.LFReference = AL_EAXREVERB_DEFAULT_LFREFERENCE; - props.Reverb.RoomRolloffFactor = AL_EAXREVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_EAXREVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - - -void StdReverb_setParami(EffectProps *props, ALenum param, int val) +void StdReverbEffectHandler::GetParamiv(const ReverbProps &props, ALenum param, int *vals) +{ GetParami(props, param, vals); } +void StdReverbEffectHandler::GetParamf(const ReverbProps &props, ALenum param, float *val) { switch(param) { - case AL_REVERB_DECAY_HFLIMIT: - if(!(val >= AL_REVERB_MIN_DECAY_HFLIMIT && val <= AL_REVERB_MAX_DECAY_HFLIMIT)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay hflimit out of range"}; - props->Reverb.DecayHFLimit = val != AL_FALSE; - break; + case AL_REVERB_DENSITY: *val = props.Density; break; + case AL_REVERB_DIFFUSION: *val = props.Diffusion; break; + case AL_REVERB_GAIN: *val = props.Gain; break; + case AL_REVERB_GAINHF: *val = props.GainHF; break; + case AL_REVERB_DECAY_TIME: *val = props.DecayTime; break; + case AL_REVERB_DECAY_HFRATIO: *val = props.DecayHFRatio; break; + case AL_REVERB_REFLECTIONS_GAIN: *val = props.ReflectionsGain; break; + case AL_REVERB_REFLECTIONS_DELAY: *val = props.ReflectionsDelay; break; + case AL_REVERB_LATE_REVERB_GAIN: *val = props.LateReverbGain; break; + case AL_REVERB_LATE_REVERB_DELAY: *val = props.LateReverbDelay; break; + case AL_REVERB_AIR_ABSORPTION_GAINHF: *val = props.AirAbsorptionGainHF; break; + case AL_REVERB_ROOM_ROLLOFF_FACTOR: *val = props.RoomRolloffFactor; break; default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; + throw effect_exception{AL_INVALID_ENUM, "Invalid EAX reverb float property 0x%04x", param}; } } -void StdReverb_setParamiv(EffectProps *props, ALenum param, const int *vals) -{ StdReverb_setParami(props, param, vals[0]); } -void StdReverb_setParamf(EffectProps *props, ALenum param, float val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - if(!(val >= AL_REVERB_MIN_DENSITY && val <= AL_REVERB_MAX_DENSITY)) - throw effect_exception{AL_INVALID_VALUE, "Reverb density out of range"}; - props->Reverb.Density = val; - break; +void StdReverbEffectHandler::GetParamfv(const ReverbProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } - case AL_REVERB_DIFFUSION: - if(!(val >= AL_REVERB_MIN_DIFFUSION && val <= AL_REVERB_MAX_DIFFUSION)) - throw effect_exception{AL_INVALID_VALUE, "Reverb diffusion out of range"}; - props->Reverb.Diffusion = val; - break; - - case AL_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_GAIN && val <= AL_REVERB_MAX_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Reverb gain out of range"}; - props->Reverb.Gain = val; - break; - - case AL_REVERB_GAINHF: - if(!(val >= AL_REVERB_MIN_GAINHF && val <= AL_REVERB_MAX_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "Reverb gainhf out of range"}; - props->Reverb.GainHF = val; - break; - - case AL_REVERB_DECAY_TIME: - if(!(val >= AL_REVERB_MIN_DECAY_TIME && val <= AL_REVERB_MAX_DECAY_TIME)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay time out of range"}; - props->Reverb.DecayTime = val; - break; - - case AL_REVERB_DECAY_HFRATIO: - if(!(val >= AL_REVERB_MIN_DECAY_HFRATIO && val <= AL_REVERB_MAX_DECAY_HFRATIO)) - throw effect_exception{AL_INVALID_VALUE, "Reverb decay hfratio out of range"}; - props->Reverb.DecayHFRatio = val; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_GAIN && val <= AL_REVERB_MAX_REFLECTIONS_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Reverb reflections gain out of range"}; - props->Reverb.ReflectionsGain = val; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - if(!(val >= AL_REVERB_MIN_REFLECTIONS_DELAY && val <= AL_REVERB_MAX_REFLECTIONS_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Reverb reflections delay out of range"}; - props->Reverb.ReflectionsDelay = val; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_GAIN && val <= AL_REVERB_MAX_LATE_REVERB_GAIN)) - throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb gain out of range"}; - props->Reverb.LateReverbGain = val; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - if(!(val >= AL_REVERB_MIN_LATE_REVERB_DELAY && val <= AL_REVERB_MAX_LATE_REVERB_DELAY)) - throw effect_exception{AL_INVALID_VALUE, "Reverb late reverb delay out of range"}; - props->Reverb.LateReverbDelay = val; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - if(!(val >= AL_REVERB_MIN_AIR_ABSORPTION_GAINHF && val <= AL_REVERB_MAX_AIR_ABSORPTION_GAINHF)) - throw effect_exception{AL_INVALID_VALUE, "Reverb air absorption gainhf out of range"}; - props->Reverb.AirAbsorptionGainHF = val; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - if(!(val >= AL_REVERB_MIN_ROOM_ROLLOFF_FACTOR && val <= AL_REVERB_MAX_ROOM_ROLLOFF_FACTOR)) - throw effect_exception{AL_INVALID_VALUE, "Reverb room rolloff factor out of range"}; - props->Reverb.RoomRolloffFactor = val; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; - } -} -void StdReverb_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ StdReverb_setParamf(props, param, vals[0]); } - -void StdReverb_getParami(const EffectProps *props, ALenum param, int *val) -{ - switch(param) - { - case AL_REVERB_DECAY_HFLIMIT: - *val = props->Reverb.DecayHFLimit; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb integer property 0x%04x", param}; - } -} -void StdReverb_getParamiv(const EffectProps *props, ALenum param, int *vals) -{ StdReverb_getParami(props, param, vals); } -void StdReverb_getParamf(const EffectProps *props, ALenum param, float *val) -{ - switch(param) - { - case AL_REVERB_DENSITY: - *val = props->Reverb.Density; - break; - - case AL_REVERB_DIFFUSION: - *val = props->Reverb.Diffusion; - break; - - case AL_REVERB_GAIN: - *val = props->Reverb.Gain; - break; - - case AL_REVERB_GAINHF: - *val = props->Reverb.GainHF; - break; - - case AL_REVERB_DECAY_TIME: - *val = props->Reverb.DecayTime; - break; - - case AL_REVERB_DECAY_HFRATIO: - *val = props->Reverb.DecayHFRatio; - break; - - case AL_REVERB_REFLECTIONS_GAIN: - *val = props->Reverb.ReflectionsGain; - break; - - case AL_REVERB_REFLECTIONS_DELAY: - *val = props->Reverb.ReflectionsDelay; - break; - - case AL_REVERB_LATE_REVERB_GAIN: - *val = props->Reverb.LateReverbGain; - break; - - case AL_REVERB_LATE_REVERB_DELAY: - *val = props->Reverb.LateReverbDelay; - break; - - case AL_REVERB_AIR_ABSORPTION_GAINHF: - *val = props->Reverb.AirAbsorptionGainHF; - break; - - case AL_REVERB_ROOM_ROLLOFF_FACTOR: - *val = props->Reverb.RoomRolloffFactor; - break; - - default: - throw effect_exception{AL_INVALID_ENUM, "Invalid reverb float property 0x%04x", param}; - } -} -void StdReverb_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ StdReverb_getParamf(props, param, vals); } - -EffectProps genDefaultStdProps() noexcept -{ - EffectProps props{}; - props.Reverb.Density = AL_REVERB_DEFAULT_DENSITY; - props.Reverb.Diffusion = AL_REVERB_DEFAULT_DIFFUSION; - props.Reverb.Gain = AL_REVERB_DEFAULT_GAIN; - props.Reverb.GainHF = AL_REVERB_DEFAULT_GAINHF; - props.Reverb.GainLF = 1.0f; - props.Reverb.DecayTime = AL_REVERB_DEFAULT_DECAY_TIME; - props.Reverb.DecayHFRatio = AL_REVERB_DEFAULT_DECAY_HFRATIO; - props.Reverb.DecayLFRatio = 1.0f; - props.Reverb.ReflectionsGain = AL_REVERB_DEFAULT_REFLECTIONS_GAIN; - props.Reverb.ReflectionsDelay = AL_REVERB_DEFAULT_REFLECTIONS_DELAY; - props.Reverb.ReflectionsPan[0] = 0.0f; - props.Reverb.ReflectionsPan[1] = 0.0f; - props.Reverb.ReflectionsPan[2] = 0.0f; - props.Reverb.LateReverbGain = AL_REVERB_DEFAULT_LATE_REVERB_GAIN; - props.Reverb.LateReverbDelay = AL_REVERB_DEFAULT_LATE_REVERB_DELAY; - props.Reverb.LateReverbPan[0] = 0.0f; - props.Reverb.LateReverbPan[1] = 0.0f; - props.Reverb.LateReverbPan[2] = 0.0f; - props.Reverb.EchoTime = 0.25f; - props.Reverb.EchoDepth = 0.0f; - props.Reverb.ModulationTime = 0.25f; - props.Reverb.ModulationDepth = 0.0f; - props.Reverb.AirAbsorptionGainHF = AL_REVERB_DEFAULT_AIR_ABSORPTION_GAINHF; - props.Reverb.HFReference = 5000.0f; - props.Reverb.LFReference = 250.0f; - props.Reverb.RoomRolloffFactor = AL_REVERB_DEFAULT_ROOM_ROLLOFF_FACTOR; - props.Reverb.DecayHFLimit = AL_REVERB_DEFAULT_DECAY_HFLIMIT; - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Reverb); - -const EffectProps ReverbEffectProps{genDefaultProps()}; - -DEFINE_ALEFFECT_VTABLE(StdReverb); - -const EffectProps StdReverbEffectProps{genDefaultStdProps()}; #ifdef ALSOFT_EAX namespace { @@ -945,7 +845,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_DECAYTIMESCALE) != 0) { - props.flDecayTime = clamp( + props.flDecayTime = std::clamp( props.flDecayTime * scale, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); @@ -954,7 +854,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSSCALE) != 0 && (props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.lReflections = clamp( + props.lReflections = std::clamp( props.lReflections - static_cast(gain_to_level_mb(scale)), EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); @@ -962,7 +862,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.flReflectionsDelay = clamp( + props.flReflectionsDelay = std::clamp( props.flReflectionsDelay * scale, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); @@ -972,7 +872,7 @@ struct EnvironmentSizeDeferrer2 { { const auto log_scalar = ((props.dwFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; - props.lReverb = clamp( + props.lReverb = std::clamp( props.lReverb - static_cast(std::log10(scale) * log_scalar), EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); @@ -980,7 +880,7 @@ struct EnvironmentSizeDeferrer2 { if ((props.dwFlags & EAX2LISTENERFLAGS_REVERBDELAYSCALE) != 0) { - props.flReverbDelay = clamp( + props.flReverbDelay = std::clamp( props.flReverbDelay * scale, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); @@ -1015,7 +915,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) { - props.flDecayTime = clamp( + props.flDecayTime = std::clamp( props.flDecayTime * scale, EAXREVERB_MINDECAYTIME, EAXREVERB_MAXDECAYTIME); @@ -1024,7 +924,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSSCALE) != 0 && (props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.lReflections = clamp( + props.lReflections = std::clamp( props.lReflections - static_cast(gain_to_level_mb(scale)), EAXREVERB_MINREFLECTIONS, EAXREVERB_MAXREFLECTIONS); @@ -1032,7 +932,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REFLECTIONSDELAYSCALE) != 0) { - props.flReflectionsDelay = clamp( + props.flReflectionsDelay = std::clamp( props.flReflectionsDelay * scale, EAXREVERB_MINREFLECTIONSDELAY, EAXREVERB_MAXREFLECTIONSDELAY); @@ -1041,7 +941,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REVERBSCALE) != 0) { const auto log_scalar = ((props.ulFlags & EAXREVERBFLAGS_DECAYTIMESCALE) != 0) ? 2'000.0F : 3'000.0F; - props.lReverb = clamp( + props.lReverb = std::clamp( props.lReverb - static_cast(std::log10(scale) * log_scalar), EAXREVERB_MINREVERB, EAXREVERB_MAXREVERB); @@ -1049,7 +949,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_REVERBDELAYSCALE) != 0) { - props.flReverbDelay = clamp( + props.flReverbDelay = std::clamp( props.flReverbDelay * scale, EAXREVERB_MINREVERBDELAY, EAXREVERB_MAXREVERBDELAY); @@ -1057,7 +957,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_ECHOTIMESCALE) != 0) { - props.flEchoTime = clamp( + props.flEchoTime = std::clamp( props.flEchoTime * scale, EAXREVERB_MINECHOTIME, EAXREVERB_MAXECHOTIME); @@ -1065,7 +965,7 @@ struct EnvironmentSizeDeferrer3 { if ((props.ulFlags & EAXREVERBFLAGS_MODULATIONTIMESCALE) != 0) { - props.flModulationTime = clamp( + props.flModulationTime = std::clamp( props.flModulationTime * scale, EAXREVERB_MINMODULATIONTIME, EAXREVERB_MAXMODULATIONTIME); @@ -1086,111 +986,87 @@ struct EaxReverbCommitter::Exception : public EaxReverbEffectException throw Exception{message}; } -void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EaxEffectProps& dst) noexcept +void EaxReverbCommitter::translate(const EAX_REVERBPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { assert(src.environment <= EAX1REVERB_MAXENVIRONMENT); - dst.mType = EaxEffectType::Reverb; - dst.mReverb = EAXREVERB_PRESETS[src.environment]; - dst.mReverb.flDecayTime = src.fDecayTime_sec; - dst.mReverb.flDecayHFRatio = src.fDamping; - dst.mReverb.lReverb = mini(static_cast(gain_to_level_mb(src.fVolume)), 0); + dst = EAXREVERB_PRESETS[src.environment]; + dst.flDecayTime = src.fDecayTime_sec; + dst.flDecayHFRatio = src.fDamping; + dst.lReverb = static_cast(std::min(gain_to_level_mb(src.fVolume), 0.0f)); } -void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EaxEffectProps& dst) noexcept +void EaxReverbCommitter::translate(const EAX20LISTENERPROPERTIES& src, EAXREVERBPROPERTIES& dst) noexcept { assert(src.dwEnvironment <= EAX1REVERB_MAXENVIRONMENT); - const auto& env = EAXREVERB_PRESETS[src.dwEnvironment]; - dst.mType = EaxEffectType::Reverb; - dst.mReverb.ulEnvironment = src.dwEnvironment; - dst.mReverb.flEnvironmentSize = src.flEnvironmentSize; - dst.mReverb.flEnvironmentDiffusion = src.flEnvironmentDiffusion; - dst.mReverb.lRoom = src.lRoom; - dst.mReverb.lRoomHF = src.lRoomHF; - dst.mReverb.lRoomLF = env.lRoomLF; - dst.mReverb.flDecayTime = src.flDecayTime; - dst.mReverb.flDecayHFRatio = src.flDecayHFRatio; - dst.mReverb.flDecayLFRatio = env.flDecayLFRatio; - dst.mReverb.lReflections = src.lReflections; - dst.mReverb.flReflectionsDelay = src.flReflectionsDelay; - dst.mReverb.vReflectionsPan = env.vReflectionsPan; - dst.mReverb.lReverb = src.lReverb; - dst.mReverb.flReverbDelay = src.flReverbDelay; - dst.mReverb.vReverbPan = env.vReverbPan; - dst.mReverb.flEchoTime = env.flEchoTime; - dst.mReverb.flEchoDepth = env.flEchoDepth; - dst.mReverb.flModulationTime = env.flModulationTime; - dst.mReverb.flModulationDepth = env.flModulationDepth; - dst.mReverb.flAirAbsorptionHF = src.flAirAbsorptionHF; - dst.mReverb.flHFReference = env.flHFReference; - dst.mReverb.flLFReference = env.flLFReference; - dst.mReverb.flRoomRolloffFactor = src.flRoomRolloffFactor; - dst.mReverb.ulFlags = src.dwFlags; -} - -void EaxReverbCommitter::translate(const EAXREVERBPROPERTIES& src, EaxEffectProps& dst) noexcept -{ - dst.mType = EaxEffectType::Reverb; - dst.mReverb = src; + dst = EAXREVERB_PRESETS[src.dwEnvironment]; + dst.ulEnvironment = src.dwEnvironment; + dst.flEnvironmentSize = src.flEnvironmentSize; + dst.flEnvironmentDiffusion = src.flEnvironmentDiffusion; + dst.lRoom = src.lRoom; + dst.lRoomHF = src.lRoomHF; + dst.flDecayTime = src.flDecayTime; + dst.flDecayHFRatio = src.flDecayHFRatio; + dst.lReflections = src.lReflections; + dst.flReflectionsDelay = src.flReflectionsDelay; + dst.lReverb = src.lReverb; + dst.flReverbDelay = src.flReverbDelay; + dst.flAirAbsorptionHF = src.flAirAbsorptionHF; + dst.flRoomRolloffFactor = src.flRoomRolloffFactor; + dst.ulFlags = src.dwFlags; } bool EaxReverbCommitter::commit(const EAX_REVERBPROPERTIES &props) { - EaxEffectProps dst{}; + EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } bool EaxReverbCommitter::commit(const EAX20LISTENERPROPERTIES &props) { - EaxEffectProps dst{}; + EAXREVERBPROPERTIES dst{}; translate(props, dst); return commit(dst); } bool EaxReverbCommitter::commit(const EAXREVERBPROPERTIES &props) { - EaxEffectProps dst{}; - translate(props, dst); - return commit(dst); -} - -bool EaxReverbCommitter::commit(const EaxEffectProps &props) -{ - if(props.mType == mEaxProps.mType - && memcmp(&props.mReverb, &mEaxProps.mReverb, sizeof(mEaxProps.mReverb)) == 0) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; - const auto size = props.mReverb.flEnvironmentSize; - const auto density = (size * size * size) / 16.0F; - mAlProps.Reverb.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY); - mAlProps.Reverb.Diffusion = props.mReverb.flEnvironmentDiffusion; - mAlProps.Reverb.Gain = level_mb_to_gain(static_cast(props.mReverb.lRoom)); - mAlProps.Reverb.GainHF = level_mb_to_gain(static_cast(props.mReverb.lRoomHF)); - mAlProps.Reverb.GainLF = level_mb_to_gain(static_cast(props.mReverb.lRoomLF)); - mAlProps.Reverb.DecayTime = props.mReverb.flDecayTime; - mAlProps.Reverb.DecayHFRatio = props.mReverb.flDecayHFRatio; - mAlProps.Reverb.DecayLFRatio = mEaxProps.mReverb.flDecayLFRatio; - mAlProps.Reverb.ReflectionsGain = level_mb_to_gain(static_cast(props.mReverb.lReflections)); - mAlProps.Reverb.ReflectionsDelay = props.mReverb.flReflectionsDelay; - mAlProps.Reverb.ReflectionsPan[0] = props.mReverb.vReflectionsPan.x; - mAlProps.Reverb.ReflectionsPan[1] = props.mReverb.vReflectionsPan.y; - mAlProps.Reverb.ReflectionsPan[2] = props.mReverb.vReflectionsPan.z; - mAlProps.Reverb.LateReverbGain = level_mb_to_gain(static_cast(props.mReverb.lReverb)); - mAlProps.Reverb.LateReverbDelay = props.mReverb.flReverbDelay; - mAlProps.Reverb.LateReverbPan[0] = props.mReverb.vReverbPan.x; - mAlProps.Reverb.LateReverbPan[1] = props.mReverb.vReverbPan.y; - mAlProps.Reverb.LateReverbPan[2] = props.mReverb.vReverbPan.z; - mAlProps.Reverb.EchoTime = props.mReverb.flEchoTime; - mAlProps.Reverb.EchoDepth = props.mReverb.flEchoDepth; - mAlProps.Reverb.ModulationTime = props.mReverb.flModulationTime; - mAlProps.Reverb.ModulationDepth = props.mReverb.flModulationDepth; - mAlProps.Reverb.AirAbsorptionGainHF = level_mb_to_gain(props.mReverb.flAirAbsorptionHF); - mAlProps.Reverb.HFReference = props.mReverb.flHFReference; - mAlProps.Reverb.LFReference = props.mReverb.flLFReference; - mAlProps.Reverb.RoomRolloffFactor = props.mReverb.flRoomRolloffFactor; - mAlProps.Reverb.DecayHFLimit = ((props.mReverb.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + const auto size = props.flEnvironmentSize; + const auto density = (size * size * size) / 16.0f; + mAlProps = [&]{ + ReverbProps ret{}; + ret.Density = std::min(density, AL_EAXREVERB_MAX_DENSITY); + ret.Diffusion = props.flEnvironmentDiffusion; + ret.Gain = level_mb_to_gain(static_cast(props.lRoom)); + ret.GainHF = level_mb_to_gain(static_cast(props.lRoomHF)); + ret.GainLF = level_mb_to_gain(static_cast(props.lRoomLF)); + ret.DecayTime = props.flDecayTime; + ret.DecayHFRatio = props.flDecayHFRatio; + ret.DecayLFRatio = props.flDecayLFRatio; + ret.ReflectionsGain = level_mb_to_gain(static_cast(props.lReflections)); + ret.ReflectionsDelay = props.flReflectionsDelay; + ret.ReflectionsPan = {props.vReflectionsPan.x, props.vReflectionsPan.y, + props.vReflectionsPan.z}; + ret.LateReverbGain = level_mb_to_gain(static_cast(props.lReverb)); + ret.LateReverbDelay = props.flReverbDelay; + ret.LateReverbPan = {props.vReverbPan.x, props.vReverbPan.y, props.vReverbPan.z}; + ret.EchoTime = props.flEchoTime; + ret.EchoDepth = props.flEchoDepth; + ret.ModulationTime = props.flModulationTime; + ret.ModulationDepth = props.flModulationDepth; + ret.AirAbsorptionGainHF = level_mb_to_gain(props.flAirAbsorptionHF); + ret.HFReference = props.flHFReference; + ret.LFReference = props.flLFReference; + ret.RoomRolloffFactor = props.flRoomRolloffFactor; + ret.DecayHFLimit = ((props.ulFlags & EAXREVERBFLAGS_DECAYHFLIMIT) != 0); + return ret; + }(); + return true; } @@ -1212,8 +1088,7 @@ void EaxReverbCommitter::SetDefaults(EAXREVERBPROPERTIES &props) void EaxReverbCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::Reverb; - SetDefaults(props.mReverb); + SetDefaults(props.emplace()); } @@ -1288,11 +1163,6 @@ void EaxReverbCommitter::Get(const EaxCall &call, const EAXREVERBPROPERTIES &pro } } -void EaxReverbCommitter::Get(const EaxCall &call, const EaxEffectProps &props) -{ - Get(call, props.mReverb); -} - void EaxReverbCommitter::Set(const EaxCall &call, EAX_REVERBPROPERTIES &props) { @@ -1311,71 +1181,23 @@ void EaxReverbCommitter::Set(const EaxCall &call, EAX20LISTENERPROPERTIES &props { switch(call.get_property_id()) { - case DSPROPERTY_EAX20LISTENER_NONE: - break; - - case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: - defer(call, props); - break; - - case DSPROPERTY_EAX20LISTENER_ROOM: - defer(call, props.lRoom); - break; - - case DSPROPERTY_EAX20LISTENER_ROOMHF: - defer(call, props.lRoomHF); - break; - - case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: - defer(call, props.flRoomRolloffFactor); - break; - - case DSPROPERTY_EAX20LISTENER_DECAYTIME: - defer(call, props.flDecayTime); - break; - - case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: - defer(call, props.flDecayHFRatio); - break; - - case DSPROPERTY_EAX20LISTENER_REFLECTIONS: - defer(call, props.lReflections); - break; - - case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: - defer(call, props.flReverbDelay); - break; - - case DSPROPERTY_EAX20LISTENER_REVERB: - defer(call, props.lReverb); - break; - - case DSPROPERTY_EAX20LISTENER_REVERBDELAY: - defer(call, props.flReverbDelay); - break; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: - defer(call, props, props.dwEnvironment); - break; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: - defer(call, props, props.flEnvironmentSize); - break; - - case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: - defer(call, props.flEnvironmentDiffusion); - break; - - case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: - defer(call, props.flAirAbsorptionHF); - break; - - case DSPROPERTY_EAX20LISTENER_FLAGS: - defer(call, props.dwFlags); - break; - - default: - fail_unknown_property_id(); + case DSPROPERTY_EAX20LISTENER_NONE: break; + case DSPROPERTY_EAX20LISTENER_ALLPARAMETERS: defer(call, props); break; + case DSPROPERTY_EAX20LISTENER_ROOM: defer(call, props.lRoom); break; + case DSPROPERTY_EAX20LISTENER_ROOMHF: defer(call, props.lRoomHF); break; + case DSPROPERTY_EAX20LISTENER_ROOMROLLOFFFACTOR: defer(call, props.flRoomRolloffFactor); break; + case DSPROPERTY_EAX20LISTENER_DECAYTIME: defer(call, props.flDecayTime); break; + case DSPROPERTY_EAX20LISTENER_DECAYHFRATIO: defer(call, props.flDecayHFRatio); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONS: defer(call, props.lReflections); break; + case DSPROPERTY_EAX20LISTENER_REFLECTIONSDELAY: defer(call, props.flReverbDelay); break; + case DSPROPERTY_EAX20LISTENER_REVERB: defer(call, props.lReverb); break; + case DSPROPERTY_EAX20LISTENER_REVERBDELAY: defer(call, props.flReverbDelay); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENT: defer(call, props, props.dwEnvironment); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTSIZE: defer(call, props, props.flEnvironmentSize); break; + case DSPROPERTY_EAX20LISTENER_ENVIRONMENTDIFFUSION: defer(call, props.flEnvironmentDiffusion); break; + case DSPROPERTY_EAX20LISTENER_AIRABSORPTIONHF: defer(call, props.flAirAbsorptionHF); break; + case DSPROPERTY_EAX20LISTENER_FLAGS: defer(call, props.dwFlags); break; + default: fail_unknown_property_id(); } } @@ -1383,117 +1205,34 @@ void EaxReverbCommitter::Set(const EaxCall &call, EAXREVERBPROPERTIES &props) { switch(call.get_property_id()) { - case EAXREVERB_NONE: - break; - - case EAXREVERB_ALLPARAMETERS: - defer(call, props); - break; - - case EAXREVERB_ENVIRONMENT: - defer(call, props, props.ulEnvironment); - break; - - case EAXREVERB_ENVIRONMENTSIZE: - defer(call, props, props.flEnvironmentSize); - break; - - case EAXREVERB_ENVIRONMENTDIFFUSION: - defer3(call, props, props.flEnvironmentDiffusion); - break; - - case EAXREVERB_ROOM: - defer3(call, props, props.lRoom); - break; - - case EAXREVERB_ROOMHF: - defer3(call, props, props.lRoomHF); - break; - - case EAXREVERB_ROOMLF: - defer3(call, props, props.lRoomLF); - break; - - case EAXREVERB_DECAYTIME: - defer3(call, props, props.flDecayTime); - break; - - case EAXREVERB_DECAYHFRATIO: - defer3(call, props, props.flDecayHFRatio); - break; - - case EAXREVERB_DECAYLFRATIO: - defer3(call, props, props.flDecayLFRatio); - break; - - case EAXREVERB_REFLECTIONS: - defer3(call, props, props.lReflections); - break; - - case EAXREVERB_REFLECTIONSDELAY: - defer3(call, props, props.flReflectionsDelay); - break; - - case EAXREVERB_REFLECTIONSPAN: - defer3(call, props, props.vReflectionsPan); - break; - - case EAXREVERB_REVERB: - defer3(call, props, props.lReverb); - break; - - case EAXREVERB_REVERBDELAY: - defer3(call, props, props.flReverbDelay); - break; - - case EAXREVERB_REVERBPAN: - defer3(call, props, props.vReverbPan); - break; - - case EAXREVERB_ECHOTIME: - defer3(call, props, props.flEchoTime); - break; - - case EAXREVERB_ECHODEPTH: - defer3(call, props, props.flEchoDepth); - break; - - case EAXREVERB_MODULATIONTIME: - defer3(call, props, props.flModulationTime); - break; - - case EAXREVERB_MODULATIONDEPTH: - defer3(call, props, props.flModulationDepth); - break; - - case EAXREVERB_AIRABSORPTIONHF: - defer3(call, props, props.flAirAbsorptionHF); - break; - - case EAXREVERB_HFREFERENCE: - defer3(call, props, props.flHFReference); - break; - - case EAXREVERB_LFREFERENCE: - defer3(call, props, props.flLFReference); - break; - - case EAXREVERB_ROOMROLLOFFFACTOR: - defer3(call, props, props.flRoomRolloffFactor); - break; - - case EAXREVERB_FLAGS: - defer3(call, props, props.ulFlags); - break; - - default: - fail_unknown_property_id(); + case EAXREVERB_NONE: break; + case EAXREVERB_ALLPARAMETERS: defer(call, props); break; + case EAXREVERB_ENVIRONMENT: defer(call, props, props.ulEnvironment); break; + case EAXREVERB_ENVIRONMENTSIZE: defer(call, props, props.flEnvironmentSize); break; + case EAXREVERB_ENVIRONMENTDIFFUSION: defer3(call, props, props.flEnvironmentDiffusion); break; + case EAXREVERB_ROOM: defer3(call, props, props.lRoom); break; + case EAXREVERB_ROOMHF: defer3(call, props, props.lRoomHF); break; + case EAXREVERB_ROOMLF: defer3(call, props, props.lRoomLF); break; + case EAXREVERB_DECAYTIME: defer3(call, props, props.flDecayTime); break; + case EAXREVERB_DECAYHFRATIO: defer3(call, props, props.flDecayHFRatio); break; + case EAXREVERB_DECAYLFRATIO: defer3(call, props, props.flDecayLFRatio); break; + case EAXREVERB_REFLECTIONS: defer3(call, props, props.lReflections); break; + case EAXREVERB_REFLECTIONSDELAY: defer3(call, props, props.flReflectionsDelay); break; + case EAXREVERB_REFLECTIONSPAN: defer3(call, props, props.vReflectionsPan); break; + case EAXREVERB_REVERB: defer3(call, props, props.lReverb); break; + case EAXREVERB_REVERBDELAY: defer3(call, props, props.flReverbDelay); break; + case EAXREVERB_REVERBPAN: defer3(call, props, props.vReverbPan); break; + case EAXREVERB_ECHOTIME: defer3(call, props, props.flEchoTime); break; + case EAXREVERB_ECHODEPTH: defer3(call, props, props.flEchoDepth); break; + case EAXREVERB_MODULATIONTIME: defer3(call, props, props.flModulationTime); break; + case EAXREVERB_MODULATIONDEPTH: defer3(call, props, props.flModulationDepth); break; + case EAXREVERB_AIRABSORPTIONHF: defer3(call, props, props.flAirAbsorptionHF); break; + case EAXREVERB_HFREFERENCE: defer3(call, props, props.flHFReference); break; + case EAXREVERB_LFREFERENCE: defer3(call, props, props.flLFReference); break; + case EAXREVERB_ROOMROLLOFFFACTOR: defer3(call, props, props.flRoomRolloffFactor); break; + case EAXREVERB_FLAGS: defer3(call, props, props.ulFlags); break; + default: fail_unknown_property_id(); } } -void EaxReverbCommitter::Set(const EaxCall &call, EaxEffectProps &props) -{ - Set(call, props.mReverb); -} - #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/al/effects/vmorpher.cpp b/Engine/lib/openal-soft/al/effects/vmorpher.cpp index 21ea3680c..8c66957be 100644 --- a/Engine/lib/openal-soft/al/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/al/effects/vmorpher.cpp @@ -1,18 +1,18 @@ #include "config.h" +#include #include #include "AL/al.h" #include "AL/efx.h" -#include "alc/effects/base.h" -#include "aloptional.h" +#include "core/effects/base.h" #include "effects.h" #ifdef ALSOFT_EAX #include -#include "alnumeric.h" +#include "al/eax/effect.h" #include "al/eax/exception.h" #include "al/eax/utils.h" #endif // ALSOFT_EAX @@ -20,7 +20,7 @@ namespace { -al::optional PhenomeFromEnum(ALenum val) +constexpr std::optional PhenomeFromEnum(ALenum val) noexcept { #define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \ return VMorpherPhenome::x @@ -57,10 +57,10 @@ al::optional PhenomeFromEnum(ALenum val) HANDLE_PHENOME(V); HANDLE_PHENOME(Z); } - return al::nullopt; + return std::nullopt; #undef HANDLE_PHENOME } -ALenum EnumFromPhenome(VMorpherPhenome phenome) +constexpr ALenum EnumFromPhenome(VMorpherPhenome phenome) { #define HANDLE_PHENOME(x) case VMorpherPhenome::x: return AL_VOCAL_MORPHER_PHONEME_ ## x switch(phenome) @@ -100,7 +100,7 @@ ALenum EnumFromPhenome(VMorpherPhenome phenome) #undef HANDLE_PHENOME } -al::optional WaveformFromEmum(ALenum value) +constexpr std::optional WaveformFromEmum(ALenum value) noexcept { switch(value) { @@ -108,9 +108,9 @@ al::optional WaveformFromEmum(ALenum value) case AL_VOCAL_MORPHER_WAVEFORM_TRIANGLE: return VMorpherWaveform::Triangle; case AL_VOCAL_MORPHER_WAVEFORM_SAWTOOTH: return VMorpherWaveform::Sawtooth; } - return al::nullopt; + return std::nullopt; } -ALenum EnumFromWaveform(VMorpherWaveform type) +constexpr ALenum EnumFromWaveform(VMorpherWaveform type) { switch(type) { @@ -122,13 +122,29 @@ ALenum EnumFromWaveform(VMorpherWaveform type) std::to_string(static_cast(type))}; } -void Vmorpher_setParami(EffectProps *props, ALenum param, int val) +constexpr EffectProps genDefaultProps() noexcept +{ + VmorpherProps props{}; + props.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; + props.PhonemeA = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA).value(); + props.PhonemeB = PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB).value(); + props.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; + props.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; + props.Waveform = WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM).value(); + return props; +} + +} // namespace + +const EffectProps VmorpherEffectProps{genDefaultProps()}; + +void VmorpherEffectHandler::SetParami(VmorpherProps &props, ALenum param, int val) { switch(param) { case AL_VOCAL_MORPHER_PHONEMEA: if(auto phenomeopt = PhenomeFromEnum(val)) - props->Vmorpher.PhonemeA = *phenomeopt; + props.PhonemeA = *phenomeopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a out of range: 0x%04x", val}; break; @@ -136,12 +152,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEA_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEA_COARSE_TUNING)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-a coarse tuning out of range"}; - props->Vmorpher.PhonemeACoarseTuning = val; + props.PhonemeACoarseTuning = val; break; case AL_VOCAL_MORPHER_PHONEMEB: if(auto phenomeopt = PhenomeFromEnum(val)) - props->Vmorpher.PhonemeB = *phenomeopt; + props.PhonemeB = *phenomeopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b out of range: 0x%04x", val}; break; @@ -149,12 +165,12 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: if(!(val >= AL_VOCAL_MORPHER_MIN_PHONEMEB_COARSE_TUNING && val <= AL_VOCAL_MORPHER_MAX_PHONEMEB_COARSE_TUNING)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher phoneme-b coarse tuning out of range"}; - props->Vmorpher.PhonemeBCoarseTuning = val; + props.PhonemeBCoarseTuning = val; break; case AL_VOCAL_MORPHER_WAVEFORM: if(auto formopt = WaveformFromEmum(val)) - props->Vmorpher.Waveform = *formopt; + props.Waveform = *formopt; else throw effect_exception{AL_INVALID_VALUE, "Vocal morpher waveform out of range: 0x%04x", val}; break; @@ -164,19 +180,19 @@ void Vmorpher_setParami(EffectProps *props, ALenum param, int val) param}; } } -void Vmorpher_setParamiv(EffectProps*, ALenum param, const int*) +void VmorpherEffectHandler::SetParamiv(VmorpherProps&, ALenum param, const int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param}; } -void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) +void VmorpherEffectHandler::SetParamf(VmorpherProps &props, ALenum param, float val) { switch(param) { case AL_VOCAL_MORPHER_RATE: if(!(val >= AL_VOCAL_MORPHER_MIN_RATE && val <= AL_VOCAL_MORPHER_MAX_RATE)) throw effect_exception{AL_INVALID_VALUE, "Vocal morpher rate out of range"}; - props->Vmorpher.Rate = val; + props.Rate = val; break; default: @@ -184,49 +200,35 @@ void Vmorpher_setParamf(EffectProps *props, ALenum param, float val) param}; } } -void Vmorpher_setParamfv(EffectProps *props, ALenum param, const float *vals) -{ Vmorpher_setParamf(props, param, vals[0]); } +void VmorpherEffectHandler::SetParamfv(VmorpherProps &props, ALenum param, const float *vals) +{ SetParamf(props, param, *vals); } -void Vmorpher_getParami(const EffectProps *props, ALenum param, int* val) +void VmorpherEffectHandler::GetParami(const VmorpherProps &props, ALenum param, int* val) { switch(param) { - case AL_VOCAL_MORPHER_PHONEMEA: - *val = EnumFromPhenome(props->Vmorpher.PhonemeA); - break; - - case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: - *val = props->Vmorpher.PhonemeACoarseTuning; - break; - - case AL_VOCAL_MORPHER_PHONEMEB: - *val = EnumFromPhenome(props->Vmorpher.PhonemeB); - break; - - case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: - *val = props->Vmorpher.PhonemeBCoarseTuning; - break; - - case AL_VOCAL_MORPHER_WAVEFORM: - *val = EnumFromWaveform(props->Vmorpher.Waveform); - break; + case AL_VOCAL_MORPHER_PHONEMEA: *val = EnumFromPhenome(props.PhonemeA); break; + case AL_VOCAL_MORPHER_PHONEMEA_COARSE_TUNING: *val = props.PhonemeACoarseTuning; break; + case AL_VOCAL_MORPHER_PHONEMEB: *val = EnumFromPhenome(props.PhonemeB); break; + case AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING: *val = props.PhonemeBCoarseTuning; break; + case AL_VOCAL_MORPHER_WAVEFORM: *val = EnumFromWaveform(props.Waveform); break; default: throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer property 0x%04x", param}; } } -void Vmorpher_getParamiv(const EffectProps*, ALenum param, int*) +void VmorpherEffectHandler::GetParamiv(const VmorpherProps&, ALenum param, int*) { throw effect_exception{AL_INVALID_ENUM, "Invalid vocal morpher integer-vector property 0x%04x", param}; } -void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) +void VmorpherEffectHandler::GetParamf(const VmorpherProps &props, ALenum param, float *val) { switch(param) { case AL_VOCAL_MORPHER_RATE: - *val = props->Vmorpher.Rate; + *val = props.Rate; break; default: @@ -234,26 +236,9 @@ void Vmorpher_getParamf(const EffectProps *props, ALenum param, float *val) param}; } } -void Vmorpher_getParamfv(const EffectProps *props, ALenum param, float *vals) -{ Vmorpher_getParamf(props, param, vals); } +void VmorpherEffectHandler::GetParamfv(const VmorpherProps &props, ALenum param, float *vals) +{ GetParamf(props, param, vals); } -EffectProps genDefaultProps() noexcept -{ - EffectProps props{}; - props.Vmorpher.Rate = AL_VOCAL_MORPHER_DEFAULT_RATE; - props.Vmorpher.PhonemeA = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEA); - props.Vmorpher.PhonemeB = *PhenomeFromEnum(AL_VOCAL_MORPHER_DEFAULT_PHONEMEB); - props.Vmorpher.PhonemeACoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEA_COARSE_TUNING; - props.Vmorpher.PhonemeBCoarseTuning = AL_VOCAL_MORPHER_DEFAULT_PHONEMEB_COARSE_TUNING; - props.Vmorpher.Waveform = *WaveformFromEmum(AL_VOCAL_MORPHER_DEFAULT_WAVEFORM); - return props; -} - -} // namespace - -DEFINE_ALEFFECT_VTABLE(Vmorpher); - -const EffectProps VmorpherEffectProps{genDefaultProps()}; #ifdef ALSOFT_EAX namespace { @@ -352,16 +337,9 @@ template<> throw Exception{message}; } -template<> -bool VocalMorpherCommitter::commit(const EaxEffectProps &props) +bool EaxVocalMorpherCommitter::commit(const EAXVOCALMORPHERPROPERTIES &props) { - if(props.mType == mEaxProps.mType - && mEaxProps.mVocalMorpher.ulPhonemeA == props.mVocalMorpher.ulPhonemeA - && mEaxProps.mVocalMorpher.lPhonemeACoarseTuning == props.mVocalMorpher.lPhonemeACoarseTuning - && mEaxProps.mVocalMorpher.ulPhonemeB == props.mVocalMorpher.ulPhonemeB - && mEaxProps.mVocalMorpher.lPhonemeBCoarseTuning == props.mVocalMorpher.lPhonemeBCoarseTuning - && mEaxProps.mVocalMorpher.ulWaveform == props.mVocalMorpher.ulWaveform - && mEaxProps.mVocalMorpher.flRate == props.mVocalMorpher.flRate) + if(auto *cur = std::get_if(&mEaxProps); cur && *cur == props) return false; mEaxProps = props; @@ -413,107 +391,65 @@ bool VocalMorpherCommitter::commit(const EaxEffectProps &props) return VMorpherWaveform::Sinusoid; }; - mAlProps.Vmorpher.PhonemeA = get_phoneme(props.mVocalMorpher.ulPhonemeA); - mAlProps.Vmorpher.PhonemeACoarseTuning = static_cast(props.mVocalMorpher.lPhonemeACoarseTuning); - mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB); - mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast(props.mVocalMorpher.lPhonemeBCoarseTuning); - mAlProps.Vmorpher.Waveform = get_waveform(props.mVocalMorpher.ulWaveform); - mAlProps.Vmorpher.Rate = props.mVocalMorpher.flRate; + mAlProps = [&]{ + VmorpherProps ret{}; + ret.PhonemeA = get_phoneme(props.ulPhonemeA); + ret.PhonemeACoarseTuning = static_cast(props.lPhonemeACoarseTuning); + ret.PhonemeB = get_phoneme(props.ulPhonemeB); + ret.PhonemeBCoarseTuning = static_cast(props.lPhonemeBCoarseTuning); + ret.Waveform = get_waveform(props.ulWaveform); + ret.Rate = props.flRate; + return ret; + }(); return true; } -template<> -void VocalMorpherCommitter::SetDefaults(EaxEffectProps &props) +void EaxVocalMorpherCommitter::SetDefaults(EaxEffectProps &props) { - props.mType = EaxEffectType::VocalMorpher; - props.mVocalMorpher.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; - props.mVocalMorpher.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; - props.mVocalMorpher.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; - props.mVocalMorpher.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; - props.mVocalMorpher.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; - props.mVocalMorpher.flRate = EAXVOCALMORPHER_DEFAULTRATE; + static constexpr EAXVOCALMORPHERPROPERTIES defprops{[] + { + EAXVOCALMORPHERPROPERTIES ret{}; + ret.ulPhonemeA = EAXVOCALMORPHER_DEFAULTPHONEMEA; + ret.lPhonemeACoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEACOARSETUNING; + ret.ulPhonemeB = EAXVOCALMORPHER_DEFAULTPHONEMEB; + ret.lPhonemeBCoarseTuning = EAXVOCALMORPHER_DEFAULTPHONEMEBCOARSETUNING; + ret.ulWaveform = EAXVOCALMORPHER_DEFAULTWAVEFORM; + ret.flRate = EAXVOCALMORPHER_DEFAULTRATE; + return ret; + }()}; + props = defprops; } -template<> -void VocalMorpherCommitter::Get(const EaxCall &call, const EaxEffectProps &props) +void EaxVocalMorpherCommitter::Get(const EaxCall &call, const EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { - case EAXVOCALMORPHER_NONE: - break; - - case EAXVOCALMORPHER_ALLPARAMETERS: - call.set_value(props.mVocalMorpher); - break; - - case EAXVOCALMORPHER_PHONEMEA: - call.set_value(props.mVocalMorpher.ulPhonemeA); - break; - - case EAXVOCALMORPHER_PHONEMEACOARSETUNING: - call.set_value(props.mVocalMorpher.lPhonemeACoarseTuning); - break; - - case EAXVOCALMORPHER_PHONEMEB: - call.set_value(props.mVocalMorpher.ulPhonemeB); - break; - - case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: - call.set_value(props.mVocalMorpher.lPhonemeBCoarseTuning); - break; - - case EAXVOCALMORPHER_WAVEFORM: - call.set_value(props.mVocalMorpher.ulWaveform); - break; - - case EAXVOCALMORPHER_RATE: - call.set_value(props.mVocalMorpher.flRate); - break; - - default: - fail_unknown_property_id(); + case EAXVOCALMORPHER_NONE: break; + case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value(props); break; + case EAXVOCALMORPHER_PHONEMEA: call.set_value(props.ulPhonemeA); break; + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value(props.lPhonemeACoarseTuning); break; + case EAXVOCALMORPHER_PHONEMEB: call.set_value(props.ulPhonemeB); break; + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value(props.lPhonemeBCoarseTuning); break; + case EAXVOCALMORPHER_WAVEFORM: call.set_value(props.ulWaveform); break; + case EAXVOCALMORPHER_RATE: call.set_value(props.flRate); break; + default: fail_unknown_property_id(); } } -template<> -void VocalMorpherCommitter::Set(const EaxCall &call, EaxEffectProps &props) +void EaxVocalMorpherCommitter::Set(const EaxCall &call, EAXVOCALMORPHERPROPERTIES &props) { switch(call.get_property_id()) { - case EAXVOCALMORPHER_NONE: - break; - - case EAXVOCALMORPHER_ALLPARAMETERS: - defer(call, props.mVocalMorpher); - break; - - case EAXVOCALMORPHER_PHONEMEA: - defer(call, props.mVocalMorpher.ulPhonemeA); - break; - - case EAXVOCALMORPHER_PHONEMEACOARSETUNING: - defer(call, props.mVocalMorpher.lPhonemeACoarseTuning); - break; - - case EAXVOCALMORPHER_PHONEMEB: - defer(call, props.mVocalMorpher.ulPhonemeB); - break; - - case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: - defer(call, props.mVocalMorpher.lPhonemeBCoarseTuning); - break; - - case EAXVOCALMORPHER_WAVEFORM: - defer(call, props.mVocalMorpher.ulWaveform); - break; - - case EAXVOCALMORPHER_RATE: - defer(call, props.mVocalMorpher.flRate); - break; - - default: - fail_unknown_property_id(); + case EAXVOCALMORPHER_NONE: break; + case EAXVOCALMORPHER_ALLPARAMETERS: defer(call, props); break; + case EAXVOCALMORPHER_PHONEMEA: defer(call, props.ulPhonemeA); break; + case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer(call, props.lPhonemeACoarseTuning); break; + case EAXVOCALMORPHER_PHONEMEB: defer(call, props.ulPhonemeB); break; + case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer(call, props.lPhonemeBCoarseTuning); break; + case EAXVOCALMORPHER_WAVEFORM: defer(call, props.ulWaveform); break; + case EAXVOCALMORPHER_RATE: defer(call, props.flRate); break; + default: fail_unknown_property_id(); } } diff --git a/Engine/lib/openal-soft/al/error.cpp b/Engine/lib/openal-soft/al/error.cpp index afa7019a9..24cc51dca 100644 --- a/Engine/lib/openal-soft/al/error.cpp +++ b/Engine/lib/openal-soft/al/error.cpp @@ -20,6 +20,8 @@ #include "config.h" +#include "error.h" + #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include @@ -29,27 +31,45 @@ #include #include #include +#include #include -#include +#include +#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" +#include "al/debug.h" +#include "alc/alconfig.h" #include "alc/context.h" -#include "almalloc.h" -#include "core/except.h" +#include "alc/inprogext.h" #include "core/logging.h" #include "opthelpers.h" -#include "vector.h" +#include "strutils.h" -bool TrapALError{false}; +namespace al { +context_error::context_error(ALenum code, const char *msg, ...) : mErrorCode{code} +{ + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + std::va_list args; + va_start(args, msg); + setMessage(msg, args); + va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ +} +context_error::~context_error() = default; +} /* namespace al */ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) { - auto message = al::vector(256); + auto message = std::vector(256); - va_list args, args2; + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + std::va_list args, args2; va_start(args, msg); va_copy(args2, args); int msglen{std::vsnprintf(message.data(), message.size(), msg, args)}; @@ -60,9 +80,15 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) } va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ - if(msglen >= 0) msg = message.data(); - else msg = ""; + if(msglen >= 0) + msg = message.data(); + else + { + msg = ""; + msglen = static_cast(strlen(msg)); + } WARN("Error generated on context %p, code 0x%04x, \"%s\"\n", decltype(std::declval()){this}, errorCode, msg); @@ -77,30 +103,55 @@ void ALCcontext::setError(ALenum errorCode, const char *msg, ...) #endif } - ALenum curerr{AL_NO_ERROR}; - mLastError.compare_exchange_strong(curerr, errorCode); + if(mLastThreadError.get() == AL_NO_ERROR) + mLastThreadError.set(errorCode); + + debugMessage(DebugSource::API, DebugType::Error, 0, DebugSeverity::High, + {msg, static_cast(msglen)}); } -AL_API ALenum AL_APIENTRY alGetError(void) -START_API_FUNC +/* Special-case alGetError since it (potentially) raises a debug signal and + * returns a non-default value for a null context. + */ +AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY - { - static constexpr ALenum deferror{AL_INVALID_OPERATION}; - WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); - if(TrapALError) - { -#ifdef _WIN32 - if(IsDebuggerPresent()) - DebugBreak(); -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - return deferror; - } + if(auto context = GetContextRef()) LIKELY + return alGetErrorDirect(context.get()); - return context->mLastError.exchange(AL_NO_ERROR); + auto get_value = [](const char *envname, const char *optname) -> ALenum + { + auto optstr = al::getenv(envname); + if(!optstr) + optstr = ConfigValueStr({}, "game_compat", optname); + if(optstr) + { + char *end{}; + auto value = std::strtoul(optstr->c_str(), &end, 0); + if(end && *end == '\0' && value <= std::numeric_limits::max()) + return static_cast(value); + ERR("Invalid default error value: \"%s\"", optstr->c_str()); + } + return AL_INVALID_OPERATION; + }; + static const ALenum deferror{get_value("__ALSOFT_DEFAULT_ERROR", "default-error")}; + + WARN("Querying error state on null context (implicitly 0x%04x)\n", deferror); + if(TrapALError) + { +#ifdef _WIN32 + if(IsDebuggerPresent()) + DebugBreak(); +#elif defined(SIGTRAP) + raise(SIGTRAP); +#endif + } + return deferror; +} + +FORCE_ALIGN ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) noexcept +{ + ALenum ret{context->mLastThreadError.get()}; + if(ret != AL_NO_ERROR) UNLIKELY + context->mLastThreadError.set(AL_NO_ERROR); + return ret; } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/error.h b/Engine/lib/openal-soft/al/error.h new file mode 100644 index 000000000..c50011a54 --- /dev/null +++ b/Engine/lib/openal-soft/al/error.h @@ -0,0 +1,27 @@ +#ifndef AL_ERROR_H +#define AL_ERROR_H + +#include "AL/al.h" + +#include "core/except.h" + +namespace al { + +class context_error final : public al::base_exception { + ALenum mErrorCode{}; + +public: +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] +#else + [[gnu::format(printf, 3, 4)]] +#endif + context_error(ALenum code, const char *msg, ...); + ~context_error() final; + + [[nodiscard]] auto errorCode() const noexcept -> ALenum { return mErrorCode; } +}; + +} /* namespace al */ + +#endif /* AL_ERROR_H */ diff --git a/Engine/lib/openal-soft/al/event.cpp b/Engine/lib/openal-soft/al/event.cpp index 1bc39d1ea..5d6a1c38c 100644 --- a/Engine/lib/openal-soft/al/event.cpp +++ b/Engine/lib/openal-soft/al/event.cpp @@ -3,35 +3,49 @@ #include "event.h" -#include +#include #include -#include +#include #include #include #include #include +#include #include +#include #include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" +#include "AL/alext.h" -#include "albyte.h" #include "alc/context.h" -#include "alc/effects/base.h" -#include "alc/inprogext.h" -#include "almalloc.h" +#include "alsem.h" +#include "alspan.h" #include "core/async_event.h" -#include "core/except.h" +#include "core/context.h" +#include "core/effects/base.h" #include "core/logging.h" -#include "core/voice_change.h" +#include "debug.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" #include "ringbuffer.h" -#include "threads.h" -static int EventThread(ALCcontext *context) +namespace { + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + +int EventThread(ALCcontext *context) { RingBuffer *ring{context->mAsyncEvents.get()}; bool quitnow{false}; @@ -44,76 +58,98 @@ static int EventThread(ALCcontext *context) continue; } - std::lock_guard _{context->mEventCbLock}; - do { - auto *evt_ptr = reinterpret_cast(evt_data.buf); - evt_data.buf += sizeof(AsyncEvent); - evt_data.len -= 1; - - AsyncEvent evt{*evt_ptr}; - al::destroy_at(evt_ptr); - ring->readAdvance(1); - - quitnow = evt.EnumType == AsyncEvent::KillThread; + std::lock_guard eventlock{context->mEventCbLock}; + auto evt_span = al::span{std::launder(reinterpret_cast(evt_data.buf)), + evt_data.len}; + for(auto &event : evt_span) + { + quitnow = std::holds_alternative(event); if(quitnow) UNLIKELY break; - if(evt.EnumType == AsyncEvent::ReleaseEffectState) - { - al::intrusive_ptr{evt.u.mEffectState}; - continue; - } - auto enabledevts = context->mEnabledEvts.load(std::memory_order_acquire); - if(!context->mEventCb || !enabledevts.test(evt.EnumType)) - continue; - - if(evt.EnumType == AsyncEvent::SourceStateChange) + auto proc_killthread = [](AsyncKillThread&) { }; + auto proc_release = [](AsyncEffectReleaseEvent &evt) { + al::intrusive_ptr{evt.mEffectState}; + }; + auto proc_srcstate = [context,enabledevts](AsyncSourceStateEvent &evt) + { + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::SourceState))) + return; + ALuint state{}; - std::string msg{"Source ID " + std::to_string(evt.u.srcstate.id)}; + std::string msg{"Source ID " + std::to_string(evt.mId)}; msg += " state has changed to "; - switch(evt.u.srcstate.state) + switch(evt.mState) { - case AsyncEvent::SrcState::Reset: + case AsyncSrcState::Reset: msg += "AL_INITIAL"; state = AL_INITIAL; break; - case AsyncEvent::SrcState::Stop: + case AsyncSrcState::Stop: msg += "AL_STOPPED"; state = AL_STOPPED; break; - case AsyncEvent::SrcState::Play: + case AsyncSrcState::Play: msg += "AL_PLAYING"; state = AL_PLAYING; break; - case AsyncEvent::SrcState::Pause: + case AsyncSrcState::Pause: msg += "AL_PAUSED"; state = AL_PAUSED; break; } - context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.u.srcstate.id, - state, static_cast(msg.length()), msg.c_str(), context->mEventParam); - } - else if(evt.EnumType == AsyncEvent::BufferCompleted) + context->mEventCb(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT, evt.mId, state, + static_cast(msg.length()), msg.c_str(), context->mEventParam); + }; + auto proc_buffercomp = [context,enabledevts](AsyncBufferCompleteEvent &evt) { - std::string msg{std::to_string(evt.u.bufcomp.count)}; - if(evt.u.bufcomp.count == 1) msg += " buffer completed"; + if(!context->mEventCb + || !enabledevts.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) + return; + + std::string msg{std::to_string(evt.mCount)}; + if(evt.mCount == 1) msg += " buffer completed"; else msg += " buffers completed"; - context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.u.bufcomp.id, - evt.u.bufcomp.count, static_cast(msg.length()), msg.c_str(), - context->mEventParam); - } - else if(evt.EnumType == AsyncEvent::Disconnected) + context->mEventCb(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT, evt.mId, evt.mCount, + static_cast(msg.length()), msg.c_str(), context->mEventParam); + }; + auto proc_disconnect = [context,enabledevts](AsyncDisconnectEvent &evt) { - context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, - static_cast(strlen(evt.u.disconnect.msg)), evt.u.disconnect.msg, - context->mEventParam); - } - } while(evt_data.len != 0); + context->debugMessage(DebugSource::System, DebugType::Error, 0, + DebugSeverity::High, evt.msg); + + if(context->mEventCb + && enabledevts.test(al::to_underlying(AsyncEnableBits::Disconnected))) + context->mEventCb(AL_EVENT_TYPE_DISCONNECTED_SOFT, 0, 0, + static_cast(evt.msg.length()), evt.msg.c_str(), + context->mEventParam); + }; + + std::visit(overloaded{proc_srcstate, proc_buffercomp, proc_release, proc_disconnect, + proc_killthread}, event); + } + std::destroy(evt_span.begin(), evt_span.end()); + ring->readAdvance(evt_span.size()); } return 0; } +constexpr std::optional GetEventType(ALenum etype) noexcept +{ + switch(etype) + { + case AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT: return AsyncEnableBits::BufferCompleted; + case AL_EVENT_TYPE_DISCONNECTED_SOFT: return AsyncEnableBits::Disconnected; + case AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT: return AsyncEnableBits::SourceState; + } + return std::nullopt; +} + +} // namespace + + void StartEventThrd(ALCcontext *ctx) { try { @@ -138,7 +174,7 @@ void StopEventThrd(ALCcontext *ctx) evt_data = ring->getWriteVector().first; } while(evt_data.len == 0); } - al::construct_at(reinterpret_cast(evt_data.buf), AsyncEvent::KillThread); + std::ignore = InitAsyncEvent(evt_data.buf); ring->writeAdvance(1); ctx->mEventSem.post(); @@ -146,34 +182,25 @@ void StopEventThrd(ALCcontext *ctx) ctx->mEventThread.join(); } -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT3(void, alEventControl,SOFT, ALsizei,count, const ALenum*,types, ALboolean,enable) +FORCE_ALIGN void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, + const ALenum *types, ALboolean enable) noexcept +try { + if(count < 0) + throw al::context_error{AL_INVALID_VALUE, "Controlling %d events", count}; + if(count <= 0) UNLIKELY return; - if(count < 0) context->setError(AL_INVALID_VALUE, "Controlling %d events", count); - if(count <= 0) return; - if(!types) return context->setError(AL_INVALID_VALUE, "NULL pointer"); + if(!types) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; ContextBase::AsyncEventBitset flags{}; - const ALenum *types_end = types+count; - auto bad_type = std::find_if_not(types, types_end, - [&flags](ALenum type) noexcept -> bool - { - if(type == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) - flags.set(AsyncEvent::BufferCompleted); - else if(type == AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT) - flags.set(AsyncEvent::SourceStateChange); - else if(type == AL_EVENT_TYPE_DISCONNECTED_SOFT) - flags.set(AsyncEvent::Disconnected); - else - return false; - return true; - } - ); - if(bad_type != types_end) - return context->setError(AL_INVALID_ENUM, "Invalid event type 0x%04x", *bad_type); + for(ALenum evttype : al::span{types, static_cast(count)}) + { + auto etype = GetEventType(evttype); + if(!etype) + throw al::context_error{AL_INVALID_ENUM, "Invalid event type 0x%04x", evttype}; + flags.set(al::to_underlying(*etype)); + } if(enable) { @@ -196,20 +223,18 @@ START_API_FUNC /* Wait to ensure the event handler sees the changed flags before * returning. */ - std::lock_guard _{context->mEventCbLock}; + std::lock_guard eventlock{context->mEventCbLock}; } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) -START_API_FUNC +AL_API DECL_FUNCEXT2(void, alEventCallback,SOFT, ALEVENTPROCSOFT,callback, void*,userParam) +FORCE_ALIGN void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, + ALEVENTPROCSOFT callback, void *userParam) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mEventCbLock}; + std::lock_guard eventlock{context->mEventCbLock}; context->mEventCb = callback; context->mEventParam = userParam; } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/extension.cpp b/Engine/lib/openal-soft/al/extension.cpp index 3ead0af82..16dc015aa 100644 --- a/Engine/lib/openal-soft/al/extension.cpp +++ b/Engine/lib/openal-soft/al/extension.cpp @@ -20,63 +20,59 @@ #include "config.h" -#include -#include -#include +#include +#include #include "AL/al.h" #include "AL/alc.h" #include "alc/context.h" +#include "alc/inprogext.h" #include "alstring.h" -#include "core/except.h" +#include "direct_defs.h" #include "opthelpers.h" -AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extName) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsExtensionPresent, const ALchar*,extName) +FORCE_ALIGN ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extName) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - if(!extName) UNLIKELY { context->setError(AL_INVALID_VALUE, "NULL pointer"); return AL_FALSE; } - size_t len{strlen(extName)}; - const char *ptr{context->mExtensionList}; - while(ptr && *ptr) + const std::string_view tofind{extName}; + for(std::string_view ext : context->mExtensions) { - if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) + if(al::case_compare(ext, tofind) == 0) return AL_TRUE; - - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } } return AL_FALSE; } -END_API_FUNC -AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) -START_API_FUNC +AL_API ALvoid* AL_APIENTRY alGetProcAddress(const ALchar *funcName) noexcept { if(!funcName) return nullptr; return alcGetProcAddress(nullptr, funcName); } -END_API_FUNC -AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) -START_API_FUNC +FORCE_ALIGN ALvoid* AL_APIENTRY alGetProcAddressDirect(ALCcontext*, const ALchar *funcName) noexcept { - if(!enumName) return static_cast(0); + if(!funcName) return nullptr; + return alcGetProcAddress(nullptr, funcName); +} + +AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *enumName) noexcept +{ + if(!enumName) return ALenum{0}; + return alcGetEnumValue(nullptr, enumName); +} + +FORCE_ALIGN ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext*, const ALchar *enumName) noexcept +{ + if(!enumName) return ALenum{0}; return alcGetEnumValue(nullptr, enumName); } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/filter.cpp b/Engine/lib/openal-soft/al/filter.cpp index 73efa01f8..5e56c6f04 100644 --- a/Engine/lib/openal-soft/al/filter.cpp +++ b/Engine/lib/openal-soft/al/filter.cpp @@ -29,8 +29,10 @@ #include #include #include -#include #include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" @@ -39,252 +41,19 @@ #include "albit.h" #include "alc/context.h" #include "alc/device.h" +#include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "core/except.h" +#include "alspan.h" +#include "direct_defs.h" +#include "error.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" namespace { -class filter_exception final : public al::base_exception { - ALenum mErrorCode; - -public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] -#else - [[gnu::format(printf, 3, 4)]] -#endif - filter_exception(ALenum code, const char *msg, ...); - ~filter_exception() override; - - ALenum errorCode() const noexcept { return mErrorCode; } -}; - -filter_exception::filter_exception(ALenum code, const char* msg, ...) : mErrorCode{code} -{ - std::va_list args; - va_start(args, msg); - setMessage(msg, args); - va_end(args); -} -filter_exception::~filter_exception() = default; - - -#define DEFINE_ALFILTER_VTABLE(T) \ -const ALfilter::Vtable T##_vtable = { \ - T##_setParami, T##_setParamiv, T##_setParamf, T##_setParamfv, \ - T##_getParami, T##_getParamiv, T##_getParamf, T##_getParamfv, \ -} - -void ALlowpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } -void ALlowpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", - param}; -} -void ALlowpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_LOWPASS_GAINHF: - if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) - throw filter_exception{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; - filter->GainHF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; - } -} -void ALlowpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALlowpass_setParamf(filter, param, vals[0]); } - -void ALlowpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } -void ALlowpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass integer-vector property 0x%04x", - param}; -} -void ALlowpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_LOWPASS_GAIN: - *val = filter->Gain; - break; - - case AL_LOWPASS_GAINHF: - *val = filter->GainHF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; - } -} -void ALlowpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALlowpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALlowpass); - - -void ALhighpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } -void ALhighpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", - param}; -} -void ALhighpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_HIGHPASS_GAINLF: - if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) - throw filter_exception{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; - filter->GainLF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; - } -} -void ALhighpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALhighpass_setParamf(filter, param, vals[0]); } - -void ALhighpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } -void ALhighpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass integer-vector property 0x%04x", - param}; -} -void ALhighpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_HIGHPASS_GAIN: - *val = filter->Gain; - break; - - case AL_HIGHPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; - } -} -void ALhighpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALhighpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALhighpass); - - -void ALbandpass_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } -void ALbandpass_setParamiv(ALfilter*, ALenum param, const int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", - param}; -} -void ALbandpass_setParamf(ALfilter *filter, ALenum param, float val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; - filter->Gain = val; - break; - - case AL_BANDPASS_GAINHF: - if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; - filter->GainHF = val; - break; - - case AL_BANDPASS_GAINLF: - if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) - throw filter_exception{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; - filter->GainLF = val; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; - } -} -void ALbandpass_setParamfv(ALfilter *filter, ALenum param, const float *vals) -{ ALbandpass_setParamf(filter, param, vals[0]); } - -void ALbandpass_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } -void ALbandpass_getParamiv(const ALfilter*, ALenum param, int*) -{ - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass integer-vector property 0x%04x", - param}; -} -void ALbandpass_getParamf(const ALfilter *filter, ALenum param, float *val) -{ - switch(param) - { - case AL_BANDPASS_GAIN: - *val = filter->Gain; - break; - - case AL_BANDPASS_GAINHF: - *val = filter->GainHF; - break; - - case AL_BANDPASS_GAINLF: - *val = filter->GainLF; - break; - - default: - throw filter_exception{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; - } -} -void ALbandpass_getParamfv(const ALfilter *filter, ALenum param, float *vals) -{ ALbandpass_getParamf(filter, param, vals); } - -DEFINE_ALFILTER_VTABLE(ALbandpass); - - -void ALnullfilter_setParami(ALfilter*, ALenum param, int) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamiv(ALfilter*, ALenum param, const int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamf(ALfilter*, ALenum param, float) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_setParamfv(ALfilter*, ALenum param, const float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } - -void ALnullfilter_getParami(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamiv(const ALfilter*, ALenum param, int*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamf(const ALfilter*, ALenum param, float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } -void ALnullfilter_getParamfv(const ALfilter*, ALenum param, float*) -{ throw filter_exception{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } - -DEFINE_ALFILTER_VTABLE(ALnullfilter); +using SubListAllocator = al::allocator>; void InitFilterParams(ALfilter *filter, ALenum type) @@ -293,44 +62,44 @@ void InitFilterParams(ALfilter *filter, ALenum type) { filter->Gain = AL_LOWPASS_DEFAULT_GAIN; filter->GainHF = AL_LOWPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALlowpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_HIGHPASS) { filter->Gain = AL_HIGHPASS_DEFAULT_GAIN; filter->GainHF = 1.0f; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = AL_HIGHPASS_DEFAULT_GAINLF; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALhighpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } else if(type == AL_FILTER_BANDPASS) { filter->Gain = AL_BANDPASS_DEFAULT_GAIN; filter->GainHF = AL_BANDPASS_DEFAULT_GAINHF; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = AL_BANDPASS_DEFAULT_GAINLF; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALbandpass_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } else { filter->Gain = 1.0f; filter->GainHF = 1.0f; - filter->HFReference = LOWPASSFREQREF; + filter->HFReference = LowPassFreqRef; filter->GainLF = 1.0f; - filter->LFReference = HIGHPASSFREQREF; - filter->vtab = &ALnullfilter_vtable; + filter->LFReference = HighPassFreqRef; + filter->mTypeVariant.emplace(); } filter->type = type; } -bool EnsureFilters(ALCdevice *device, size_t needed) -{ - size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), size_t{0}, +auto EnsureFilters(ALCdevice *device, size_t needed) noexcept -> bool +try { + size_t count{std::accumulate(device->FilterList.cbegin(), device->FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; @@ -339,22 +108,20 @@ bool EnsureFilters(ALCdevice *device, size_t needed) if(device->FilterList.size() >= 1<<25) UNLIKELY return false; - device->FilterList.emplace_back(); - auto sublist = device->FilterList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Filters = static_cast(al_calloc(alignof(ALfilter), sizeof(ALfilter)*64)); - if(!sublist->Filters) UNLIKELY - { - device->FilterList.pop_back(); - return false; - } - count += 64; + FilterSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Filters = SubListAllocator{}.allocate(1); + device->FilterList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } return true; } +catch(...) { + return false; +} -ALfilter *AllocFilter(ALCdevice *device) +ALfilter *AllocFilter(ALCdevice *device) noexcept { auto sublist = std::find_if(device->FilterList.begin(), device->FilterList.end(), [](const FilterSubList &entry) noexcept -> bool @@ -363,7 +130,7 @@ ALfilter *AllocFilter(ALCdevice *device) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALfilter *filter{al::construct_at(sublist->Filters + slidx)}; + ALfilter *filter{al::construct_at(al::to_address(sublist->Filters->begin() + slidx))}; InitFilterParams(filter, AL_FILTER_NULL); /* Add 1 to avoid filter ID 0. */ @@ -376,17 +143,19 @@ ALfilter *AllocFilter(ALCdevice *device) void FreeFilter(ALCdevice *device, ALfilter *filter) { + device->mFilterNames.erase(filter->id); + const ALuint id{filter->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; - al::destroy_at(filter); + std::destroy_at(filter); device->FilterList[lidx].FreeMask |= 1_u64 << slidx; } -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) +auto LookupFilter(ALCdevice *device, ALuint id) noexcept -> ALfilter* { const size_t lidx{(id-1) >> 6}; const ALuint slidx{(id-1) & 0x3f}; @@ -396,327 +165,466 @@ inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) FilterSubList &sublist = device->FilterList[lidx]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Filters + slidx; + return al::to_address(sublist.Filters->begin() + slidx); } } // namespace -AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) -START_API_FUNC +/* Null filter parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter*, ALenum param, const int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::setParamf(ALfilter*, ALenum param, float) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::setParamfv(ALfilter*, ALenum param, const float*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParamf(const ALfilter*, ALenum param, float*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } +template<> +void FilterTable::getParamfv(const ALfilter*, ALenum param, float*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid null filter property 0x%04x", param}; } + +/* Lowpass parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) +{ setParami(filter, param, *values); } +template<> +void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d filters", n); - if(n <= 0) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - if(!EnsureFilters(device, static_cast(n))) + switch(param) { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, (n==1)?"":"s"); + case AL_LOWPASS_GAIN: + if(!(val >= AL_LOWPASS_MIN_GAIN && val <= AL_LOWPASS_MAX_GAIN)) + throw al::context_error{AL_INVALID_VALUE, "Low-pass gain %f out of range", val}; + filter->Gain = val; + return; + + case AL_LOWPASS_GAINHF: + if(!(val >= AL_LOWPASS_MIN_GAINHF && val <= AL_LOWPASS_MAX_GAINHF)) + throw al::context_error{AL_INVALID_VALUE, "Low-pass gainhf %f out of range", val}; + filter->GainHF = val; return; } - - if(n == 1) LIKELY - { - /* Special handling for the easy and normal case. */ - ALfilter *filter{AllocFilter(device)}; - if(filter) filters[0] = filter->id; - } - else - { - /* Store the allocated buffer IDs in a separate local list, to avoid - * modifying the user storage in case of failure. - */ - al::vector ids; - ids.reserve(static_cast(n)); - do { - ALfilter *filter{AllocFilter(device)}; - ids.emplace_back(filter->id); - } while(--n); - std::copy(ids.begin(), ids.end(), filters); - } + throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; } -END_API_FUNC - -AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) -START_API_FUNC +template<> +void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ setParamf(filter, param, *vals); } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass integer property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) +{ getParami(filter, param, values); } +template<> +void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + switch(param) + { + case AL_LOWPASS_GAIN: *val = filter->Gain; return; + case AL_LOWPASS_GAINHF: *val = filter->GainHF; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid low-pass float property 0x%04x", param}; +} +template<> +void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ getParamf(filter, param, vals); } - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d filters", n); +/* Highpass parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) +{ setParami(filter, param, *values); } +template<> +void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_HIGHPASS_GAIN: + if(!(val >= AL_HIGHPASS_MIN_GAIN && val <= AL_HIGHPASS_MAX_GAIN)) + throw al::context_error{AL_INVALID_VALUE, "High-pass gain %f out of range", val}; + filter->Gain = val; + return; + + case AL_HIGHPASS_GAINLF: + if(!(val >= AL_HIGHPASS_MIN_GAINLF && val <= AL_HIGHPASS_MAX_GAINLF)) + throw al::context_error{AL_INVALID_VALUE, "High-pass gainlf %f out of range", val}; + filter->GainLF = val; + return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; +} +template<> +void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ setParamf(filter, param, *vals); } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass integer property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) +{ getParami(filter, param, values); } +template<> +void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_HIGHPASS_GAIN: *val = filter->Gain; return; + case AL_HIGHPASS_GAINLF: *val = filter->GainLF; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid high-pass float property 0x%04x", param}; +} +template<> +void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ getParamf(filter, param, vals); } + +/* Bandpass parameter handlers */ +template<> +void FilterTable::setParami(ALfilter*, ALenum param, int) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +template<> +void FilterTable::setParamiv(ALfilter *filter, ALenum param, const int *values) +{ setParami(filter, param, *values); } +template<> +void FilterTable::setParamf(ALfilter *filter, ALenum param, float val) +{ + switch(param) + { + case AL_BANDPASS_GAIN: + if(!(val >= AL_BANDPASS_MIN_GAIN && val <= AL_BANDPASS_MAX_GAIN)) + throw al::context_error{AL_INVALID_VALUE, "Band-pass gain %f out of range", val}; + filter->Gain = val; + return; + + case AL_BANDPASS_GAINHF: + if(!(val >= AL_BANDPASS_MIN_GAINHF && val <= AL_BANDPASS_MAX_GAINHF)) + throw al::context_error{AL_INVALID_VALUE, "Band-pass gainhf %f out of range", val}; + filter->GainHF = val; + return; + + case AL_BANDPASS_GAINLF: + if(!(val >= AL_BANDPASS_MIN_GAINLF && val <= AL_BANDPASS_MAX_GAINLF)) + throw al::context_error{AL_INVALID_VALUE, "Band-pass gainlf %f out of range", val}; + filter->GainLF = val; + return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; +} +template<> +void FilterTable::setParamfv(ALfilter *filter, ALenum param, const float *vals) +{ setParamf(filter, param, *vals); } +template<> +void FilterTable::getParami(const ALfilter*, ALenum param, int*) +{ throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass integer property 0x%04x", param}; } +template<> +void FilterTable::getParamiv(const ALfilter *filter, ALenum param, int *values) +{ getParami(filter, param, values); } +template<> +void FilterTable::getParamf(const ALfilter *filter, ALenum param, float *val) +{ + switch(param) + { + case AL_BANDPASS_GAIN: *val = filter->Gain; return; + case AL_BANDPASS_GAINHF: *val = filter->GainHF; return; + case AL_BANDPASS_GAINLF: *val = filter->GainLF; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid band-pass float property 0x%04x", param}; +} +template<> +void FilterTable::getParamfv(const ALfilter *filter, ALenum param, float *vals) +{ getParamf(filter, param, vals); } + + +AL_API DECL_FUNC2(void, alGenFilters, ALsizei,n, ALuint*,filters) +FORCE_ALIGN void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d filters", n}; if(n <= 0) UNLIKELY return; ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; + + const al::span fids{filters, static_cast(n)}; + if(!EnsureFilters(device, fids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d filter%s", n, + (n == 1) ? "" : "s"}; + + std::generate(fids.begin(), fids.end(), [device]{ return AllocFilter(device)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC2(void, alDeleteFilters, ALsizei,n, const ALuint*,filters) +FORCE_ALIGN void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, + const ALuint *filters) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d filters", n}; + if(n <= 0) UNLIKELY return; + + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; /* First try to find any filters that are invalid. */ auto validate_filter = [device](const ALuint fid) -> bool { return !fid || LookupFilter(device, fid) != nullptr; }; - const ALuint *filters_end = filters + n; - auto invflt = std::find_if_not(filters, filters_end, validate_filter); - if(invflt != filters_end) UNLIKELY - { - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", *invflt); - return; - } + const al::span fids{filters, static_cast(n)}; + auto invflt = std::find_if_not(fids.begin(), fids.end(), validate_filter); + if(invflt != fids.end()) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", *invflt}; /* All good. Delete non-0 filter IDs. */ auto delete_filter = [device](const ALuint fid) -> void { - ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}; - if(filter) FreeFilter(device, filter); + if(ALfilter *filter{fid ? LookupFilter(device, fid) : nullptr}) + FreeFilter(device, filter); }; - std::for_each(filters, filters_end, delete_filter); + std::for_each(fids.begin(), fids.end(), delete_filter); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsFilter, ALuint,filter) +FORCE_ALIGN ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - if(!filter || LookupFilter(device, filter)) - return AL_TRUE; - } + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + if(!filter || LookupFilter(device, filter)) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC3(void, alFilteri, ALuint,filter, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint value) noexcept +try { ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - { - if(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS - || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS) - InitFilterParams(alfilt, value); - else - context->setError(AL_INVALID_VALUE, "Invalid filter type 0x%04x", value); - } - else try - { - /* Call the appropriate handler */ - alfilt->setParami(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } - } -} -END_API_FUNC + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; -AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *values) -START_API_FUNC -{ switch(param) { case AL_FILTER_TYPE: - alFilteri(filter, param, values[0]); + if(!(value == AL_FILTER_NULL || value == AL_FILTER_LOWPASS + || value == AL_FILTER_HIGHPASS || value == AL_FILTER_BANDPASS)) + throw al::context_error{AL_INVALID_VALUE, "Invalid filter type 0x%04x", value}; + InitFilterParams(alfilt, value); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamiv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.setParami(alfilt, param, value);}, + alfilt->mTypeVariant); } -END_API_FUNC - -AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamf(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->setParamfv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else - { - if(param == AL_FILTER_TYPE) - *value = alfilt->type; - else try - { - /* Call the appropriate handler */ - alfilt->getParami(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *values) -START_API_FUNC -{ +AL_API DECL_FUNC3(void, alFilteriv, ALuint,filter, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, + const ALint *values) noexcept +try { switch(param) { case AL_FILTER_TYPE: - alGetFilteri(filter, param, values); + alFilteriDirect(context, filter, param, *values); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.setParamiv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alFilterf, ALuint,filter, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.setParamf(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alFilterfv, ALuint,filter, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, + const ALfloat *values) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.setParamfv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilteri, ALuint,filter, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint *value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + switch(param) + { + case AL_FILTER_TYPE: + *value = alfilt->type; + return; + } + + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.getParami(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilteriv, ALuint,filter, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, + ALint *values) noexcept +try { + switch(param) + { + case AL_FILTER_TYPE: + alGetFilteriDirect(context, filter, param, values); + return; + } ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.getParamiv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilterf, ALuint,filter, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat *value) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; const ALfilter *alfilt{LookupFilter(device, filter)}; if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamiv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; -AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *value) -START_API_FUNC + /* Call the appropriate handler */ + std::visit([alfilt,param,value](auto&& thunk){thunk.getParamf(alfilt, param, value);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetFilterfv, ALuint,filter, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, + ALfloat *values) noexcept +try { + ALCdevice *device{context->mALDevice.get()}; + std::lock_guard filterlock{device->FilterLock}; + + const ALfilter *alfilt{LookupFilter(device, filter)}; + if(!alfilt) UNLIKELY + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", filter}; + + /* Call the appropriate handler */ + std::visit([alfilt,param,values](auto&& thunk){thunk.getParamfv(alfilt, param, values);}, + alfilt->mTypeVariant); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + + +void ALfilter::SetName(ALCcontext *context, ALuint id, std::string_view name) { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; + std::lock_guard filterlock{device->FilterLock}; - const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamf(param, value); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } + auto filter = LookupFilter(device, id); + if(!filter) + throw al::context_error{AL_INVALID_NAME, "Invalid filter ID %u", id}; + + device->mFilterNames.insert_or_assign(id, name); } -END_API_FUNC - -AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALCdevice *device{context->mALDevice.get()}; - std::lock_guard _{device->FilterLock}; - - const ALfilter *alfilt{LookupFilter(device, filter)}; - if(!alfilt) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid filter ID %u", filter); - else try - { - /* Call the appropriate handler */ - alfilt->getParamfv(param, values); - } - catch(filter_exception &e) { - context->setError(e.errorCode(), "%s", e.what()); - } -} -END_API_FUNC FilterSubList::~FilterSubList() { + if(!Filters) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; - al::destroy_at(Filters+idx); + std::destroy_at(al::to_address(Filters->begin() + idx)); usemask &= ~(1_u64 << idx); } FreeMask = ~usemask; - al_free(Filters); + SubListAllocator{}.deallocate(Filters, 1); Filters = nullptr; } diff --git a/Engine/lib/openal-soft/al/filter.h b/Engine/lib/openal-soft/al/filter.h index 65a9e30fd..562ec52e2 100644 --- a/Engine/lib/openal-soft/al/filter.h +++ b/Engine/lib/openal-soft/al/filter.h @@ -1,15 +1,40 @@ #ifndef AL_FILTER_H #define AL_FILTER_H +#include +#include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" -#include "AL/alext.h" +#include "AL/efx.h" #include "almalloc.h" +#include "alnumeric.h" -#define LOWPASSFREQREF 5000.0f -#define HIGHPASSFREQREF 250.0f + +inline constexpr float LowPassFreqRef{5000.0f}; +inline constexpr float HighPassFreqRef{250.0f}; + +template +struct FilterTable { + static void setParami(struct ALfilter*, ALenum, int); + static void setParamiv(struct ALfilter*, ALenum, const int*); + static void setParamf(struct ALfilter*, ALenum, float); + static void setParamfv(struct ALfilter*, ALenum, const float*); + + static void getParami(const struct ALfilter*, ALenum, int*); + static void getParamiv(const struct ALfilter*, ALenum, int*); + static void getParamf(const struct ALfilter*, ALenum, float*); + static void getParamfv(const struct ALfilter*, ALenum, float*); +}; + +struct NullFilterTable : public FilterTable { }; +struct LowpassFilterTable : public FilterTable { }; +struct HighpassFilterTable : public FilterTable { }; +struct BandpassFilterTable : public FilterTable { }; struct ALfilter { @@ -17,36 +42,35 @@ struct ALfilter { float Gain{1.0f}; float GainHF{1.0f}; - float HFReference{LOWPASSFREQREF}; + float HFReference{LowPassFreqRef}; float GainLF{1.0f}; - float LFReference{HIGHPASSFREQREF}; + float LFReference{HighPassFreqRef}; - struct Vtable { - void (*const setParami )(ALfilter *filter, ALenum param, int val); - void (*const setParamiv)(ALfilter *filter, ALenum param, const int *vals); - void (*const setParamf )(ALfilter *filter, ALenum param, float val); - void (*const setParamfv)(ALfilter *filter, ALenum param, const float *vals); - - void (*const getParami )(const ALfilter *filter, ALenum param, int *val); - void (*const getParamiv)(const ALfilter *filter, ALenum param, int *vals); - void (*const getParamf )(const ALfilter *filter, ALenum param, float *val); - void (*const getParamfv)(const ALfilter *filter, ALenum param, float *vals); - }; - const Vtable *vtab{nullptr}; + using TableTypes = std::variant; + TableTypes mTypeVariant; /* Self ID */ ALuint id{0}; - void setParami(ALenum param, int value) { vtab->setParami(this, param, value); } - void setParamiv(ALenum param, const int *values) { vtab->setParamiv(this, param, values); } - void setParamf(ALenum param, float value) { vtab->setParamf(this, param, value); } - void setParamfv(ALenum param, const float *values) { vtab->setParamfv(this, param, values); } - void getParami(ALenum param, int *value) const { vtab->getParami(this, param, value); } - void getParamiv(ALenum param, int *values) const { vtab->getParamiv(this, param, values); } - void getParamf(ALenum param, float *value) const { vtab->getParamf(this, param, value); } - void getParamfv(ALenum param, float *values) const { vtab->getParamfv(this, param, values); } + static void SetName(ALCcontext *context, ALuint id, std::string_view name); - DISABLE_ALLOC() + DISABLE_ALLOC +}; + +struct FilterSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Filters{nullptr}; + + FilterSubList() noexcept = default; + FilterSubList(const FilterSubList&) = delete; + FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} + { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } + ~FilterSubList(); + + FilterSubList& operator=(const FilterSubList&) = delete; + FilterSubList& operator=(FilterSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } }; #endif diff --git a/Engine/lib/openal-soft/al/listener.cpp b/Engine/lib/openal-soft/al/listener.cpp index 06d7c370d..30ceed040 100644 --- a/Engine/lib/openal-soft/al/listener.cpp +++ b/Engine/lib/openal-soft/al/listener.cpp @@ -22,6 +22,7 @@ #include "listener.h" +#include #include #include @@ -30,9 +31,10 @@ #include "AL/efx.h" #include "alc/context.h" -#include "almalloc.h" -#include "atomic.h" -#include "core/except.h" +#include "alc/inprogext.h" +#include "alspan.h" +#include "direct_defs.h" +#include "error.h" #include "opthelpers.h" @@ -68,377 +70,333 @@ inline void CommitAndUpdateProps(ALCcontext *context) } // namespace -AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - +AL_API DECL_FUNC2(void, alListenerf, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) noexcept +try { ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_GAIN: if(!(value >= 0.0f && std::isfinite(value))) - return context->setError(AL_INVALID_VALUE, "Listener gain out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener gain out of range"}; listener.Gain = value; - UpdateProps(context.get()); - break; + UpdateProps(context); + return; case AL_METERS_PER_UNIT: if(!(value >= AL_MIN_METERS_PER_UNIT && value <= AL_MAX_METERS_PER_UNIT)) - return context->setError(AL_INVALID_VALUE, "Listener meters per unit out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener meters per unit out of range"}; listener.mMetersPerUnit = value; - UpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float property"); + UpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC - -AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC4(void, alListener3f, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, + ALfloat value2, ALfloat value3) noexcept +try { ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_POSITION: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - return context->setError(AL_INVALID_VALUE, "Listener position out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener position out of range"}; listener.Position[0] = value1; listener.Position[1] = value2; listener.Position[2] = value3; - CommitAndUpdateProps(context.get()); - break; + CommitAndUpdateProps(context); + return; case AL_VELOCITY: if(!(std::isfinite(value1) && std::isfinite(value2) && std::isfinite(value3))) - return context->setError(AL_INVALID_VALUE, "Listener velocity out of range"); + throw al::context_error{AL_INVALID_VALUE, "Listener velocity out of range"}; listener.Velocity[0] = value1; listener.Velocity[1] = value2; listener.Velocity[2] = value3; - CommitAndUpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); + CommitAndUpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) -START_API_FUNC -{ - if(values) +AL_API DECL_FUNC2(void, alListenerfv, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, + const ALfloat *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + switch(param) { - switch(param) - { - case AL_GAIN: - case AL_METERS_PER_UNIT: - alListenerf(param, values[0]); - return; + case AL_GAIN: + case AL_METERS_PER_UNIT: + alListenerfDirect(context, param, *values); + return; - case AL_POSITION: - case AL_VELOCITY: - alListener3f(param, values[0], values[1], values[2]); - return; - } + case AL_POSITION: + case AL_VELOCITY: + auto vals = al::span{values, 3_uz}; + alListener3fDirect(context, param, vals[0], vals[1], vals[2]); + return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; switch(param) { case AL_ORIENTATION: - if(!(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) && - std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5]))) + auto vals = al::span{values, 6_uz}; + if(!std::all_of(vals.cbegin(), vals.cend(), [](float f) { return std::isfinite(f); })) return context->setError(AL_INVALID_VALUE, "Listener orientation out of range"); /* AT then UP */ - listener.OrientAt[0] = values[0]; - listener.OrientAt[1] = values[1]; - listener.OrientAt[2] = values[2]; - listener.OrientUp[0] = values[3]; - listener.OrientUp[1] = values[4]; - listener.OrientUp[2] = values[5]; - CommitAndUpdateProps(context.get()); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property"); + std::copy_n(vals.cbegin(), 3, listener.OrientAt.begin()); + std::copy_n(vals.cbegin()+3, 3, listener.OrientUp.begin()); + CommitAndUpdateProps(context); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alListeneri(ALenum param, ALint /*value*/) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3) -START_API_FUNC -{ + +AL_API DECL_FUNC2(void, alListeneri, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint /*value*/) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC4(void, alListener3i, ALenum,param, ALint,value1, ALint,value2, ALint,value3) +FORCE_ALIGN void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, + ALint value2, ALint value3) noexcept +try { switch(param) { case AL_POSITION: case AL_VELOCITY: - alListener3f(param, static_cast(value1), static_cast(value2), - static_cast(value3)); + alListener3fDirect(context, param, static_cast(value1), + static_cast(value2), static_cast(value3)); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} - std::lock_guard _{context->mPropLock}; +AL_API DECL_FUNC2(void, alListeneriv, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, + const ALint *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + al::span vals; switch(param) { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property"); + case AL_POSITION: + case AL_VELOCITY: + vals = {values, 3_uz}; + alListener3fDirect(context, param, static_cast(vals[0]), + static_cast(vals[1]), static_cast(vals[2])); + return; + + case AL_ORIENTATION: + vals = {values, 6_uz}; + const std::array fvals{static_cast(vals[0]), static_cast(vals[1]), + static_cast(vals[2]), static_cast(vals[3]), + static_cast(vals[4]), static_cast(vals[5]), + }; + alListenerfvDirect(context, param, fvals.data()); + return; } + + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", + param}; } -END_API_FUNC - -AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values) -START_API_FUNC -{ - if(values) - { - ALfloat fvals[6]; - switch(param) - { - case AL_POSITION: - case AL_VELOCITY: - alListener3f(param, static_cast(values[0]), static_cast(values[1]), - static_cast(values[2])); - return; - - case AL_ORIENTATION: - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(values[5]); - alListenerfv(param, fvals); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - if(!values) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; +AL_API DECL_FUNC2(void, alGetListenerf, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, + ALfloat *value) noexcept +try { if(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - case AL_GAIN: - *value = listener.Gain; - break; - - case AL_METERS_PER_UNIT: - *value = listener.mMetersPerUnit; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float property"); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; + switch(param) + { + case AL_GAIN: *value = listener.Gain; return; + case AL_METERS_PER_UNIT: *value = listener.mMetersPerUnit; return; + } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC4(void, alGetListener3f, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) +FORCE_ALIGN void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, + ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept +try { if(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + ALlistener &listener = context->mListener; + std::lock_guard proplock{context->mPropLock}; + switch(param) { case AL_POSITION: *value1 = listener.Position[0]; *value2 = listener.Position[1]; *value3 = listener.Position[2]; - break; + return; case AL_VELOCITY: *value1 = listener.Velocity[0]; *value2 = listener.Velocity[1]; *value3 = listener.Velocity[2]; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-float property"); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-float property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) -START_API_FUNC -{ +AL_API DECL_FUNC2(void, alGetListenerfv, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, + ALfloat *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + switch(param) { case AL_GAIN: case AL_METERS_PER_UNIT: - alGetListenerf(param, values); + alGetListenerfDirect(context, param, values); return; case AL_POSITION: case AL_VELOCITY: - alGetListener3f(param, values+0, values+1, values+2); + auto vals = al::span{values, 3_uz}; + alGetListener3fDirect(context, param, &vals[0], &vals[1], &vals[2]); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard proplock{context->mPropLock}; + switch(param) { case AL_ORIENTATION: + al::span vals{values, 6_uz}; // AT then UP - values[0] = listener.OrientAt[0]; - values[1] = listener.OrientAt[1]; - values[2] = listener.OrientAt[2]; - values[3] = listener.OrientUp[0]; - values[4] = listener.OrientUp[1]; - values[5] = listener.OrientUp[2]; - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener float-vector property"); + std::copy_n(listener.OrientAt.cbegin(), 3, vals.begin()); + std::copy_n(listener.OrientUp.cbegin(), 3, vals.begin()+3); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener float-vector property 0x%x", param}; } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - if(!value) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) - { - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer property"); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; + +AL_API DECL_FUNC2(void, alGetListeneri, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) noexcept +try { + if(!value) throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + std::lock_guard proplock{context->mPropLock}; + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC4(void, alGetListener3i, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) +FORCE_ALIGN void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, + ALint *value1, ALint *value2, ALint *value3) noexcept +try { + if(!value1 || !value2 || !value3) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!value1 || !value2 || !value3) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard proplock{context->mPropLock}; + switch(param) { case AL_POSITION: *value1 = static_cast(listener.Position[0]); *value2 = static_cast(listener.Position[1]); *value3 = static_cast(listener.Position[2]); - break; + return; case AL_VELOCITY: *value1 = static_cast(listener.Velocity[0]); *value2 = static_cast(listener.Velocity[1]); *value3 = static_cast(listener.Velocity[2]); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener 3-integer property"); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener 3-integer property 0x%x", param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint* values) -START_API_FUNC -{ +AL_API DECL_FUNC2(void, alGetListeneriv, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, + ALint *values) noexcept +try { + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + switch(param) { case AL_POSITION: case AL_VELOCITY: - alGetListener3i(param, values+0, values+1, values+2); + auto vals = al::span{values, 3_uz}; + alGetListener3iDirect(context, param, &vals[0], &vals[1], &vals[2]); return; } - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - ALlistener &listener = context->mListener; - std::lock_guard _{context->mPropLock}; - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(param) + std::lock_guard proplock{context->mPropLock}; + + static constexpr auto f2i = [](const float val) noexcept { return static_cast(val); }; + switch(param) { case AL_ORIENTATION: + auto vals = al::span{values, 6_uz}; // AT then UP - values[0] = static_cast(listener.OrientAt[0]); - values[1] = static_cast(listener.OrientAt[1]); - values[2] = static_cast(listener.OrientAt[2]); - values[3] = static_cast(listener.OrientUp[0]); - values[4] = static_cast(listener.OrientUp[1]); - values[5] = static_cast(listener.OrientUp[2]); - break; - - default: - context->setError(AL_INVALID_ENUM, "Invalid listener integer-vector property"); + std::transform(listener.OrientAt.cbegin(), listener.OrientAt.cend(), vals.begin(), f2i); + std::transform(listener.OrientUp.cbegin(), listener.OrientUp.cend(), vals.begin()+3, f2i); + return; } + throw al::context_error{AL_INVALID_ENUM, "Invalid listener integer-vector property 0x%x", + param}; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC diff --git a/Engine/lib/openal-soft/al/listener.h b/Engine/lib/openal-soft/al/listener.h index 815328771..7a4ff530f 100644 --- a/Engine/lib/openal-soft/al/listener.h +++ b/Engine/lib/openal-soft/al/listener.h @@ -3,8 +3,6 @@ #include -#include "AL/al.h" -#include "AL/alc.h" #include "AL/efx.h" #include "almalloc.h" @@ -18,7 +16,7 @@ struct ALlistener { float Gain{1.0f}; float mMetersPerUnit{AL_DEFAULT_METERS_PER_UNIT}; - DISABLE_ALLOC() + DISABLE_ALLOC }; #endif diff --git a/Engine/lib/openal-soft/al/source.cpp b/Engine/lib/openal-soft/al/source.cpp index cba33862e..514d4f7fe 100644 --- a/Engine/lib/openal-soft/al/source.cpp +++ b/Engine/lib/openal-soft/al/source.cpp @@ -25,22 +25,28 @@ #include #include #include +#include #include #include -#include +#include #include #include -#include -#include +#include +#include #include #include #include #include -#include #include +#include +#include #include -#include +#include +#include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -48,41 +54,43 @@ #include "AL/efx.h" #include "albit.h" -#include "alc/alu.h" #include "alc/backends/base.h" #include "alc/context.h" #include "alc/device.h" #include "alc/inprogext.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "atomic.h" #include "auxeffectslot.h" #include "buffer.h" -#include "core/ambidefs.h" -#include "core/bformatdec.h" -#include "core/except.h" -#include "core/filters/nfc.h" -#include "core/filters/splitter.h" +#include "core/buffer_storage.h" #include "core/logging.h" +#include "core/mixer/defs.h" #include "core/voice_change.h" -#include "event.h" +#include "direct_defs.h" +#include "error.h" #include "filter.h" +#include "flexarray.h" +#include "intrusive_ptr.h" #include "opthelpers.h" -#include "ringbuffer.h" -#include "threads.h" #ifdef ALSOFT_EAX -#include -#endif // ALSOFT_EAX - -bool sBufferSubDataCompat{false}; +#include "eax/api.h" +#include "eax/call.h" +#include "eax/fx_slot_index.h" +#include "eax/utils.h" +#endif namespace { -using namespace std::placeholders; +using SubListAllocator = al::allocator>; using std::chrono::nanoseconds; +using seconds_d = std::chrono::duration; +using source_store_array = std::array; +using source_store_vector = std::vector; +using source_store_variant = std::variant; + Voice *GetSourceVoice(ALsource *source, ALCcontext *context) { @@ -95,7 +103,7 @@ Voice *GetSourceVoice(ALsource *source, ALCcontext *context) if(voice->mSourceID.load(std::memory_order_acquire) == sid) return voice; } - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; return nullptr; } @@ -153,6 +161,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context props->Radius = source->Radius; props->EnhWidth = source->EnhWidth; + props->Panning = source->mPanningEnabled ? source->mPan : 0.0f; props->Direct.Gain = source->Direct.Gain; props->Direct.GainHF = source->Direct.GainHF; @@ -171,7 +180,7 @@ void UpdateSourceProps(const ALsource *source, Voice *voice, ALCcontext *context ret.LFReference = srcsend.LFReference; return ret; }; - std::transform(source->Send.cbegin(), source->Send.cend(), props->Send, copy_send); + std::transform(source->Send.cbegin(), source->Send.cend(), props->Send.begin(), copy_send); if(!props->Send[0].Slot && context->mDefaultSlot) props->Send[0].Slot = context->mDefaultSlot->mSlot; @@ -202,7 +211,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds do { refcount = device->waitForMix(); - *clocktime = GetDeviceClockTime(device); + *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { @@ -212,7 +221,7 @@ int64_t GetSourceSampleOffset(ALsource *Source, ALCcontext *context, nanoseconds readPos += voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != device->MixCount.load(std::memory_order_relaxed)); + } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return 0; @@ -242,7 +251,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl do { refcount = device->waitForMix(); - *clocktime = GetDeviceClockTime(device); + *clocktime = device->getClockTime(); voice = GetSourceVoice(Source, context); if(voice) { @@ -252,7 +261,7 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl readPos += voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != device->MixCount.load(std::memory_order_relaxed)); + } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) return 0.0f; @@ -281,7 +290,8 @@ double GetSourceSecOffset(ALsource *Source, ALCcontext *context, nanoseconds *cl * (Bytes, Samples or Seconds). The offset is relative to the start of the * queue (not the start of the current buffer). */ -double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) +template +NOINLINE T GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) { ALCdevice *device{context->mALDevice.get()}; const VoiceBufferItem *Current{}; @@ -301,10 +311,10 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) readPosFrac = voice->mPositionFrac.load(std::memory_order_relaxed); } std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != device->MixCount.load(std::memory_order_relaxed)); + } while(refcount != device->mMixCount.load(std::memory_order_relaxed)); if(!voice) - return 0.0; + return T{0}; const ALbuffer *BufferFmt{nullptr}; auto BufferList = Source->mQueue.cbegin(); @@ -321,24 +331,48 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) } ASSUME(BufferFmt != nullptr); - double offset{}; + T offset{}; switch(name) { case AL_SEC_OFFSET: - offset = static_cast(readPos) + readPosFrac/double{MixerFracOne}; - offset /= BufferFmt->mSampleRate; + if constexpr(std::is_floating_point_v) + { + offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + offset /= static_cast(BufferFmt->mSampleRate); + } + else + { + readPos /= BufferFmt->mSampleRate; + offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + std::numeric_limits::max())); + } break; case AL_SAMPLE_OFFSET: - offset = static_cast(readPos) + readPosFrac/double{MixerFracOne}; + if constexpr(std::is_floating_point_v) + offset = static_cast(readPos) + static_cast(readPosFrac)/T{MixerFracOne}; + else + offset = static_cast(std::clamp(readPos, std::numeric_limits::min(), + std::numeric_limits::max())); break; case AL_BYTE_OFFSET: const ALuint BlockSamples{BufferFmt->mBlockAlign}; const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; - /* Round down to the block boundary. */ - offset = static_cast(readPos / BlockSamples) * BlockSize; + readPos = readPos / BlockSamples * BlockSize; + + if constexpr(std::is_floating_point_v) + offset = static_cast(readPos); + else + { + if(readPos > std::numeric_limits::max()) + offset = RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + else if(readPos < std::numeric_limits::min()) + offset = RoundUp(std::numeric_limits::min(), static_cast(BlockSize)); + else + offset = static_cast(readPos); + } break; } return offset; @@ -349,7 +383,8 @@ double GetSourceOffset(ALsource *Source, ALenum name, ALCcontext *context) * Gets the length of the given Source's buffer queue, in the appropriate * format (Bytes, Samples or Seconds). */ -double GetSourceLength(const ALsource *source, ALenum name) +template +NOINLINE T GetSourceLength(const ALsource *source, ALenum name) { uint64_t length{0}; const ALbuffer *BufferFmt{nullptr}; @@ -360,25 +395,40 @@ double GetSourceLength(const ALsource *source, ALenum name) length += listitem.mSampleLen; } if(length == 0) - return 0.0; + return T{0}; ASSUME(BufferFmt != nullptr); switch(name) { case AL_SEC_LENGTH_SOFT: - return static_cast(length) / BufferFmt->mSampleRate; + if constexpr(std::is_floating_point_v) + return static_cast(length) / static_cast(BufferFmt->mSampleRate); + else + return static_cast(std::min(length/BufferFmt->mSampleRate, + std::numeric_limits::max())); case AL_SAMPLE_LENGTH_SOFT: - return static_cast(length); + if constexpr(std::is_floating_point_v) + return static_cast(length); + else + return static_cast(std::min(length, std::numeric_limits::max())); case AL_BYTE_LENGTH_SOFT: const ALuint BlockSamples{BufferFmt->mBlockAlign}; const ALuint BlockSize{BufferFmt->blockSizeFromFmt()}; - /* Round down to the block boundary. */ - return static_cast(length / BlockSamples) * BlockSize; + length = length / BlockSamples * BlockSize; + + if constexpr(std::is_floating_point_v) + return static_cast(length); + else + { + if(length > uint64_t{std::numeric_limits::max()}) + return RoundDown(std::numeric_limits::max(), static_cast(BlockSize)); + return static_cast(length); + } } - return 0.0; + return T{0}; } @@ -392,11 +442,11 @@ struct VoicePos { * GetSampleOffset * * Retrieves the voice position, fixed-point fraction, and bufferlist item - * using the givem offset type and offset. If the offset is out of range, + * using the given offset type and offset. If the offset is out of range, * returns an empty optional. */ -al::optional GetSampleOffset(al::deque &BufferList, ALenum OffsetType, - double Offset) +std::optional GetSampleOffset(std::deque &BufferList, + ALenum OffsetType, double Offset) { /* Find the first valid Buffer in the Queue */ const ALbuffer *BufferFmt{nullptr}; @@ -406,7 +456,7 @@ al::optional GetSampleOffset(al::deque &BufferList, if(BufferFmt) break; } if(!BufferFmt) UNLIKELY - return al::nullopt; + return std::nullopt; /* Get sample frame offset */ int64_t offset{}; @@ -426,7 +476,7 @@ al::optional GetSampleOffset(al::deque &BufferList, dblfrac += 1.0; } offset = static_cast(dbloff); - frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_SAMPLE_OFFSET: @@ -437,7 +487,7 @@ al::optional GetSampleOffset(al::deque &BufferList, dblfrac += 1.0; } offset = static_cast(dbloff); - frac = static_cast(mind(dblfrac*MixerFracOne, MixerFracOne-1.0)); + frac = static_cast(std::min(dblfrac*MixerFracOne, MixerFracOne-1.0)); break; case AL_BYTE_OFFSET: @@ -452,12 +502,12 @@ al::optional GetSampleOffset(al::deque &BufferList, if(offset < 0) { if(offset < std::numeric_limits::min()) - return al::nullopt; + return std::nullopt; return VoicePos{static_cast(offset), frac, &BufferList.front()}; } if(BufferFmt->mCallback) - return al::nullopt; + return std::nullopt; int64_t totalBufferLen{0}; for(auto &item : BufferList) @@ -473,7 +523,7 @@ al::optional GetSampleOffset(al::deque &BufferList, } /* Offset is out of range of the queue */ - return al::nullopt; + return std::nullopt; } @@ -485,9 +535,12 @@ void InitVoice(Voice *voice, ALsource *source, ALbufferQueueItem *BufferList, AL ALbuffer *buffer{BufferList->mBuffer}; voice->mFrequency = buffer->mSampleRate; - voice->mFmtChannels = - (buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) ? - FmtSuperStereo : buffer->mChannels; + if(buffer->mChannels == FmtMono && source->mPanningEnabled) + voice->mFmtChannels = FmtMonoDup; + else if(buffer->mChannels == FmtStereo && source->mStereoMode == SourceStereo::Enhanced) + voice->mFmtChannels = FmtSuperStereo; + else + voice->mFmtChannels = buffer->mChannels; voice->mFmtType = buffer->mType; voice->mFrameStep = buffer->channelsFromFmt(); voice->mBytesPerBlock = buffer->blockSizeFromFmt(); @@ -534,7 +587,7 @@ void SendVoiceChanges(ALCcontext *ctx, VoiceChange *tail) oldhead->mNext.store(tail, std::memory_order_release); const bool connected{device->Connected.load(std::memory_order_acquire)}; - device->waitForMix(); + std::ignore = device->waitForMix(); if(!connected) UNLIKELY { if(ctx->mStopVoicesOnDisconnect.load(std::memory_order_acquire)) @@ -640,7 +693,7 @@ bool SetVoiceOffset(Voice *oldvoice, const VoicePos &vpos, ALsource *source, ALC return true; /* Otherwise, wait for any current mix to finish and check one last time. */ - device->waitForMix(); + std::ignore = device->waitForMix(); if(newvoice->mPlayState.load(std::memory_order_acquire) != Voice::Pending) return true; /* The change-over failed because the old voice stopped before the new @@ -676,31 +729,30 @@ inline ALenum GetSourceState(ALsource *source, Voice *voice) bool EnsureSources(ALCcontext *context, size_t needed) { - size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), - size_t{0}, + size_t count{std::accumulate(context->mSourceList.cbegin(), context->mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(sublist.FreeMask)); })}; - while(needed > count) - { - if(context->mSourceList.size() >= 1<<25) UNLIKELY - return false; - - context->mSourceList.emplace_back(); - auto sublist = context->mSourceList.end() - 1; - sublist->FreeMask = ~0_u64; - sublist->Sources = static_cast(al_calloc(alignof(ALsource), sizeof(ALsource)*64)); - if(!sublist->Sources) UNLIKELY + try { + while(needed > count) { - context->mSourceList.pop_back(); - return false; + if(context->mSourceList.size() >= 1<<25) UNLIKELY + return false; + + SourceSubList sublist{}; + sublist.FreeMask = ~0_u64; + sublist.Sources = SubListAllocator{}.allocate(1); + context->mSourceList.emplace_back(std::move(sublist)); + count += std::tuple_size_v; } - count += 64; + } + catch(...) { + return false; } return true; } -ALsource *AllocSource(ALCcontext *context) +ALsource *AllocSource(ALCcontext *context) noexcept { auto sublist = std::find_if(context->mSourceList.begin(), context->mSourceList.end(), [](const SourceSubList &entry) noexcept -> bool @@ -709,7 +761,10 @@ ALsource *AllocSource(ALCcontext *context) auto slidx = static_cast(al::countr_zero(sublist->FreeMask)); ASSUME(slidx < 64); - ALsource *source{al::construct_at(sublist->Sources + slidx)}; + ALsource *source{al::construct_at(al::to_address(sublist->Sources->begin() + slidx))}; +#ifdef ALSOFT_EAX + source->eaxInitialize(context); +#endif // ALSOFT_EAX /* Add 1 to avoid source ID 0. */ source->id = ((lidx<<6) | slidx) + 1; @@ -722,6 +777,8 @@ ALsource *AllocSource(ALCcontext *context) void FreeSource(ALCcontext *context, ALsource *source) { + context->mSourceNames.erase(source->id); + const ALuint id{source->id - 1}; const size_t lidx{id >> 6}; const ALuint slidx{id & 0x3f}; @@ -738,7 +795,7 @@ void FreeSource(ALCcontext *context, ALsource *source) SendVoiceChanges(context, vchg); } - al::destroy_at(source); + std::destroy_at(source); context->mSourceList[lidx].FreeMask |= 1_u64 << slidx; context->mNumSources--; @@ -755,59 +812,58 @@ inline ALsource *LookupSource(ALCcontext *context, ALuint id) noexcept SourceSubList &sublist{context->mSourceList[lidx]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Sources + slidx; + return al::to_address(sublist.Sources->begin() + slidx); } -inline ALbuffer *LookupBuffer(ALCdevice *device, ALuint id) noexcept +auto LookupBuffer = [](ALCdevice *device, auto id) noexcept -> ALbuffer* { - const size_t lidx{(id-1) >> 6}; - const ALuint slidx{(id-1) & 0x3f}; + const auto lidx{(id-1) >> 6}; + const auto slidx{(id-1) & 0x3f}; if(lidx >= device->BufferList.size()) UNLIKELY return nullptr; - BufferSubList &sublist = device->BufferList[lidx]; + BufferSubList &sublist = device->BufferList[static_cast(lidx)]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Buffers + slidx; -} + return al::to_address(sublist.Buffers->begin() + static_cast(slidx)); +}; -inline ALfilter *LookupFilter(ALCdevice *device, ALuint id) noexcept +auto LookupFilter = [](ALCdevice *device, auto id) noexcept -> ALfilter* { - const size_t lidx{(id-1) >> 6}; - const ALuint slidx{(id-1) & 0x3f}; + const auto lidx{(id-1) >> 6}; + const auto slidx{(id-1) & 0x3f}; if(lidx >= device->FilterList.size()) UNLIKELY return nullptr; - FilterSubList &sublist = device->FilterList[lidx]; + FilterSubList &sublist = device->FilterList[static_cast(lidx)]; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.Filters + slidx; -} + return al::to_address(sublist.Filters->begin() + static_cast(slidx)); +}; -inline ALeffectslot *LookupEffectSlot(ALCcontext *context, ALuint id) noexcept +auto LookupEffectSlot = [](ALCcontext *context, auto id) noexcept -> ALeffectslot* { - const size_t lidx{(id-1) >> 6}; - const ALuint slidx{(id-1) & 0x3f}; + const auto lidx{(id-1) >> 6}; + const auto slidx{(id-1) & 0x3f}; if(lidx >= context->mEffectSlotList.size()) UNLIKELY return nullptr; - EffectSlotSubList &sublist{context->mEffectSlotList[lidx]}; + EffectSlotSubList &sublist{context->mEffectSlotList[static_cast(lidx)]}; if(sublist.FreeMask & (1_u64 << slidx)) UNLIKELY return nullptr; - return sublist.EffectSlots + slidx; -} + return al::to_address(sublist.EffectSlots->begin() + static_cast(slidx)); +}; -al::optional StereoModeFromEnum(ALenum mode) +auto StereoModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { case AL_NORMAL_SOFT: return SourceStereo::Normal; case AL_SUPER_STEREO_SOFT: return SourceStereo::Enhanced; } - WARN("Unsupported stereo mode: 0x%04x\n", mode); - return al::nullopt; -} + return std::nullopt; +}; ALenum EnumFromStereoMode(SourceStereo mode) { switch(mode) @@ -818,7 +874,7 @@ ALenum EnumFromStereoMode(SourceStereo mode) throw std::runtime_error{"Invalid SourceStereo: "+std::to_string(int(mode))}; } -al::optional SpatializeModeFromEnum(ALenum mode) +auto SpatializeModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { @@ -826,9 +882,8 @@ al::optional SpatializeModeFromEnum(ALenum mode) case AL_TRUE: return SpatializeMode::On; case AL_AUTO_SOFT: return SpatializeMode::Auto; } - WARN("Unsupported spatialize mode: 0x%04x\n", mode); - return al::nullopt; -} + return std::nullopt; +}; ALenum EnumFromSpatializeMode(SpatializeMode mode) { switch(mode) @@ -840,7 +895,7 @@ ALenum EnumFromSpatializeMode(SpatializeMode mode) throw std::runtime_error{"Invalid SpatializeMode: "+std::to_string(int(mode))}; } -al::optional DirectModeFromEnum(ALenum mode) +auto DirectModeFromEnum = [](auto mode) noexcept -> std::optional { switch(mode) { @@ -848,9 +903,8 @@ al::optional DirectModeFromEnum(ALenum mode) case AL_DROP_UNMATCHED_SOFT: return DirectMode::DropMismatch; case AL_REMIX_UNMATCHED_SOFT: return DirectMode::RemixMismatch; } - WARN("Unsupported direct mode: 0x%04x\n", mode); - return al::nullopt; -} + return std::nullopt; +}; ALenum EnumFromDirectMode(DirectMode mode) { switch(mode) @@ -862,7 +916,7 @@ ALenum EnumFromDirectMode(DirectMode mode) throw std::runtime_error{"Invalid DirectMode: "+std::to_string(int(mode))}; } -al::optional DistanceModelFromALenum(ALenum model) +auto DistanceModelFromALenum = [](auto model) noexcept -> std::optional { switch(model) { @@ -874,8 +928,8 @@ al::optional DistanceModelFromALenum(ALenum model) case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent; case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped; } - return al::nullopt; -} + return std::nullopt; +}; ALenum ALenumFromDistanceModel(DistanceModel model) { switch(model) @@ -970,11 +1024,13 @@ enum SourceProp : ALenum { /* AL_SOFT_buffer_sub_data */ srcByteRWOffsetsSOFT = AL_BYTE_RW_OFFSETS_SOFT, srcSampleRWOffsetsSOFT = AL_SAMPLE_RW_OFFSETS_SOFT, + + /* AL_SOFT_source_panning */ + srcPanningEnabledSOFT = AL_PANNING_ENABLED_SOFT, + srcPanSOFT = AL_PAN_SOFT, }; -constexpr size_t MaxValues{6u}; - constexpr ALuint IntValsByProp(ALenum prop) { switch(static_cast(prop)) @@ -999,6 +1055,8 @@ constexpr ALuint IntValsByProp(ALenum prop) case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1076,6 +1134,8 @@ constexpr ALuint Int64ValsByProp(ALenum prop) case AL_SOURCE_RESAMPLER_SOFT: case AL_SOURCE_SPATIALIZE_SOFT: case AL_STEREO_MODE_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1169,6 +1229,8 @@ constexpr ALuint FloatValsByProp(ALenum prop) case AL_SEC_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1242,6 +1304,8 @@ constexpr ALuint DoubleValsByProp(ALenum prop) case AL_SEC_LENGTH_SOFT: case AL_STEREO_MODE_SOFT: case AL_SUPER_STEREO_WIDTH_SOFT: + case AL_PANNING_ENABLED_SOFT: + case AL_PAN_SOFT: return 1; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ @@ -1276,22 +1340,6 @@ constexpr ALuint DoubleValsByProp(ALenum prop) } -void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); - -struct check_exception : std::exception { -}; -struct check_size_exception final : check_exception { - const char *what() const noexcept override - { return "check_size_exception"; } -}; -struct check_value_exception final : check_exception { - const char *what() const noexcept override - { return "check_value_exception"; } -}; - - void UpdateSourceProps(ALsource *source, ALCcontext *context) { if(!context->mDeferUpdates) @@ -1327,10 +1375,40 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) #endif +template +struct PropType { }; +template<> +struct PropType { static const char *Name() { return "integer"; } }; +template<> +struct PropType { static const char *Name() { return "int64"; } }; +template<> +struct PropType { static const char *Name() { return "float"; } }; +template<> +struct PropType { static const char *Name() { return "double"; } }; + +struct HexPrinter { + std::array mStr{}; + + template + HexPrinter(T value) + { + using ST = std::make_signed_t>; + if constexpr(std::is_same_v) + std::snprintf(mStr.data(), mStr.size(), "0x%x", value); + else if constexpr(std::is_same_v) + std::snprintf(mStr.data(), mStr.size(), "0x%lx", value); + else if constexpr(std::is_same_v) + std::snprintf(mStr.data(), mStr.size(), "0x%llx", value); + } + + [[nodiscard]] auto c_str() const noexcept -> const char* { return mStr.data(); } +}; + + /** - * Returns a pair of lambdas to check the following setters and getters. + * Returns a pair of lambdas to check the following setter. * - * The first lambda checks the size of the span is valid for its given size, + * The first lambda checks the size of the span is valid for the required size, * setting the proper context error and throwing a check_size_exception if it * fails. * @@ -1338,1007 +1416,918 @@ inline void CommitAndUpdateSourceProps(ALsource *source, ALCcontext *context) * context error and throwing a check_value_exception if it failed. */ template -auto GetCheckers(ALCcontext *const Context, const SourceProp prop, const al::span values) +auto GetCheckers(const SourceProp prop, const al::span values) { return std::make_pair( [=](size_t expect) -> void { - if(values.size() == expect) LIKELY return; - Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", - prop, expect, values.size()); - throw check_size_exception{}; + if(values.size() == expect) return; + throw al::context_error{AL_INVALID_ENUM, + "Property 0x%04x expects %zu value(s), got %zu", prop, expect, values.size()}; }, - [Context](bool passed) -> void + [](bool passed) -> void { - if(passed) LIKELY return; - Context->setError(AL_INVALID_VALUE, "Value out of range"); - throw check_value_exception{}; + if(passed) return; + throw al::context_error{AL_INVALID_VALUE, "Value out of range"}; } ); } -void SetSourcefv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - /* Structured bindings would be nice (C++17). */ - auto Checkers = GetCheckers(Context, prop, values); - auto &CheckSize = Checkers.first; - auto &CheckValue = Checkers.second; - int ival; +template +NOINLINE void SetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +{ + auto [CheckSize, CheckValue] = GetCheckers(prop, values); + ALCdevice *device{Context->mALDevice.get()}; switch(prop) { + case AL_SOURCE_STATE: + case AL_SOURCE_TYPE: + case AL_BUFFERS_QUEUED: + case AL_BUFFERS_PROCESSED: + if constexpr(std::is_integral_v) + { + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; + } + break; + + case AL_BYTE_LENGTH_SOFT: + case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: + case AL_SAMPLE_OFFSET_LATENCY_SOFT: case AL_SEC_OFFSET_LATENCY_SOFT: + case AL_SAMPLE_OFFSET_CLOCK_SOFT: case AL_SEC_OFFSET_CLOCK_SOFT: /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); + throw al::context_error{AL_INVALID_OPERATION, "Setting read-only source property 0x%04x", + prop}; case AL_PITCH: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->Pitch = values[0]; + Source->Pitch = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_INNER_ANGLE: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 360.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{360}); - Source->InnerAngle = values[0]; + Source->InnerAngle = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_CONE_OUTER_ANGLE: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 360.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{360}); - Source->OuterAngle = values[0]; + Source->OuterAngle = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->Gain = values[0]; + Source->Gain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_DISTANCE: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->MaxDistance = values[0]; + Source->MaxDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_ROLLOFF_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->RolloffFactor = values[0]; + Source->RolloffFactor = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_REFERENCE_DISTANCE: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->RefDistance = values[0]; + Source->RefDistance = static_cast(values[0]); return CommitAndUpdateSourceProps(Source, Context); case AL_MIN_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->MinGain = values[0]; + Source->MinGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_MAX_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f); + CheckValue(values[0] >= T{0}); - Source->MaxGain = values[0]; + Source->MaxGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAIN: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->OuterGain = values[0]; + Source->OuterGain = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_CONE_OUTER_GAINHF: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->OuterGainHF = values[0]; + Source->OuterGainHF = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 10.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{10}); - Source->AirAbsorptionFactor = values[0]; + Source->AirAbsorptionFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 10.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->RoomRolloffFactor = values[0]; + Source->RoomRolloffFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_DOPPLER_FACTOR: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->DopplerFactor = values[0]; + Source->DopplerFactor = static_cast(values[0]); return UpdateSourceProps(Source, Context); + + case AL_SOURCE_RELATIVE: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->HeadRelative = values[0] != AL_FALSE; + return CommitAndUpdateSourceProps(Source, Context); + } + break; + + case AL_LOOPING: + if constexpr(std::is_integral_v) + { + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->Looping = values[0] != AL_FALSE; + if(Voice *voice{GetSourceVoice(Source, Context)}) + { + if(Source->Looping) + voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); + else + voice->mLoopBuffer.store(nullptr, std::memory_order_release); + + /* If the source is playing, wait for the current mix to finish + * to ensure it isn't currently looping back or reaching the + * end. + */ + std::ignore = device->waitForMix(); + } + return; + } + break; + + case AL_BUFFER: + if constexpr(std::is_integral_v) + { + CheckSize(1); + if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + state == AL_PLAYING || state == AL_PAUSED) + throw al::context_error{AL_INVALID_OPERATION, + "Setting buffer on playing or paused source %u", Source->id}; + + std::deque oldlist; + if(values[0]) + { + using UT = std::make_unsigned_t; + std::lock_guard buflock{device->BufferLock}; + ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; + if(!buffer) + throw al::context_error{AL_INVALID_VALUE, "Invalid buffer ID %s", + std::to_string(values[0]).c_str()}; + if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Setting non-persistently mapped buffer %u", buffer->id}; + if(buffer->mCallback && buffer->ref.load(std::memory_order_relaxed) != 0) + throw al::context_error{AL_INVALID_OPERATION, + "Setting already-set callback buffer %u", buffer->id}; + + /* Add the selected buffer to a one-item queue */ + std::deque newlist; + newlist.emplace_back(); + newlist.back().mCallback = buffer->mCallback; + newlist.back().mUserData = buffer->mUserData; + newlist.back().mBlockAlign = buffer->mBlockAlign; + newlist.back().mSampleLen = buffer->mSampleLen; + newlist.back().mLoopStart = buffer->mLoopStart; + newlist.back().mLoopEnd = buffer->mLoopEnd; + newlist.back().mSamples = buffer->mData; + newlist.back().mBuffer = buffer; + IncrementRef(buffer->ref); + + /* Source is now Static */ + Source->SourceType = AL_STATIC; + Source->mQueue.swap(oldlist); + Source->mQueue.swap(newlist); + } + else + { + /* Source is now Undetermined */ + Source->SourceType = AL_UNDETERMINED; + Source->mQueue.swap(oldlist); + } + + /* Delete all elements in the previous queue */ + for(auto &item : oldlist) + { + if(ALbuffer *buffer{item.mBuffer}) + DecrementRef(buffer->ref); + } + return; + } + break; + + case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CheckSize(1); - CheckValue(std::isfinite(values[0])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(values[0])); if(Voice *voice{GetSourceVoice(Source, Context)}) { - auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid offset"); + auto vpos = GetSampleOffset(Source->mQueue, prop, static_cast(values[0])); + if(!vpos) throw al::context_error{AL_INVALID_VALUE, "Invalid offset"}; if(SetVoiceOffset(voice, *vpos, Source, Context, Context->mALDevice.get())) return; } Source->OffsetType = prop; - Source->Offset = values[0]; + Source->Offset = static_cast(values[0]); return; case AL_SAMPLE_RW_OFFSETS_SOFT: + if(sBufferSubDataCompat) + { + if constexpr(std::is_integral_v) + { + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; + } + } break; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ if(sBufferSubDataCompat) + { + if constexpr(std::is_integral_v) + { + /* Query only */ + throw al::context_error{AL_INVALID_OPERATION, + "Setting read-only source property 0x%04x", prop}; + } break; + } CheckSize(1); - CheckValue(values[0] >= 0.0f && std::isfinite(values[0])); + if constexpr(std::is_floating_point_v) + CheckValue(values[0] >= T{0} && std::isfinite(static_cast(values[0]))); + else + CheckValue(values[0] >= T{0}); - Source->Radius = values[0]; + Source->Radius = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); - CheckValue(values[0] >= 0.0f && values[0] <= 1.0f); + CheckValue(values[0] >= T{0} && values[0] <= T{1}); - Source->EnhWidth = values[0]; + Source->EnhWidth = static_cast(values[0]); + return UpdateSourceProps(Source, Context); + + case AL_PANNING_ENABLED_SOFT: + CheckSize(1); + if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + state == AL_PLAYING || state == AL_PAUSED) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying panning enabled on playing or paused source %u", Source->id}; + + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->mPanningEnabled = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); + + case AL_PAN_SOFT: + CheckSize(1); + CheckValue(values[0] >= T{-1} && values[0] <= T{1}); + + Source->mPan = static_cast(values[0]); return UpdateSourceProps(Source, Context); case AL_STEREO_ANGLES: CheckSize(2); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1]))); - Source->StereoPan[0] = values[0]; - Source->StereoPan[1] = values[1]; + Source->StereoPan[0] = static_cast(values[0]); + Source->StereoPan[1] = static_cast(values[1]); return UpdateSourceProps(Source, Context); case AL_POSITION: CheckSize(3); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2]))); - Source->Position[0] = values[0]; - Source->Position[1] = values[1]; - Source->Position[2] = values[2]; + Source->Position[0] = static_cast(values[0]); + Source->Position[1] = static_cast(values[1]); + Source->Position[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_VELOCITY: CheckSize(3); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2]))); - Source->Velocity[0] = values[0]; - Source->Velocity[1] = values[1]; - Source->Velocity[2] = values[2]; + Source->Velocity[0] = static_cast(values[0]); + Source->Velocity[1] = static_cast(values[1]); + Source->Velocity[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_DIRECTION: CheckSize(3); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2]))); - Source->Direction[0] = values[0]; - Source->Direction[1] = values[1]; - Source->Direction[2] = values[2]; + Source->Direction[0] = static_cast(values[0]); + Source->Direction[1] = static_cast(values[1]); + Source->Direction[2] = static_cast(values[2]); return CommitAndUpdateSourceProps(Source, Context); case AL_ORIENTATION: CheckSize(6); - CheckValue(std::isfinite(values[0]) && std::isfinite(values[1]) && std::isfinite(values[2]) - && std::isfinite(values[3]) && std::isfinite(values[4]) && std::isfinite(values[5])); + if constexpr(std::is_floating_point_v) + CheckValue(std::isfinite(static_cast(values[0])) + && std::isfinite(static_cast(values[1])) + && std::isfinite(static_cast(values[2])) + && std::isfinite(static_cast(values[3])) + && std::isfinite(static_cast(values[4])) + && std::isfinite(static_cast(values[5]))); - Source->OrientAt[0] = values[0]; - Source->OrientAt[1] = values[1]; - Source->OrientAt[2] = values[2]; - Source->OrientUp[0] = values[3]; - Source->OrientUp[1] = values[4]; - Source->OrientUp[2] = values[5]; + Source->OrientAt[0] = static_cast(values[0]); + Source->OrientAt[1] = static_cast(values[1]); + Source->OrientAt[2] = static_cast(values[2]); + Source->OrientUp[0] = static_cast(values[3]); + Source->OrientUp[1] = static_cast(values[4]); + Source->OrientUp[2] = static_cast(values[5]); return UpdateSourceProps(Source, Context); - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_SOURCE_TYPE: - case AL_DISTANCE_MODEL: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - ival = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {&ival, 1u}); - - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - CheckSize(1); - ival = static_cast(static_cast(values[0])); - return SetSourceiv(Source, Context, prop, {&ival, 1u}); - - case AL_BUFFER: case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source float property 0x%04x", prop); -} -catch(check_exception&) { -} - -void SetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto Checkers = GetCheckers(Context, prop, values); - auto &CheckSize = Checkers.first; - auto &CheckValue = Checkers.second; - ALCdevice *device{Context->mALDevice.get()}; - ALeffectslot *slot{nullptr}; - al::deque oldlist; - std::unique_lock slotlock; - float fvals[6]; - - switch(prop) - { - case AL_SOURCE_STATE: - case AL_SOURCE_TYPE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - - case AL_SOURCE_RELATIVE: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->HeadRelative = values[0] != AL_FALSE; - return CommitAndUpdateSourceProps(Source, Context); - - case AL_LOOPING: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->Looping = values[0] != AL_FALSE; - if(Voice *voice{GetSourceVoice(Source, Context)}) + if constexpr(std::is_integral_v) { - if(Source->Looping) - voice->mLoopBuffer.store(&Source->mQueue.front(), std::memory_order_release); + CheckSize(1); + const auto filterid = static_cast>(values[0]); + if(values[0]) + { + std::lock_guard filterlock{device->FilterLock}; + ALfilter *filter{LookupFilter(device, filterid)}; + if(!filter) + throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", + std::to_string(filterid).c_str()}; + Source->Direct.Gain = filter->Gain; + Source->Direct.GainHF = filter->GainHF; + Source->Direct.HFReference = filter->HFReference; + Source->Direct.GainLF = filter->GainLF; + Source->Direct.LFReference = filter->LFReference; + } else - voice->mLoopBuffer.store(nullptr, std::memory_order_release); - - /* If the source is playing, wait for the current mix to finish to - * ensure it isn't currently looping back or reaching the end. - */ - device->waitForMix(); + { + Source->Direct.Gain = 1.0f; + Source->Direct.GainHF = 1.0f; + Source->Direct.HFReference = LowPassFreqRef; + Source->Direct.GainLF = 1.0f; + Source->Direct.LFReference = HighPassFreqRef; + } + return UpdateSourceProps(Source, Context); } - return; + break; - case AL_BUFFER: - CheckSize(1); + case AL_DIRECT_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) { - const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; - if(state == AL_PLAYING || state == AL_PAUSED) - return Context->setError(AL_INVALID_OPERATION, - "Setting buffer on playing or paused source %u", Source->id); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->DryGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); } - if(values[0]) + break; + + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if constexpr(std::is_integral_v) { - std::lock_guard _{device->BufferLock}; - ALbuffer *buffer{LookupBuffer(device, static_cast(values[0]))}; - if(!buffer) - return Context->setError(AL_INVALID_VALUE, "Invalid buffer ID %u", - static_cast(values[0])); - if(buffer->MappedAccess && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - return Context->setError(AL_INVALID_OPERATION, - "Setting non-persistently mapped buffer %u", buffer->id); - if(buffer->mCallback && ReadRef(buffer->ref) != 0) - return Context->setError(AL_INVALID_OPERATION, - "Setting already-set callback buffer %u", buffer->id); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - /* Add the selected buffer to a one-item queue */ - al::deque newlist; - newlist.emplace_back(); - newlist.back().mCallback = buffer->mCallback; - newlist.back().mUserData = buffer->mUserData; - newlist.back().mBlockAlign = buffer->mBlockAlign; - newlist.back().mSampleLen = buffer->mSampleLen; - newlist.back().mLoopStart = buffer->mLoopStart; - newlist.back().mLoopEnd = buffer->mLoopEnd; - newlist.back().mSamples = buffer->mData.data(); - newlist.back().mBuffer = buffer; - IncrementRef(buffer->ref); - - /* Source is now Static */ - Source->SourceType = AL_STATIC; - Source->mQueue.swap(oldlist); - Source->mQueue.swap(newlist); + Source->WetGainAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); } - else + break; + + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) { - /* Source is now Undetermined */ - Source->SourceType = AL_UNDETERMINED; - Source->mQueue.swap(oldlist); + CheckSize(1); + CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); + + Source->WetGainHFAuto = values[0] != AL_FALSE; + return UpdateSourceProps(Source, Context); } + break; - /* Delete all elements in the previous queue */ - for(auto &item : oldlist) + case AL_DIRECT_CHANNELS_SOFT: + if constexpr(std::is_integral_v) { - if(ALbuffer *buffer{item.mBuffer}) - DecrementRef(buffer->ref); + CheckSize(1); + if(auto mode = DirectModeFromEnum(values[0])) + { + Source->DirectChannels = *mode; + return UpdateSourceProps(Source, Context); + } + throw al::context_error{AL_INVALID_VALUE, "Invalid direct channels mode: %s\n", + HexPrinter{values[0]}.c_str()}; } - return; + break; - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - CheckSize(1); - - if(Voice *voice{GetSourceVoice(Source, Context)}) + case AL_DISTANCE_MODEL: + if constexpr(std::is_integral_v) { - auto vpos = GetSampleOffset(Source->mQueue, prop, values[0]); - if(!vpos) return Context->setError(AL_INVALID_VALUE, "Invalid source offset"); - - if(SetVoiceOffset(voice, *vpos, Source, Context, device)) + CheckSize(1); + if(auto model = DistanceModelFromALenum(values[0])) + { + Source->mDistanceModel = *model; + if(Context->mSourceDistanceModel) + UpdateSourceProps(Source, Context); return; + } + throw al::context_error{AL_INVALID_VALUE, "Invalid distance model: %s\n", + HexPrinter{values[0]}.c_str()}; } - Source->OffsetType = prop; - Source->Offset = values[0]; - return; + break; - case AL_DIRECT_FILTER: - CheckSize(1); - if(values[0]) + case AL_SOURCE_RESAMPLER_SOFT: + if constexpr(std::is_integral_v) { - std::lock_guard _{device->FilterLock}; - ALfilter *filter{LookupFilter(device, static_cast(values[0]))}; - if(!filter) - return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", - static_cast(values[0])); - Source->Direct.Gain = filter->Gain; - Source->Direct.GainHF = filter->GainHF; - Source->Direct.HFReference = filter->HFReference; - Source->Direct.GainLF = filter->GainLF; - Source->Direct.LFReference = filter->LFReference; - } - else - { - Source->Direct.Gain = 1.0f; - Source->Direct.GainHF = 1.0f; - Source->Direct.HFReference = LOWPASSFREQREF; - Source->Direct.GainLF = 1.0f; - Source->Direct.LFReference = HIGHPASSFREQREF; - } - return UpdateSourceProps(Source, Context); + CheckSize(1); + CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); - case AL_DIRECT_FILTER_GAINHF_AUTO: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->DryGainHFAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->WetGainAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CheckSize(1); - CheckValue(values[0] == AL_FALSE || values[0] == AL_TRUE); - - Source->WetGainHFAuto = values[0] != AL_FALSE; - return UpdateSourceProps(Source, Context); - - case AL_DIRECT_CHANNELS_SOFT: - CheckSize(1); - if(auto mode = DirectModeFromEnum(values[0])) - { - Source->DirectChannels = *mode; + Source->mResampler = static_cast(values[0]); return UpdateSourceProps(Source, Context); } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_DIRECT_CHANNELS_SOFT: 0x%04x\n", - values[0]); - return; + break; - case AL_DISTANCE_MODEL: - CheckSize(1); - if(auto model = DistanceModelFromALenum(values[0])) + case AL_SOURCE_SPATIALIZE_SOFT: + if constexpr(std::is_integral_v) { - Source->mDistanceModel = *model; - if(Context->mSourceDistanceModel) + CheckSize(1); + if(auto mode = SpatializeModeFromEnum(values[0])) + { + Source->mSpatialize = *mode; + return UpdateSourceProps(Source, Context); + } + throw al::context_error{AL_INVALID_VALUE, "Invalid source spatialize mode: %s\n", + HexPrinter{values[0]}.c_str()}; + } + break; + + case AL_STEREO_MODE_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + if(const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; + state == AL_PLAYING || state == AL_PAUSED) + throw al::context_error{AL_INVALID_OPERATION, + "Modifying stereo mode on playing or paused source %u", Source->id}; + + if(auto mode = StereoModeFromEnum(values[0])) + { + Source->mStereoMode = *mode; + return; + } + throw al::context_error{AL_INVALID_VALUE, "Invalid stereo mode: %s\n", + HexPrinter{values[0]}.c_str()}; + } + break; + + case AL_AUXILIARY_SEND_FILTER: + if constexpr(std::is_integral_v) + { + CheckSize(3); + const auto slotid = static_cast>(values[0]); + const auto sendidx = static_cast>(values[1]); + const auto filterid = static_cast>(values[2]); + + std::unique_lock slotlock{Context->mEffectSlotLock}; + ALeffectslot *slot{}; + if(values[0]) + { + slot = LookupEffectSlot(Context, slotid); + if(!slot) + throw al::context_error{AL_INVALID_VALUE, "Invalid effect ID %s", + std::to_string(slotid).c_str()}; + } + + if(sendidx >= device->NumAuxSends) + throw al::context_error{AL_INVALID_VALUE, "Invalid send %s", + std::to_string(sendidx).c_str()}; + auto &send = Source->Send[static_cast(sendidx)]; + + if(values[2]) + { + std::lock_guard filterlock{device->FilterLock}; + ALfilter *filter{LookupFilter(device, filterid)}; + if(!filter) + throw al::context_error{AL_INVALID_VALUE, "Invalid filter ID %s", + std::to_string(filterid).c_str()}; + + send.Gain = filter->Gain; + send.GainHF = filter->GainHF; + send.HFReference = filter->HFReference; + send.GainLF = filter->GainLF; + send.LFReference = filter->LFReference; + } + else + { + /* Disable filter */ + send.Gain = 1.0f; + send.GainHF = 1.0f; + send.HFReference = LowPassFreqRef; + send.GainLF = 1.0f; + send.LFReference = HighPassFreqRef; + } + + /* We must force an update if the current auxiliary slot is valid + * and about to be changed on an active source, in case the old + * slot is about to be deleted. + */ + if(send.Slot && slot != send.Slot && IsPlayingOrPaused(Source)) + { + /* Add refcount on the new slot, and release the previous slot */ + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = send.Slot) + DecrementRef(oldslot->ref); + send.Slot = slot; + + Voice *voice{GetSourceVoice(Source, Context)}; + if(voice) UpdateSourceProps(Source, voice, Context); + else Source->mPropsDirty = true; + } + else + { + if(slot) IncrementRef(slot->ref); + if(auto *oldslot = send.Slot) + DecrementRef(oldslot->ref); + send.Slot = slot; UpdateSourceProps(Source, Context); + } return; } - Context->setError(AL_INVALID_VALUE, "Distance model out of range: 0x%04x", values[0]); - return; - - case AL_SOURCE_RESAMPLER_SOFT: - CheckSize(1); - CheckValue(values[0] >= 0 && values[0] <= static_cast(Resampler::Max)); - - Source->mResampler = static_cast(values[0]); - return UpdateSourceProps(Source, Context); - - case AL_SOURCE_SPATIALIZE_SOFT: - CheckSize(1); - if(auto mode = SpatializeModeFromEnum(values[0])) - { - Source->mSpatialize = *mode; - return UpdateSourceProps(Source, Context); - } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_SOURCE_SPATIALIZE_SOFT: 0x%04x\n", - values[0]); - return; - - case AL_STEREO_MODE_SOFT: - CheckSize(1); - { - const ALenum state{GetSourceState(Source, GetSourceVoice(Source, Context))}; - if(state == AL_PLAYING || state == AL_PAUSED) - return Context->setError(AL_INVALID_OPERATION, - "Modifying stereo mode on playing or paused source %u", Source->id); - } - if(auto mode = StereoModeFromEnum(values[0])) - { - Source->mStereoMode = *mode; - return; - } - Context->setError(AL_INVALID_VALUE, "Unsupported AL_STEREO_MODE_SOFT: 0x%04x\n", - values[0]); - return; - - case AL_AUXILIARY_SEND_FILTER: - CheckSize(3); - slotlock = std::unique_lock{Context->mEffectSlotLock}; - if(values[0] && (slot=LookupEffectSlot(Context, static_cast(values[0]))) == nullptr) - return Context->setError(AL_INVALID_VALUE, "Invalid effect ID %u", values[0]); - if(static_cast(values[1]) >= device->NumAuxSends) - return Context->setError(AL_INVALID_VALUE, "Invalid send %u", values[1]); - - if(values[2]) - { - std::lock_guard _{device->FilterLock}; - ALfilter *filter{LookupFilter(device, static_cast(values[2]))}; - if(!filter) - return Context->setError(AL_INVALID_VALUE, "Invalid filter ID %u", values[2]); - - auto &send = Source->Send[static_cast(values[1])]; - send.Gain = filter->Gain; - send.GainHF = filter->GainHF; - send.HFReference = filter->HFReference; - send.GainLF = filter->GainLF; - send.LFReference = filter->LFReference; - } - else - { - /* Disable filter */ - auto &send = Source->Send[static_cast(values[1])]; - send.Gain = 1.0f; - send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; - send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; - } - - /* We must force an update if the current auxiliary slot is valid and - * about to be changed on an active source, in case the old slot is - * about to be deleted. - */ - if(Source->Send[static_cast(values[1])].Slot - && slot != Source->Send[static_cast(values[1])].Slot - && IsPlayingOrPaused(Source)) - { - /* Add refcount on the new slot, and release the previous slot */ - if(slot) IncrementRef(slot->ref); - if(auto *oldslot = Source->Send[static_cast(values[1])].Slot) - DecrementRef(oldslot->ref); - Source->Send[static_cast(values[1])].Slot = slot; - - Voice *voice{GetSourceVoice(Source, Context)}; - if(voice) UpdateSourceProps(Source, voice, Context); - else Source->mPropsDirty = true; - } - else - { - if(slot) IncrementRef(slot->ref); - if(auto *oldslot = Source->Send[static_cast(values[1])].Slot) - DecrementRef(oldslot->ref); - Source->Send[static_cast(values[1])].Slot = slot; - UpdateSourceProps(Source, Context); - } - return; - - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - break; - - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - /*fall-through*/ - - /* 1x float */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_SEC_LENGTH_SOFT: - case AL_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - fvals[0] = static_cast(values[0]); - return SetSourcefv(Source, Context, prop, {fvals, 1u}); - - /* 3x float */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - return SetSourcefv(Source, Context, prop, {fvals, 3u}); - - /* 6x float */ - case AL_ORIENTATION: - CheckSize(6); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(values[5]); - return SetSourcefv(Source, Context, prop, {fvals, 6u}); - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - case AL_STEREO_ANGLES: break; } - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); -} -catch(check_exception&) { -} - -void SetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto Checkers = GetCheckers(Context, prop, values); - auto &CheckSize = Checkers.first; - auto &CheckValue = Checkers.second; - float fvals[MaxValues]; - int ivals[MaxValues]; - - switch(prop) - { - case AL_SOURCE_TYPE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_STATE: - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - - /* 1x int */ - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - CheckValue(values[0] <= INT_MAX && values[0] >= INT_MIN); - - ivals[0] = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {ivals, 1u}); - - /* 1x uint */ - case AL_BUFFER: - case AL_DIRECT_FILTER: - CheckSize(1); - CheckValue(values[0] <= UINT_MAX && values[0] >= 0); - - ivals[0] = static_cast(values[0]); - return SetSourceiv(Source, Context, prop, {ivals, 1u}); - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - CheckSize(3); - CheckValue(values[0] <= UINT_MAX && values[0] >= 0 && values[1] <= UINT_MAX - && values[1] >= 0 && values[2] <= UINT_MAX && values[2] >= 0); - - ivals[0] = static_cast(values[0]); - ivals[1] = static_cast(values[1]); - ivals[2] = static_cast(values[2]); - return SetSourceiv(Source, Context, prop, {ivals, 3u}); - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - { - /* Query only */ - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - } - break; - - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - return Context->setError(AL_INVALID_OPERATION, - "Setting read-only source property 0x%04x", prop); - /*fall-through*/ - - /* 1x float */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_DOPPLER_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_SEC_LENGTH_SOFT: - case AL_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - fvals[0] = static_cast(values[0]); - return SetSourcefv(Source, Context, prop, {fvals, 1u}); - - /* 3x float */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - return SetSourcefv(Source, Context, prop, {fvals, 3u}); - - /* 6x float */ - case AL_ORIENTATION: - CheckSize(6); - fvals[0] = static_cast(values[0]); - fvals[1] = static_cast(values[1]); - fvals[2] = static_cast(values[2]); - fvals[3] = static_cast(values[3]); - fvals[4] = static_cast(values[4]); - fvals[5] = static_cast(values[5]); - return SetSourcefv(Source, Context, prop, {fvals, 6u}); - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - case AL_STEREO_ANGLES: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); -} -catch(check_exception&) { + ERR("Unexpected %s property: 0x%04x\n", PropType::Name(), prop); + throw al::context_error{AL_INVALID_ENUM, "Invalid source %s property 0x%04x", + PropType::Name(), prop}; } template -auto GetSizeChecker(ALCcontext *const Context, const SourceProp prop, const al::span values) +auto GetSizeChecker(const SourceProp prop, const al::span values) { return [=](size_t expect) -> void { if(values.size() == expect) LIKELY return; - Context->setError(AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", - prop, expect, values.size()); - throw check_size_exception{}; + throw al::context_error{AL_INVALID_ENUM, "Property 0x%04x expects %zu value(s), got %zu", + prop, expect, values.size()}; }; } -bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); -bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, const al::span values); - -bool GetSourcedv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto CheckSize = GetSizeChecker(Context, prop, values); +template +NOINLINE void GetProperty(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, + const al::span values) +{ + using std::chrono::duration_cast; + auto CheckSize = GetSizeChecker(prop, values); ALCdevice *device{Context->mALDevice.get()}; - ClockLatency clocktime; - nanoseconds srcclock; - int ivals[MaxValues]; - bool err; switch(prop) { case AL_GAIN: CheckSize(1); - values[0] = Source->Gain; - return true; + values[0] = static_cast(Source->Gain); + return; case AL_PITCH: CheckSize(1); - values[0] = Source->Pitch; - return true; + values[0] = static_cast(Source->Pitch); + return; case AL_MAX_DISTANCE: CheckSize(1); - values[0] = Source->MaxDistance; - return true; + values[0] = static_cast(Source->MaxDistance); + return; case AL_ROLLOFF_FACTOR: CheckSize(1); - values[0] = Source->RolloffFactor; - return true; + values[0] = static_cast(Source->RolloffFactor); + return; case AL_REFERENCE_DISTANCE: CheckSize(1); - values[0] = Source->RefDistance; - return true; + values[0] = static_cast(Source->RefDistance); + return; case AL_CONE_INNER_ANGLE: CheckSize(1); - values[0] = Source->InnerAngle; - return true; + values[0] = static_cast(Source->InnerAngle); + return; case AL_CONE_OUTER_ANGLE: CheckSize(1); - values[0] = Source->OuterAngle; - return true; + values[0] = static_cast(Source->OuterAngle); + return; case AL_MIN_GAIN: CheckSize(1); - values[0] = Source->MinGain; - return true; + values[0] = static_cast(Source->MinGain); + return; case AL_MAX_GAIN: CheckSize(1); - values[0] = Source->MaxGain; - return true; + values[0] = static_cast(Source->MaxGain); + return; case AL_CONE_OUTER_GAIN: CheckSize(1); - values[0] = Source->OuterGain; - return true; + values[0] = static_cast(Source->OuterGain); + return; case AL_SEC_OFFSET: case AL_SAMPLE_OFFSET: case AL_BYTE_OFFSET: CheckSize(1); - values[0] = GetSourceOffset(Source, prop, Context); - return true; + values[0] = GetSourceOffset(Source, prop, Context); + return; case AL_CONE_OUTER_GAINHF: CheckSize(1); - values[0] = Source->OuterGainHF; - return true; + values[0] = static_cast(Source->OuterGainHF); + return; case AL_AIR_ABSORPTION_FACTOR: CheckSize(1); - values[0] = Source->AirAbsorptionFactor; - return true; + values[0] = static_cast(Source->AirAbsorptionFactor); + return; case AL_ROOM_ROLLOFF_FACTOR: CheckSize(1); - values[0] = Source->RoomRolloffFactor; - return true; + values[0] = static_cast(Source->RoomRolloffFactor); + return; case AL_DOPPLER_FACTOR: CheckSize(1); - values[0] = Source->DopplerFactor; - return true; + values[0] = static_cast(Source->DopplerFactor); + return; case AL_SAMPLE_RW_OFFSETS_SOFT: + if constexpr(std::is_integral_v) + { + if(sBufferSubDataCompat) + { + CheckSize(2); + values[0] = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context); + /* FIXME: values[1] should be ahead of values[0] by the device + * update time. It needs to clamp or wrap the length of the + * buffer queue. + */ + values[1] = values[0]; + return; + } + } break; case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - break; + if constexpr(std::is_floating_point_v) + { + if(sBufferSubDataCompat) + break; - CheckSize(1); - values[0] = Source->Radius; - return true; + CheckSize(1); + values[0] = static_cast(Source->Radius); + return; + } + else + { + if(sBufferSubDataCompat) + { + CheckSize(2); + values[0] = GetSourceOffset(Source, AL_BYTE_OFFSET, Context); + /* FIXME: values[1] should be ahead of values[0] by the device + * update time. It needs to clamp or wrap the length of the + * buffer queue. + */ + values[1] = values[0]; + return; + } + break; + } case AL_SUPER_STEREO_WIDTH_SOFT: CheckSize(1); - values[0] = Source->EnhWidth; - return true; + values[0] = static_cast(Source->EnhWidth); + return; case AL_BYTE_LENGTH_SOFT: case AL_SAMPLE_LENGTH_SOFT: case AL_SEC_LENGTH_SOFT: CheckSize(1); - values[0] = GetSourceLength(Source, prop); - return true; + values[0] = GetSourceLength(Source, prop); + return; + + case AL_PANNING_ENABLED_SOFT: + CheckSize(1); + values[0] = Source->mPanningEnabled; + return; + + case AL_PAN_SOFT: + CheckSize(1); + values[0] = static_cast(Source->mPan); + return; case AL_STEREO_ANGLES: - CheckSize(2); - values[0] = Source->StereoPan[0]; - values[1] = Source->StereoPan[1]; - return true; + if constexpr(std::is_floating_point_v) + { + CheckSize(2); + values[0] = static_cast(Source->StereoPan[0]); + values[1] = static_cast(Source->StereoPan[1]); + return; + } + break; + + case AL_SAMPLE_OFFSET_LATENCY_SOFT: + if constexpr(std::is_same_v) + { + CheckSize(2); + /* Get the source offset with the clock time first. Then get the + * clock time with the device latency. Order is important. + */ + ClockLatency clocktime{}; + nanoseconds srcclock{}; + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + { + std::lock_guard statelock{device->StateLock}; + clocktime = GetClockLatency(device, device->Backend.get()); + } + if(srcclock == clocktime.ClockTime) + values[1] = nanoseconds{clocktime.Latency}.count(); + else + { + /* If the clock time incremented, reduce the latency by that + * much since it's that much closer to the source offset it got + * earlier. + */ + const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock); + values[1] = nanoseconds{clocktime.Latency - diff}.count(); + } + return; + } + break; + + case AL_SAMPLE_OFFSET_CLOCK_SOFT: + if constexpr(std::is_same_v) + { + CheckSize(2); + nanoseconds srcclock{}; + values[0] = GetSourceSampleOffset(Source, Context, &srcclock); + values[1] = srcclock.count(); + return; + } + break; case AL_SEC_OFFSET_LATENCY_SOFT: - CheckSize(2); - /* Get the source offset with the clock time first. Then get the clock - * time with the device latency. Order is important. - */ - values[0] = GetSourceSecOffset(Source, Context, &srcclock); + if constexpr(std::is_same_v) { - std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device, device->Backend.get()); - } - if(srcclock == clocktime.ClockTime) - values[1] = static_cast(clocktime.Latency.count()) / 1000000000.0; - else - { - /* If the clock time incremented, reduce the latency by that much - * since it's that much closer to the source offset it got earlier. + CheckSize(2); + /* Get the source offset with the clock time first. Then get the + * clock time with the device latency. Order is important. */ - const nanoseconds diff{clocktime.ClockTime - srcclock}; - const nanoseconds latency{clocktime.Latency - std::min(clocktime.Latency, diff)}; - values[1] = static_cast(latency.count()) / 1000000000.0; + ClockLatency clocktime{}; + nanoseconds srcclock{}; + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + { + std::lock_guard statelock{device->StateLock}; + clocktime = GetClockLatency(device, device->Backend.get()); + } + if(srcclock == clocktime.ClockTime) + values[1] = duration_cast(clocktime.Latency).count(); + else + { + /* If the clock time incremented, reduce the latency by that + * much since it's that much closer to the source offset it got + * earlier. + */ + const auto diff = std::min(clocktime.Latency, clocktime.ClockTime-srcclock); + values[1] = duration_cast(clocktime.Latency - diff).count(); + } + return; } - return true; + break; case AL_SEC_OFFSET_CLOCK_SOFT: - CheckSize(2); - values[0] = GetSourceSecOffset(Source, Context, &srcclock); - values[1] = static_cast(srcclock.count()) / 1000000000.0; - return true; + if constexpr(std::is_same_v) + { + CheckSize(2); + nanoseconds srcclock{}; + values[0] = GetSourceSecOffset(Source, Context, &srcclock); + values[1] = duration_cast(srcclock).count(); + return; + } + break; case AL_POSITION: CheckSize(3); - values[0] = Source->Position[0]; - values[1] = Source->Position[1]; - values[2] = Source->Position[2]; - return true; + values[0] = static_cast(Source->Position[0]); + values[1] = static_cast(Source->Position[1]); + values[2] = static_cast(Source->Position[2]); + return; case AL_VELOCITY: CheckSize(3); - values[0] = Source->Velocity[0]; - values[1] = Source->Velocity[1]; - values[2] = Source->Velocity[2]; - return true; + values[0] = static_cast(Source->Velocity[0]); + values[1] = static_cast(Source->Velocity[1]); + values[2] = static_cast(Source->Velocity[2]); + return; case AL_DIRECTION: CheckSize(3); - values[0] = Source->Direction[0]; - values[1] = Source->Direction[1]; - values[2] = Source->Direction[2]; - return true; + values[0] = static_cast(Source->Direction[0]); + values[1] = static_cast(Source->Direction[1]); + values[2] = static_cast(Source->Direction[2]); + return; case AL_ORIENTATION: CheckSize(6); - values[0] = Source->OrientAt[0]; - values[1] = Source->OrientAt[1]; - values[2] = Source->OrientAt[2]; - values[3] = Source->OrientUp[0]; - values[4] = Source->OrientUp[1]; - values[5] = Source->OrientUp[2]; - return true; + values[0] = static_cast(Source->OrientAt[0]); + values[1] = static_cast(Source->OrientAt[1]); + values[2] = static_cast(Source->OrientAt[2]); + values[3] = static_cast(Source->OrientUp[0]); + values[4] = static_cast(Source->OrientUp[1]); + values[5] = static_cast(Source->OrientUp[2]); + return; + - /* 1x int */ case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = static_cast(ivals[0]); - return err; - - case AL_BUFFER: - case AL_DIRECT_FILTER: - case AL_AUXILIARY_SEND_FILTER: - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; - } - - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source double property 0x%04x", prop); - return false; -} -catch(check_exception&) { - return false; -} - -bool GetSourceiv(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto CheckSize = GetSizeChecker(Context, prop, values); - double dvals[MaxValues]; - bool err; - - switch(prop) - { - case AL_SOURCE_RELATIVE: - CheckSize(1); - values[0] = Source->HeadRelative; - return true; - - case AL_LOOPING: - CheckSize(1); - values[0] = Source->Looping; - return true; - - case AL_BUFFER: - CheckSize(1); + if constexpr(std::is_integral_v) { + CheckSize(1); + values[0] = Source->HeadRelative; + return; + } + break; + + case AL_LOOPING: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->Looping; + return; + } + break; + + case AL_BUFFER: + if constexpr(std::is_integral_v) + { + CheckSize(1); ALbufferQueueItem *BufferList{}; /* HACK: This query should technically only return the buffer set * on a static source. However, some apps had used it to detect @@ -2356,381 +2345,150 @@ try { BufferList = static_cast(Current); } ALbuffer *buffer{BufferList ? BufferList->mBuffer : nullptr}; - values[0] = buffer ? static_cast(buffer->id) : 0; + values[0] = buffer ? static_cast(buffer->id) : T{0}; + return; } - return true; + break; case AL_SOURCE_STATE: - CheckSize(1); - values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); - return true; + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = GetSourceState(Source, GetSourceVoice(Source, Context)); + return; + } + break; case AL_BUFFERS_QUEUED: - CheckSize(1); - values[0] = static_cast(Source->mQueue.size()); - return true; + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = static_cast(Source->mQueue.size()); + return; + } + break; case AL_BUFFERS_PROCESSED: - CheckSize(1); - if(Source->Looping || Source->SourceType != AL_STREAMING) + if constexpr(std::is_integral_v) { - /* Buffers on a looping source are in a perpetual state of PENDING, - * so don't report any as PROCESSED - */ - values[0] = 0; - } - else - { - int played{0}; - if(Source->state != AL_INITIAL) + CheckSize(1); + if(Source->Looping || Source->SourceType != AL_STREAMING) { - const VoiceBufferItem *Current{nullptr}; - if(Voice *voice{GetSourceVoice(Source, Context)}) - Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); - for(auto &item : Source->mQueue) - { - if(&item == Current) - break; - ++played; - } + /* Buffers on a looping source are in a perpetual state of + * PENDING, so don't report any as PROCESSED + */ + values[0] = 0; } - values[0] = played; - } - return true; - - case AL_SOURCE_TYPE: - CheckSize(1); - values[0] = Source->SourceType; - return true; - - case AL_DIRECT_FILTER_GAINHF_AUTO: - CheckSize(1); - values[0] = Source->DryGainHFAuto; - return true; - - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - CheckSize(1); - values[0] = Source->WetGainAuto; - return true; - - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - CheckSize(1); - values[0] = Source->WetGainHFAuto; - return true; - - case AL_DIRECT_CHANNELS_SOFT: - CheckSize(1); - values[0] = EnumFromDirectMode(Source->DirectChannels); - return true; - - case AL_DISTANCE_MODEL: - CheckSize(1); - values[0] = ALenumFromDistanceModel(Source->mDistanceModel); - return true; - - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SEC_LENGTH_SOFT: - CheckSize(1); - values[0] = static_cast(mind(GetSourceLength(Source, prop), - std::numeric_limits::max())); - return true; - - case AL_SOURCE_RESAMPLER_SOFT: - CheckSize(1); - values[0] = static_cast(Source->mResampler); - return true; - - case AL_SOURCE_SPATIALIZE_SOFT: - CheckSize(1); - values[0] = EnumFromSpatializeMode(Source->mSpatialize); - return true; - - case AL_STEREO_MODE_SOFT: - CheckSize(1); - values[0] = EnumFromStereoMode(Source->mStereoMode); - return true; - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - { - CheckSize(2); - const auto offset = GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(mind(offset, std::numeric_limits::max())); - values[1] = values[0]; - return true; + else + { + int played{0}; + if(Source->state != AL_INITIAL) + { + const VoiceBufferItem *Current{nullptr}; + if(Voice *voice{GetSourceVoice(Source, Context)}) + Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); + for(auto &item : Source->mQueue) + { + if(&item == Current) + break; + ++played; + } + } + values[0] = played; + } + return; } break; - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) + + case AL_SOURCE_TYPE: + if constexpr(std::is_integral_v) { - CheckSize(2); - const auto offset = GetSourceOffset(Source, AL_BYTE_OFFSET, Context); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(mind(offset, std::numeric_limits::max())); - values[1] = values[0]; - return true; + CheckSize(1); + values[0] = Source->SourceType; + return; } - /*fall-through*/ + break; - /* 1x float/double */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DOPPLER_FACTOR: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) - values[0] = static_cast(dvals[0]); - return err; - - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) + case AL_DIRECT_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); + CheckSize(1); + values[0] = Source->DryGainHFAuto; + return; } - return err; + break; - /* 6x float/double */ - case AL_ORIENTATION: - CheckSize(6); - if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) + case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: + if constexpr(std::is_integral_v) { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - values[3] = static_cast(dvals[3]); - values[4] = static_cast(dvals[4]); - values[5] = static_cast(dvals[5]); + CheckSize(1); + values[0] = Source->WetGainAuto; + return; } - return err; + break; - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - break; /* i64 only */ - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ + case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = Source->WetGainHFAuto; + return; + } + break; + + case AL_DIRECT_CHANNELS_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = EnumFromDirectMode(Source->DirectChannels); + return; + } + break; + + case AL_DISTANCE_MODEL: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = ALenumFromDistanceModel(Source->mDistanceModel); + return; + } + break; + + case AL_SOURCE_RESAMPLER_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = static_cast(Source->mResampler); + return; + } + break; + + case AL_SOURCE_SPATIALIZE_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = EnumFromSpatializeMode(Source->mSpatialize); + return; + } + break; + + case AL_STEREO_MODE_SOFT: + if constexpr(std::is_integral_v) + { + CheckSize(1); + values[0] = EnumFromStereoMode(Source->mStereoMode); + return; + } + break; case AL_DIRECT_FILTER: case AL_AUXILIARY_SEND_FILTER: - break; /* ??? */ - } - - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source integer property 0x%04x", prop); - return false; -} -catch(check_exception&) { - return false; -} - -bool GetSourcei64v(ALsource *const Source, ALCcontext *const Context, const SourceProp prop, - const al::span values) -try { - auto CheckSize = GetSizeChecker(Context, prop, values); - ALCdevice *device{Context->mALDevice.get()}; - ClockLatency clocktime; - nanoseconds srcclock; - double dvals[MaxValues]; - int ivals[MaxValues]; - bool err; - - switch(prop) - { - case AL_BYTE_LENGTH_SOFT: - case AL_SAMPLE_LENGTH_SOFT: - case AL_SEC_LENGTH_SOFT: - CheckSize(1); - values[0] = static_cast(GetSourceLength(Source, prop)); - return true; - - case AL_SAMPLE_OFFSET_LATENCY_SOFT: - CheckSize(2); - /* Get the source offset with the clock time first. Then get the clock - * time with the device latency. Order is important. - */ - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - { - std::lock_guard _{device->StateLock}; - clocktime = GetClockLatency(device, device->Backend.get()); - } - if(srcclock == clocktime.ClockTime) - values[1] = clocktime.Latency.count(); - else - { - /* If the clock time incremented, reduce the latency by that much - * since it's that much closer to the source offset it got earlier. - */ - const nanoseconds diff{clocktime.ClockTime - srcclock}; - values[1] = nanoseconds{clocktime.Latency - std::min(clocktime.Latency, diff)}.count(); - } - return true; - - case AL_SAMPLE_OFFSET_CLOCK_SOFT: - CheckSize(2); - values[0] = GetSourceSampleOffset(Source, Context, &srcclock); - values[1] = srcclock.count(); - return true; - - case AL_SAMPLE_RW_OFFSETS_SOFT: - if(sBufferSubDataCompat) - { - CheckSize(2); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(GetSourceOffset(Source, AL_SAMPLE_OFFSET, Context)); - values[1] = values[0]; - return true; - } break; - case AL_SOURCE_RADIUS: /*AL_BYTE_RW_OFFSETS_SOFT:*/ - if(sBufferSubDataCompat) - { - CheckSize(2); - /* FIXME: values[1] should be ahead of values[0] by the device - * update time. It needs to clamp or wrap the length of the buffer - * queue. - */ - values[0] = static_cast(GetSourceOffset(Source, AL_BYTE_OFFSET, Context)); - values[1] = values[0]; - return true; - } - /*fall-through*/ - - /* 1x float/double */ - case AL_CONE_INNER_ANGLE: - case AL_CONE_OUTER_ANGLE: - case AL_PITCH: - case AL_GAIN: - case AL_MIN_GAIN: - case AL_MAX_GAIN: - case AL_REFERENCE_DISTANCE: - case AL_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAIN: - case AL_MAX_DISTANCE: - case AL_SEC_OFFSET: - case AL_SAMPLE_OFFSET: - case AL_BYTE_OFFSET: - case AL_DOPPLER_FACTOR: - case AL_AIR_ABSORPTION_FACTOR: - case AL_ROOM_ROLLOFF_FACTOR: - case AL_CONE_OUTER_GAINHF: - case AL_SUPER_STEREO_WIDTH_SOFT: - CheckSize(1); - if((err=GetSourcedv(Source, Context, prop, {dvals, 1u})) != false) - values[0] = static_cast(dvals[0]); - return err; - - /* 3x float/double */ - case AL_POSITION: - case AL_VELOCITY: - case AL_DIRECTION: - CheckSize(3); - if((err=GetSourcedv(Source, Context, prop, {dvals, 3u})) != false) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - } - return err; - - /* 6x float/double */ - case AL_ORIENTATION: - CheckSize(6); - if((err=GetSourcedv(Source, Context, prop, {dvals, 6u})) != false) - { - values[0] = static_cast(dvals[0]); - values[1] = static_cast(dvals[1]); - values[2] = static_cast(dvals[2]); - values[3] = static_cast(dvals[3]); - values[4] = static_cast(dvals[4]); - values[5] = static_cast(dvals[5]); - } - return err; - - /* 1x int */ - case AL_SOURCE_RELATIVE: - case AL_LOOPING: - case AL_SOURCE_STATE: - case AL_BUFFERS_QUEUED: - case AL_BUFFERS_PROCESSED: - case AL_SOURCE_TYPE: - case AL_DIRECT_FILTER_GAINHF_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAIN_AUTO: - case AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO: - case AL_DIRECT_CHANNELS_SOFT: - case AL_DISTANCE_MODEL: - case AL_SOURCE_RESAMPLER_SOFT: - case AL_SOURCE_SPATIALIZE_SOFT: - case AL_STEREO_MODE_SOFT: - CheckSize(1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = ivals[0]; - return err; - - /* 1x uint */ - case AL_BUFFER: - case AL_DIRECT_FILTER: - CheckSize(1); - if((err=GetSourceiv(Source, Context, prop, {ivals, 1u})) != false) - values[0] = static_cast(ivals[0]); - return err; - - /* 3x uint */ - case AL_AUXILIARY_SEND_FILTER: - CheckSize(3); - if((err=GetSourceiv(Source, Context, prop, {ivals, 3u})) != false) - { - values[0] = static_cast(ivals[0]); - values[1] = static_cast(ivals[1]); - values[2] = static_cast(ivals[2]); - } - return err; - - case AL_SEC_OFFSET_LATENCY_SOFT: - case AL_SEC_OFFSET_CLOCK_SOFT: - break; /* Double only */ - case AL_STEREO_ANGLES: - break; /* Float/double only */ } - ERR("Unexpected property: 0x%04x\n", prop); - Context->setError(AL_INVALID_ENUM, "Invalid source integer64 property 0x%04x", prop); - return false; -} -catch(check_exception&) { - return false; + ERR("Unexpected %s query property: 0x%04x\n", PropType::Name(), prop); + throw al::context_error{AL_INVALID_ENUM, "Invalid source %s query property 0x%04x", + PropType::Name(), prop}; } @@ -2905,717 +2663,639 @@ void StartSources(ALCcontext *const context, const al::span srchandle } // namespace -AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Generating %d sources", n); +AL_API DECL_FUNC2(void, alGenSources, ALsizei,n, ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Generating %d sources", n}; if(n <= 0) UNLIKELY return; std::unique_lock srclock{context->mSourceLock}; ALCdevice *device{context->mALDevice.get()}; - if(static_cast(n) > device->SourcesMax-context->mNumSources) - { - context->setError(AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)", - device->SourcesMax, context->mNumSources, n); - return; - } - if(!EnsureSources(context.get(), static_cast(n))) - { - context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, (n==1)?"":"s"); - return; - } - if(n == 1) - { - ALsource *source{AllocSource(context.get())}; - sources[0] = source->id; + const al::span sids{sources, static_cast(n)}; + if(sids.size() > device->SourcesMax-context->mNumSources) + throw al::context_error{AL_OUT_OF_MEMORY, "Exceeding %u source limit (%u + %d)", + device->SourcesMax, context->mNumSources, n}; + if(!EnsureSources(context, sids.size())) + throw al::context_error{AL_OUT_OF_MEMORY, "Failed to allocate %d source%s", n, + (n == 1) ? "" : "s"}; -#ifdef ALSOFT_EAX - source->eaxInitialize(context.get()); -#endif // ALSOFT_EAX - } - else - { - al::vector ids; - ids.reserve(static_cast(n)); - do { - ALsource *source{AllocSource(context.get())}; - ids.emplace_back(source->id); - -#ifdef ALSOFT_EAX - source->eaxInitialize(context.get()); -#endif // ALSOFT_EAX - } while(--n); - std::copy(ids.cbegin(), ids.cend(), sources); - } + std::generate(sids.begin(), sids.end(), [context]{ return AllocSource(context)->id; }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Deleting %d sources", n); +AL_API DECL_FUNC2(void, alDeleteSources, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Deleting %d sources", n}; if(n <= 0) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; + std::lock_guard srclock{context->mSourceLock}; /* Check that all Sources are valid */ - auto validate_source = [&context](const ALuint sid) -> bool - { return LookupSource(context.get(), sid) != nullptr; }; + auto validate_source = [context](const ALuint sid) -> bool + { return LookupSource(context, sid) != nullptr; }; - const ALuint *sources_end = sources + n; - auto invsrc = std::find_if_not(sources, sources_end, validate_source); - if(invsrc != sources_end) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *invsrc); + const al::span sids{sources, static_cast(n)}; + auto invsrc = std::find_if_not(sids.begin(), sids.end(), validate_source); + if(invsrc != sids.end()) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", *invsrc}; /* All good. Delete source IDs. */ auto delete_source = [&context](const ALuint sid) -> void { - ALsource *src{LookupSource(context.get(), sid)}; - if(src) FreeSource(context.get(), src); + if(ALsource *src{LookupSource(context, sid)}) + FreeSource(context, src); }; - std::for_each(sources, sources_end, delete_source); + std::for_each(sids.begin(), sids.end(), delete_source); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsSource, ALuint,source) +FORCE_ALIGN ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) noexcept { - ContextRef context{GetContextRef()}; - if(context) LIKELY - { - std::lock_guard _{context->mSourceLock}; - if(LookupSource(context.get(), source) != nullptr) - return AL_TRUE; - } + std::lock_guard srclock{context->mSourceLock}; + if(LookupSource(context, source) != nullptr) + return AL_TRUE; return AL_FALSE; } -END_API_FUNC -AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC3(void, alSourcef, ALuint,source, ALenum,param, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourcefv(Source, context.get(), static_cast(param), {&value, 1u}); + SetProperty(Source, context, static_cast(param), {&value, 1u}); } -END_API_FUNC - -AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const float fvals[3]{ value1, value2, value3 }; - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alSource3f, ALuint,source, ALenum,param, ALfloat,value1, ALfloat,value2, ALfloat,value3) +FORCE_ALIGN void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat value1, ALfloat value2, ALfloat value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array fvals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), fvals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alSourcefv, ALuint,source, ALenum,param, const ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, + const ALfloat *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{FloatValsByProp(param)}; - SetSourcefv(Source, context.get(), static_cast(param), {values, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const float fval[1]{static_cast(value)}; - SetSourcefv(Source, context.get(), static_cast(param), fval); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const float fvals[3]{static_cast(value1), static_cast(value2), - static_cast(value3)}; - SetSourcefv(Source, context.get(), static_cast(param), fvals); - } +AL_API DECL_FUNCEXT3(void, alSourced,SOFT, ALuint,source, ALenum,param, ALdouble,value) +FORCE_ALIGN void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, + ALdouble value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + SetProperty(Source, context, static_cast(param), {&value, 1}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alSource3d,SOFT, ALuint,source, ALenum,param, ALdouble,value1, ALdouble,value2, ALdouble,value3) +FORCE_ALIGN void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, + ALdouble value1, ALdouble value2, ALdouble value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array dvals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), dvals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alSourcedv,SOFT, ALuint,source, ALenum,param, const ALdouble*,values) +FORCE_ALIGN void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, + const ALdouble *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{DoubleValsByProp(param)}; - float fvals[MaxValues]; - std::copy_n(values, count, fvals); - SetSourcefv(Source, context.get(), static_cast(param), {fvals, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourceiv(Source, context.get(), static_cast(param), {&value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const int ivals[3]{ value1, value2, value3 }; - SetSourceiv(Source, context.get(), static_cast(param), ivals); - } +AL_API DECL_FUNC3(void, alSourcei, ALuint,source, ALenum,param, ALint,value) +FORCE_ALIGN void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, + ALint value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + SetProperty(Source, context, static_cast(param), {&value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alSource3i, ALuint,buffer, ALenum,param, ALint,value1, ALint,value2, ALint,value3) +FORCE_ALIGN void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, + ALint value1, ALint value2, ALint value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source = LookupSource(context.get(), source); - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array ivals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), ivals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alSourceiv, ALuint,source, ALenum,param, const ALint*,values) +FORCE_ALIGN void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, + const ALint *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{IntValsByProp(param)}; - SetSourceiv(Source, context.get(), static_cast(param), {values, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - SetSourcei64v(Source, context.get(), static_cast(param), {&value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else - { - const int64_t i64vals[3]{ value1, value2, value3 }; - SetSourcei64v(Source, context.get(), static_cast(param), i64vals); - } +AL_API DECL_FUNCEXT3(void, alSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value) +FORCE_ALIGN void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT value) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + SetProperty(Source, context, static_cast(param), {&value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT,value1, ALint64SOFT,value2, ALint64SOFT,value3) +FORCE_ALIGN void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mPropLock}; - std::lock_guard __{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + const std::array i64vals{value1, value2, value3}; + SetProperty(Source, context, static_cast(param), i64vals); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alSourcei64v,SOFT, ALuint,source, ALenum,param, const ALint64SOFT*,values) +FORCE_ALIGN void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, const ALint64SOFT *values) noexcept +try { + std::lock_guard proplock{context->mPropLock}; + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{Int64ValsByProp(param)}; - SetSourcei64v(Source, context.get(), static_cast(param), {values, count}); + SetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dval[1]; - if(GetSourcedv(Source, context.get(), static_cast(param), dval)) - *value = static_cast(dval[0]); - } +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - *value1 = static_cast(dvals[0]); - *value2 = static_cast(dvals[1]); - *value3 = static_cast(dvals[2]); - } - } +AL_API DECL_FUNC3(void, alGetSourcef, ALuint,source, ALenum,param, ALfloat*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alGetSource3f, ALuint,source, ALenum,param, ALfloat*,value1, ALfloat*,value2, ALfloat*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat *value1, ALfloat *value2, ALfloat *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + std::array fvals{}; + GetProperty(Source, context, static_cast(param), fvals); + *value1 = fvals[0]; + *value2 = fvals[1]; + *value3 = fvals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC3(void, alGetSourcefv, ALuint,source, ALenum,param, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, + ALfloat *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{FloatValsByProp(param)}; - double dvals[MaxValues]; - if(GetSourcedv(Source, context.get(), static_cast(param), {dvals, count})) - std::copy_n(dvals, count, values); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcedv(Source, context.get(), static_cast(param), {value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - double dvals[3]; - if(GetSourcedv(Source, context.get(), static_cast(param), dvals)) - { - *value1 = dvals[0]; - *value2 = dvals[1]; - *value3 = dvals[2]; - } - } +AL_API DECL_FUNCEXT3(void, alGetSourced,SOFT, ALuint,source, ALenum,param, ALdouble*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALdouble *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alGetSource3d,SOFT, ALuint,source, ALenum,param, ALdouble*,value1, ALdouble*,value2, ALdouble*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + std::array dvals{}; + GetProperty(Source, context, static_cast(param), dvals); + *value1 = dvals[0]; + *value2 = dvals[1]; + *value3 = dvals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetSourcedv,SOFT, ALuint,source, ALenum,param, ALdouble*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALdouble *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{DoubleValsByProp(param)}; - GetSourcedv(Source, context.get(), static_cast(param), {values, count}); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourceiv(Source, context.get(), static_cast(param), {value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - int ivals[3]; - if(GetSourceiv(Source, context.get(), static_cast(param), ivals)) - { - *value1 = ivals[0]; - *value2 = ivals[1]; - *value3 = ivals[2]; - } - } +AL_API DECL_FUNC3(void, alGetSourcei, ALuint,source, ALenum,param, ALint*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, + ALint *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNC5(void, alGetSource3i, ALuint,source, ALenum,param, ALint*,value1, ALint*,value2, ALint*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, + ALint *value1, ALint *value2, ALint *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + std::array ivals{}; + GetProperty(Source, context, static_cast(param), ivals); + *value1 = ivals[0]; + *value2 = ivals[1]; + *value3 = ivals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); +AL_API DECL_FUNC3(void, alGetSourceiv, ALuint,source, ALenum,param, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, + ALint *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{IntValsByProp(param)}; - GetSourceiv(Source, context.get(), static_cast(param), {values, count}); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!value) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - GetSourcei64v(Source, context.get(), static_cast(param), {value, 1u}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - else if(!(value1 && value2 && value3)) UNLIKELY - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else - { - int64_t i64vals[3]; - if(GetSourcei64v(Source, context.get(), static_cast(param), i64vals)) - { - *value1 = i64vals[0]; - *value2 = i64vals[1]; - *value3 = i64vals[2]; - } - } +AL_API DECL_FUNCEXT3(void, alGetSourcei64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value) +FORCE_ALIGN void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!value) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; + + GetProperty(Source, context, static_cast(param), al::span{value, 1u}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +AL_API DECL_FUNCEXT5(void, alGetSource3i64,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,value1, ALint64SOFT*,value2, ALint64SOFT*,value3) +FORCE_ALIGN void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!(value1 && value2 && value3)) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; - std::lock_guard _{context->mSourceLock}; - ALsource *Source{LookupSource(context.get(), source)}; - if(!Source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - if(!values) UNLIKELY - return context->setError(AL_INVALID_VALUE, "NULL pointer"); + std::array i64vals{}; + GetProperty(Source, context, static_cast(param), i64vals); + *value1 = i64vals[0]; + *value2 = i64vals[1]; + *value3 = i64vals[2]; +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNCEXT3(void, alGetSourcei64v,SOFT, ALuint,source, ALenum,param, ALint64SOFT*,values) +FORCE_ALIGN void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, + ALenum param, ALint64SOFT *values) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + if(!values) + throw al::context_error{AL_INVALID_VALUE, "NULL pointer"}; const ALuint count{Int64ValsByProp(param)}; - GetSourcei64v(Source, context.get(), static_cast(param), {values, count}); + GetProperty(Source, context, static_cast(param), al::span{values, count}); } -END_API_FUNC - - -AL_API void AL_APIENTRY alSourcePlay(ALuint source) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mSourceLock}; - ALsource *srchandle{LookupSource(context.get(), source)}; - if(!srchandle) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - - StartSources(context.get(), {&srchandle, 1}); +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - if(start_time < 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time); +AL_API DECL_FUNC1(void, alSourcePlay, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) noexcept +try { + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; - std::lock_guard _{context->mSourceLock}; - ALsource *srchandle{LookupSource(context.get(), source)}; - if(!srchandle) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", source); - - StartSources(context.get(), {&srchandle, 1}, nanoseconds{start_time}); + StartSources(context, {&Source, 1}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; +FORCE_ALIGN DECL_FUNCEXT2(void, alSourcePlayAtTime,SOFT, ALuint,source, ALint64SOFT,start_time) +FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, + ALint64SOFT start_time) noexcept +try { + if(start_time < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d sources", n); + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *Source{LookupSource(context, source)}; + if(!Source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", source}; + + StartSources(context, {&Source, 1}, nanoseconds{start_time}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} + +AL_API DECL_FUNC2(void, alSourcePlayv, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); - StartSources(context.get(), srchandles); + StartSources(context, srchandles); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Playing %d sources", n); +FORCE_ALIGN DECL_FUNCEXT3(void, alSourcePlayAtTimev,SOFT, ALsizei,n, const ALuint*,sources, ALint64SOFT,start_time) +FORCE_ALIGN void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, + const ALuint *sources, ALint64SOFT start_time) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Playing %d sources", n}; if(n <= 0) UNLIKELY return; - if(start_time < 0) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time); + if(start_time < 0) + throw al::context_error{AL_INVALID_VALUE, "Invalid time point %" PRId64, start_time}; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); - StartSources(context.get(), srchandles, nanoseconds{start_time}); + StartSources(context, srchandles, nanoseconds{start_time}); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourcePause(ALuint source) -START_API_FUNC -{ alSourcePausev(1, &source); } -END_API_FUNC +AL_API DECL_FUNC1(void, alSourcePause, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) noexcept +{ alSourcePausevDirect(context, 1, &source); } -AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Pausing %d sources", n); +AL_API DECL_FUNC2(void, alSourcePausev, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Pausing %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); /* Pausing has to be done in two steps. First, for each source that's * detected to be playing, chamge the voice (asynchronously) to @@ -3624,14 +3304,14 @@ START_API_FUNC VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { - Voice *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; if(GetSourceState(source, voice) == AL_PLAYING) { if(!cur) - cur = tail = GetVoiceChanger(context.get()); + cur = tail = GetVoiceChanger(context); else { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } cur->mVoice = voice; @@ -3641,7 +3321,7 @@ START_API_FUNC } if(tail) LIKELY { - SendVoiceChanges(context.get(), tail); + SendVoiceChanges(context, tail); /* Second, now that the voice changes have been sent, because it's * possible that the voice stopped after it was detected playing and * before the voice got paused, recheck that the source is still @@ -3649,60 +3329,57 @@ START_API_FUNC */ for(ALsource *source : srchandles) { - Voice *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; if(GetSourceState(source, voice) == AL_PLAYING) source->state = AL_PAUSED; } } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alSourceStop(ALuint source) -START_API_FUNC -{ alSourceStopv(1, &source); } -END_API_FUNC +AL_API DECL_FUNC1(void, alSourceStop, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) noexcept +{ alSourceStopvDirect(context, 1, &source); } -AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Stopping %d sources", n); +AL_API DECL_FUNC2(void, alSourceStopv, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Stopping %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { - if(Voice *voice{GetSourceVoice(source, context.get())}) + if(Voice *voice{GetSourceVoice(source, context)}) { if(!cur) - cur = tail = GetVoiceChanger(context.get()); + cur = tail = GetVoiceChanger(context); else { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } voice->mPendingChange.store(true, std::memory_order_relaxed); @@ -3713,60 +3390,57 @@ START_API_FUNC } source->Offset = 0.0; source->OffsetType = AL_NONE; - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; } if(tail) LIKELY - SendVoiceChanges(context.get(), tail); + SendVoiceChanges(context, tail); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceRewind(ALuint source) -START_API_FUNC -{ alSourceRewindv(1, &source); } -END_API_FUNC +AL_API DECL_FUNC1(void, alSourceRewind, ALuint,source) +FORCE_ALIGN void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) noexcept +{ alSourceRewindvDirect(context, 1, &source); } -AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(n < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Rewinding %d sources", n); +AL_API DECL_FUNC2(void, alSourceRewindv, ALsizei,n, const ALuint*,sources) +FORCE_ALIGN void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, + const ALuint *sources) noexcept +try { + if(n < 0) + throw al::context_error{AL_INVALID_VALUE, "Rewinding %d sources", n}; if(n <= 0) UNLIKELY return; - al::vector extra_sources; - std::array source_storage; - al::span srchandles; - if(static_cast(n) <= source_storage.size()) LIKELY - srchandles = {source_storage.data(), static_cast(n)}; - else + al::span sids{sources, static_cast(n)}; + source_store_variant source_store; + const auto srchandles = [&source_store](size_t count) -> al::span { - extra_sources.resize(static_cast(n)); - srchandles = {extra_sources.data(), extra_sources.size()}; - } + if(count > std::tuple_size_v) + return al::span{source_store.emplace(count)}; + return al::span{source_store.emplace()}.first(count); + }(sids.size()); - std::lock_guard _{context->mSourceLock}; - for(auto &srchdl : srchandles) + std::lock_guard sourcelock{context->mSourceLock}; + auto lookup_src = [context](const ALuint sid) -> ALsource* { - srchdl = LookupSource(context.get(), *sources); - if(!srchdl) - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", *sources); - ++sources; - } + if(ALsource *src{LookupSource(context, sid)}) + return src; + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", sid}; + }; + std::transform(sids.cbegin(), sids.cend(), srchandles.begin(), lookup_src); VoiceChange *tail{}, *cur{}; for(ALsource *source : srchandles) { - Voice *voice{GetSourceVoice(source, context.get())}; + Voice *voice{GetSourceVoice(source, context)}; if(source->state != AL_INITIAL) { if(!cur) - cur = tail = GetVoiceChanger(context.get()); + cur = tail = GetVoiceChanger(context); else { - cur->mNext.store(GetVoiceChanger(context.get()), std::memory_order_relaxed); + cur->mNext.store(GetVoiceChanger(context), std::memory_order_relaxed); cur = cur->mNext.load(std::memory_order_relaxed); } if(voice) @@ -3778,32 +3452,32 @@ START_API_FUNC } source->Offset = 0.0; source->OffsetType = AL_NONE; - source->VoiceIdx = INVALID_VOICE_IDX; + source->VoiceIdx = InvalidVoiceIndex; } if(tail) LIKELY - SendVoiceChanges(context.get(), tail); + SendVoiceChanges(context, tail); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint src, ALsizei nb, const ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(nb < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Queueing %d buffers", nb); +AL_API DECL_FUNC3(void, alSourceQueueBuffers, ALuint,source, ALsizei,nb, const ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint src, + ALsizei nb, const ALuint *buffers) noexcept +try { + if(nb < 0) + throw al::context_error{AL_INVALID_VALUE, "Queueing %d buffers", nb}; if(nb <= 0) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(!source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src); + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *source{LookupSource(context,src)}; + if(!source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; /* Can't queue on a Static Source */ - if(source->SourceType == AL_STATIC) UNLIKELY - return context->setError(AL_INVALID_OPERATION, "Queueing onto static source %u", src); + if(source->SourceType == AL_STATIC) + throw al::context_error{AL_INVALID_OPERATION, "Queueing onto static source %u", src}; /* Check for a valid Buffer, for its frequency and format */ ALCdevice *device{context->mALDevice.get()}; @@ -3815,90 +3489,85 @@ START_API_FUNC } std::unique_lock buflock{device->BufferLock}; + const auto bids = al::span{buffers, static_cast(nb)}; const size_t NewListStart{source->mQueue.size()}; - ALbufferQueueItem *BufferList{nullptr}; - for(ALsizei i{0};i < nb;i++) - { - bool fmt_mismatch{false}; - ALbuffer *buffer{nullptr}; - if(buffers[i] && (buffer=LookupBuffer(device, buffers[i])) == nullptr) + try { + ALbufferQueueItem *BufferList{nullptr}; + std::for_each(bids.cbegin(), bids.cend(), + [source,device,&BufferFmt,&BufferList](const ALuint bid) { - context->setError(AL_INVALID_NAME, "Queueing invalid buffer ID %u", buffers[i]); - goto buffer_error; - } - if(buffer) - { - if(buffer->mSampleRate < 1) - { - context->setError(AL_INVALID_OPERATION, "Queueing buffer %u with no format", - buffer->id); - goto buffer_error; - } - if(buffer->mCallback) - { - context->setError(AL_INVALID_OPERATION, "Queueing callback buffer %u", buffer->id); - goto buffer_error; - } - if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) - { - context->setError(AL_INVALID_OPERATION, - "Queueing non-persistently mapped buffer %u", buffer->id); - goto buffer_error; - } - } + ALbuffer *buffer{bid ? LookupBuffer(device, bid) : nullptr}; + if(bid && !buffer) + throw al::context_error{AL_INVALID_NAME, "Queueing invalid buffer ID %u", bid}; - source->mQueue.emplace_back(); - if(!BufferList) - BufferList = &source->mQueue.back(); - else - { - auto &item = source->mQueue.back(); - BufferList->mNext.store(&item, std::memory_order_relaxed); - BufferList = &item; - } - if(!buffer) continue; - BufferList->mBlockAlign = buffer->mBlockAlign; - BufferList->mSampleLen = buffer->mSampleLen; - BufferList->mLoopEnd = buffer->mSampleLen; - BufferList->mSamples = buffer->mData.data(); - BufferList->mBuffer = buffer; - IncrementRef(buffer->ref); - - if(BufferFmt == nullptr) - BufferFmt = buffer; - else - { - fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; - fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; - fmt_mismatch |= BufferFmt->mType != buffer->mType; - if(BufferFmt->isBFormat()) + if(buffer) { - fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; - fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; - } - fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; - } - if(fmt_mismatch) UNLIKELY - { - context->setError(AL_INVALID_OPERATION, "Queueing buffer with mismatched format\n" - " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate, - NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), - buffer->mSampleRate, NameFromFormat(buffer->mType), - NameFromFormat(buffer->mChannels)); + if(buffer->mSampleRate < 1) + throw al::context_error{AL_INVALID_OPERATION, + "Queueing buffer %u with no format", buffer->id}; - buffer_error: - /* A buffer failed (invalid ID or format), so unlock and release - * each buffer we had. - */ - auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); - for(;iter != source->mQueue.end();++iter) - { - if(ALbuffer *buf{iter->mBuffer}) - DecrementRef(buf->ref); + if(buffer->mCallback) + throw al::context_error{AL_INVALID_OPERATION, "Queueing callback buffer %u", + buffer->id}; + + if(buffer->MappedAccess != 0 && !(buffer->MappedAccess&AL_MAP_PERSISTENT_BIT_SOFT)) + throw al::context_error{AL_INVALID_OPERATION, + "Queueing non-persistently mapped buffer %u", buffer->id}; } - source->mQueue.resize(NewListStart); - return; + + source->mQueue.emplace_back(); + if(!BufferList) + BufferList = &source->mQueue.back(); + else + { + auto &item = source->mQueue.back(); + BufferList->mNext.store(&item, std::memory_order_relaxed); + BufferList = &item; + } + if(!buffer) return; + BufferList->mBlockAlign = buffer->mBlockAlign; + BufferList->mSampleLen = buffer->mSampleLen; + BufferList->mLoopEnd = buffer->mSampleLen; + BufferList->mSamples = buffer->mData; + BufferList->mBuffer = buffer; + IncrementRef(buffer->ref); + + bool fmt_mismatch{false}; + if(BufferFmt == nullptr) + BufferFmt = buffer; + else + { + fmt_mismatch |= BufferFmt->mSampleRate != buffer->mSampleRate; + fmt_mismatch |= BufferFmt->mChannels != buffer->mChannels; + fmt_mismatch |= BufferFmt->mType != buffer->mType; + if(BufferFmt->isBFormat()) + { + fmt_mismatch |= BufferFmt->mAmbiLayout != buffer->mAmbiLayout; + fmt_mismatch |= BufferFmt->mAmbiScaling != buffer->mAmbiScaling; + } + fmt_mismatch |= BufferFmt->mAmbiOrder != buffer->mAmbiOrder; + } + if(fmt_mismatch) + throw al::context_error{AL_INVALID_OPERATION, + "Queueing buffer with mismatched format\n" + " Expected: %uhz, %s, %s ; Got: %uhz, %s, %s\n", BufferFmt->mSampleRate, + NameFromFormat(BufferFmt->mType), NameFromFormat(BufferFmt->mChannels), + buffer->mSampleRate, NameFromFormat(buffer->mType), + NameFromFormat(buffer->mChannels)}; + }); + } + catch(...) { + /* A buffer failed (invalid ID or format), or there was some other + * unexpected error, so unlock and release each buffer we had. + */ + auto iter = source->mQueue.begin() + ptrdiff_t(NewListStart); + for(;iter != source->mQueue.end();++iter) + { + if(ALbuffer *buf{iter->mBuffer}) + DecrementRef(buf->ref); } + source->mQueue.resize(NewListStart); + throw; } /* All buffers good. */ buflock.unlock(); @@ -3912,35 +3581,35 @@ START_API_FUNC (iter-1)->mNext.store(al::to_address(iter), std::memory_order_release); } } -END_API_FUNC +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); +} -AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint src, ALsizei nb, ALuint *buffers) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(nb < 0) UNLIKELY - context->setError(AL_INVALID_VALUE, "Unqueueing %d buffers", nb); +AL_API DECL_FUNC3(void, alSourceUnqueueBuffers, ALuint,source, ALsizei,nb, ALuint*,buffers) +FORCE_ALIGN void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint src, + ALsizei nb, ALuint *buffers) noexcept +try { + if(nb < 0) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffers", nb}; if(nb <= 0) UNLIKELY return; - std::lock_guard _{context->mSourceLock}; - ALsource *source{LookupSource(context.get(),src)}; - if(!source) UNLIKELY - return context->setError(AL_INVALID_NAME, "Invalid source ID %u", src); + std::lock_guard sourcelock{context->mSourceLock}; + ALsource *source{LookupSource(context,src)}; + if(!source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", src}; - if(source->SourceType != AL_STREAMING) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u", - src); - if(source->Looping) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Unqueueing from looping source %u", src); + if(source->SourceType != AL_STREAMING) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing from a non-streaming source %u",src}; + if(source->Looping) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing from looping source %u", src}; /* Make sure enough buffers have been processed to unqueue. */ - uint processed{0u}; + const al::span bids{buffers, static_cast(nb)}; + size_t processed{0}; if(source->state != AL_INITIAL) LIKELY { VoiceBufferItem *Current{nullptr}; - if(Voice *voice{GetSourceVoice(source, context.get())}) + if(Voice *voice{GetSourceVoice(source, context)}) Current = voice->mCurrentBuffer.load(std::memory_order_relaxed); for(auto &item : source->mQueue) { @@ -3949,51 +3618,52 @@ START_API_FUNC ++processed; } } - if(processed < static_cast(nb)) UNLIKELY - return context->setError(AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %u processed)", - nb, (nb==1)?"":"s", processed); + if(processed < bids.size()) + throw al::context_error{AL_INVALID_VALUE, "Unqueueing %d buffer%s (only %zu processed)", + nb, (nb==1)?"":"s", processed}; - do { + std::generate(bids.begin(), bids.end(), [source]() noexcept -> ALuint + { auto &head = source->mQueue.front(); + ALuint bid{0}; if(ALbuffer *buffer{head.mBuffer}) { - *(buffers++) = buffer->id; + bid = buffer->id; DecrementRef(buffer->ref); } - else - *(buffers++) = 0; source->mQueue.pop_front(); - } while(--nb); + return bid; + }); +} +catch(al::context_error& e) { + context->setError(e.errorCode(), "%s", e.what()); } -END_API_FUNC -AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) -START_API_FUNC +AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint, ALsizei, const ALuint*) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; context->setError(AL_INVALID_OPERATION, "alSourceQueueBufferLayersSOFT not supported"); } -END_API_FUNC -ALsource::ALsource() +ALsource::ALsource() noexcept { Direct.Gain = 1.0f; Direct.GainHF = 1.0f; - Direct.HFReference = LOWPASSFREQREF; + Direct.HFReference = LowPassFreqRef; Direct.GainLF = 1.0f; - Direct.LFReference = HIGHPASSFREQREF; + Direct.LFReference = HighPassFreqRef; for(auto &send : Send) { send.Slot = nullptr; send.Gain = 1.0f; send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; + send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; + send.LFReference = HighPassFreqRef; } } @@ -4012,13 +3682,13 @@ ALsource::~ALsource() void UpdateAllSourceProps(ALCcontext *context) { - std::lock_guard _{context->mSourceLock}; + std::lock_guard srclock{context->mSourceLock}; auto voicelist = context->getVoicesSpan(); ALuint vidx{0u}; for(Voice *voice : voicelist) { ALuint sid{voice->mSourceID.load(std::memory_order_acquire)}; - ALsource *source = sid ? LookupSource(context, sid) : nullptr; + ALsource *source{sid ? LookupSource(context, sid) : nullptr}; if(source && source->VoiceIdx == vidx) { if(std::exchange(source->mPropsDirty, false)) @@ -4028,25 +3698,37 @@ void UpdateAllSourceProps(ALCcontext *context) } } +void ALsource::SetName(ALCcontext *context, ALuint id, std::string_view name) +{ + std::lock_guard srclock{context->mSourceLock}; + + auto source = LookupSource(context, id); + if(!source) + throw al::context_error{AL_INVALID_NAME, "Invalid source ID %u", id}; + + context->mSourceNames.insert_or_assign(id, name); +} + + SourceSubList::~SourceSubList() { + if(!Sources) + return; + uint64_t usemask{~FreeMask}; while(usemask) { const int idx{al::countr_zero(usemask)}; usemask &= ~(1_u64 << idx); - al::destroy_at(Sources+idx); + std::destroy_at(al::to_address(Sources->begin() + idx)); } FreeMask = ~usemask; - al_free(Sources); + SubListAllocator{}.deallocate(Sources, 1); Sources = nullptr; } #ifdef ALSOFT_EAX -constexpr const ALsource::EaxFxSlotIds ALsource::eax4_fx_slot_ids; -constexpr const ALsource::EaxFxSlotIds ALsource::eax5_fx_slot_ids; - void ALsource::eaxInitialize(ALCcontext *context) noexcept { assert(context != nullptr); @@ -4097,7 +3779,8 @@ ALsource* ALsource::EaxLookupSource(ALCcontext& al_context, ALuint source_id) no void ALsource::eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept { - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) { + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) + { auto& send = sends[i]; send.guidReceivingFXSlotID = *(ids[i]); send.lSend = EAXSOURCE_DEFAULTSEND; @@ -4209,7 +3892,8 @@ void ALsource::eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noex void ALsource::eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept { - for (auto i = size_t{}; i < eax_max_speakers; ++i) { + for(size_t i{0};i < eax_max_speakers;++i) + { auto& speaker_level = speaker_levels[i]; speaker_level.lSpeakerID = static_cast(EAXSPEAKER_FRONT_LEFT + i); speaker_level.lLevel = EAXSOURCE_DEFAULTSPEAKERLEVEL; @@ -4251,7 +3935,7 @@ void ALsource::eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept else { dst.source.ulFlags &= ~EAXSOURCEFLAGS_ROOMAUTO; - dst.sends[0].lSend = clamp(static_cast(gain_to_level_mb(src.fMix)), + dst.sends[0].lSend = std::clamp(static_cast(gain_to_level_mb(src.fMix)), EAXSOURCE_MINSEND, EAXSOURCE_MAXSEND); } } @@ -4280,7 +3964,7 @@ void ALsource::eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept dst.source.ulFlags = src.dwFlags; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; - // Set everyting else to defaults. + // Set everything else to defaults. // eax5_set_sends_defaults(dst.sends); eax5_set_active_fx_slots_defaults(dst.active_fx_slots); @@ -4294,7 +3978,7 @@ void ALsource::eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept static_cast(dst.source) = src; dst.source.flMacroFXFactor = EAXSOURCE_DEFAULTMACROFXFACTOR; - // Set everyting else to defaults. + // Set everything else to defaults. // eax5_set_sends_defaults(dst.sends); eax5_set_active_fx_slots_defaults(dst.active_fx_slots); @@ -4312,32 +3996,35 @@ void ALsource::eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept // dst.sends = src.sends; - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) dst.sends[i].guidReceivingFXSlotID = *(eax5_fx_slot_ids[i]); // Active FX slots. // - for (auto i = 0; i < EAX50_MAX_ACTIVE_FXSLOTS; ++i) { + for(size_t i{0};i < EAX50_MAX_ACTIVE_FXSLOTS;++i) + { auto& dst_id = dst.active_fx_slots.guidActiveFXSlots[i]; - if (i < EAX40_MAX_ACTIVE_FXSLOTS) { + if(i < EAX40_MAX_ACTIVE_FXSLOTS) + { const auto& src_id = src.active_fx_slots.guidActiveFXSlots[i]; - if (src_id == EAX_NULL_GUID) + if(src_id == EAX_NULL_GUID) dst_id = EAX_NULL_GUID; - else if (src_id == EAX_PrimaryFXSlotID) + else if(src_id == EAX_PrimaryFXSlotID) dst_id = EAX_PrimaryFXSlotID; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot0) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot0) dst_id = EAXPROPERTYID_EAX50_FXSlot0; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot1) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot1) dst_id = EAXPROPERTYID_EAX50_FXSlot1; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot2) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot2) dst_id = EAXPROPERTYID_EAX50_FXSlot2; - else if (src_id == EAXPROPERTYID_EAX40_FXSlot3) + else if(src_id == EAXPROPERTYID_EAX40_FXSlot3) dst_id = EAXPROPERTYID_EAX50_FXSlot3; else assert(false && "Unknown active FX slot ID."); - } else + } + else dst_id = EAX_NULL_GUID; } @@ -4374,19 +4061,21 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept static_cast(mEax.source.lDirectHF) + static_cast(mEax.source.lObstruction); - for (auto i = std::size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) continue; - if(has_source_occlusion) { + if(has_source_occlusion) + { const auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); const auto& fx_slot_eax = fx_slot.eax_get_eax_fx_slot(); const auto is_environmental_fx = ((fx_slot_eax.ulFlags & EAXFXSLOTFLAGS_ENVIRONMENT) != 0); const auto is_primary = (mEaxPrimaryFxSlotId.value_or(-1) == fx_slot.eax_get_index()); const auto is_listener_environment = (is_environmental_fx && is_primary); - if(is_listener_environment) { + if(is_listener_environment) + { gain_mb += eax_calculate_dst_occlusion_mb( mEax.source.lOcclusion, mEax.source.flOcclusionDirectRatio, @@ -4398,7 +4087,8 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept const auto& send = mEax.sends[i]; - if(send.lOcclusion != 0) { + if(send.lOcclusion != 0) + { gain_mb += eax_calculate_dst_occlusion_mb( send.lOcclusion, send.flOcclusionDirectRatio, @@ -4408,11 +4098,9 @@ EaxAlLowPassParam ALsource::eax_create_direct_filter_param() const noexcept } } - const auto al_low_pass_param = EaxAlLowPassParam{ + return EaxAlLowPassParam{ level_mb_to_gain(gain_mb), - minf(level_mb_to_gain(gain_hf_mb), 1.0f)}; - - return al_low_pass_param; + std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; } EaxAlLowPassParam ALsource::eax_create_room_filter_param( @@ -4453,11 +4141,9 @@ EaxAlLowPassParam ALsource::eax_create_room_filter_param( static_cast(mEax.source.lExclusion + send.lExclusion) : 0.0f); - const auto al_low_pass_param = EaxAlLowPassParam{ + return EaxAlLowPassParam{ level_mb_to_gain(gain_mb), - minf(level_mb_to_gain(gain_hf_mb), 1.0f)}; - - return al_low_pass_param; + std::min(level_mb_to_gain(gain_hf_mb), 1.0f)}; } void ALsource::eax_update_direct_filter() @@ -4465,16 +4151,17 @@ void ALsource::eax_update_direct_filter() const auto& direct_param = eax_create_direct_filter_param(); Direct.Gain = direct_param.gain; Direct.GainHF = direct_param.gain_hf; - Direct.HFReference = LOWPASSFREQREF; + Direct.HFReference = LowPassFreqRef; Direct.GainLF = 1.0f; - Direct.LFReference = HIGHPASSFREQREF; + Direct.LFReference = HighPassFreqRef; mPropsDirty = true; } void ALsource::eax_update_room_filters() { - for (auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) { - if (!mEaxActiveFxSlots[i]) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) + { + if(!mEaxActiveFxSlots[i]) continue; auto& fx_slot = mEaxAlContext->eaxGetFxSlot(i); @@ -4486,7 +4173,7 @@ void ALsource::eax_update_room_filters() void ALsource::eax_set_efx_outer_gain_hf() { - OuterGainHF = clamp( + OuterGainHF = std::clamp( level_mb_to_gain(static_cast(mEax.source.lOutsideVolumeHF)), AL_MIN_CONE_OUTER_GAINHF, AL_MAX_CONE_OUTER_GAINHF); @@ -4755,7 +4442,7 @@ void ALsource::eax4_set(const EaxCall& call, Eax4Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax4_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); + eax4_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; default: @@ -4815,10 +4502,13 @@ void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) case EAXSOURCE_ROLLOFFFACTOR: case EAXSOURCE_ROOMROLLOFFFACTOR: case EAXSOURCE_AIRABSORPTIONFACTOR: - case EAXSOURCE_FLAGS: eax3_set(call, props.source); break; + case EAXSOURCE_FLAGS: + eax_defer(call, props.source.ulFlags); + break; + case EAXSOURCE_SENDPARAMETERS: eax5_defer_sends(call, props.sends); break; @@ -4836,7 +4526,7 @@ void ALsource::eax5_set(const EaxCall& call, Eax5Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax5_defer_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); + eax5_defer_active_fx_slot_id(call, al::span{props.active_fx_slots.guidActiveFXSlots}); break; case EAXSOURCE_MACROFXFACTOR: @@ -4872,13 +4562,11 @@ void ALsource::eax_set(const EaxCall& call) mEaxVersion = eax_version; } -void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count) +void ALsource::eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids) { - assert(ids != nullptr); - assert(max_count == EAX40_MAX_ACTIVE_FXSLOTS || max_count == EAX50_MAX_ACTIVE_FXSLOTS); - const auto dst_ids = call.get_values(max_count); - const auto count = dst_ids.size(); - std::uninitialized_copy_n(ids, count, dst_ids.begin()); + assert(src_ids.size()==EAX40_MAX_ACTIVE_FXSLOTS || src_ids.size()==EAX50_MAX_ACTIVE_FXSLOTS); + const auto dst_ids = call.get_values(src_ids.size()); + std::uninitialized_copy_n(src_ids.begin(), dst_ids.size(), dst_ids.begin()); } void ALsource::eax1_get(const EaxCall& call, const Eax1Props& props) @@ -5126,7 +4814,7 @@ void ALsource::eax4_get(const EaxCall& call, const Eax4Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX40_MAX_ACTIVE_FXSLOTS); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; default: @@ -5198,7 +4886,7 @@ void ALsource::eax5_get(const EaxCall& call, const Eax5Props& props) break; case EAXSOURCE_ACTIVEFXSLOTID: - eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots, EAX50_MAX_ACTIVE_FXSLOTS); + eax_get_active_fx_slot_id(call, props.active_fx_slots.guidActiveFXSlots); break; case EAXSOURCE_MACROFXFACTOR: @@ -5238,9 +4926,9 @@ void ALsource::eax_set_al_source_send(ALeffectslot *slot, size_t sendidx, const auto &send = Send[sendidx]; send.Gain = filter.gain; send.GainHF = filter.gain_hf; - send.HFReference = LOWPASSFREQREF; + send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; + send.LFReference = HighPassFreqRef; if(slot != nullptr) IncrementRef(slot->ref); @@ -5280,7 +4968,7 @@ void ALsource::eax_commit_active_fx_slots() // Deactivate EFX auxiliary effect slots for inactive slots. Active slots // will be updated with the room filters. - for(auto i = size_t{}; i < EAX_MAX_FXSLOTS; ++i) + for(size_t i{0};i < EAX_MAX_FXSLOTS;++i) { if(!mEaxActiveFxSlots[i]) eax_set_al_source_send(nullptr, i, EaxAlLowPassParam{1.0f, 1.0f}); diff --git a/Engine/lib/openal-soft/al/source.h b/Engine/lib/openal-soft/al/source.h index ac97c8a73..9f70bfafd 100644 --- a/Engine/lib/openal-soft/al/source.h +++ b/Engine/lib/openal-soft/al/source.h @@ -2,26 +2,26 @@ #define AL_SOURCE_H #include -#include #include -#include -#include +#include #include +#include +#include +#include #include "AL/al.h" #include "AL/alc.h" +#include "AL/alext.h" -#include "alc/alu.h" -#include "alc/context.h" -#include "alc/inprogext.h" -#include "aldeque.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" -#include "atomic.h" +#include "alspan.h" +#include "core/context.h" #include "core/voice.h" -#include "vector.h" #ifdef ALSOFT_EAX +#include "eax/api.h" #include "eax/call.h" #include "eax/exception.h" #include "eax/fx_slot_index.h" @@ -30,23 +30,23 @@ struct ALbuffer; struct ALeffectslot; - +enum class Resampler : uint8_t; enum class SourceStereo : bool { Normal = AL_NORMAL_SOFT, Enhanced = AL_SUPER_STEREO_SOFT }; -#define DEFAULT_SENDS 2 +inline constexpr size_t DefaultSendCount{2}; -#define INVALID_VOICE_IDX static_cast(-1) +inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits::max()}; -extern bool sBufferSubDataCompat; +inline bool sBufferSubDataCompat{false}; struct ALbufferQueueItem : public VoiceBufferItem { ALbuffer *mBuffer{nullptr}; - DISABLE_ALLOC() + DISABLE_ALLOC }; @@ -88,6 +88,7 @@ struct ALsource { DirectMode DirectChannels{DirectMode::Off}; SpatializeMode mSpatialize{SpatializeMode::Auto}; SourceStereo mStereoMode{SourceStereo::Normal}; + bool mPanningEnabled{false}; bool DryGainHFAuto{true}; bool WetGainAuto{true}; @@ -105,24 +106,27 @@ struct ALsource { float Radius{0.0f}; float EnhWidth{0.593f}; + float mPan{0.0f}; /** Direct filter and auxiliary send info. */ - struct { - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; - } Direct; - struct SendData { - ALeffectslot *Slot; - float Gain; - float GainHF; - float HFReference; - float GainLF; - float LFReference; + struct DirectData { + float Gain{}; + float GainHF{}; + float HFReference{}; + float GainLF{}; + float LFReference{}; }; - std::array Send; + DirectData Direct; + + struct SendData { + ALeffectslot *Slot{}; + float Gain{}; + float GainHF{}; + float HFReference{}; + float GainLF{}; + float LFReference{}; + }; + std::array Send; /** * Last user-specified offset, and the offset type (bytes, samples, or @@ -138,26 +142,28 @@ struct ALsource { ALenum state{AL_INITIAL}; /** Source Buffer Queue head. */ - al::deque mQueue; + std::deque mQueue; bool mPropsDirty{true}; /* Index into the context's Voices array. Lazily updated, only checked and * reset when looking up the voice. */ - ALuint VoiceIdx{INVALID_VOICE_IDX}; + ALuint VoiceIdx{InvalidVoiceIndex}; /** Self ID */ ALuint id{0}; - ALsource(); + ALsource() noexcept; ~ALsource(); ALsource(const ALsource&) = delete; ALsource& operator=(const ALsource&) = delete; - DISABLE_ALLOC() + static void SetName(ALCcontext *context, ALuint id, std::string_view name); + + DISABLE_ALLOC #ifdef ALSOFT_EAX public: @@ -171,18 +177,18 @@ public: private: using Exception = EaxSourceException; - static constexpr auto eax_max_speakers = 9; + static constexpr auto eax_max_speakers{9u}; - using EaxFxSlotIds = const GUID* [EAX_MAX_FXSLOTS]; + using EaxFxSlotIds = std::array; - static constexpr const EaxFxSlotIds eax4_fx_slot_ids = { + static constexpr const EaxFxSlotIds eax4_fx_slot_ids{ &EAXPROPERTYID_EAX40_FXSlot0, &EAXPROPERTYID_EAX40_FXSlot1, &EAXPROPERTYID_EAX40_FXSlot2, &EAXPROPERTYID_EAX40_FXSlot3, }; - static constexpr const EaxFxSlotIds eax5_fx_slot_ids = { + static constexpr const EaxFxSlotIds eax5_fx_slot_ids{ &EAXPROPERTYID_EAX50_FXSlot0, &EAXPROPERTYID_EAX50_FXSlot1, &EAXPROPERTYID_EAX50_FXSlot2, @@ -215,11 +221,6 @@ private: Eax3Props source; EaxSends sends; EAX40ACTIVEFXSLOTS active_fx_slots; - - bool operator==(const Eax4Props& rhs) noexcept - { - return std::memcmp(this, &rhs, sizeof(Eax4Props)) == 0; - } }; struct Eax4State { @@ -232,11 +233,6 @@ private: EaxSends sends; EAX50ACTIVEFXSLOTS active_fx_slots; EaxSpeakerLevels speaker_levels; - - bool operator==(const Eax5Props& rhs) noexcept - { - return std::memcmp(this, &rhs, sizeof(Eax5Props)) == 0; - } }; struct Eax5State { @@ -546,7 +542,24 @@ private: struct Eax5SourceAllValidator { void operator()(const EAX50SOURCEPROPERTIES& props) const { - Eax3SourceAllValidator{}(static_cast(props)); + Eax2SourceDirectValidator{}(props.lDirect); + Eax2SourceDirectHfValidator{}(props.lDirectHF); + Eax2SourceRoomValidator{}(props.lRoom); + Eax2SourceRoomHfValidator{}(props.lRoomHF); + Eax2SourceObstructionValidator{}(props.lObstruction); + Eax2SourceObstructionLfRatioValidator{}(props.flObstructionLFRatio); + Eax2SourceOcclusionValidator{}(props.lOcclusion); + Eax2SourceOcclusionLfRatioValidator{}(props.flOcclusionLFRatio); + Eax2SourceOcclusionRoomRatioValidator{}(props.flOcclusionRoomRatio); + Eax3SourceOcclusionDirectRatioValidator{}(props.flOcclusionDirectRatio); + Eax3SourceExclusionValidator{}(props.lExclusion); + Eax3SourceExclusionLfRatioValidator{}(props.flExclusionLFRatio); + Eax2SourceOutsideVolumeHfValidator{}(props.lOutsideVolumeHF); + Eax3SourceDopplerFactorValidator{}(props.flDopplerFactor); + Eax3SourceRolloffFactorValidator{}(props.flRolloffFactor); + Eax2SourceRoomRolloffFactorValidator{}(props.flRoomRolloffFactor); + Eax2SourceAirAbsorptionFactorValidator{}(props.flAirAbsorptionFactor); + Eax5SourceFlagsValidator{}(props.ulFlags); Eax5SourceMacroFXFactorValidator{}(props.flMacroFXFactor); } }; @@ -809,39 +822,38 @@ private: [[noreturn]] static void eax_fail_unknown_active_fx_slot_id(); [[noreturn]] static void eax_fail_unknown_receiving_fx_slot_id(); - void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; - void eax1_set_defaults(Eax1Props& props) noexcept; + static void eax_set_sends_defaults(EaxSends& sends, const EaxFxSlotIds& ids) noexcept; + static void eax1_set_defaults(Eax1Props& props) noexcept; void eax1_set_defaults() noexcept; - void eax2_set_defaults(Eax2Props& props) noexcept; + static void eax2_set_defaults(Eax2Props& props) noexcept; void eax2_set_defaults() noexcept; - void eax3_set_defaults(Eax3Props& props) noexcept; + static void eax3_set_defaults(Eax3Props& props) noexcept; void eax3_set_defaults() noexcept; - void eax4_set_sends_defaults(EaxSends& sends) noexcept; - void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; + static void eax4_set_sends_defaults(EaxSends& sends) noexcept; + static void eax4_set_active_fx_slots_defaults(EAX40ACTIVEFXSLOTS& slots) noexcept; void eax4_set_defaults() noexcept; - void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; - void eax5_set_sends_defaults(EaxSends& sends) noexcept; - void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; - void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; - void eax5_set_defaults(Eax5Props& props) noexcept; + static void eax5_set_source_defaults(EAX50SOURCEPROPERTIES& props) noexcept; + static void eax5_set_sends_defaults(EaxSends& sends) noexcept; + static void eax5_set_active_fx_slots_defaults(EAX50ACTIVEFXSLOTS& slots) noexcept; + static void eax5_set_speaker_levels_defaults(EaxSpeakerLevels& speaker_levels) noexcept; + static void eax5_set_defaults(Eax5Props& props) noexcept; void eax5_set_defaults() noexcept; void eax_set_defaults() noexcept; - void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; - void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; - void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; - void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; + static void eax1_translate(const Eax1Props& src, Eax5Props& dst) noexcept; + static void eax2_translate(const Eax2Props& src, Eax5Props& dst) noexcept; + static void eax3_translate(const Eax3Props& src, Eax5Props& dst) noexcept; + static void eax4_translate(const Eax4Props& src, Eax5Props& dst) noexcept; static float eax_calculate_dst_occlusion_mb( long src_occlusion_mb, float path_ratio, float lf_ratio) noexcept; - EaxAlLowPassParam eax_create_direct_filter_param() const noexcept; + [[nodiscard]] auto eax_create_direct_filter_param() const noexcept -> EaxAlLowPassParam; - EaxAlLowPassParam eax_create_room_filter_param( - const ALeffectslot& fx_slot, - const EAXSOURCEALLSENDPROPERTIES& send) const noexcept; + [[nodiscard]] auto eax_create_room_filter_param(const ALeffectslot& fx_slot, + const EAXSOURCEALLSENDPROPERTIES& send) const noexcept -> EaxAlLowPassParam; void eax_update_direct_filter(); void eax_update_room_filters(); @@ -894,16 +906,16 @@ private: } } - void eax_get_active_fx_slot_id(const EaxCall& call, const GUID* ids, size_t max_count); - void eax1_get(const EaxCall& call, const Eax1Props& props); - void eax2_get(const EaxCall& call, const Eax2Props& props); - void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); - void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); - void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); - void eax3_get(const EaxCall& call, const Eax3Props& props); + static void eax_get_active_fx_slot_id(const EaxCall& call, const al::span src_ids); + static void eax1_get(const EaxCall& call, const Eax1Props& props); + static void eax2_get(const EaxCall& call, const Eax2Props& props); + static void eax3_get_obstruction(const EaxCall& call, const Eax3Props& props); + static void eax3_get_occlusion(const EaxCall& call, const Eax3Props& props); + static void eax3_get_exclusion(const EaxCall& call, const Eax3Props& props); + static void eax3_get(const EaxCall& call, const Eax3Props& props); void eax4_get(const EaxCall& call, const Eax4Props& props); - void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); - void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); + static void eax5_get_all_2d(const EaxCall& call, const EAX50SOURCEPROPERTIES& props); + static void eax5_get_speaker_levels(const EaxCall& call, const EaxSpeakerLevels& props); void eax5_get(const EaxCall& call, const Eax5Props& props); void eax_get(const EaxCall& call); @@ -976,21 +988,21 @@ private: } template - void eax_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount]) + void eax_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { const auto src_ids = call.get_values(TIdCount); std::for_each(src_ids.cbegin(), src_ids.cend(), TValidator{}); - std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids); + std::uninitialized_copy(src_ids.cbegin(), src_ids.cend(), dst_ids.begin()); } template - void eax4_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount]) + void eax4_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } template - void eax5_defer_active_fx_slot_id(const EaxCall& call, GUID (&dst_ids)[TIdCount]) + void eax5_defer_active_fx_slot_id(const EaxCall& call, const al::span dst_ids) { eax_defer_active_fx_slot_id(call, dst_ids); } @@ -1022,12 +1034,12 @@ private: void eax_set_efx_wet_gain_auto(); void eax_set_efx_wet_gain_hf_auto(); - void eax1_set(const EaxCall& call, Eax1Props& props); - void eax2_set(const EaxCall& call, Eax2Props& props); + static void eax1_set(const EaxCall& call, Eax1Props& props); + static void eax2_set(const EaxCall& call, Eax2Props& props); void eax3_set(const EaxCall& call, Eax3Props& props); void eax4_set(const EaxCall& call, Eax4Props& props); - void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); - void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); + static void eax5_defer_all_2d(const EaxCall& call, EAX50SOURCEPROPERTIES& props); + static void eax5_defer_speaker_levels(const EaxCall& call, EaxSpeakerLevels& props); void eax5_set(const EaxCall& call, Eax5Props& props); void eax_set(const EaxCall& call); @@ -1041,4 +1053,19 @@ private: void UpdateAllSourceProps(ALCcontext *context); +struct SourceSubList { + uint64_t FreeMask{~0_u64}; + gsl::owner*> Sources{nullptr}; + + SourceSubList() noexcept = default; + SourceSubList(const SourceSubList&) = delete; + SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} + { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } + ~SourceSubList(); + + SourceSubList& operator=(const SourceSubList&) = delete; + SourceSubList& operator=(SourceSubList&& rhs) noexcept + { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } +}; + #endif diff --git a/Engine/lib/openal-soft/al/state.cpp b/Engine/lib/openal-soft/al/state.cpp index 86d81b130..f0216b22f 100644 --- a/Engine/lib/openal-soft/al/state.cpp +++ b/Engine/lib/openal-soft/al/state.cpp @@ -22,26 +22,32 @@ #include "version.h" +#include #include #include +#include #include +#include #include #include +#include #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" +#include "al/debug.h" +#include "al/listener.h" #include "alc/alu.h" #include "alc/context.h" #include "alc/inprogext.h" #include "alnumeric.h" -#include "aloptional.h" #include "atomic.h" #include "core/context.h" -#include "core/except.h" +#include "core/logging.h" #include "core/mixer/defs.h" #include "core/voice.h" +#include "direct_defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" @@ -56,17 +62,19 @@ namespace { -constexpr ALchar alVendor[] = "OpenAL Community"; -constexpr ALchar alVersion[] = "1.1 ALSOFT " ALSOFT_VERSION; -constexpr ALchar alRenderer[] = "OpenAL Soft"; +[[nodiscard]] constexpr auto GetVendorString() noexcept { return "OpenAL Community"; } +[[nodiscard]] constexpr auto GetVersionString() noexcept { return "1.1 ALSOFT " ALSOFT_VERSION; } +[[nodiscard]] constexpr auto GetRendererString() noexcept { return "OpenAL Soft"; } -// Error Messages -constexpr ALchar alNoError[] = "No Error"; -constexpr ALchar alErrInvalidName[] = "Invalid Name"; -constexpr ALchar alErrInvalidEnum[] = "Invalid Enum"; -constexpr ALchar alErrInvalidValue[] = "Invalid Value"; -constexpr ALchar alErrInvalidOp[] = "Invalid Operation"; -constexpr ALchar alErrOutOfMemory[] = "Out of Memory"; +/* Error Messages */ +[[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } +[[nodiscard]] constexpr auto GetInvalidNameString() noexcept { return "Invalid Name"; } +[[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } +[[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } +[[nodiscard]] constexpr auto GetInvalidOperationString() noexcept { return "Invalid Operation"; } +[[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } +[[nodiscard]] constexpr auto GetStackOverflowString() noexcept { return "Stack Overflow"; } +[[nodiscard]] constexpr auto GetStackUnderflowString() noexcept { return "Stack Underflow"; } /* Resampler strings */ template struct ResamplerName { }; @@ -74,8 +82,10 @@ template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Nearest"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "Linear"; } }; -template<> struct ResamplerName -{ static constexpr const ALchar *Get() noexcept { return "Cubic"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "Cubic Spline"; } }; +template<> struct ResamplerName +{ static constexpr const ALchar *Get() noexcept { return "4-point Gaussian"; } }; template<> struct ResamplerName { static constexpr const ALchar *Get() noexcept { return "11th order Sinc (fast)"; } }; template<> struct ResamplerName @@ -92,7 +102,8 @@ const ALchar *GetResamplerName(const Resampler rtype) { HANDLE_RESAMPLER(Resampler::Point); HANDLE_RESAMPLER(Resampler::Linear); - HANDLE_RESAMPLER(Resampler::Cubic); + HANDLE_RESAMPLER(Resampler::Spline); + HANDLE_RESAMPLER(Resampler::Gaussian); HANDLE_RESAMPLER(Resampler::FastBSinc12); HANDLE_RESAMPLER(Resampler::BSinc12); HANDLE_RESAMPLER(Resampler::FastBSinc24); @@ -103,7 +114,7 @@ const ALchar *GetResamplerName(const Resampler rtype) throw std::runtime_error{"Unexpected resampler index"}; } -al::optional DistanceModelFromALenum(ALenum model) +constexpr auto DistanceModelFromALenum(ALenum model) noexcept -> std::optional { switch(model) { @@ -115,9 +126,9 @@ al::optional DistanceModelFromALenum(ALenum model) case AL_EXPONENT_DISTANCE: return DistanceModel::Exponent; case AL_EXPONENT_DISTANCE_CLAMPED: return DistanceModel::ExponentClamped; } - return al::nullopt; + return std::nullopt; } -ALenum ALenumFromDistanceModel(DistanceModel model) +constexpr auto ALenumFromDistanceModel(DistanceModel model) -> ALenum { switch(model) { @@ -132,810 +143,463 @@ ALenum ALenumFromDistanceModel(DistanceModel model) throw std::runtime_error{"Unexpected distance model "+std::to_string(static_cast(model))}; } +enum PropertyValue : ALenum { + DopplerFactor = AL_DOPPLER_FACTOR, + DopplerVelocity = AL_DOPPLER_VELOCITY, + DistanceModel = AL_DISTANCE_MODEL, + SpeedOfSound = AL_SPEED_OF_SOUND, + DeferredUpdates = AL_DEFERRED_UPDATES_SOFT, + GainLimit = AL_GAIN_LIMIT_SOFT, + NumResamplers = AL_NUM_RESAMPLERS_SOFT, + DefaultResampler = AL_DEFAULT_RESAMPLER_SOFT, + DebugLoggedMessages = AL_DEBUG_LOGGED_MESSAGES_EXT, + DebugNextLoggedMessageLength = AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT, + MaxDebugMessageLength = AL_MAX_DEBUG_MESSAGE_LENGTH_EXT, + MaxDebugLoggedMessages = AL_MAX_DEBUG_LOGGED_MESSAGES_EXT, + MaxDebugGroupDepth = AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT, + MaxLabelLength = AL_MAX_LABEL_LENGTH_EXT, + ContextFlags = AL_CONTEXT_FLAGS_EXT, +#ifdef ALSOFT_EAX + EaxRamSize = AL_EAX_RAM_SIZE, + EaxRamFree = AL_EAX_RAM_FREE, +#endif +}; + +template +struct PropertyCastType { + template + constexpr auto operator()(U&& value) const noexcept + { return static_cast(std::forward(value)); } +}; +/* Special-case ALboolean to be an actual bool instead of a char type. */ +template<> +struct PropertyCastType { + template + constexpr ALboolean operator()(U&& value) const noexcept + { return static_cast(std::forward(value)) ? AL_TRUE : AL_FALSE; } +}; + + +template +void GetValue(ALCcontext *context, ALenum pname, T *values) +{ + auto cast_value = PropertyCastType{}; + + switch(static_cast(pname)) + { + case AL_DOPPLER_FACTOR: + *values = cast_value(context->mDopplerFactor); + return; + + case AL_DOPPLER_VELOCITY: + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, + DebugSeverity::Medium, + "AL_DOPPLER_VELOCITY is deprecated in AL 1.1, use AL_SPEED_OF_SOUND; " + "AL_DOPPLER_VELOCITY -> AL_SPEED_OF_SOUND / 343.3f"); + *values = cast_value(context->mDopplerVelocity); + return; + + case AL_SPEED_OF_SOUND: + *values = cast_value(context->mSpeedOfSound); + return; + + case AL_GAIN_LIMIT_SOFT: + *values = cast_value(GainMixMax / context->mGainBoost); + return; + + case AL_DEFERRED_UPDATES_SOFT: + *values = cast_value(context->mDeferUpdates ? AL_TRUE : AL_FALSE); + return; + + case AL_DISTANCE_MODEL: + *values = cast_value(ALenumFromDistanceModel(context->mDistanceModel)); + return; + + case AL_NUM_RESAMPLERS_SOFT: + *values = cast_value(al::to_underlying(Resampler::Max) + 1); + return; + + case AL_DEFAULT_RESAMPLER_SOFT: + *values = cast_value(al::to_underlying(ResamplerDefault)); + return; + + case AL_DEBUG_LOGGED_MESSAGES_EXT: + { + std::lock_guard debuglock{context->mDebugCbLock}; + *values = cast_value(context->mDebugLog.size()); + return; + } + + case AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT: + { + std::lock_guard debuglock{context->mDebugCbLock}; + *values = cast_value(context->mDebugLog.empty() ? 0_uz + : (context->mDebugLog.front().mMessage.size()+1)); + return; + } + + case AL_MAX_DEBUG_MESSAGE_LENGTH_EXT: + *values = cast_value(MaxDebugMessageLength); + return; + + case AL_MAX_DEBUG_LOGGED_MESSAGES_EXT: + *values = cast_value(MaxDebugLoggedMessages); + return; + + case AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT: + *values = cast_value(MaxDebugGroupDepth); + return; + + case AL_MAX_LABEL_LENGTH_EXT: + *values = cast_value(MaxObjectLabelLength); + return; + + case AL_CONTEXT_FLAGS_EXT: + *values = cast_value(context->mContextFlags.to_ulong()); + return; + +#ifdef ALSOFT_EAX +#define EAX_ERROR "[alGetInteger] EAX not enabled" + + case AL_EAX_RAM_SIZE: + if(eax_g_is_enabled) + { + *values = cast_value(eax_x_ram_max_size); + return; + } + ERR(EAX_ERROR "\n"); + break; + + case AL_EAX_RAM_FREE: + if(eax_g_is_enabled) + { + auto device = context->mALDevice.get(); + std::lock_guard device_lock{device->BufferLock}; + *values = cast_value(device->eax_x_ram_free_size); + return; + } + ERR(EAX_ERROR "\n"); + break; + +#undef EAX_ERROR +#endif // ALSOFT_EAX + } + context->setError(AL_INVALID_ENUM, "Invalid context property 0x%04x", pname); +} + + +inline void UpdateProps(ALCcontext *context) +{ + if(!context->mDeferUpdates) + UpdateContextProps(context); + else + context->mPropsDirty = true; +} + } // namespace /* WARNING: Non-standard export! Not part of any extension, or exposed in the * alcFunctions list. */ -AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) -START_API_FUNC +AL_API auto AL_APIENTRY alsoft_get_version() noexcept -> const ALchar* { static const auto spoof = al::getenv("ALSOFT_SPOOF_VERSION"); if(spoof) return spoof->c_str(); return ALSOFT_VERSION; } -END_API_FUNC - -#define DO_UPDATEPROPS() do { \ - if(!context->mDeferUpdates) \ - UpdateContextProps(context.get()); \ - else \ - context->mPropsDirty = true; \ -} while(0) -AL_API void AL_APIENTRY alEnable(ALenum capability) -START_API_FUNC +AL_API DECL_FUNC1(void, alEnable, ALenum,capability) +FORCE_ALIGN void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mSourceDistanceModel = true; - DO_UPDATEPROPS(); + UpdateProps(context); } - break; + return; + + case AL_DEBUG_OUTPUT_EXT: + context->mDebugEnabled.store(true); + return; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: context->setError(AL_INVALID_OPERATION, "Re-enabling AL_STOP_SOURCES_ON_DISCONNECT_SOFT not yet supported"); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); + return; } + context->setError(AL_INVALID_VALUE, "Invalid enable property 0x%04x", capability); } -END_API_FUNC -AL_API void AL_APIENTRY alDisable(ALenum capability) -START_API_FUNC +AL_API DECL_FUNC1(void, alDisable, ALenum,capability) +FORCE_ALIGN void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - switch(capability) { case AL_SOURCE_DISTANCE_MODEL: { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mSourceDistanceModel = false; - DO_UPDATEPROPS(); + UpdateProps(context); } - break; + return; + + case AL_DEBUG_OUTPUT_EXT: + context->mDebugEnabled.store(false); + return; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: - context->mStopVoicesOnDisconnect = false; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); + context->mStopVoicesOnDisconnect.store(false); + return; } + context->setError(AL_INVALID_VALUE, "Invalid disable property 0x%04x", capability); } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) -START_API_FUNC +AL_API DECL_FUNC1(ALboolean, alIsEnabled, ALenum,capability) +FORCE_ALIGN ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - - std::lock_guard _{context->mPropLock}; - ALboolean value{AL_FALSE}; + std::lock_guard proplock{context->mPropLock}; switch(capability) { - case AL_SOURCE_DISTANCE_MODEL: - value = context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; - break; - + case AL_SOURCE_DISTANCE_MODEL: return context->mSourceDistanceModel ? AL_TRUE : AL_FALSE; + case AL_DEBUG_OUTPUT_EXT: return context->mDebugEnabled ? AL_TRUE : AL_FALSE; case AL_STOP_SOURCES_ON_DISCONNECT_SOFT: - value = context->mStopVoicesOnDisconnect ? AL_TRUE : AL_FALSE; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + return context->mStopVoicesOnDisconnect.load() ? AL_TRUE : AL_FALSE; } - - return value; + context->setError(AL_INVALID_VALUE, "Invalid is enabled property 0x%04x", capability); + return AL_FALSE; } -END_API_FUNC -AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return AL_FALSE; - - std::lock_guard _{context->mPropLock}; - ALboolean value{AL_FALSE}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - if(context->mDopplerFactor != 0.0f) - value = AL_TRUE; - break; - - case AL_DOPPLER_VELOCITY: - if(context->mDopplerVelocity != 0.0f) - value = AL_TRUE; - break; - - case AL_DISTANCE_MODEL: - if(context->mDistanceModel == DistanceModel::Default) - value = AL_TRUE; - break; - - case AL_SPEED_OF_SOUND: - if(context->mSpeedOfSound != 0.0f) - value = AL_TRUE; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - if(GainMixMax/context->mGainBoost != 0.0f) - value = AL_TRUE; - break; - - case AL_NUM_RESAMPLERS_SOFT: - /* Always non-0. */ - value = AL_TRUE; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault) ? AL_TRUE : AL_FALSE; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid boolean property 0x%04x", pname); - } - - return value; +#define DECL_GETFUNC(R, Name, Ext) \ +AL_API auto AL_APIENTRY Name##Ext(ALenum pname) noexcept -> R \ +{ \ + R value{}; \ + auto context = GetContextRef(); \ + if(!context) UNLIKELY return value; \ + Name##vDirect##Ext(GetContextRef().get(), pname, &value); \ + return value; \ +} \ +FORCE_ALIGN auto AL_APIENTRY Name##Direct##Ext(ALCcontext *context, ALenum pname) noexcept -> R \ +{ \ + R value{}; \ + Name##vDirect##Ext(context, pname, &value); \ + return value; \ } -END_API_FUNC -AL_API ALdouble AL_APIENTRY alGetDouble(ALenum pname) -START_API_FUNC +DECL_GETFUNC(ALboolean, alGetBoolean,) +DECL_GETFUNC(ALdouble, alGetDouble,) +DECL_GETFUNC(ALfloat, alGetFloat,) +DECL_GETFUNC(ALint, alGetInteger,) + +DECL_GETFUNC(ALint64SOFT, alGetInteger64,SOFT) +DECL_GETFUNC(ALvoid*, alGetPointer,SOFT) + +#undef DECL_GETFUNC + + +AL_API DECL_FUNC2(void, alGetBooleanv, ALenum,pname, ALboolean*,values) +FORCE_ALIGN void AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum pname, ALboolean *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0.0; - - std::lock_guard _{context->mPropLock}; - ALdouble value{0.0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = context->mDopplerFactor; - break; - - case AL_DOPPLER_VELOCITY: - value = context->mDopplerVelocity; - break; - - case AL_DISTANCE_MODEL: - value = static_cast(ALenumFromDistanceModel(context->mDistanceModel)); - break; - - case AL_SPEED_OF_SOUND: - value = context->mSpeedOfSound; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = ALdouble{GainMixMax}/context->mGainBoost; - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1.0; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid double property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALfloat AL_APIENTRY alGetFloat(ALenum pname) -START_API_FUNC +AL_API DECL_FUNC2(void, alGetDoublev, ALenum,pname, ALdouble*,values) +FORCE_ALIGN void AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum pname, ALdouble *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0.0f; - - std::lock_guard _{context->mPropLock}; - ALfloat value{0.0f}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = context->mDopplerFactor; - break; - - case AL_DOPPLER_VELOCITY: - value = context->mDopplerVelocity; - break; - - case AL_DISTANCE_MODEL: - value = static_cast(ALenumFromDistanceModel(context->mDistanceModel)); - break; - - case AL_SPEED_OF_SOUND: - value = context->mSpeedOfSound; - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = static_cast(AL_TRUE); - break; - - case AL_GAIN_LIMIT_SOFT: - value = GainMixMax/context->mGainBoost; - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1.0f; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid float property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALint AL_APIENTRY alGetInteger(ALenum pname) -START_API_FUNC +AL_API DECL_FUNC2(void, alGetFloatv, ALenum,pname, ALfloat*,values) +FORCE_ALIGN void AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum pname, ALfloat *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0; - - std::lock_guard _{context->mPropLock}; - ALint value{0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = static_cast(context->mDopplerFactor); - break; - - case AL_DOPPLER_VELOCITY: - value = static_cast(context->mDopplerVelocity); - break; - - case AL_DISTANCE_MODEL: - value = ALenumFromDistanceModel(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = static_cast(context->mSpeedOfSound); - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - value = static_cast(GainMixMax/context->mGainBoost); - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - -#ifdef ALSOFT_EAX - -#define EAX_ERROR "[alGetInteger] EAX not enabled." - - case AL_EAX_RAM_SIZE: - if (eax_g_is_enabled) - { - value = eax_x_ram_max_size; - } - else - { - context->setError(AL_INVALID_VALUE, EAX_ERROR); - } - - break; - - case AL_EAX_RAM_FREE: - if (eax_g_is_enabled) - { - auto device = context->mALDevice.get(); - std::lock_guard device_lock{device->BufferLock}; - - value = static_cast(device->eax_x_ram_free_size); - } - else - { - context->setError(AL_INVALID_VALUE, EAX_ERROR); - } - - break; - -#undef EAX_ERROR - -#endif // ALSOFT_EAX - - default: - context->setError(AL_INVALID_VALUE, "Invalid integer property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) -START_API_FUNC +AL_API DECL_FUNC2(void, alGetIntegerv, ALenum,pname, ALint*,values) +FORCE_ALIGN void AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum pname, ALint *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return 0_i64; - - std::lock_guard _{context->mPropLock}; - ALint64SOFT value{0}; - switch(pname) - { - case AL_DOPPLER_FACTOR: - value = static_cast(context->mDopplerFactor); - break; - - case AL_DOPPLER_VELOCITY: - value = static_cast(context->mDopplerVelocity); - break; - - case AL_DISTANCE_MODEL: - value = ALenumFromDistanceModel(context->mDistanceModel); - break; - - case AL_SPEED_OF_SOUND: - value = static_cast(context->mSpeedOfSound); - break; - - case AL_DEFERRED_UPDATES_SOFT: - if(context->mDeferUpdates) - value = AL_TRUE; - break; - - case AL_GAIN_LIMIT_SOFT: - value = static_cast(GainMixMax/context->mGainBoost); - break; - - case AL_NUM_RESAMPLERS_SOFT: - value = static_cast(Resampler::Max) + 1; - break; - - case AL_DEFAULT_RESAMPLER_SOFT: - value = static_cast(ResamplerDefault); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid integer64 property 0x%04x", pname); - } - - return value; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); } -END_API_FUNC -AL_API ALvoid* AL_APIENTRY alGetPointerSOFT(ALenum pname) -START_API_FUNC +AL_API DECL_FUNCEXT2(void, alGetInteger64v,SOFT, ALenum,pname, ALint64SOFT*,values) +FORCE_ALIGN void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); + GetValue(context, pname, values); +} + +AL_API DECL_FUNCEXT2(void, alGetPointerv,SOFT, ALenum,pname, ALvoid**,values) +FORCE_ALIGN void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, ALvoid **values) noexcept +{ + if(!values) UNLIKELY + return context->setError(AL_INVALID_VALUE, "NULL pointer"); - std::lock_guard _{context->mPropLock}; - void *value{nullptr}; switch(pname) { case AL_EVENT_CALLBACK_FUNCTION_SOFT: - value = reinterpret_cast(context->mEventCb); - break; + *values = reinterpret_cast(context->mEventCb); + return; case AL_EVENT_CALLBACK_USER_PARAM_SOFT: - value = context->mEventParam; - break; + *values = context->mEventParam; + return; - default: - context->setError(AL_INVALID_VALUE, "Invalid pointer property 0x%04x", pname); + case AL_DEBUG_CALLBACK_FUNCTION_EXT: + *values = reinterpret_cast(context->mDebugCb); + return; + + case AL_DEBUG_CALLBACK_USER_PARAM_EXT: + *values = context->mDebugParam; + return; } - - return value; + context->setError(AL_INVALID_ENUM, "Invalid context pointer property 0x%04x", pname); } -END_API_FUNC -AL_API void AL_APIENTRY alGetBooleanv(ALenum pname, ALboolean *values) -START_API_FUNC +AL_API DECL_FUNC1(const ALchar*, alGetString, ALenum,pname) +FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum pname) noexcept { - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetBoolean(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid boolean-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetDoublev(ALenum pname, ALdouble *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetDouble(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid double-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetFloatv(ALenum pname, ALfloat *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetFloat(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid float-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetIntegerv(ALenum pname, ALint *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetInteger(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid integer-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_DOPPLER_FACTOR: - case AL_DOPPLER_VELOCITY: - case AL_DISTANCE_MODEL: - case AL_SPEED_OF_SOUND: - case AL_DEFERRED_UPDATES_SOFT: - case AL_GAIN_LIMIT_SOFT: - case AL_NUM_RESAMPLERS_SOFT: - case AL_DEFAULT_RESAMPLER_SOFT: - values[0] = alGetInteger64SOFT(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid integer64-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, ALvoid **values) -START_API_FUNC -{ - if(values) - { - switch(pname) - { - case AL_EVENT_CALLBACK_FUNCTION_SOFT: - case AL_EVENT_CALLBACK_USER_PARAM_SOFT: - values[0] = alGetPointerSOFT(pname); - return; - } - } - - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!values) - context->setError(AL_INVALID_VALUE, "NULL pointer"); - else switch(pname) - { - default: - context->setError(AL_INVALID_VALUE, "Invalid pointer-vector property 0x%04x", pname); - } -} -END_API_FUNC - -AL_API const ALchar* AL_APIENTRY alGetString(ALenum pname) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; - - const ALchar *value{nullptr}; switch(pname) { - case AL_VENDOR: - value = alVendor; - break; - - case AL_VERSION: - value = alVersion; - break; - - case AL_RENDERER: - value = alRenderer; - break; - - case AL_EXTENSIONS: - value = context->mExtensionList; - break; - - case AL_NO_ERROR: - value = alNoError; - break; - - case AL_INVALID_NAME: - value = alErrInvalidName; - break; - - case AL_INVALID_ENUM: - value = alErrInvalidEnum; - break; - - case AL_INVALID_VALUE: - value = alErrInvalidValue; - break; - - case AL_INVALID_OPERATION: - value = alErrInvalidOp; - break; - - case AL_OUT_OF_MEMORY: - value = alErrOutOfMemory; - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + case AL_VENDOR: return GetVendorString(); + case AL_VERSION: return GetVersionString(); + case AL_RENDERER: return GetRendererString(); + case AL_EXTENSIONS: return context->mExtensionsString.c_str(); + case AL_NO_ERROR: return GetNoErrorString(); + case AL_INVALID_NAME: return GetInvalidNameString(); + case AL_INVALID_ENUM: return GetInvalidEnumString(); + case AL_INVALID_VALUE: return GetInvalidValueString(); + case AL_INVALID_OPERATION: return GetInvalidOperationString(); + case AL_OUT_OF_MEMORY: return GetOutOfMemoryString(); + case AL_STACK_OVERFLOW_EXT: return GetStackOverflowString(); + case AL_STACK_UNDERFLOW_EXT: return GetStackUnderflowString(); } - return value; + context->setError(AL_INVALID_VALUE, "Invalid string property 0x%04x", pname); + return nullptr; } -END_API_FUNC -AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) -START_API_FUNC +AL_API DECL_FUNC1(void, alDopplerFactor, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) noexcept { - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler factor %f out of range", value); else { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mDopplerFactor = value; - DO_UPDATEPROPS(); + UpdateProps(context); } } -END_API_FUNC -AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) -START_API_FUNC +AL_API DECL_FUNC1(void, alSpeedOfSound, ALfloat,value) +FORCE_ALIGN void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) noexcept +{ + if(!(value > 0.0f && std::isfinite(value))) + context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); + else + { + std::lock_guard proplock{context->mPropLock}; + context->mSpeedOfSound = value; + UpdateProps(context); + } +} + +AL_API DECL_FUNC1(void, alDistanceModel, ALenum,value) +FORCE_ALIGN void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum value) noexcept +{ + if(auto model = DistanceModelFromALenum(value)) + { + std::lock_guard proplock{context->mPropLock}; + context->mDistanceModel = *model; + if(!context->mSourceDistanceModel) + UpdateProps(context); + } + else + context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); +} + + +AL_API DECL_FUNCEXT(void, alDeferUpdates,SOFT) +FORCE_ALIGN void AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) noexcept +{ + std::lock_guard proplock{context->mPropLock}; + context->deferUpdates(); +} + +AL_API DECL_FUNCEXT(void, alProcessUpdates,SOFT) +FORCE_ALIGN void AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) noexcept +{ + std::lock_guard proplock{context->mPropLock}; + context->processUpdates(); +} + + +AL_API DECL_FUNCEXT2(const ALchar*, alGetStringi,SOFT, ALenum,pname, ALsizei,index) +FORCE_ALIGN const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) noexcept +{ + switch(pname) + { + case AL_RESAMPLER_NAME_SOFT: + if(index >= 0 && index <= static_cast(Resampler::Max)) + return GetResamplerName(static_cast(index)); + context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); + return nullptr; + } + context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); + return nullptr; +} + + +AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) noexcept { ContextRef context{GetContextRef()}; if(!context) UNLIKELY return; + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + context->debugMessage(DebugSource::API, DebugType::DeprecatedBehavior, 0, + DebugSeverity::Medium, + "alDopplerVelocity is deprecated in AL 1.1, use alSpeedOfSound; " + "alDopplerVelocity(x) -> alSpeedOfSound(343.3f * x)"); + if(!(value >= 0.0f && std::isfinite(value))) context->setError(AL_INVALID_VALUE, "Doppler velocity %f out of range", value); else { - std::lock_guard _{context->mPropLock}; + std::lock_guard proplock{context->mPropLock}; context->mDopplerVelocity = value; - DO_UPDATEPROPS(); + UpdateProps(context.get()); } } -END_API_FUNC - -AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(!(value > 0.0f && std::isfinite(value))) - context->setError(AL_INVALID_VALUE, "Speed of sound %f out of range", value); - else - { - std::lock_guard _{context->mPropLock}; - context->mSpeedOfSound = value; - DO_UPDATEPROPS(); - } -} -END_API_FUNC - -AL_API void AL_APIENTRY alDistanceModel(ALenum value) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - if(auto model = DistanceModelFromALenum(value)) - { - std::lock_guard _{context->mPropLock}; - context->mDistanceModel = *model; - if(!context->mSourceDistanceModel) - DO_UPDATEPROPS(); - } - else - context->setError(AL_INVALID_VALUE, "Distance model 0x%04x out of range", value); -} -END_API_FUNC - - -AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - context->deferUpdates(); -} -END_API_FUNC - -AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return; - - std::lock_guard _{context->mPropLock}; - context->processUpdates(); -} -END_API_FUNC - - -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) -START_API_FUNC -{ - ContextRef context{GetContextRef()}; - if(!context) UNLIKELY return nullptr; - - const ALchar *value{nullptr}; - switch(pname) - { - case AL_RESAMPLER_NAME_SOFT: - if(index < 0 || index > static_cast(Resampler::Max)) - context->setError(AL_INVALID_VALUE, "Resampler name index %d out of range", index); - else - value = GetResamplerName(static_cast(index)); - break; - - default: - context->setError(AL_INVALID_VALUE, "Invalid string indexed property"); - } - return value; -} -END_API_FUNC void UpdateContextProps(ALCcontext *context) { - /* Get an unused proprty container, or allocate a new one as needed. */ + /* Get an unused property container, or allocate a new one as needed. */ ContextProps *props{context->mFreeContextProps.load(std::memory_order_acquire)}; if(!props) - props = new ContextProps{}; - else { - ContextProps *next; - do { - next = props->next.load(std::memory_order_relaxed); - } while(context->mFreeContextProps.compare_exchange_weak(props, next, - std::memory_order_seq_cst, std::memory_order_acquire) == 0); + context->allocContextProps(); + props = context->mFreeContextProps.load(std::memory_order_acquire); } + ContextProps *next; + do { + next = props->next.load(std::memory_order_relaxed); + } while(context->mFreeContextProps.compare_exchange_weak(props, next, + std::memory_order_acq_rel, std::memory_order_acquire) == false); /* Copy in current property values. */ - ALlistener &listener = context->mListener; + const auto &listener = context->mListener; props->Position = listener.Position; props->Velocity = listener.Velocity; props->OrientAt = listener.OrientAt; diff --git a/Engine/lib/openal-soft/alc/alc.cpp b/Engine/lib/openal-soft/alc/alc.cpp index af8ff55d4..f87fb2076 100644 --- a/Engine/lib/openal-soft/alc/alc.cpp +++ b/Engine/lib/openal-soft/alc/alc.cpp @@ -38,21 +38,24 @@ #include #include #include -#include +#include #include #include #include +#include #include #include #include #include #include #include -#include +#include #include #include -#include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -61,16 +64,16 @@ #include "al/auxeffectslot.h" #include "al/buffer.h" +#include "al/debug.h" #include "al/effect.h" #include "al/filter.h" -#include "al/listener.h" #include "al/source.h" +#include "alc/events.h" #include "albit.h" -#include "albyte.h" #include "alconfig.h" #include "almalloc.h" +#include "alnumbers.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" @@ -83,25 +86,24 @@ #include "core/cpu_caps.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" -#include "core/except.h" +#include "core/filters/nfc.h" #include "core/helpers.h" #include "core/mastering.h" -#include "core/mixer/hrtfdefs.h" #include "core/fpu_ctrl.h" -#include "core/front_stablizer.h" #include "core/logging.h" #include "core/uhjfilter.h" #include "core/voice.h" #include "core/voice_change.h" #include "device.h" #include "effects/base.h" +#include "export_list.h" +#include "flexarray.h" #include "inprogext.h" #include "intrusive_ptr.h" #include "opthelpers.h" #include "strutils.h" -#include "threads.h" -#include "vector.h" #include "backends/base.h" #include "backends/null.h" @@ -156,18 +158,11 @@ #endif #ifdef ALSOFT_EAX +#include "al/eax/api.h" #include "al/eax/globals.h" -#include "al/eax/x_ram.h" -#endif // ALSOFT_EAX - - -FILE *gLogFile{stderr}; -#ifdef _DEBUG -LogLevel gLogLevel{LogLevel::Warning}; -#else -LogLevel gLogLevel{LogLevel::Error}; #endif + /************************************************ * Library initialization ************************************************/ @@ -188,7 +183,7 @@ BOOL APIENTRY DllMain(HINSTANCE module, DWORD reason, LPVOID /*reserved*/) namespace { -using namespace std::placeholders; +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; @@ -201,59 +196,59 @@ using float2 = std::array; ************************************************/ struct BackendInfo { const char *name; - BackendFactory& (*getFactory)(void); + BackendFactory& (*getFactory)(); }; -BackendInfo BackendList[] = { +std::array BackendList{ #ifdef HAVE_PIPEWIRE - { "pipewire", PipeWireBackendFactory::getFactory }, + BackendInfo{"pipewire", PipeWireBackendFactory::getFactory}, #endif #ifdef HAVE_PULSEAUDIO - { "pulse", PulseBackendFactory::getFactory }, + BackendInfo{"pulse", PulseBackendFactory::getFactory}, #endif #ifdef HAVE_WASAPI - { "wasapi", WasapiBackendFactory::getFactory }, + BackendInfo{"wasapi", WasapiBackendFactory::getFactory}, #endif #ifdef HAVE_COREAUDIO - { "core", CoreAudioBackendFactory::getFactory }, + BackendInfo{"core", CoreAudioBackendFactory::getFactory}, #endif #ifdef HAVE_OBOE - { "oboe", OboeBackendFactory::getFactory }, + BackendInfo{"oboe", OboeBackendFactory::getFactory}, #endif #ifdef HAVE_OPENSL - { "opensl", OSLBackendFactory::getFactory }, + BackendInfo{"opensl", OSLBackendFactory::getFactory}, #endif #ifdef HAVE_ALSA - { "alsa", AlsaBackendFactory::getFactory }, + BackendInfo{"alsa", AlsaBackendFactory::getFactory}, #endif #ifdef HAVE_SOLARIS - { "solaris", SolarisBackendFactory::getFactory }, + BackendInfo{"solaris", SolarisBackendFactory::getFactory}, #endif #ifdef HAVE_SNDIO - { "sndio", SndIOBackendFactory::getFactory }, + BackendInfo{"sndio", SndIOBackendFactory::getFactory}, #endif #ifdef HAVE_OSS - { "oss", OSSBackendFactory::getFactory }, + BackendInfo{"oss", OSSBackendFactory::getFactory}, #endif #ifdef HAVE_JACK - { "jack", JackBackendFactory::getFactory }, + BackendInfo{"jack", JackBackendFactory::getFactory}, #endif #ifdef HAVE_DSOUND - { "dsound", DSoundBackendFactory::getFactory }, + BackendInfo{"dsound", DSoundBackendFactory::getFactory}, #endif #ifdef HAVE_WINMM - { "winmm", WinMMBackendFactory::getFactory }, + BackendInfo{"winmm", WinMMBackendFactory::getFactory}, #endif #ifdef HAVE_PORTAUDIO - { "port", PortBackendFactory::getFactory }, + BackendInfo{"port", PortBackendFactory::getFactory}, #endif #ifdef HAVE_SDL2 - { "sdl2", SDL2BackendFactory::getFactory }, + BackendInfo{"sdl2", SDL2BackendFactory::getFactory}, #endif - { "null", NullBackendFactory::getFactory }, + BackendInfo{"null", NullBackendFactory::getFactory}, #ifdef HAVE_WAVE - { "wave", WaveBackendFactory::getFactory }, + BackendInfo{"wave", WaveBackendFactory::getFactory}, #endif }; @@ -261,688 +256,22 @@ BackendFactory *PlaybackFactory{}; BackendFactory *CaptureFactory{}; -/************************************************ - * Functions, enums, and errors - ************************************************/ -#define DECL(x) { #x, reinterpret_cast(x) } -const struct { - const char *funcName; - void *address; -} alcFunctions[] = { - DECL(alcCreateContext), - DECL(alcMakeContextCurrent), - DECL(alcProcessContext), - DECL(alcSuspendContext), - DECL(alcDestroyContext), - DECL(alcGetCurrentContext), - DECL(alcGetContextsDevice), - DECL(alcOpenDevice), - DECL(alcCloseDevice), - DECL(alcGetError), - DECL(alcIsExtensionPresent), - DECL(alcGetProcAddress), - DECL(alcGetEnumValue), - DECL(alcGetString), - DECL(alcGetIntegerv), - DECL(alcCaptureOpenDevice), - DECL(alcCaptureCloseDevice), - DECL(alcCaptureStart), - DECL(alcCaptureStop), - DECL(alcCaptureSamples), - - DECL(alcSetThreadContext), - DECL(alcGetThreadContext), - - DECL(alcLoopbackOpenDeviceSOFT), - DECL(alcIsRenderFormatSupportedSOFT), - DECL(alcRenderSamplesSOFT), - - DECL(alcDevicePauseSOFT), - DECL(alcDeviceResumeSOFT), - - DECL(alcGetStringiSOFT), - DECL(alcResetDeviceSOFT), - - DECL(alcGetInteger64vSOFT), - - DECL(alcReopenDeviceSOFT), - - DECL(alEnable), - DECL(alDisable), - DECL(alIsEnabled), - DECL(alGetString), - DECL(alGetBooleanv), - DECL(alGetIntegerv), - DECL(alGetFloatv), - DECL(alGetDoublev), - DECL(alGetBoolean), - DECL(alGetInteger), - DECL(alGetFloat), - DECL(alGetDouble), - DECL(alGetError), - DECL(alIsExtensionPresent), - DECL(alGetProcAddress), - DECL(alGetEnumValue), - DECL(alListenerf), - DECL(alListener3f), - DECL(alListenerfv), - DECL(alListeneri), - DECL(alListener3i), - DECL(alListeneriv), - DECL(alGetListenerf), - DECL(alGetListener3f), - DECL(alGetListenerfv), - DECL(alGetListeneri), - DECL(alGetListener3i), - DECL(alGetListeneriv), - DECL(alGenSources), - DECL(alDeleteSources), - DECL(alIsSource), - DECL(alSourcef), - DECL(alSource3f), - DECL(alSourcefv), - DECL(alSourcei), - DECL(alSource3i), - DECL(alSourceiv), - DECL(alGetSourcef), - DECL(alGetSource3f), - DECL(alGetSourcefv), - DECL(alGetSourcei), - DECL(alGetSource3i), - DECL(alGetSourceiv), - DECL(alSourcePlayv), - DECL(alSourceStopv), - DECL(alSourceRewindv), - DECL(alSourcePausev), - DECL(alSourcePlay), - DECL(alSourceStop), - DECL(alSourceRewind), - DECL(alSourcePause), - DECL(alSourceQueueBuffers), - DECL(alSourceUnqueueBuffers), - DECL(alGenBuffers), - DECL(alDeleteBuffers), - DECL(alIsBuffer), - DECL(alBufferData), - DECL(alBufferf), - DECL(alBuffer3f), - DECL(alBufferfv), - DECL(alBufferi), - DECL(alBuffer3i), - DECL(alBufferiv), - DECL(alGetBufferf), - DECL(alGetBuffer3f), - DECL(alGetBufferfv), - DECL(alGetBufferi), - DECL(alGetBuffer3i), - DECL(alGetBufferiv), - DECL(alDopplerFactor), - DECL(alDopplerVelocity), - DECL(alSpeedOfSound), - DECL(alDistanceModel), - - DECL(alGenFilters), - DECL(alDeleteFilters), - DECL(alIsFilter), - DECL(alFilteri), - DECL(alFilteriv), - DECL(alFilterf), - DECL(alFilterfv), - DECL(alGetFilteri), - DECL(alGetFilteriv), - DECL(alGetFilterf), - DECL(alGetFilterfv), - DECL(alGenEffects), - DECL(alDeleteEffects), - DECL(alIsEffect), - DECL(alEffecti), - DECL(alEffectiv), - DECL(alEffectf), - DECL(alEffectfv), - DECL(alGetEffecti), - DECL(alGetEffectiv), - DECL(alGetEffectf), - DECL(alGetEffectfv), - DECL(alGenAuxiliaryEffectSlots), - DECL(alDeleteAuxiliaryEffectSlots), - DECL(alIsAuxiliaryEffectSlot), - DECL(alAuxiliaryEffectSloti), - DECL(alAuxiliaryEffectSlotiv), - DECL(alAuxiliaryEffectSlotf), - DECL(alAuxiliaryEffectSlotfv), - DECL(alGetAuxiliaryEffectSloti), - DECL(alGetAuxiliaryEffectSlotiv), - DECL(alGetAuxiliaryEffectSlotf), - DECL(alGetAuxiliaryEffectSlotfv), - - DECL(alDeferUpdatesSOFT), - DECL(alProcessUpdatesSOFT), - - DECL(alSourcedSOFT), - DECL(alSource3dSOFT), - DECL(alSourcedvSOFT), - DECL(alGetSourcedSOFT), - DECL(alGetSource3dSOFT), - DECL(alGetSourcedvSOFT), - DECL(alSourcei64SOFT), - DECL(alSource3i64SOFT), - DECL(alSourcei64vSOFT), - DECL(alGetSourcei64SOFT), - DECL(alGetSource3i64SOFT), - DECL(alGetSourcei64vSOFT), - - DECL(alGetStringiSOFT), - - DECL(alBufferStorageSOFT), - DECL(alMapBufferSOFT), - DECL(alUnmapBufferSOFT), - DECL(alFlushMappedBufferSOFT), - - DECL(alEventControlSOFT), - DECL(alEventCallbackSOFT), - DECL(alGetPointerSOFT), - DECL(alGetPointervSOFT), - - DECL(alBufferCallbackSOFT), - DECL(alGetBufferPtrSOFT), - DECL(alGetBuffer3PtrSOFT), - DECL(alGetBufferPtrvSOFT), - - DECL(alAuxiliaryEffectSlotPlaySOFT), - DECL(alAuxiliaryEffectSlotPlayvSOFT), - DECL(alAuxiliaryEffectSlotStopSOFT), - DECL(alAuxiliaryEffectSlotStopvSOFT), - - DECL(alSourcePlayAtTimeSOFT), - DECL(alSourcePlayAtTimevSOFT), - - DECL(alBufferSubDataSOFT), - - DECL(alBufferDataStatic), -#ifdef ALSOFT_EAX -}, eaxFunctions[] = { - DECL(EAXGet), - DECL(EAXSet), - DECL(EAXGetBufferMode), - DECL(EAXSetBufferMode), -#endif -}; -#undef DECL - -#define DECL(x) { #x, (x) } -constexpr struct { - const ALCchar *enumName; - ALCenum value; -} alcEnumerations[] = { - DECL(ALC_INVALID), - DECL(ALC_FALSE), - DECL(ALC_TRUE), - - DECL(ALC_MAJOR_VERSION), - DECL(ALC_MINOR_VERSION), - DECL(ALC_ATTRIBUTES_SIZE), - DECL(ALC_ALL_ATTRIBUTES), - DECL(ALC_DEFAULT_DEVICE_SPECIFIER), - DECL(ALC_DEVICE_SPECIFIER), - DECL(ALC_ALL_DEVICES_SPECIFIER), - DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER), - DECL(ALC_EXTENSIONS), - DECL(ALC_FREQUENCY), - DECL(ALC_REFRESH), - DECL(ALC_SYNC), - DECL(ALC_MONO_SOURCES), - DECL(ALC_STEREO_SOURCES), - DECL(ALC_CAPTURE_DEVICE_SPECIFIER), - DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER), - DECL(ALC_CAPTURE_SAMPLES), - DECL(ALC_CONNECTED), - - DECL(ALC_EFX_MAJOR_VERSION), - DECL(ALC_EFX_MINOR_VERSION), - DECL(ALC_MAX_AUXILIARY_SENDS), - - DECL(ALC_FORMAT_CHANNELS_SOFT), - DECL(ALC_FORMAT_TYPE_SOFT), - - DECL(ALC_MONO_SOFT), - DECL(ALC_STEREO_SOFT), - DECL(ALC_QUAD_SOFT), - DECL(ALC_5POINT1_SOFT), - DECL(ALC_6POINT1_SOFT), - DECL(ALC_7POINT1_SOFT), - DECL(ALC_BFORMAT3D_SOFT), - - DECL(ALC_BYTE_SOFT), - DECL(ALC_UNSIGNED_BYTE_SOFT), - DECL(ALC_SHORT_SOFT), - DECL(ALC_UNSIGNED_SHORT_SOFT), - DECL(ALC_INT_SOFT), - DECL(ALC_UNSIGNED_INT_SOFT), - DECL(ALC_FLOAT_SOFT), - - DECL(ALC_HRTF_SOFT), - DECL(ALC_DONT_CARE_SOFT), - DECL(ALC_HRTF_STATUS_SOFT), - DECL(ALC_HRTF_DISABLED_SOFT), - DECL(ALC_HRTF_ENABLED_SOFT), - DECL(ALC_HRTF_DENIED_SOFT), - DECL(ALC_HRTF_REQUIRED_SOFT), - DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT), - DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT), - DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT), - DECL(ALC_HRTF_SPECIFIER_SOFT), - DECL(ALC_HRTF_ID_SOFT), - - DECL(ALC_AMBISONIC_LAYOUT_SOFT), - DECL(ALC_AMBISONIC_SCALING_SOFT), - DECL(ALC_AMBISONIC_ORDER_SOFT), - DECL(ALC_ACN_SOFT), - DECL(ALC_FUMA_SOFT), - DECL(ALC_N3D_SOFT), - DECL(ALC_SN3D_SOFT), - - DECL(ALC_OUTPUT_LIMITER_SOFT), - - DECL(ALC_DEVICE_CLOCK_SOFT), - DECL(ALC_DEVICE_LATENCY_SOFT), - DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT), - DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT), - DECL(AL_SEC_OFFSET_CLOCK_SOFT), - - DECL(ALC_OUTPUT_MODE_SOFT), - DECL(ALC_ANY_SOFT), - DECL(ALC_STEREO_BASIC_SOFT), - DECL(ALC_STEREO_UHJ_SOFT), - DECL(ALC_STEREO_HRTF_SOFT), - DECL(ALC_SURROUND_5_1_SOFT), - DECL(ALC_SURROUND_6_1_SOFT), - DECL(ALC_SURROUND_7_1_SOFT), - - DECL(ALC_NO_ERROR), - DECL(ALC_INVALID_DEVICE), - DECL(ALC_INVALID_CONTEXT), - DECL(ALC_INVALID_ENUM), - DECL(ALC_INVALID_VALUE), - DECL(ALC_OUT_OF_MEMORY), - - - DECL(AL_INVALID), - DECL(AL_NONE), - DECL(AL_FALSE), - DECL(AL_TRUE), - - DECL(AL_SOURCE_RELATIVE), - DECL(AL_CONE_INNER_ANGLE), - DECL(AL_CONE_OUTER_ANGLE), - DECL(AL_PITCH), - DECL(AL_POSITION), - DECL(AL_DIRECTION), - DECL(AL_VELOCITY), - DECL(AL_LOOPING), - DECL(AL_BUFFER), - DECL(AL_GAIN), - DECL(AL_MIN_GAIN), - DECL(AL_MAX_GAIN), - DECL(AL_ORIENTATION), - DECL(AL_REFERENCE_DISTANCE), - DECL(AL_ROLLOFF_FACTOR), - DECL(AL_CONE_OUTER_GAIN), - DECL(AL_MAX_DISTANCE), - DECL(AL_SEC_OFFSET), - DECL(AL_SAMPLE_OFFSET), - DECL(AL_BYTE_OFFSET), - DECL(AL_SOURCE_TYPE), - DECL(AL_STATIC), - DECL(AL_STREAMING), - DECL(AL_UNDETERMINED), - DECL(AL_METERS_PER_UNIT), - DECL(AL_LOOP_POINTS_SOFT), - DECL(AL_DIRECT_CHANNELS_SOFT), - - DECL(AL_DIRECT_FILTER), - DECL(AL_AUXILIARY_SEND_FILTER), - DECL(AL_AIR_ABSORPTION_FACTOR), - DECL(AL_ROOM_ROLLOFF_FACTOR), - DECL(AL_CONE_OUTER_GAINHF), - DECL(AL_DIRECT_FILTER_GAINHF_AUTO), - DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO), - DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO), - - DECL(AL_SOURCE_STATE), - DECL(AL_INITIAL), - DECL(AL_PLAYING), - DECL(AL_PAUSED), - DECL(AL_STOPPED), - - DECL(AL_BUFFERS_QUEUED), - DECL(AL_BUFFERS_PROCESSED), - - DECL(AL_FORMAT_MONO8), - DECL(AL_FORMAT_MONO16), - DECL(AL_FORMAT_MONO_FLOAT32), - DECL(AL_FORMAT_MONO_DOUBLE_EXT), - DECL(AL_FORMAT_STEREO8), - DECL(AL_FORMAT_STEREO16), - DECL(AL_FORMAT_STEREO_FLOAT32), - DECL(AL_FORMAT_STEREO_DOUBLE_EXT), - DECL(AL_FORMAT_MONO_IMA4), - DECL(AL_FORMAT_STEREO_IMA4), - DECL(AL_FORMAT_MONO_MSADPCM_SOFT), - DECL(AL_FORMAT_STEREO_MSADPCM_SOFT), - DECL(AL_FORMAT_QUAD8_LOKI), - DECL(AL_FORMAT_QUAD16_LOKI), - DECL(AL_FORMAT_QUAD8), - DECL(AL_FORMAT_QUAD16), - DECL(AL_FORMAT_QUAD32), - DECL(AL_FORMAT_51CHN8), - DECL(AL_FORMAT_51CHN16), - DECL(AL_FORMAT_51CHN32), - DECL(AL_FORMAT_61CHN8), - DECL(AL_FORMAT_61CHN16), - DECL(AL_FORMAT_61CHN32), - DECL(AL_FORMAT_71CHN8), - DECL(AL_FORMAT_71CHN16), - DECL(AL_FORMAT_71CHN32), - DECL(AL_FORMAT_REAR8), - DECL(AL_FORMAT_REAR16), - DECL(AL_FORMAT_REAR32), - DECL(AL_FORMAT_MONO_MULAW), - DECL(AL_FORMAT_MONO_MULAW_EXT), - DECL(AL_FORMAT_STEREO_MULAW), - DECL(AL_FORMAT_STEREO_MULAW_EXT), - DECL(AL_FORMAT_QUAD_MULAW), - DECL(AL_FORMAT_51CHN_MULAW), - DECL(AL_FORMAT_61CHN_MULAW), - DECL(AL_FORMAT_71CHN_MULAW), - DECL(AL_FORMAT_REAR_MULAW), - DECL(AL_FORMAT_MONO_ALAW_EXT), - DECL(AL_FORMAT_STEREO_ALAW_EXT), - - DECL(AL_FORMAT_BFORMAT2D_8), - DECL(AL_FORMAT_BFORMAT2D_16), - DECL(AL_FORMAT_BFORMAT2D_FLOAT32), - DECL(AL_FORMAT_BFORMAT2D_MULAW), - DECL(AL_FORMAT_BFORMAT3D_8), - DECL(AL_FORMAT_BFORMAT3D_16), - DECL(AL_FORMAT_BFORMAT3D_FLOAT32), - DECL(AL_FORMAT_BFORMAT3D_MULAW), - - DECL(AL_FREQUENCY), - DECL(AL_BITS), - DECL(AL_CHANNELS), - DECL(AL_SIZE), - DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT), - DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT), - - DECL(AL_SOURCE_RADIUS), - - DECL(AL_SAMPLE_OFFSET_LATENCY_SOFT), - DECL(AL_SEC_OFFSET_LATENCY_SOFT), - - DECL(AL_STEREO_ANGLES), - - DECL(AL_UNUSED), - DECL(AL_PENDING), - DECL(AL_PROCESSED), - - DECL(AL_NO_ERROR), - DECL(AL_INVALID_NAME), - DECL(AL_INVALID_ENUM), - DECL(AL_INVALID_VALUE), - DECL(AL_INVALID_OPERATION), - DECL(AL_OUT_OF_MEMORY), - - DECL(AL_VENDOR), - DECL(AL_VERSION), - DECL(AL_RENDERER), - DECL(AL_EXTENSIONS), - - DECL(AL_DOPPLER_FACTOR), - DECL(AL_DOPPLER_VELOCITY), - DECL(AL_DISTANCE_MODEL), - DECL(AL_SPEED_OF_SOUND), - DECL(AL_SOURCE_DISTANCE_MODEL), - DECL(AL_DEFERRED_UPDATES_SOFT), - DECL(AL_GAIN_LIMIT_SOFT), - - DECL(AL_INVERSE_DISTANCE), - DECL(AL_INVERSE_DISTANCE_CLAMPED), - DECL(AL_LINEAR_DISTANCE), - DECL(AL_LINEAR_DISTANCE_CLAMPED), - DECL(AL_EXPONENT_DISTANCE), - DECL(AL_EXPONENT_DISTANCE_CLAMPED), - - DECL(AL_FILTER_TYPE), - DECL(AL_FILTER_NULL), - DECL(AL_FILTER_LOWPASS), - DECL(AL_FILTER_HIGHPASS), - DECL(AL_FILTER_BANDPASS), - - DECL(AL_LOWPASS_GAIN), - DECL(AL_LOWPASS_GAINHF), - - DECL(AL_HIGHPASS_GAIN), - DECL(AL_HIGHPASS_GAINLF), - - DECL(AL_BANDPASS_GAIN), - DECL(AL_BANDPASS_GAINHF), - DECL(AL_BANDPASS_GAINLF), - - DECL(AL_EFFECT_TYPE), - DECL(AL_EFFECT_NULL), - DECL(AL_EFFECT_REVERB), - DECL(AL_EFFECT_EAXREVERB), - DECL(AL_EFFECT_CHORUS), - DECL(AL_EFFECT_DISTORTION), - DECL(AL_EFFECT_ECHO), - DECL(AL_EFFECT_FLANGER), - DECL(AL_EFFECT_PITCH_SHIFTER), - DECL(AL_EFFECT_FREQUENCY_SHIFTER), - DECL(AL_EFFECT_VOCAL_MORPHER), - DECL(AL_EFFECT_RING_MODULATOR), - DECL(AL_EFFECT_AUTOWAH), - DECL(AL_EFFECT_COMPRESSOR), - DECL(AL_EFFECT_EQUALIZER), - DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), - DECL(AL_EFFECT_DEDICATED_DIALOGUE), - - DECL(AL_EFFECTSLOT_EFFECT), - DECL(AL_EFFECTSLOT_GAIN), - DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO), - DECL(AL_EFFECTSLOT_NULL), - - DECL(AL_EAXREVERB_DENSITY), - DECL(AL_EAXREVERB_DIFFUSION), - DECL(AL_EAXREVERB_GAIN), - DECL(AL_EAXREVERB_GAINHF), - DECL(AL_EAXREVERB_GAINLF), - DECL(AL_EAXREVERB_DECAY_TIME), - DECL(AL_EAXREVERB_DECAY_HFRATIO), - DECL(AL_EAXREVERB_DECAY_LFRATIO), - DECL(AL_EAXREVERB_REFLECTIONS_GAIN), - DECL(AL_EAXREVERB_REFLECTIONS_DELAY), - DECL(AL_EAXREVERB_REFLECTIONS_PAN), - DECL(AL_EAXREVERB_LATE_REVERB_GAIN), - DECL(AL_EAXREVERB_LATE_REVERB_DELAY), - DECL(AL_EAXREVERB_LATE_REVERB_PAN), - DECL(AL_EAXREVERB_ECHO_TIME), - DECL(AL_EAXREVERB_ECHO_DEPTH), - DECL(AL_EAXREVERB_MODULATION_TIME), - DECL(AL_EAXREVERB_MODULATION_DEPTH), - DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_EAXREVERB_HFREFERENCE), - DECL(AL_EAXREVERB_LFREFERENCE), - DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_EAXREVERB_DECAY_HFLIMIT), - - DECL(AL_REVERB_DENSITY), - DECL(AL_REVERB_DIFFUSION), - DECL(AL_REVERB_GAIN), - DECL(AL_REVERB_GAINHF), - DECL(AL_REVERB_DECAY_TIME), - DECL(AL_REVERB_DECAY_HFRATIO), - DECL(AL_REVERB_REFLECTIONS_GAIN), - DECL(AL_REVERB_REFLECTIONS_DELAY), - DECL(AL_REVERB_LATE_REVERB_GAIN), - DECL(AL_REVERB_LATE_REVERB_DELAY), - DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), - DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), - DECL(AL_REVERB_DECAY_HFLIMIT), - - DECL(AL_CHORUS_WAVEFORM), - DECL(AL_CHORUS_PHASE), - DECL(AL_CHORUS_RATE), - DECL(AL_CHORUS_DEPTH), - DECL(AL_CHORUS_FEEDBACK), - DECL(AL_CHORUS_DELAY), - - DECL(AL_DISTORTION_EDGE), - DECL(AL_DISTORTION_GAIN), - DECL(AL_DISTORTION_LOWPASS_CUTOFF), - DECL(AL_DISTORTION_EQCENTER), - DECL(AL_DISTORTION_EQBANDWIDTH), - - DECL(AL_ECHO_DELAY), - DECL(AL_ECHO_LRDELAY), - DECL(AL_ECHO_DAMPING), - DECL(AL_ECHO_FEEDBACK), - DECL(AL_ECHO_SPREAD), - - DECL(AL_FLANGER_WAVEFORM), - DECL(AL_FLANGER_PHASE), - DECL(AL_FLANGER_RATE), - DECL(AL_FLANGER_DEPTH), - DECL(AL_FLANGER_FEEDBACK), - DECL(AL_FLANGER_DELAY), - - DECL(AL_FREQUENCY_SHIFTER_FREQUENCY), - DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION), - DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION), - - DECL(AL_RING_MODULATOR_FREQUENCY), - DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), - DECL(AL_RING_MODULATOR_WAVEFORM), - - DECL(AL_PITCH_SHIFTER_COARSE_TUNE), - DECL(AL_PITCH_SHIFTER_FINE_TUNE), - - DECL(AL_COMPRESSOR_ONOFF), - - DECL(AL_EQUALIZER_LOW_GAIN), - DECL(AL_EQUALIZER_LOW_CUTOFF), - DECL(AL_EQUALIZER_MID1_GAIN), - DECL(AL_EQUALIZER_MID1_CENTER), - DECL(AL_EQUALIZER_MID1_WIDTH), - DECL(AL_EQUALIZER_MID2_GAIN), - DECL(AL_EQUALIZER_MID2_CENTER), - DECL(AL_EQUALIZER_MID2_WIDTH), - DECL(AL_EQUALIZER_HIGH_GAIN), - DECL(AL_EQUALIZER_HIGH_CUTOFF), - - DECL(AL_DEDICATED_GAIN), - - DECL(AL_AUTOWAH_ATTACK_TIME), - DECL(AL_AUTOWAH_RELEASE_TIME), - DECL(AL_AUTOWAH_RESONANCE), - DECL(AL_AUTOWAH_PEAK_GAIN), - - DECL(AL_VOCAL_MORPHER_PHONEMEA), - DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), - DECL(AL_VOCAL_MORPHER_PHONEMEB), - DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), - DECL(AL_VOCAL_MORPHER_WAVEFORM), - DECL(AL_VOCAL_MORPHER_RATE), - - DECL(AL_EFFECTSLOT_TARGET_SOFT), - - DECL(AL_NUM_RESAMPLERS_SOFT), - DECL(AL_DEFAULT_RESAMPLER_SOFT), - DECL(AL_SOURCE_RESAMPLER_SOFT), - DECL(AL_RESAMPLER_NAME_SOFT), - - DECL(AL_SOURCE_SPATIALIZE_SOFT), - DECL(AL_AUTO_SOFT), - - DECL(AL_MAP_READ_BIT_SOFT), - DECL(AL_MAP_WRITE_BIT_SOFT), - DECL(AL_MAP_PERSISTENT_BIT_SOFT), - DECL(AL_PRESERVE_DATA_BIT_SOFT), - - DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT), - DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), - DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), - DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), - DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), - - DECL(AL_DROP_UNMATCHED_SOFT), - DECL(AL_REMIX_UNMATCHED_SOFT), - - DECL(AL_AMBISONIC_LAYOUT_SOFT), - DECL(AL_AMBISONIC_SCALING_SOFT), - DECL(AL_FUMA_SOFT), - DECL(AL_ACN_SOFT), - DECL(AL_SN3D_SOFT), - DECL(AL_N3D_SOFT), - - DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), - DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), - - DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), - - DECL(AL_EFFECT_CONVOLUTION_REVERB_SOFT), - DECL(AL_EFFECTSLOT_STATE_SOFT), - - DECL(AL_FORMAT_UHJ2CHN8_SOFT), - DECL(AL_FORMAT_UHJ2CHN16_SOFT), - DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), - DECL(AL_FORMAT_UHJ3CHN8_SOFT), - DECL(AL_FORMAT_UHJ3CHN16_SOFT), - DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), - DECL(AL_FORMAT_UHJ4CHN8_SOFT), - DECL(AL_FORMAT_UHJ4CHN16_SOFT), - DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), - DECL(AL_STEREO_MODE_SOFT), - DECL(AL_NORMAL_SOFT), - DECL(AL_SUPER_STEREO_SOFT), - DECL(AL_SUPER_STEREO_WIDTH_SOFT), - - DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT), - DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT), - DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT), - DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT), - DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT), - DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT), - DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT), - DECL(AL_FORMAT_UHJ4CHN_ALAW_SOFT), - - DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), - -#ifdef ALSOFT_EAX -}, eaxEnumerations[] = { - DECL(AL_EAX_RAM_SIZE), - DECL(AL_EAX_RAM_FREE), - DECL(AL_STORAGE_AUTOMATIC), - DECL(AL_STORAGE_HARDWARE), - DECL(AL_STORAGE_ACCESSIBLE), -#endif // ALSOFT_EAX -}; -#undef DECL - -constexpr ALCchar alcNoError[] = "No Error"; -constexpr ALCchar alcErrInvalidDevice[] = "Invalid Device"; -constexpr ALCchar alcErrInvalidContext[] = "Invalid Context"; -constexpr ALCchar alcErrInvalidEnum[] = "Invalid Enum"; -constexpr ALCchar alcErrInvalidValue[] = "Invalid Value"; -constexpr ALCchar alcErrOutOfMemory[] = "Out of Memory"; - +[[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } +[[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept { return "Invalid Device"; } +[[nodiscard]] constexpr auto GetInvalidContextString() noexcept { return "Invalid Context"; } +[[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } +[[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } +[[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OpenAL Soft\0"; } /************************************************ * Global variables ************************************************/ /* Enumerated device names */ -constexpr ALCchar alcDefaultName[] = "OpenAL Soft\0"; - +std::vector alcAllDevicesArray; +std::vector alcCaptureDeviceArray; std::string alcAllDevicesList; std::string alcCaptureDeviceList; @@ -970,31 +299,41 @@ constexpr uint DitherRNGSeed{22222u}; /************************************************ * ALC information ************************************************/ -constexpr ALCchar alcNoDeviceExtList[] = - "ALC_ENUMERATE_ALL_EXT " - "ALC_ENUMERATION_EXT " - "ALC_EXT_CAPTURE " - "ALC_EXT_EFX " - "ALC_EXT_thread_local_context " - "ALC_SOFT_loopback " - "ALC_SOFT_loopback_bformat " - "ALC_SOFT_reopen_device"; -constexpr ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT " - "ALC_ENUMERATION_EXT " - "ALC_EXT_CAPTURE " - "ALC_EXT_DEDICATED " - "ALC_EXT_disconnect " - "ALC_EXT_EFX " - "ALC_EXT_thread_local_context " - "ALC_SOFT_device_clock " - "ALC_SOFT_HRTF " - "ALC_SOFT_loopback " - "ALC_SOFT_loopback_bformat " - "ALC_SOFT_output_limiter " - "ALC_SOFT_output_mode " - "ALC_SOFT_pause_device " - "ALC_SOFT_reopen_device"; +[[nodiscard]] constexpr auto GetNoDeviceExtList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT " + "ALC_ENUMERATION_EXT " + "ALC_EXT_CAPTURE " + "ALC_EXT_direct_context " + "ALC_EXT_EFX " + "ALC_EXT_thread_local_context " + "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_reopen_device " + "ALC_SOFT_system_events"sv; +} +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT " + "ALC_ENUMERATION_EXT " + "ALC_EXT_CAPTURE " + "ALC_EXT_debug " + "ALC_EXT_DEDICATED " + "ALC_EXT_direct_context " + "ALC_EXT_disconnect " + "ALC_EXT_EFX " + "ALC_EXT_thread_local_context " + "ALC_SOFT_device_clock " + "ALC_SOFT_HRTF " + "ALC_SOFT_loopback " + "ALC_SOFT_loopback_bformat " + "ALC_SOFT_output_limiter " + "ALC_SOFT_output_mode " + "ALC_SOFT_pause_device " + "ALC_SOFT_reopen_device " + "ALC_SOFT_system_events"sv; +} + constexpr int alcMajorVersion{1}; constexpr int alcMinorVersion{1}; @@ -1008,13 +347,13 @@ using DeviceRef = al::intrusive_ptr; /************************************************ * Device lists ************************************************/ -al::vector DeviceList; -al::vector ContextList; +std::vector DeviceList; +std::vector ContextList; std::recursive_mutex ListLock; -void alc_initconfig(void) +void alc_initconfig() { if(auto loglevel = al::getenv("ALSOFT_LOGLEVEL")) { @@ -1034,7 +373,7 @@ void alc_initconfig(void) if(logf) gLogFile = logf; else { - auto u8name = wstr_to_utf8(logfile->c_str()); + auto u8name = wstr_to_utf8(*logfile); ERR("Failed to open log file '%s'\n", u8name.c_str()); } } @@ -1051,7 +390,7 @@ void alc_initconfig(void) ALSOFT_GIT_BRANCH); { std::string names; - if(al::size(BackendList) < 1) + if(std::size(BackendList) < 1) names = "(none)"; else { @@ -1069,7 +408,7 @@ void alc_initconfig(void) if(auto suspendmode = al::getenv("__ALSOFT_SUSPEND_CONTEXT")) { - if(al::strcasecmp(suspendmode->c_str(), "ignore") == 0) + if(al::case_compare(*suspendmode, "ignore"sv) == 0) { SuspendDefers = false; TRACE("Selected context suspend behavior, \"ignore\"\n"); @@ -1091,39 +430,39 @@ void alc_initconfig(void) #ifdef HAVE_NEON capfilter |= CPU_CAP_NEON; #endif - if(auto cpuopt = ConfigValueStr(nullptr, nullptr, "disable-cpu-exts")) + if(auto cpuopt = ConfigValueStr({}, {}, "disable-cpu-exts"sv)) { - const char *str{cpuopt->c_str()}; - if(al::strcasecmp(str, "all") == 0) + std::string_view cpulist{*cpuopt}; + if(al::case_compare(cpulist, "all"sv) == 0) capfilter = 0; - else + else while(!cpulist.empty()) { - const char *next = str; - do { - str = next; - while(isspace(str[0])) - str++; - next = strchr(str, ','); + auto nextpos = std::min(cpulist.find(','), cpulist.size()); + auto entry = cpulist.substr(0, nextpos); - if(!str[0] || str[0] == ',') - continue; + while(nextpos < cpulist.size() && cpulist[nextpos] == ',') + ++nextpos; + cpulist.remove_prefix(nextpos); - size_t len{next ? static_cast(next-str) : strlen(str)}; - while(len > 0 && isspace(str[len-1])) - len--; - if(len == 3 && al::strncasecmp(str, "sse", len) == 0) - capfilter &= ~CPU_CAP_SSE; - else if(len == 4 && al::strncasecmp(str, "sse2", len) == 0) - capfilter &= ~CPU_CAP_SSE2; - else if(len == 4 && al::strncasecmp(str, "sse3", len) == 0) - capfilter &= ~CPU_CAP_SSE3; - else if(len == 6 && al::strncasecmp(str, "sse4.1", len) == 0) - capfilter &= ~CPU_CAP_SSE4_1; - else if(len == 4 && al::strncasecmp(str, "neon", len) == 0) - capfilter &= ~CPU_CAP_NEON; - else - WARN("Invalid CPU extension \"%s\"\n", str); - } while(next++); + while(!entry.empty() && std::isspace(entry.front())) + entry.remove_prefix(1); + while(!entry.empty() && std::isspace(entry.back())) + entry.remove_suffix(1); + if(entry.empty()) + continue; + + if(al::case_compare(entry, "sse"sv) == 0) + capfilter &= ~CPU_CAP_SSE; + else if(al::case_compare(entry, "sse2"sv) == 0) + capfilter &= ~CPU_CAP_SSE2; + else if(al::case_compare(entry, "sse3"sv) == 0) + capfilter &= ~CPU_CAP_SSE3; + else if(al::case_compare(entry, "sse4.1"sv) == 0) + capfilter &= ~CPU_CAP_SSE4_1; + else if(al::case_compare(entry, "neon"sv) == 0) + capfilter &= ~CPU_CAP_NEON; + else + WARN("Invalid CPU extension \"%.*s\"\n", al::sizei(entry), entry.data()); } } if(auto cpuopt = GetCPUInfo()) @@ -1144,64 +483,56 @@ void alc_initconfig(void) CPUCapFlags = caps & capfilter; } - if(auto priopt = ConfigValueInt(nullptr, nullptr, "rt-prio")) + if(auto priopt = ConfigValueInt({}, {}, "rt-prio"sv)) RTPrioLevel = *priopt; - if(auto limopt = ConfigValueBool(nullptr, nullptr, "rt-time-limit")) + if(auto limopt = ConfigValueBool({}, {}, "rt-time-limit"sv)) AllowRTTimeLimit = *limopt; { CompatFlagBitset compatflags{}; - auto checkflag = [](const char *envname, const char *optname) -> bool + auto checkflag = [](const char *envname, const std::string_view optname) -> bool { if(auto optval = al::getenv(envname)) { - if(al::strcasecmp(optval->c_str(), "true") == 0 - || strtol(optval->c_str(), nullptr, 0) == 1) - return true; - return false; + return al::case_compare(*optval, "true"sv) == 0 + || strtol(optval->c_str(), nullptr, 0) == 1; } - return GetConfigValueBool(nullptr, "game_compat", optname, false); + return GetConfigValueBool({}, "game_compat", optname, false); }; - sBufferSubDataCompat = checkflag("__ALSOFT_ENABLE_SUB_DATA_EXT", "enable-sub-data-ext"); - compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x")); - compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y")); - compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z")); + sBufferSubDataCompat = checkflag("__ALSOFT_ENABLE_SUB_DATA_EXT", "enable-sub-data-ext"sv); + compatflags.set(CompatFlags::ReverseX, checkflag("__ALSOFT_REVERSE_X", "reverse-x"sv)); + compatflags.set(CompatFlags::ReverseY, checkflag("__ALSOFT_REVERSE_Y", "reverse-y"sv)); + compatflags.set(CompatFlags::ReverseZ, checkflag("__ALSOFT_REVERSE_Z", "reverse-z"sv)); - aluInit(compatflags, ConfigValueFloat(nullptr, "game_compat", "nfc-scale").value_or(1.0f)); + aluInit(compatflags, ConfigValueFloat({}, "game_compat"sv, "nfc-scale"sv).value_or(1.0f)); } - Voice::InitMixer(ConfigValueStr(nullptr, nullptr, "resampler")); + Voice::InitMixer(ConfigValueStr({}, {}, "resampler"sv)); - auto uhjfiltopt = ConfigValueStr(nullptr, "uhj", "decode-filter"); - if(!uhjfiltopt) + if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "decode-filter"sv)) { - if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "filter"))) - WARN("uhj/filter is deprecated, please use uhj/decode-filter\n"); - } - if(uhjfiltopt) - { - if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) UhjDecodeQuality = UhjQualityType::FIR256; - else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) UhjDecodeQuality = UhjQualityType::FIR512; - else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjDecodeQuality = UhjQualityType::IIR; else WARN("Unsupported uhj/decode-filter: %s\n", uhjfiltopt->c_str()); } - if((uhjfiltopt = ConfigValueStr(nullptr, "uhj", "encode-filter"))) + if(auto uhjfiltopt = ConfigValueStr({}, "uhj"sv, "encode-filter"sv)) { - if(al::strcasecmp(uhjfiltopt->c_str(), "fir256") == 0) + if(al::case_compare(*uhjfiltopt, "fir256"sv) == 0) UhjEncodeQuality = UhjQualityType::FIR256; - else if(al::strcasecmp(uhjfiltopt->c_str(), "fir512") == 0) + else if(al::case_compare(*uhjfiltopt, "fir512"sv) == 0) UhjEncodeQuality = UhjQualityType::FIR512; - else if(al::strcasecmp(uhjfiltopt->c_str(), "iir") == 0) + else if(al::case_compare(*uhjfiltopt, "iir"sv) == 0) UhjEncodeQuality = UhjQualityType::IIR; else WARN("Unsupported uhj/encode-filter: %s\n", uhjfiltopt->c_str()); } - auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); - if(traperr && (al::strcasecmp(traperr->c_str(), "true") == 0 + if(auto traperr = al::getenv("ALSOFT_TRAP_ERROR"); traperr + && (al::case_compare(*traperr, "true"sv) == 0 || std::strtol(traperr->c_str(), nullptr, 0) == 1)) { TrapALError = true; @@ -1211,66 +542,69 @@ void alc_initconfig(void) { traperr = al::getenv("ALSOFT_TRAP_AL_ERROR"); if(traperr) - TrapALError = al::strcasecmp(traperr->c_str(), "true") == 0 + TrapALError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else - TrapALError = !!GetConfigValueBool(nullptr, nullptr, "trap-al-error", false); + TrapALError = GetConfigValueBool({}, {}, "trap-al-error"sv, false); traperr = al::getenv("ALSOFT_TRAP_ALC_ERROR"); if(traperr) - TrapALCError = al::strcasecmp(traperr->c_str(), "true") == 0 + TrapALCError = al::case_compare(*traperr, "true"sv) == 0 || strtol(traperr->c_str(), nullptr, 0) == 1; else - TrapALCError = !!GetConfigValueBool(nullptr, nullptr, "trap-alc-error", false); + TrapALCError = GetConfigValueBool({}, {}, "trap-alc-error"sv, false); } - if(auto boostopt = ConfigValueFloat(nullptr, "reverb", "boost")) + if(auto boostopt = ConfigValueFloat({}, "reverb"sv, "boost"sv)) { - const float valf{std::isfinite(*boostopt) ? clampf(*boostopt, -24.0f, 24.0f) : 0.0f}; + const float valf{std::isfinite(*boostopt) ? std::clamp(*boostopt, -24.0f, 24.0f) : 0.0f}; ReverbBoost *= std::pow(10.0f, valf / 20.0f); } - auto BackendListEnd = std::end(BackendList); + auto BackendListEnd = BackendList.end(); auto devopt = al::getenv("ALSOFT_DRIVERS"); - if(devopt || (devopt=ConfigValueStr(nullptr, nullptr, "drivers"))) + if(!devopt) devopt = ConfigValueStr({}, {}, "drivers"sv); + if(devopt) { - auto backendlist_cur = std::begin(BackendList); + auto backendlist_cur = BackendList.begin(); bool endlist{true}; - const char *next{devopt->c_str()}; - do { - const char *devs{next}; - while(isspace(devs[0])) - devs++; - next = strchr(devs, ','); + std::string_view drvlist{*devopt}; + while(!drvlist.empty()) + { + auto nextpos = std::min(drvlist.find(','), drvlist.size()); + auto entry = drvlist.substr(0, nextpos); - const bool delitem{devs[0] == '-'}; - if(devs[0] == '-') devs++; - - if(!devs[0] || devs[0] == ',') + endlist = true; + if(nextpos < drvlist.size()) { endlist = false; - continue; + while(nextpos < drvlist.size() && drvlist[nextpos] == ',') + ++nextpos; } - endlist = true; + drvlist.remove_prefix(nextpos); + + while(!entry.empty() && std::isspace(entry.front())) + entry.remove_prefix(1); + const bool delitem{!entry.empty() && entry.front() == '-'}; + if(delitem) entry.remove_prefix(1); + + while(!entry.empty() && std::isspace(entry.back())) + entry.remove_suffix(1); + if(entry.empty()) + continue; - size_t len{next ? (static_cast(next-devs)) : strlen(devs)}; - while(len > 0 && isspace(devs[len-1])) --len; #ifdef HAVE_WASAPI /* HACK: For backwards compatibility, convert backend references of * mmdevapi to wasapi. This should eventually be removed. */ - if(len == 8 && strncmp(devs, "mmdevapi", len) == 0) - { - devs = "wasapi"; - len = 6; - } + if(entry == "mmdevapi"sv) + entry = "wasapi"sv; #endif - auto find_backend = [devs,len](const BackendInfo &backend) -> bool - { return len == strlen(backend.name) && strncmp(backend.name, devs, len) == 0; }; - auto this_backend = std::find_if(std::begin(BackendList), BackendListEnd, - find_backend); + auto find_backend = [entry](const BackendInfo &backend) -> bool + { return entry == backend.name; }; + auto this_backend = std::find_if(BackendList.begin(), BackendListEnd, find_backend); if(this_backend == BackendListEnd) continue; @@ -1279,7 +613,7 @@ void alc_initconfig(void) BackendListEnd = std::move(this_backend+1, BackendListEnd, this_backend); else backendlist_cur = std::rotate(backendlist_cur, this_backend, this_backend+1); - } while(next++); + } if(endlist) BackendListEnd = backendlist_cur; @@ -1309,7 +643,7 @@ void alc_initconfig(void) TRACE("Added \"%s\" for capture\n", backend.name); } }; - std::for_each(std::begin(BackendList), BackendListEnd, init_backend); + std::for_each(BackendList.begin(), BackendListEnd, init_backend); LoopbackBackendFactory::getFactory().init(); @@ -1318,36 +652,32 @@ void alc_initconfig(void) if(!CaptureFactory) WARN("No capture backend available!\n"); - if(auto exclopt = ConfigValueStr(nullptr, nullptr, "excludefx")) + if(auto exclopt = ConfigValueStr({}, {}, "excludefx"sv)) { - const char *next{exclopt->c_str()}; - do { - const char *str{next}; - next = strchr(str, ','); + std::string_view exclude{*exclopt}; + while(!exclude.empty()) + { + const auto nextpos = exclude.find(','); + const auto entry = exclude.substr(0, nextpos); + exclude.remove_prefix((nextpos < exclude.size()) ? nextpos+1 : exclude.size()); - if(!str[0] || next == str) - continue; - - size_t len{next ? static_cast(next-str) : strlen(str)}; - for(const EffectList &effectitem : gEffectList) - { - if(len == strlen(effectitem.name) && - strncmp(effectitem.name, str, len) == 0) - DisabledEffects[effectitem.type] = true; - } - } while(next++); + std::for_each(gEffectList.cbegin(), gEffectList.cend(), + [entry](const EffectList &effectitem) noexcept + { + if(entry == std::data(effectitem.name)) + DisabledEffects.set(effectitem.type); + }); + } } InitEffect(&ALCcontext::sDefaultEffect); auto defrevopt = al::getenv("ALSOFT_DEFAULT_REVERB"); - if(defrevopt || (defrevopt=ConfigValueStr(nullptr, nullptr, "default-reverb"))) - LoadReverbPreset(defrevopt->c_str(), &ALCcontext::sDefaultEffect); + if(!defrevopt) defrevopt = ConfigValueStr({}, {}, "default-reverb"sv); + if(defrevopt) LoadReverbPreset(*defrevopt, &ALCcontext::sDefaultEffect); #ifdef ALSOFT_EAX { - static constexpr char eax_block_name[] = "eax"; - - if(const auto eax_enable_opt = ConfigValueBool(nullptr, eax_block_name, "enable")) + if(const auto eax_enable_opt = ConfigValueBool({}, "eax", "enable")) { eax_g_is_enabled = *eax_enable_opt; if(!eax_g_is_enabled) @@ -1356,15 +686,15 @@ void alc_initconfig(void) else eax_g_is_enabled = true; - if((DisabledEffects[EAXREVERB_EFFECT] || DisabledEffects[CHORUS_EFFECT]) + if((DisabledEffects.test(EAXREVERB_EFFECT) || DisabledEffects.test(CHORUS_EFFECT)) && eax_g_is_enabled) { eax_g_is_enabled = false; TRACE("EAX disabled because %s disabled.\n", - (DisabledEffects[EAXREVERB_EFFECT] && DisabledEffects[CHORUS_EFFECT]) + (DisabledEffects.test(EAXREVERB_EFFECT) && DisabledEffects.test(CHORUS_EFFECT)) ? "EAXReverb and Chorus are" : - DisabledEffects[EAXREVERB_EFFECT] ? "EAXReverb is" : - DisabledEffects[CHORUS_EFFECT] ? "Chorus is" : ""); + DisabledEffects.test(EAXREVERB_EFFECT) ? "EAXReverb is" : + DisabledEffects.test(CHORUS_EFFECT) ? "Chorus is" : ""); } } #endif // ALSOFT_EAX @@ -1380,75 +710,111 @@ void ProbeAllDevicesList() { InitConfig(); - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; if(!PlaybackFactory) + { + decltype(alcAllDevicesArray){}.swap(alcAllDevicesArray); decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + } else { - std::string names{PlaybackFactory->probe(BackendType::Playback)}; - if(names.empty()) names += '\0'; - names.swap(alcAllDevicesList); + alcAllDevicesArray = PlaybackFactory->enumerate(BackendType::Playback); + decltype(alcAllDevicesList){}.swap(alcAllDevicesList); + if(alcAllDevicesArray.empty()) + alcAllDevicesList += '\0'; + else for(auto &devname : alcAllDevicesArray) + alcAllDevicesList.append(devname) += '\0'; } } void ProbeCaptureDeviceList() { InitConfig(); - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; if(!CaptureFactory) + { + decltype(alcCaptureDeviceArray){}.swap(alcCaptureDeviceArray); decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + } else { - std::string names{CaptureFactory->probe(BackendType::Capture)}; - if(names.empty()) names += '\0'; - names.swap(alcCaptureDeviceList); + alcCaptureDeviceArray = CaptureFactory->enumerate(BackendType::Capture); + decltype(alcCaptureDeviceList){}.swap(alcCaptureDeviceList); + if(alcCaptureDeviceArray.empty()) + alcCaptureDeviceList += '\0'; + else for(auto &devname : alcCaptureDeviceArray) + alcCaptureDeviceList.append(devname) += '\0'; } } -struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; -al::optional DecomposeDevFormat(ALenum format) +al::span SpanFromAttributeList(const ALCint *attribs) noexcept { - static const struct { + al::span attrSpan; + if(attribs) + { + const ALCint *attrEnd{attribs}; + while(*attrEnd != 0) + attrEnd += 2; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + attrSpan = {attribs, attrEnd}; + } + return attrSpan; +} + +struct DevFmtPair { DevFmtChannels chans; DevFmtType type; }; +std::optional DecomposeDevFormat(ALenum format) +{ + struct FormatType { ALenum format; DevFmtChannels channels; DevFmtType type; - } list[] = { - { AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte }, - { AL_FORMAT_MONO16, DevFmtMono, DevFmtShort }, - { AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat }, + }; + static constexpr std::array list{ + FormatType{AL_FORMAT_MONO8, DevFmtMono, DevFmtUByte}, + FormatType{AL_FORMAT_MONO16, DevFmtMono, DevFmtShort}, + FormatType{AL_FORMAT_MONO_I32, DevFmtMono, DevFmtInt}, + FormatType{AL_FORMAT_MONO_FLOAT32, DevFmtMono, DevFmtFloat}, - { AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte }, - { AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort }, - { AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat }, + FormatType{AL_FORMAT_STEREO8, DevFmtStereo, DevFmtUByte}, + FormatType{AL_FORMAT_STEREO16, DevFmtStereo, DevFmtShort}, + FormatType{AL_FORMAT_STEREO_I32, DevFmtStereo, DevFmtInt}, + FormatType{AL_FORMAT_STEREO_FLOAT32, DevFmtStereo, DevFmtFloat}, - { AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte }, - { AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort }, - { AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat }, + FormatType{AL_FORMAT_QUAD8, DevFmtQuad, DevFmtUByte}, + FormatType{AL_FORMAT_QUAD16, DevFmtQuad, DevFmtShort}, + FormatType{AL_FORMAT_QUAD32, DevFmtQuad, DevFmtFloat}, + FormatType{AL_FORMAT_QUAD_I32, DevFmtQuad, DevFmtInt}, + FormatType{AL_FORMAT_QUAD_FLOAT32, DevFmtQuad, DevFmtFloat}, - { AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte }, - { AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort }, - { AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat }, + FormatType{AL_FORMAT_51CHN8, DevFmtX51, DevFmtUByte}, + FormatType{AL_FORMAT_51CHN16, DevFmtX51, DevFmtShort}, + FormatType{AL_FORMAT_51CHN32, DevFmtX51, DevFmtFloat}, + FormatType{AL_FORMAT_51CHN_I32, DevFmtX51, DevFmtInt}, + FormatType{AL_FORMAT_51CHN_FLOAT32, DevFmtX51, DevFmtFloat}, - { AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte }, - { AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort }, - { AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat }, + FormatType{AL_FORMAT_61CHN8, DevFmtX61, DevFmtUByte}, + FormatType{AL_FORMAT_61CHN16, DevFmtX61, DevFmtShort}, + FormatType{AL_FORMAT_61CHN32, DevFmtX61, DevFmtFloat}, + FormatType{AL_FORMAT_61CHN_I32, DevFmtX61, DevFmtInt}, + FormatType{AL_FORMAT_61CHN_FLOAT32, DevFmtX61, DevFmtFloat}, - { AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte }, - { AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort }, - { AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat }, + FormatType{AL_FORMAT_71CHN8, DevFmtX71, DevFmtUByte}, + FormatType{AL_FORMAT_71CHN16, DevFmtX71, DevFmtShort}, + FormatType{AL_FORMAT_71CHN32, DevFmtX71, DevFmtFloat}, + FormatType{AL_FORMAT_71CHN_I32, DevFmtX71, DevFmtInt}, + FormatType{AL_FORMAT_71CHN_FLOAT32, DevFmtX71, DevFmtFloat}, }; for(const auto &item : list) { if(item.format == format) - return al::make_optional({item.channels, item.type}); + return DevFmtPair{item.channels, item.type}; } - return al::nullopt; + return std::nullopt; } -al::optional DevFmtTypeFromEnum(ALCenum type) +std::optional DevFmtTypeFromEnum(ALCenum type) { switch(type) { @@ -1461,7 +827,7 @@ al::optional DevFmtTypeFromEnum(ALCenum type) case ALC_FLOAT_SOFT: return DevFmtFloat; } WARN("Unsupported format type: 0x%04x\n", type); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtType type) { @@ -1478,7 +844,7 @@ ALCenum EnumFromDevFmt(DevFmtType type) throw std::runtime_error{"Invalid DevFmtType: "+std::to_string(int(type))}; } -al::optional DevFmtChannelsFromEnum(ALCenum channels) +std::optional DevFmtChannelsFromEnum(ALCenum channels) { switch(channels) { @@ -1491,7 +857,7 @@ al::optional DevFmtChannelsFromEnum(ALCenum channels) case ALC_BFORMAT3D_SOFT: return DevFmtAmbi3D; } WARN("Unsupported format channels: 0x%04x\n", channels); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevFmt(DevFmtChannels channels) { @@ -1506,12 +872,13 @@ ALCenum EnumFromDevFmt(DevFmtChannels channels) case DevFmtAmbi3D: return ALC_BFORMAT3D_SOFT; /* FIXME: Shouldn't happen. */ case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: break; } throw std::runtime_error{"Invalid DevFmtChannels: "+std::to_string(int(channels))}; } -al::optional DevAmbiLayoutFromEnum(ALCenum layout) +std::optional DevAmbiLayoutFromEnum(ALCenum layout) { switch(layout) { @@ -1519,7 +886,7 @@ al::optional DevAmbiLayoutFromEnum(ALCenum layout) case ALC_ACN_SOFT: return DevAmbiLayout::ACN; } WARN("Unsupported ambisonic layout: 0x%04x\n", layout); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiLayout layout) { @@ -1531,7 +898,7 @@ ALCenum EnumFromDevAmbi(DevAmbiLayout layout) throw std::runtime_error{"Invalid DevAmbiLayout: "+std::to_string(int(layout))}; } -al::optional DevAmbiScalingFromEnum(ALCenum scaling) +std::optional DevAmbiScalingFromEnum(ALCenum scaling) { switch(scaling) { @@ -1540,7 +907,7 @@ al::optional DevAmbiScalingFromEnum(ALCenum scaling) case ALC_N3D_SOFT: return DevAmbiScaling::N3D; } WARN("Unsupported ambisonic scaling: 0x%04x\n", scaling); - return al::nullopt; + return std::nullopt; } ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) { @@ -1554,95 +921,60 @@ ALCenum EnumFromDevAmbi(DevAmbiScaling scaling) } -/* Downmixing channel arrays, to map the given format's missing channels to - * existing ones. Based on Wine's DSound downmix values, which are based on - * PulseAudio's. +/* Downmixing channel arrays, to map a device format's missing channels to + * existing ones. Based on what PipeWire does, though simplified. */ -constexpr std::array FrontStereoSplit{{ - {FrontLeft, 0.5f}, {FrontRight, 0.5f} -}}; -constexpr std::array FrontLeft9{{ - {FrontLeft, 1.0f/9.0f} -}}; -constexpr std::array FrontRight9{{ - {FrontRight, 1.0f/9.0f} -}}; -constexpr std::array BackMonoToFrontSplit{{ - {FrontLeft, 0.5f/9.0f}, {FrontRight, 0.5f/9.0f} -}}; -constexpr std::array LeftStereoSplit{{ - {FrontLeft, 0.5f}, {BackLeft, 0.5f} -}}; -constexpr std::array RightStereoSplit{{ - {FrontRight, 0.5f}, {BackRight, 0.5f} -}}; -constexpr std::array BackStereoSplit{{ - {BackLeft, 0.5f}, {BackRight, 0.5f} -}}; -constexpr std::array SideStereoSplit{{ - {SideLeft, 0.5f}, {SideRight, 0.5f} -}}; -constexpr std::array ToSideLeft{{ - {SideLeft, 1.0f} -}}; -constexpr std::array ToSideRight{{ - {SideRight, 1.0f} -}}; -constexpr std::array BackLeftSplit{{ - {SideLeft, 0.5f}, {BackCenter, 0.5f} -}}; -constexpr std::array BackRightSplit{{ - {SideRight, 0.5f}, {BackCenter, 0.5f} -}}; +constexpr float inv_sqrt2f{static_cast(1.0 / al::numbers::sqrt2)}; +constexpr std::array FrontStereo3dB{ + InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}, + InputRemixMap::TargetMix{FrontRight, inv_sqrt2f} +}; +constexpr std::array FrontStereo6dB{ + InputRemixMap::TargetMix{FrontLeft, 0.5f}, + InputRemixMap::TargetMix{FrontRight, 0.5f} +}; +constexpr std::array SideStereo3dB{ + InputRemixMap::TargetMix{SideLeft, inv_sqrt2f}, + InputRemixMap::TargetMix{SideRight, inv_sqrt2f} +}; +constexpr std::array BackStereo3dB{ + InputRemixMap::TargetMix{BackLeft, inv_sqrt2f}, + InputRemixMap::TargetMix{BackRight, inv_sqrt2f} +}; +constexpr std::array FrontLeft3dB{InputRemixMap::TargetMix{FrontLeft, inv_sqrt2f}}; +constexpr std::array FrontRight3dB{InputRemixMap::TargetMix{FrontRight, inv_sqrt2f}}; +constexpr std::array SideLeft0dB{InputRemixMap::TargetMix{SideLeft, 1.0f}}; +constexpr std::array SideRight0dB{InputRemixMap::TargetMix{SideRight, 1.0f}}; +constexpr std::array BackLeft0dB{InputRemixMap::TargetMix{BackLeft, 1.0f}}; +constexpr std::array BackRight0dB{InputRemixMap::TargetMix{BackRight, 1.0f}}; +constexpr std::array BackCenter3dB{InputRemixMap::TargetMix{BackCenter, inv_sqrt2f}}; -const std::array StereoDownmix{{ - { FrontCenter, FrontStereoSplit }, - { SideLeft, FrontLeft9 }, - { SideRight, FrontRight9 }, - { BackLeft, FrontLeft9 }, - { BackRight, FrontRight9 }, - { BackCenter, BackMonoToFrontSplit }, -}}; -const std::array QuadDownmix{{ - { FrontCenter, FrontStereoSplit }, - { SideLeft, LeftStereoSplit }, - { SideRight, RightStereoSplit }, - { BackCenter, BackStereoSplit }, -}}; -const std::array X51Downmix{{ - { BackLeft, ToSideLeft }, - { BackRight, ToSideRight }, - { BackCenter, SideStereoSplit }, -}}; -const std::array X61Downmix{{ - { BackLeft, BackLeftSplit }, - { BackRight, BackRightSplit }, -}}; -const std::array X71Downmix{{ - { BackCenter, BackStereoSplit }, -}}; - - -/** Stores the latest ALC device error. */ -void alcSetError(ALCdevice *device, ALCenum errorCode) -{ - WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); - if(TrapALCError) - { -#ifdef _WIN32 - /* DebugBreak() will cause an exception if there is no debugger */ - if(IsDebuggerPresent()) - DebugBreak(); -#elif defined(SIGTRAP) - raise(SIGTRAP); -#endif - } - - if(device) - device->LastError.store(errorCode); - else - LastNullDeviceError.store(errorCode); -} +constexpr std::array StereoDownmix{ + InputRemixMap{FrontCenter, FrontStereo3dB}, + InputRemixMap{SideLeft, FrontLeft3dB}, + InputRemixMap{SideRight, FrontRight3dB}, + InputRemixMap{BackLeft, FrontLeft3dB}, + InputRemixMap{BackRight, FrontRight3dB}, + InputRemixMap{BackCenter, FrontStereo6dB}, +}; +constexpr std::array QuadDownmix{ + InputRemixMap{FrontCenter, FrontStereo3dB}, + InputRemixMap{SideLeft, BackLeft0dB}, + InputRemixMap{SideRight, BackRight0dB}, + InputRemixMap{BackCenter, BackStereo3dB}, +}; +constexpr std::array X51Downmix{ + InputRemixMap{BackLeft, SideLeft0dB}, + InputRemixMap{BackRight, SideRight0dB}, + InputRemixMap{BackCenter, SideStereo3dB}, +}; +constexpr std::array X61Downmix{ + InputRemixMap{BackLeft, BackCenter3dB}, + InputRemixMap{BackRight, BackCenter3dB}, +}; +constexpr std::array X71Downmix{ + InputRemixMap{BackCenter, BackStereo3dB}, +}; std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const float threshold) @@ -1674,19 +1006,23 @@ std::unique_ptr CreateDeviceLimiter(const ALCdevice *device, const f */ inline void UpdateClockBase(ALCdevice *device) { - IncrementRef(device->MixCount); - device->ClockBase += nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - device->SamplesDone = 0; - IncrementRef(device->MixCount); + const auto mixLock = device->getWriteMixLock(); + + auto samplesDone = device->mSamplesDone.load(std::memory_order_relaxed); + auto clockBase = device->mClockBase.load(std::memory_order_relaxed); + + clockBase += nanoseconds{seconds{samplesDone}} / device->Frequency; + device->mClockBase.store(clockBase, std::memory_order_relaxed); + device->mSamplesDone.store(0, std::memory_order_relaxed); } /** * Updates device parameters according to the attribute list (caller is * responsible for holding the list lock). */ -ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) +ALCenum UpdateDeviceParams(ALCdevice *device, const al::span attrList) { - if((!attrList || !attrList[0]) && device->Type == DeviceType::Loopback) + if(attrList.empty() && device->Type == DeviceType::Loopback) { WARN("Missing attributes for loopback device\n"); return ALC_INVALID_VALUE; @@ -1695,15 +1031,15 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) uint numMono{device->NumMonoSources}; uint numStereo{device->NumStereoSources}; uint numSends{device->NumAuxSends}; - al::optional stereomode; - al::optional optlimit; - al::optional optsrate; - al::optional optchans; - al::optional opttype; - al::optional optlayout; - al::optional optscale; - uint period_size{DEFAULT_UPDATE_SIZE}; - uint buffer_size{DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES}; + std::optional stereomode; + std::optional optlimit; + std::optional optsrate; + std::optional optchans; + std::optional opttype; + std::optional optlayout; + std::optional optscale; + uint period_size{DefaultUpdateSize}; + uint buffer_size{DefaultUpdateSize * DefaultNumUpdates}; int hrtf_id{-1}; uint aorder{0u}; @@ -1711,71 +1047,74 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { /* Get default settings from the user configuration */ - if(auto freqopt = device->configValue(nullptr, "frequency")) + if(auto freqopt = device->configValue({}, "frequency")) { - optsrate = clampu(*freqopt, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + optsrate = std::clamp(*freqopt, MinOutputRate, MaxOutputRate); - const double scale{static_cast(*optsrate) / DEFAULT_OUTPUT_RATE}; - period_size = static_cast(period_size*scale + 0.5); + const double scale{static_cast(*optsrate) / double{DefaultOutputRate}}; + period_size = static_cast(std::lround(period_size * scale)); } - if(auto persizeopt = device->configValue(nullptr, "period_size")) - period_size = clampu(*persizeopt, 64, 8192); - if(auto numperopt = device->configValue(nullptr, "periods")) - buffer_size = clampu(*numperopt, 2, 16) * period_size; + if(auto persizeopt = device->configValue({}, "period_size")) + period_size = std::clamp(*persizeopt, 64u, 8192u); + if(auto numperopt = device->configValue({}, "periods")) + buffer_size = std::clamp(*numperopt, 2u, 16u) * period_size; else - buffer_size = period_size * DEFAULT_NUM_UPDATES; + buffer_size = period_size * uint{DefaultNumUpdates}; - if(auto typeopt = device->configValue(nullptr, "sample-type")) + if(auto typeopt = device->configValue({}, "sample-type")) { - static constexpr struct TypeMap { - const char name[8]; + struct TypeMap { + std::string_view name; DevFmtType type; - } typelist[] = { - { "int8", DevFmtByte }, - { "uint8", DevFmtUByte }, - { "int16", DevFmtShort }, - { "uint16", DevFmtUShort }, - { "int32", DevFmtInt }, - { "uint32", DevFmtUInt }, - { "float32", DevFmtFloat }, + }; + constexpr std::array typelist{ + TypeMap{"int8"sv, DevFmtByte }, + TypeMap{"uint8"sv, DevFmtUByte }, + TypeMap{"int16"sv, DevFmtShort }, + TypeMap{"uint16"sv, DevFmtUShort}, + TypeMap{"int32"sv, DevFmtInt }, + TypeMap{"uint32"sv, DevFmtUInt }, + TypeMap{"float32"sv, DevFmtFloat }, }; const ALCchar *fmt{typeopt->c_str()}; - auto iter = std::find_if(std::begin(typelist), std::end(typelist), - [fmt](const TypeMap &entry) -> bool - { return al::strcasecmp(entry.name, fmt) == 0; }); - if(iter == std::end(typelist)) + auto iter = std::find_if(typelist.begin(), typelist.end(), + [svfmt=std::string_view{fmt}](const TypeMap &entry) -> bool + { return al::case_compare(entry.name, svfmt) == 0; }); + if(iter == typelist.end()) ERR("Unsupported sample-type: %s\n", fmt); else opttype = iter->type; } - if(auto chanopt = device->configValue(nullptr, "channels")) + if(auto chanopt = device->configValue({}, "channels")) { - static constexpr struct ChannelMap { - const char name[16]; + struct ChannelMap { + std::string_view name; DevFmtChannels chans; uint8_t order; - } chanlist[] = { - { "mono", DevFmtMono, 0 }, - { "stereo", DevFmtStereo, 0 }, - { "quad", DevFmtQuad, 0 }, - { "surround51", DevFmtX51, 0 }, - { "surround61", DevFmtX61, 0 }, - { "surround71", DevFmtX71, 0 }, - { "surround714", DevFmtX714, 0 }, - { "surround3d71", DevFmtX3D71, 0 }, - { "surround51rear", DevFmtX51, 0 }, - { "ambi1", DevFmtAmbi3D, 1 }, - { "ambi2", DevFmtAmbi3D, 2 }, - { "ambi3", DevFmtAmbi3D, 3 }, + }; + constexpr std::array chanlist{ + ChannelMap{"mono"sv, DevFmtMono, 0}, + ChannelMap{"stereo"sv, DevFmtStereo, 0}, + ChannelMap{"quad"sv, DevFmtQuad, 0}, + ChannelMap{"surround51"sv, DevFmtX51, 0}, + ChannelMap{"surround61"sv, DevFmtX61, 0}, + ChannelMap{"surround71"sv, DevFmtX71, 0}, + ChannelMap{"surround714"sv, DevFmtX714, 0}, + ChannelMap{"surround7144"sv, DevFmtX7144, 0}, + ChannelMap{"surround3d71"sv, DevFmtX3D71, 0}, + ChannelMap{"surround51rear"sv, DevFmtX51, 0}, + ChannelMap{"ambi1"sv, DevFmtAmbi3D, 1}, + ChannelMap{"ambi2"sv, DevFmtAmbi3D, 2}, + ChannelMap{"ambi3"sv, DevFmtAmbi3D, 3}, }; const ALCchar *fmt{chanopt->c_str()}; - auto iter = std::find_if(std::begin(chanlist), std::end(chanlist), - [fmt](const ChannelMap &entry) -> bool - { return al::strcasecmp(entry.name, fmt) == 0; }); - if(iter == std::end(chanlist)) + auto iter = std::find_if(chanlist.begin(), chanlist.end(), + [svfmt=std::string_view{fmt}](const ChannelMap &entry) -> bool + { return al::case_compare(entry.name, svfmt) == 0; }); + if(iter == chanlist.end()) ERR("Unsupported channels: %s\n", fmt); else { @@ -1783,73 +1122,70 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) aorder = iter->order; } } - if(auto ambiopt = device->configValue(nullptr, "ambi-format")) + if(auto ambiopt = device->configValue({}, "ambi-format"sv)) { - const ALCchar *fmt{ambiopt->c_str()}; - if(al::strcasecmp(fmt, "fuma") == 0) + if(al::case_compare(*ambiopt, "fuma"sv) == 0) { optlayout = DevAmbiLayout::FuMa; optscale = DevAmbiScaling::FuMa; } - else if(al::strcasecmp(fmt, "acn+fuma") == 0) + else if(al::case_compare(*ambiopt, "acn+fuma"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::FuMa; } - else if(al::strcasecmp(fmt, "ambix") == 0 || al::strcasecmp(fmt, "acn+sn3d") == 0) + else if(al::case_compare(*ambiopt, "ambix"sv) == 0 + || al::case_compare(*ambiopt, "acn+sn3d"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::SN3D; } - else if(al::strcasecmp(fmt, "acn+n3d") == 0) + else if(al::case_compare(*ambiopt, "acn+n3d"sv) == 0) { optlayout = DevAmbiLayout::ACN; optscale = DevAmbiScaling::N3D; } else - ERR("Unsupported ambi-format: %s\n", fmt); + ERR("Unsupported ambi-format: %s\n", ambiopt->c_str()); } - if(auto hrtfopt = device->configValue(nullptr, "hrtf")) + if(auto hrtfopt = device->configValue({}, "hrtf"sv)) { WARN("general/hrtf is deprecated, please use stereo-encoding instead\n"); - const char *hrtf{hrtfopt->c_str()}; - if(al::strcasecmp(hrtf, "true") == 0) + if(al::case_compare(*hrtfopt, "true"sv) == 0) stereomode = StereoEncoding::Hrtf; - else if(al::strcasecmp(hrtf, "false") == 0) + else if(al::case_compare(*hrtfopt, "false"sv) == 0) { if(!stereomode || *stereomode == StereoEncoding::Hrtf) stereomode = StereoEncoding::Default; } - else if(al::strcasecmp(hrtf, "auto") != 0) - ERR("Unexpected hrtf value: %s\n", hrtf); + else if(al::case_compare(*hrtfopt, "auto"sv) != 0) + ERR("Unexpected hrtf value: %s\n", hrtfopt->c_str()); } } - if(auto encopt = device->configValue(nullptr, "stereo-encoding")) + if(auto encopt = device->configValue({}, "stereo-encoding"sv)) { - const char *mode{encopt->c_str()}; - if(al::strcasecmp(mode, "basic") == 0 || al::strcasecmp(mode, "panpot") == 0) + if(al::case_compare(*encopt, "basic"sv) == 0 || al::case_compare(*encopt, "panpot"sv) == 0) stereomode = StereoEncoding::Basic; - else if(al::strcasecmp(mode, "uhj") == 0) + else if(al::case_compare(*encopt, "uhj") == 0) stereomode = StereoEncoding::Uhj; - else if(al::strcasecmp(mode, "hrtf") == 0) + else if(al::case_compare(*encopt, "hrtf") == 0) stereomode = StereoEncoding::Hrtf; else - ERR("Unexpected stereo-encoding: %s\n", mode); + ERR("Unexpected stereo-encoding: %s\n", encopt->c_str()); } // Check for app-specified attributes - if(attrList && attrList[0]) + if(!attrList.empty()) { ALenum outmode{ALC_ANY_SOFT}; - al::optional opthrtf; + std::optional opthrtf; int freqAttr{}; #define ATTRIBUTE(a) a: TRACE("%s = %d\n", #a, attrList[attrIdx + 1]); - size_t attrIdx{0}; - while(attrList[attrIdx]) + for(size_t attrIdx{0};attrIdx < attrList.size();attrIdx+=2) { switch(attrList[attrIdx]) { @@ -1894,8 +1230,8 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) case ATTRIBUTE(ALC_MAX_AUXILIARY_SENDS) numSends = static_cast(attrList[attrIdx + 1]); - if(numSends > INT_MAX) numSends = 0; - else numSends = minu(numSends, MAX_SENDS); + if(numSends > uint{std::numeric_limits::max()}) numSends = 0; + else numSends = std::min(numSends, uint{MaxSendCount}); break; case ATTRIBUTE(ALC_HRTF_SOFT) @@ -1904,7 +1240,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) else if(attrList[attrIdx + 1] == ALC_TRUE) opthrtf = true; else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) - opthrtf = al::nullopt; + opthrtf = std::nullopt; break; case ATTRIBUTE(ALC_HRTF_ID_SOFT) @@ -1917,7 +1253,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) else if(attrList[attrIdx + 1] == ALC_TRUE) optlimit = true; else if(attrList[attrIdx + 1] == ALC_DONT_CARE_SOFT) - optlimit = al::nullopt; + optlimit = std::nullopt; break; case ATTRIBUTE(ALC_OUTPUT_MODE_SOFT) @@ -1929,8 +1265,6 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) attrList[attrIdx + 1], attrList[attrIdx + 1]); break; } - - attrIdx += 2; } #undef ATTRIBUTE @@ -1938,7 +1272,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) { if(!optchans || !opttype) return ALC_INVALID_VALUE; - if(freqAttr < MIN_OUTPUT_RATE || freqAttr > MAX_OUTPUT_RATE) + if(freqAttr < int{MinOutputRate} || freqAttr > int{MaxOutputRate}) return ALC_INVALID_VALUE; if(*optchans == DevFmtAmbi3D) { @@ -2015,12 +1349,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(freqAttr) { - uint oldrate = optsrate.value_or(DEFAULT_OUTPUT_RATE); - freqAttr = clampi(freqAttr, MIN_OUTPUT_RATE, MAX_OUTPUT_RATE); + uint oldrate = optsrate.value_or(DefaultOutputRate); + freqAttr = std::clamp(freqAttr, MinOutputRate, MaxOutputRate); const double scale{static_cast(freqAttr) / oldrate}; - period_size = static_cast(period_size*scale + 0.5); - buffer_size = static_cast(buffer_size*scale + 0.5); + period_size = static_cast(std::lround(period_size * scale)); + buffer_size = static_cast(std::lround(buffer_size * scale)); optsrate = static_cast(freqAttr); } } @@ -2028,16 +1362,19 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) /* If a context is already running on the device, stop playback so the * device attributes can be updated. */ - if(device->Flags.test(DeviceRunning)) + if(device->mDeviceState == DeviceState::Playing) + { device->Backend->stop(); - device->Flags.reset(DeviceRunning); + device->mDeviceState = DeviceState::Unprepared; + } UpdateClockBase(device); } - if(device->Flags.test(DeviceRunning)) + if(device->mDeviceState == DeviceState::Playing) return ALC_NO_ERROR; + device->mDeviceState = DeviceState::Unprepared; device->AvgSpeakerDist = 0.0f; device->mNFCtrlFilter = NfcFilter{}; device->mUhjEncoder = nullptr; @@ -2091,14 +1428,14 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->mAmbiOrder = 0; device->BufferSize = buffer_size; device->UpdateSize = period_size; - device->Frequency = optsrate.value_or(DEFAULT_OUTPUT_RATE); + device->Frequency = optsrate.value_or(DefaultOutputRate); device->Flags.set(FrequencyRequest, optsrate.has_value()) .set(ChannelsRequest, optchans.has_value()) .set(SampleTypeRequest, opttype.has_value()); if(device->FmtChans == DevFmtAmbi3D) { - device->mAmbiOrder = clampu(aorder, 1, MaxAmbiOrder); + device->mAmbiOrder = std::clamp(aorder, 1u, uint{MaxAmbiOrder}); device->mAmbiLayout = optlayout.value_or(DevAmbiLayout::Default); device->mAmbiScale = optscale.value_or(DevAmbiScaling::Default); if(device->mAmbiOrder > 3 @@ -2106,11 +1443,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) || device->mAmbiScale == DevAmbiScaling::FuMa)) { ERR("FuMa is incompatible with %d%s order ambisonics (up to 3rd order only)\n", - device->mAmbiOrder, - (((device->mAmbiOrder%100)/10) == 1) ? "th" : - ((device->mAmbiOrder%10) == 1) ? "st" : - ((device->mAmbiOrder%10) == 2) ? "nd" : - ((device->mAmbiOrder%10) == 3) ? "rd" : "th"); + device->mAmbiOrder, GetCounterSuffix(device->mAmbiOrder)); device->mAmbiOrder = 3; } } @@ -2160,15 +1493,14 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(device->Type != DeviceType::Loopback) { - if(auto modeopt = device->configValue(nullptr, "stereo-mode")) + if(auto modeopt = device->configValue({}, "stereo-mode")) { - const char *mode{modeopt->c_str()}; - if(al::strcasecmp(mode, "headphones") == 0) + if(al::case_compare(*modeopt, "headphones"sv) == 0) device->Flags.set(DirectEar); - else if(al::strcasecmp(mode, "speakers") == 0) + else if(al::case_compare(*modeopt, "speakers"sv) == 0) device->Flags.reset(DirectEar); - else if(al::strcasecmp(mode, "auto") != 0) - ERR("Unexpected stereo-mode: %s\n", mode); + else if(al::case_compare(*modeopt, "auto"sv) != 0) + ERR("Unexpected stereo-mode: %s\n", modeopt->c_str()); } } @@ -2177,24 +1509,24 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) /* Calculate the max number of sources, and split them between the mono and * stereo count given the requested number of stereo sources. */ - if(auto srcsopt = device->configValue(nullptr, "sources")) + if(auto srcsopt = device->configValue({}, "sources"sv)) { if(*srcsopt <= 0) numMono = 256; - else numMono = maxu(*srcsopt, 16); + else numMono = std::max(*srcsopt, 16u); } else { - numMono = minu(numMono, INT_MAX-numStereo); - numMono = maxu(numMono+numStereo, 256); + numMono = std::min(numMono, std::numeric_limits::max()-numStereo); + numMono = std::max(numMono+numStereo, 256u); } - numStereo = minu(numStereo, numMono); + numStereo = std::min(numStereo, numMono); numMono -= numStereo; device->SourcesMax = numMono + numStereo; device->NumMonoSources = numMono; device->NumStereoSources = numStereo; - if(auto sendsopt = device->configValue(nullptr, "sends")) - numSends = minu(numSends, static_cast(clampi(*sendsopt, 0, MAX_SENDS))); + if(auto sendsopt = device->configValue({}, "sends"sv)) + numSends = std::min(numSends, std::clamp(*sendsopt, 0u, uint{MaxSendCount})); device->NumAuxSends = numSends; TRACE("Max sources: %d (%d + %d), effect slots: %d, sends: %d\n", @@ -2213,17 +1545,18 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) case DevFmtX61: device->RealOut.RemixMap = X61Downmix; break; case DevFmtX71: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX714: device->RealOut.RemixMap = X71Downmix; break; + case DevFmtX7144: device->RealOut.RemixMap = X71Downmix; break; case DevFmtX3D71: device->RealOut.RemixMap = X51Downmix; break; case DevFmtAmbi3D: break; } - nanoseconds::rep sample_delay{0}; + size_t sample_delay{0}; if(auto *encoder{device->mUhjEncoder.get()}) sample_delay += encoder->getDelay(); - if(device->getConfigValueBool(nullptr, "dither", true)) + if(device->getConfigValueBool({}, "dither"sv, true)) { - int depth{device->configValue(nullptr, "dither-depth").value_or(0)}; + int depth{device->configValue({}, "dither-depth"sv).value_or(0)}; if(depth <= 0) { switch(device->FmtType) @@ -2245,7 +1578,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) if(depth > 0) { - depth = clampi(depth, 2, 24); + depth = std::clamp(depth, 2, 24); device->DitherDepth = std::pow(2.0f, static_cast(depth-1)); } } @@ -2256,7 +1589,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) device->DitherDepth); if(!optlimit) - optlimit = device->configValue(nullptr, "output-limiter"); + optlimit = device->configValue({}, "output-limiter"); /* If the gain limiter is unset, use the limiter for integer-based output * (where samples must be clamped), and don't for floating-point (which can @@ -2278,7 +1611,7 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) break; } } - if(optlimit.value_or(false) == false) + if(!optlimit.value_or(false)) TRACE("Output limiter disabled\n"); else { @@ -2310,11 +1643,12 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) } /* Convert the sample delay from samples to nanosamples to nanoseconds. */ + sample_delay = std::min(sample_delay, std::numeric_limits::max()); device->FixedLatency += nanoseconds{seconds{sample_delay}} / device->Frequency; TRACE("Fixed device latency: %" PRId64 "ns\n", int64_t{device->FixedLatency.count()}); FPUCtl mixer_mode{}; - for(ContextBase *ctxbase : *device->mContexts.load()) + auto reset_context = [device](ContextBase *ctxbase) { auto *context = static_cast(ctxbase); @@ -2322,72 +1656,85 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) std::unique_lock slotlock{context->mEffectSlotLock}; /* Clear out unused effect slot clusters. */ - auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &cluster) + auto slot_cluster_not_in_use = [](ContextBase::EffectSlotCluster &clusterptr) -> bool { - for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) - { - if(cluster[i].InUse) - return false; - } - return true; + return std::none_of(clusterptr->begin(), clusterptr->end(), + std::mem_fn(&EffectSlot::InUse)); }; - auto slotcluster_iter = std::remove_if(context->mEffectSlotClusters.begin(), + auto slotcluster_end = std::remove_if(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), slot_cluster_not_in_use); - context->mEffectSlotClusters.erase(slotcluster_iter, context->mEffectSlotClusters.end()); + context->mEffectSlotClusters.erase(slotcluster_end, context->mEffectSlotClusters.end()); /* Free all wet buffers. Any in use will be reallocated with an updated * configuration in aluInitEffectPanning. */ - for(auto&& slots : context->mEffectSlotClusters) + auto clear_wetbuffers = [](ContextBase::EffectSlotCluster &clusterptr) { - for(size_t i{0};i < ContextBase::EffectSlotClusterSize;++i) + auto clear_buffer = [](EffectSlot &slot) { - slots[i].mWetBuffer.clear(); - slots[i].mWetBuffer.shrink_to_fit(); - slots[i].Wet.Buffer = {}; - } - } + slot.mWetBuffer.clear(); + slot.mWetBuffer.shrink_to_fit(); + slot.Wet.Buffer = {}; + }; + std::for_each(clusterptr->begin(), clusterptr->end(), clear_buffer); + }; + std::for_each(context->mEffectSlotClusters.begin(), context->mEffectSlotClusters.end(), + clear_wetbuffers); if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - aluInitEffectPanning(slot->mSlot, context); + auto *slotbase = slot->mSlot; + aluInitEffectPanning(slotbase, context); + + if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) + AtomicReplaceHead(context->mFreeEffectSlotProps, props); EffectState *state{slot->Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; state->deviceUpdate(device, slot->Buffer); - slot->updateProps(context); + slot->mPropsDirty = true; } if(EffectSlotArray *curarray{context->mActiveAuxSlots.load(std::memory_order_relaxed)}) - std::fill_n(curarray->end(), curarray->size(), nullptr); - for(auto &sublist : context->mEffectSlotList) + std::fill(curarray->begin()+ptrdiff_t(curarray->size()>>1), curarray->end(), nullptr); + auto reset_slots = [device,context](EffectSlotSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; - ALeffectslot *slot{sublist.EffectSlots + idx}; + const auto idx = static_cast(al::countr_zero(usemask)); + auto &slot = (*sublist.EffectSlots)[idx]; usemask &= ~(1_u64 << idx); - aluInitEffectPanning(slot->mSlot, context); + auto *slotbase = slot.mSlot; + aluInitEffectPanning(slotbase, context); - EffectState *state{slot->Effect.State.get()}; + if(auto *props = slotbase->Update.exchange(nullptr, std::memory_order_relaxed)) + AtomicReplaceHead(context->mFreeEffectSlotProps, props); + + EffectState *state{slot.Effect.State.get()}; state->mOutTarget = device->Dry.Buffer; - state->deviceUpdate(device, slot->Buffer); - slot->updateProps(context); + state->deviceUpdate(device, slot.Buffer); + slot.mPropsDirty = true; } - } + }; + std::for_each(context->mEffectSlotList.begin(), context->mEffectSlotList.end(), + reset_slots); + + /* Clear all effect slot props to let them get allocated again. */ + context->mEffectSlotPropClusters.clear(); + context->mFreeEffectSlotProps.store(nullptr, std::memory_order_relaxed); slotlock.unlock(); - const uint num_sends{device->NumAuxSends}; std::unique_lock srclock{context->mSourceLock}; - for(auto &sublist : context->mSourceList) + const uint num_sends{device->NumAuxSends}; + auto reset_sources = [num_sends](SourceSubList &sublist) { uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; - ALsource *source{sublist.Sources + idx}; + const auto idx = static_cast(al::countr_zero(usemask)); + auto &source = (*sublist.Sources)[idx]; usemask &= ~(1_u64 << idx); auto clear_send = [](ALsource::SendData &send) -> void @@ -2397,30 +1744,31 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) send.Slot = nullptr; send.Gain = 1.0f; send.GainHF = 1.0f; - send.HFReference = LOWPASSFREQREF; + send.HFReference = LowPassFreqRef; send.GainLF = 1.0f; - send.LFReference = HIGHPASSFREQREF; + send.LFReference = HighPassFreqRef; }; - auto send_begin = source->Send.begin() + static_cast(num_sends); - std::for_each(send_begin, source->Send.end(), clear_send); + const auto sends = al::span{source.Send}.subspan(num_sends); + std::for_each(sends.begin(), sends.end(), clear_send); - source->mPropsDirty = true; + source.mPropsDirty = true; } - } + }; + std::for_each(context->mSourceList.begin(), context->mSourceList.end(), reset_sources); - auto voicelist = context->getVoicesSpan(); - for(Voice *voice : voicelist) + auto reset_voice = [device,num_sends,context](Voice *voice) { /* Clear extraneous property set sends. */ - std::fill(std::begin(voice->mProps.Send)+num_sends, std::end(voice->mProps.Send), - VoiceProps::SendData{}); + const auto sendparams = al::span{voice->mProps.Send}.subspan(num_sends); + std::fill(sendparams.begin(), sendparams.end(), VoiceProps::SendData{}); std::fill(voice->mSend.begin()+num_sends, voice->mSend.end(), Voice::TargetData{}); - for(auto &chandata : voice->mChans) + auto clear_wetparams = [num_sends](Voice::ChannelData &chandata) { - std::fill(chandata.mWetParams.begin()+num_sends, chandata.mWetParams.end(), - SendParams{}); - } + const auto wetparams = al::span{chandata.mWetParams}.subspan(num_sends); + std::fill(wetparams.begin(), wetparams.end(), SendParams{}); + }; + std::for_each(voice->mChans.begin(), voice->mChans.end(), clear_wetparams); if(VoicePropsItem *props{voice->mUpdate.exchange(nullptr, std::memory_order_relaxed)}) AtomicReplaceHead(context->mFreeVoiceProps, props); @@ -2430,10 +1778,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) voice->mPlayState.compare_exchange_strong(vstate, Voice::Stopped, std::memory_order_acquire, std::memory_order_acquire); if(voice->mSourceID.load(std::memory_order_relaxed) == 0u) - continue; + return; voice->prepare(device); - } + }; + const auto voicespan = context->getVoicesSpan(); + std::for_each(voicespan.begin(), voicespan.end(), reset_voice); + /* Clear all voice props to let them get allocated again. */ context->mVoicePropClusters.clear(); context->mFreeVoiceProps.store(nullptr, std::memory_order_relaxed); @@ -2441,16 +1792,20 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) context->mPropsDirty = false; UpdateContextProps(context); + UpdateAllEffectSlotProps(context); UpdateAllSourceProps(context); - } + }; + auto ctxspan = al::span{*device->mContexts.load()}; + std::for_each(ctxspan.begin(), ctxspan.end(), reset_context); mixer_mode.leave(); + device->mDeviceState = DeviceState::Configured; if(!device->Flags.test(DevicePaused)) { try { auto backend = device->Backend.get(); backend->start(); - device->Flags.set(DeviceRunning); + device->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("%s\n", e.what()); @@ -2469,13 +1824,13 @@ ALCenum UpdateDeviceParams(ALCdevice *device, const int *attrList) * Updates device parameters as above, and also first clears the disconnected * status, if set. */ -bool ResetDeviceParams(ALCdevice *device, const int *attrList) +bool ResetDeviceParams(ALCdevice *device, const al::span attrList) { /* If the device was disconnected, reset it since we're opened anew. */ if(!device->Connected.load(std::memory_order_relaxed)) UNLIKELY { /* Make sure disconnection is finished before continuing on. */ - device->waitForMix(); + std::ignore = device->waitForMix(); for(ContextBase *ctxbase : *device->mContexts.load(std::memory_order_acquire)) { @@ -2486,7 +1841,7 @@ bool ResetDeviceParams(ALCdevice *device, const int *attrList) /* Clear any pending voice changes and reallocate voices to get a * clean restart. */ - std::lock_guard __{ctx->mSourceLock}; + std::lock_guard sourcelock{ctx->mSourceLock}; auto *vchg = ctx->mCurrentVoiceChange.load(std::memory_order_acquire); while(auto *next = vchg->mNext.load(std::memory_order_acquire)) vchg = next; @@ -2514,7 +1869,7 @@ bool ResetDeviceParams(ALCdevice *device, const int *attrList) /** Checks if the device handle is valid, and returns a new reference if so. */ DeviceRef VerifyDevice(ALCdevice *device) { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); if(iter != DeviceList.end() && *iter == device) { @@ -2530,7 +1885,7 @@ DeviceRef VerifyDevice(ALCdevice *device) */ ContextRef VerifyContext(ALCcontext *context) { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); if(iter != ContextList.end() && *iter == context) { @@ -2542,8 +1897,13 @@ ContextRef VerifyContext(ALCcontext *context) } // namespace +FORCE_ALIGN void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept +{ + al_set_log_callback(callback, userptr); +} + /** Returns a new reference to the currently active context for this thread. */ -ContextRef GetContextRef(void) +ContextRef GetContextRef() noexcept { ALCcontext *context{ALCcontext::getThreadContext()}; if(context) @@ -2562,89 +1922,100 @@ ContextRef GetContextRef(void) return ContextRef{context}; } +void alcSetError(ALCdevice *device, ALCenum errorCode) +{ + WARN("Error generated on device %p, code 0x%04x\n", voidp{device}, errorCode); + if(TrapALCError) + { +#ifdef _WIN32 + /* DebugBreak() will cause an exception if there is no debugger */ + if(IsDebuggerPresent()) + DebugBreak(); +#elif defined(SIGTRAP) + raise(SIGTRAP); +#endif + } + + if(device) + device->LastError.store(errorCode); + else + LastNullDeviceError.store(errorCode); +} /************************************************ * Standard ALC functions ************************************************/ -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) -START_API_FUNC +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(dev) return dev->LastError.exchange(ALC_NO_ERROR); return LastNullDeviceError.exchange(ALC_NO_ERROR); } -END_API_FUNC -ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) -START_API_FUNC +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { - if(!SuspendDefers) - return; - ContextRef ctx{VerifyContext(context)}; if(!ctx) - alcSetError(nullptr, ALC_INVALID_CONTEXT); - else { - std::lock_guard _{ctx->mPropLock}; + alcSetError(nullptr, ALC_INVALID_CONTEXT); + return; + } + + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, + "alcSuspendContext behavior is not portable -- some implementations suspend all " + "rendering, some only defer property changes, and some are completely no-op; consider " + "using alcDevicePauseSOFT to suspend all rendering, or alDeferUpdatesSOFT to only " + "defer property changes"); + + if(SuspendDefers) + { + std::lock_guard proplock{ctx->mPropLock}; ctx->deferUpdates(); } } -END_API_FUNC -ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) -START_API_FUNC +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { - if(!SuspendDefers) - return; - ContextRef ctx{VerifyContext(context)}; if(!ctx) - alcSetError(nullptr, ALC_INVALID_CONTEXT); - else { - std::lock_guard _{ctx->mPropLock}; + alcSetError(nullptr, ALC_INVALID_CONTEXT); + return; + } + + if(context->mContextFlags.test(ContextFlags::DebugBit)) UNLIKELY + ctx->debugMessage(DebugSource::API, DebugType::Portability, 0, DebugSeverity::Medium, + "alcProcessContext behavior is not portable -- some implementations resume rendering, " + "some apply deferred property changes, and some are completely no-op; consider using " + "alcDeviceResumeSOFT to resume rendering, or alProcessUpdatesSOFT to apply deferred " + "property changes"); + + if(SuspendDefers) + { + std::lock_guard proplock{ctx->mPropLock}; ctx->processUpdates(); } } -END_API_FUNC -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) -START_API_FUNC +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *Device, ALCenum param) noexcept { const ALCchar *value{nullptr}; switch(param) { - case ALC_NO_ERROR: - value = alcNoError; - break; - - case ALC_INVALID_ENUM: - value = alcErrInvalidEnum; - break; - - case ALC_INVALID_VALUE: - value = alcErrInvalidValue; - break; - - case ALC_INVALID_DEVICE: - value = alcErrInvalidDevice; - break; - - case ALC_INVALID_CONTEXT: - value = alcErrInvalidContext; - break; - - case ALC_OUT_OF_MEMORY: - value = alcErrOutOfMemory; - break; + case ALC_NO_ERROR: value = GetNoErrorString(); break; + case ALC_INVALID_ENUM: value = GetInvalidEnumString(); break; + case ALC_INVALID_VALUE: value = GetInvalidValueString(); break; + case ALC_INVALID_DEVICE: value = GetInvalidDeviceString(); break; + case ALC_INVALID_CONTEXT: value = GetInvalidContextString(); break; + case ALC_OUT_OF_MEMORY: value = GetOutOfMemoryString(); break; case ALC_DEVICE_SPECIFIER: - value = alcDefaultName; + value = GetDefaultName(); break; case ALC_ALL_DEVICES_SPECIFIER: @@ -2653,10 +2024,10 @@ START_API_FUNC if(dev->Type == DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_ENUM); else if(dev->Type == DeviceType::Loopback) - value = alcDefaultName; + value = GetDefaultName(); else { - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; value = dev->DeviceName.c_str(); } } @@ -2674,7 +2045,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_ENUM); else { - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; value = dev->DeviceName.c_str(); } } @@ -2687,7 +2058,7 @@ START_API_FUNC /* Default devices are always first in the list */ case ALC_DEFAULT_DEVICE_SPECIFIER: - value = alcDefaultName; + value = GetDefaultName(); break; case ALC_DEFAULT_ALL_DEVICES_SPECIFIER: @@ -2695,8 +2066,13 @@ START_API_FUNC ProbeAllDevicesList(); /* Copy first entry as default. */ - alcDefaultAllDevicesSpecifier = alcAllDevicesList.c_str(); - value = alcDefaultAllDevicesSpecifier.c_str(); + if(alcAllDevicesArray.empty()) + value = GetDefaultName(); + else + { + alcDefaultAllDevicesSpecifier = alcAllDevicesArray.front(); + value = alcDefaultAllDevicesSpecifier.c_str(); + } break; case ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER: @@ -2704,21 +2080,26 @@ START_API_FUNC ProbeCaptureDeviceList(); /* Copy first entry as default. */ - alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceList.c_str(); - value = alcCaptureDefaultDeviceSpecifier.c_str(); + if(alcCaptureDeviceArray.empty()) + value = GetDefaultName(); + else + { + alcCaptureDefaultDeviceSpecifier = alcCaptureDeviceArray.front(); + value = alcCaptureDefaultDeviceSpecifier.c_str(); + } break; case ALC_EXTENSIONS: if(VerifyDevice(Device)) - value = alcExtensionList; + value = GetExtensionList().data(); else - value = alcNoDeviceExtList; + value = GetNoDeviceExtList().data(); break; case ALC_HRTF_SPECIFIER_SOFT: if(DeviceRef dev{VerifyDevice(Device)}) { - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; value = (dev->mHrtf ? dev->mHrtfName.c_str() : ""); } else @@ -2732,13 +2113,10 @@ START_API_FUNC return value; } -END_API_FUNC static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values) { - size_t i; - if(values.empty()) { alcSetError(device, ALC_INVALID_VALUE); @@ -2763,7 +2141,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[0] = alcEFXMinorVersion; return 1; case ALC_MAX_AUXILIARY_SENDS: - values[0] = MAX_SENDS; + values[0] = MaxSendCount; return 1; case ALC_ATTRIBUTES_SIZE: @@ -2789,7 +2167,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 0; } - std::lock_guard _{device->StateLock}; + std::lock_guard statelock{device->StateLock}; if(device->Type == DeviceType::Capture) { static constexpr int MaxCaptureAttributes{9}; @@ -2799,11 +2177,9 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[0] = MaxCaptureAttributes; return 1; case ALC_ALL_ATTRIBUTES: - i = 0; - if(values.size() < MaxCaptureAttributes) - alcSetError(device, ALC_INVALID_VALUE); - else + if(values.size() >= MaxCaptureAttributes) { + size_t i{0}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2814,8 +2190,10 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = device->Connected.load(std::memory_order_relaxed); values[i++] = 0; assert(i == MaxCaptureAttributes); + return i; } - return i; + alcSetError(device, ALC_INVALID_VALUE); + return 0; case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; @@ -2839,7 +2217,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span } /* render device */ - auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + auto NumAttrsForDevice = [](const ALCdevice *aldev) noexcept -> uint8_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 37; @@ -2852,11 +2230,9 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 1; case ALC_ALL_ATTRIBUTES: - i = 0; - if(values.size() < static_cast(NumAttrsForDevice(device))) - alcSetError(device, ALC_INVALID_VALUE); - else + if(values.size() >= NumAttrsForDevice(device)) { + size_t i{0}; values[i++] = ALC_MAJOR_VERSION; values[i++] = alcMajorVersion; values[i++] = ALC_MINOR_VERSION; @@ -2922,8 +2298,11 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span values[i++] = static_cast(device->getOutputMode1()); values[i++] = 0; + assert(i == NumAttrsForDevice(device)); + return i; } - return i; + alcSetError(device, ALC_INVALID_VALUE); + return 0; case ALC_MAJOR_VERSION: values[0] = alcMajorVersion; @@ -3034,8 +2413,8 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span case ALC_NUM_HRTF_SPECIFIERS_SOFT: device->enumerateHrtfs(); - values[0] = static_cast(minz(device->mHrtfList.size(), - std::numeric_limits::max())); + values[0] = static_cast(std::min(device->mHrtfList.size(), + size_t{std::numeric_limits::max()})); return 1; case ALC_OUTPUT_LIMITER_SOFT: @@ -3056,8 +2435,7 @@ static size_t GetIntegerv(ALCdevice *device, ALCenum param, const al::span return 0; } -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) -START_API_FUNC +ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) @@ -3065,10 +2443,8 @@ START_API_FUNC else GetIntegerv(dev.get(), param, {values, static_cast(size)}); } -END_API_FUNC -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) -START_API_FUNC +ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALCsizei size, ALCint64SOFT *values) noexcept { DeviceRef dev{VerifyDevice(device)}; if(size <= 0 || values == nullptr) @@ -3076,94 +2452,95 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return; } + const auto valuespan = al::span{values, static_cast(size)}; if(!dev || dev->Type == DeviceType::Capture) { - auto ivals = al::vector(static_cast(size)); + auto ivals = std::vector(valuespan.size()); if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) - std::copy_n(ivals.begin(), got, values); + std::copy_n(ivals.cbegin(), got, valuespan.begin()); return; } /* render device */ - auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept + auto NumAttrsForDevice = [](ALCdevice *aldev) noexcept -> size_t { if(aldev->Type == DeviceType::Loopback && aldev->FmtChans == DevFmtAmbi3D) return 41; return 35; }; - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; switch(pname) { case ALC_ATTRIBUTES_SIZE: - *values = NumAttrsForDevice(dev.get()); + valuespan[0] = static_cast(NumAttrsForDevice(dev.get())); break; case ALC_ALL_ATTRIBUTES: - if(size < NumAttrsForDevice(dev.get())) + if(valuespan.size() < NumAttrsForDevice(dev.get())) alcSetError(dev.get(), ALC_INVALID_VALUE); else { size_t i{0}; - values[i++] = ALC_FREQUENCY; - values[i++] = dev->Frequency; + valuespan[i++] = ALC_FREQUENCY; + valuespan[i++] = dev->Frequency; if(dev->Type != DeviceType::Loopback) { - values[i++] = ALC_REFRESH; - values[i++] = dev->Frequency / dev->UpdateSize; + valuespan[i++] = ALC_REFRESH; + valuespan[i++] = dev->Frequency / dev->UpdateSize; - values[i++] = ALC_SYNC; - values[i++] = ALC_FALSE; + valuespan[i++] = ALC_SYNC; + valuespan[i++] = ALC_FALSE; } else { - values[i++] = ALC_FORMAT_CHANNELS_SOFT; - values[i++] = EnumFromDevFmt(dev->FmtChans); + valuespan[i++] = ALC_FORMAT_CHANNELS_SOFT; + valuespan[i++] = EnumFromDevFmt(dev->FmtChans); - values[i++] = ALC_FORMAT_TYPE_SOFT; - values[i++] = EnumFromDevFmt(dev->FmtType); + valuespan[i++] = ALC_FORMAT_TYPE_SOFT; + valuespan[i++] = EnumFromDevFmt(dev->FmtType); if(dev->FmtChans == DevFmtAmbi3D) { - values[i++] = ALC_AMBISONIC_LAYOUT_SOFT; - values[i++] = EnumFromDevAmbi(dev->mAmbiLayout); + valuespan[i++] = ALC_AMBISONIC_LAYOUT_SOFT; + valuespan[i++] = EnumFromDevAmbi(dev->mAmbiLayout); - values[i++] = ALC_AMBISONIC_SCALING_SOFT; - values[i++] = EnumFromDevAmbi(dev->mAmbiScale); + valuespan[i++] = ALC_AMBISONIC_SCALING_SOFT; + valuespan[i++] = EnumFromDevAmbi(dev->mAmbiScale); - values[i++] = ALC_AMBISONIC_ORDER_SOFT; - values[i++] = dev->mAmbiOrder; + valuespan[i++] = ALC_AMBISONIC_ORDER_SOFT; + valuespan[i++] = dev->mAmbiOrder; } } - values[i++] = ALC_MONO_SOURCES; - values[i++] = dev->NumMonoSources; + valuespan[i++] = ALC_MONO_SOURCES; + valuespan[i++] = dev->NumMonoSources; - values[i++] = ALC_STEREO_SOURCES; - values[i++] = dev->NumStereoSources; + valuespan[i++] = ALC_STEREO_SOURCES; + valuespan[i++] = dev->NumStereoSources; - values[i++] = ALC_MAX_AUXILIARY_SENDS; - values[i++] = dev->NumAuxSends; + valuespan[i++] = ALC_MAX_AUXILIARY_SENDS; + valuespan[i++] = dev->NumAuxSends; - values[i++] = ALC_HRTF_SOFT; - values[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); + valuespan[i++] = ALC_HRTF_SOFT; + valuespan[i++] = (dev->mHrtf ? ALC_TRUE : ALC_FALSE); - values[i++] = ALC_HRTF_STATUS_SOFT; - values[i++] = dev->mHrtfStatus; + valuespan[i++] = ALC_HRTF_STATUS_SOFT; + valuespan[i++] = dev->mHrtfStatus; - values[i++] = ALC_OUTPUT_LIMITER_SOFT; - values[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; + valuespan[i++] = ALC_OUTPUT_LIMITER_SOFT; + valuespan[i++] = dev->Limiter ? ALC_TRUE : ALC_FALSE; ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; - values[i++] = ALC_DEVICE_CLOCK_SOFT; - values[i++] = clock.ClockTime.count(); + valuespan[i++] = ALC_DEVICE_CLOCK_SOFT; + valuespan[i++] = clock.ClockTime.count(); - values[i++] = ALC_DEVICE_LATENCY_SOFT; - values[i++] = clock.Latency.count(); + valuespan[i++] = ALC_DEVICE_LATENCY_SOFT; + valuespan[i++] = clock.Latency.count(); - values[i++] = ALC_OUTPUT_MODE_SOFT; - values[i++] = static_cast(device->getOutputMode1()); + valuespan[i++] = ALC_OUTPUT_MODE_SOFT; + valuespan[i++] = al::to_underlying(device->getOutputMode1()); - values[i++] = 0; + valuespan[i++] = 0; } break; @@ -3173,16 +2550,17 @@ START_API_FUNC nanoseconds basecount; do { refcount = dev->waitForMix(); - basecount = dev->ClockBase; - samplecount = dev->SamplesDone; - } while(refcount != ReadRef(dev->MixCount)); + basecount = dev->mClockBase.load(std::memory_order_relaxed); + samplecount = dev->mSamplesDone.load(std::memory_order_relaxed); + std::atomic_thread_fence(std::memory_order_acquire); + } while(refcount != dev->mMixCount.load(std::memory_order_relaxed)); basecount += nanoseconds{seconds{samplecount}} / dev->Frequency; - *values = basecount.count(); + valuespan[0] = basecount.count(); } break; case ALC_DEVICE_LATENCY_SOFT: - *values = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); + valuespan[0] = GetClockLatency(dev.get(), dev->Backend.get()).Latency.count(); break; case ALC_DEVICE_CLOCK_LATENCY_SOFT: @@ -3191,51 +2569,48 @@ START_API_FUNC else { ClockLatency clock{GetClockLatency(dev.get(), dev->Backend.get())}; - values[0] = clock.ClockTime.count(); - values[1] = clock.Latency.count(); + valuespan[0] = clock.ClockTime.count(); + valuespan[1] = clock.Latency.count(); } break; default: - auto ivals = al::vector(static_cast(size)); + auto ivals = std::vector(valuespan.size()); if(size_t got{GetIntegerv(dev.get(), pname, ivals)}) - std::copy_n(ivals.begin(), got, values); + std::copy_n(ivals.cbegin(), got, valuespan.begin()); break; } } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extName) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!extName) - alcSetError(dev.get(), ALC_INVALID_VALUE); - else { - size_t len = strlen(extName); - const char *ptr = (dev ? alcExtensionList : alcNoDeviceExtList); - while(ptr && *ptr) - { - if(al::strncasecmp(ptr, extName, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) - return ALC_TRUE; + alcSetError(dev.get(), ALC_INVALID_VALUE); + return ALC_FALSE; + } - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } - } + const std::string_view tofind{extName}; + const auto extlist = dev ? GetExtensionList() : GetNoDeviceExtList(); + auto matchpos = extlist.find(tofind); + while(matchpos != std::string_view::npos) + { + const auto endpos = matchpos + tofind.size(); + if((matchpos == 0 || std::isspace(extlist[matchpos-1])) + && (endpos == extlist.size() || std::isspace(extlist[endpos]))) + return ALC_TRUE; + matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } -END_API_FUNC -ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) -START_API_FUNC +ALCvoid* ALC_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALCchar *funcName) noexcept +{ return alcGetProcAddress(device, funcName); } + +ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcName) noexcept { if(!funcName) { @@ -3243,6 +2618,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return nullptr; } + #ifdef ALSOFT_EAX if(eax_g_is_enabled) { @@ -3260,11 +2636,9 @@ START_API_FUNC } return nullptr; } -END_API_FUNC -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) -START_API_FUNC +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumName) noexcept { if(!enumName) { @@ -3272,6 +2646,7 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_VALUE); return 0; } + #ifdef ALSOFT_EAX if(eax_g_is_enabled) { @@ -3290,11 +2665,9 @@ START_API_FUNC return 0; } -END_API_FUNC -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) -START_API_FUNC +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrList) noexcept { /* Explicitly hold the list lock while taking the StateLock in case the * device is asynchronously destroyed, to ensure this new context is @@ -3313,24 +2686,40 @@ START_API_FUNC dev->LastError.store(ALC_NO_ERROR); - ALCenum err{UpdateDeviceParams(dev.get(), attrList)}; + const auto attrSpan = SpanFromAttributeList(attrList); + ALCenum err{UpdateDeviceParams(dev.get(), attrSpan)}; if(err != ALC_NO_ERROR) { alcSetError(dev.get(), err); return nullptr; } - ContextRef context{new ALCcontext{dev}}; + ContextFlagBitset ctxflags{0}; + for(size_t i{0};i < attrSpan.size();i+=2) + { + if(attrSpan[i] == ALC_CONTEXT_FLAGS_EXT) + { + ctxflags = static_cast(attrSpan[i+1]); + break; + } + } + + auto context = ContextRef{new(std::nothrow) ALCcontext{dev, ctxflags}}; + if(!context) + { + alcSetError(dev.get(), ALC_OUT_OF_MEMORY); + return nullptr; + } context->init(); - if(auto volopt = dev->configValue(nullptr, "volume-adjust")) + if(auto volopt = dev->configValue({}, "volume-adjust")) { const float valf{*volopt}; if(!std::isfinite(valf)) ERR("volume-adjust must be finite: %f\n", valf); else { - const float db{clampf(valf, -24.0f, 24.0f)}; + const float db{std::clamp(valf, -24.0f, 24.0f)}; if(db != valf) WARN("volume-adjust clamped: %f, range: +/-%f\n", valf, 24.0f); context->mGainBoost = std::pow(10.0f, db/20.0f); @@ -3345,8 +2734,7 @@ START_API_FUNC * old array. */ auto *oldarray = device->mContexts.load(); - const size_t newcount{oldarray->size()+1}; - std::unique_ptr newarray{ContextArray::Create(newcount)}; + auto newarray = ContextArray::Create(oldarray->size() + 1); /* Copy the current/old context handles to the new array, appending the * new context. @@ -3357,24 +2745,21 @@ START_API_FUNC /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ - dev->mContexts.store(newarray.release()); - if(oldarray != &DeviceBase::sEmptyContextArray) - { - dev->waitForMix(); - delete oldarray; - } + auto prevarray = dev->mContexts.exchange(std::move(newarray)); + std::ignore = dev->waitForMix(); } statelock.unlock(); { - std::lock_guard _{ListLock}; + listlock.lock(); auto iter = std::lower_bound(ContextList.cbegin(), ContextList.cend(), context.get()); ContextList.emplace(iter, context.get()); + listlock.unlock(); } if(ALeffectslot *slot{context->mDefaultSlot.get()}) { - ALenum sloterr{slot->initEffect(ALCcontext::sDefaultEffect.type, + ALenum sloterr{slot->initEffect(0, ALCcontext::sDefaultEffect.type, ALCcontext::sDefaultEffect.Props, context.get())}; if(sloterr == AL_NO_ERROR) slot->updateProps(context.get()); @@ -3385,10 +2770,8 @@ START_API_FUNC TRACE("Created context %p\n", voidp{context.get()}); return context.release(); } -END_API_FUNC -ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) -START_API_FUNC +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(ContextList.begin(), ContextList.end(), context); @@ -3407,33 +2790,23 @@ START_API_FUNC ALCdevice *Device{ctx->mALDevice.get()}; - std::lock_guard _{Device->StateLock}; - if(!ctx->deinit() && Device->Flags.test(DeviceRunning)) - { - Device->Backend->stop(); - Device->Flags.reset(DeviceRunning); - } + std::lock_guard statelock{Device->StateLock}; + ctx->deinit(); } -END_API_FUNC -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) -START_API_FUNC +ALC_API auto ALC_APIENTRY alcGetCurrentContext() noexcept -> ALCcontext* { ALCcontext *Context{ALCcontext::getThreadContext()}; if(!Context) Context = ALCcontext::sGlobalContext.load(); return Context; } -END_API_FUNC /** Returns the currently active thread-local context. */ -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) -START_API_FUNC +ALC_API auto ALC_APIENTRY alcGetThreadContext() noexcept -> ALCcontext* { return ALCcontext::getThreadContext(); } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { /* context must be valid or nullptr */ ContextRef ctx; @@ -3455,7 +2828,7 @@ START_API_FUNC * the current context as its refcount is decremented. */ } - ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; + ctx = ContextRef{ALCcontext::sGlobalContext.exchange(ctx.release())}; ALCcontext::sGlobalContextLock.store(false, std::memory_order_release); /* Take ownership of the thread-local context reference (if any), clearing @@ -3467,11 +2840,9 @@ START_API_FUNC return ALC_TRUE; } -END_API_FUNC /** Makes the given context the active context for the current thread. */ -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { /* context must be valid or nullptr */ ContextRef ctx; @@ -3490,11 +2861,9 @@ START_API_FUNC return ALC_TRUE; } -END_API_FUNC -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *Context) noexcept { ContextRef ctx{VerifyContext(Context)}; if(!ctx) @@ -3504,11 +2873,9 @@ START_API_FUNC } return ctx->mALDevice.get(); } -END_API_FUNC -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *deviceName) noexcept { InitConfig(); @@ -3518,26 +2885,27 @@ START_API_FUNC return nullptr; } - if(deviceName) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - TRACE("Opening playback device \"%s\"\n", deviceName); - if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 + TRACE("Opening playback device \"%.*s\"\n", al::sizei(devname), devname.data()); + if(al::case_compare(devname, GetDefaultName()) == 0 #ifdef _WIN32 /* Some old Windows apps hardcode these expecting OpenAL to use a * specific audio API, even when they're not enumerated. Creative's * router effectively ignores them too. */ - || al::strcasecmp(deviceName, "DirectSound3D") == 0 - || al::strcasecmp(deviceName, "DirectSound") == 0 - || al::strcasecmp(deviceName, "MMSYSTEM") == 0 + || al::case_compare(devname, "DirectSound3D"sv) == 0 + || al::case_compare(devname, "DirectSound"sv) == 0 + || al::case_compare(devname, "MMSYSTEM"sv) == 0 #endif /* Some old Linux apps hardcode configuration strings that were * supported by the OpenAL SI. We can't really do anything useful * with them, so just ignore. */ - || (deviceName[0] == '\'' && deviceName[1] == '(') - || al::strcasecmp(deviceName, "openal-soft") == 0) - deviceName = nullptr; + || al::starts_with(devname, "'("sv) + || al::case_compare(devname, "openal-soft"sv) == 0) + devname = {}; } else TRACE("Opening default playback device\n"); @@ -3546,17 +2914,23 @@ START_API_FUNC #ifdef ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX - DEFAULT_SENDS + uint{DefaultSendCount} }; - DeviceRef device{new ALCdevice{DeviceType::Playback}}; + DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Playback}}; + if(!device) + { + WARN("Failed to create playback device handle\n"); + alcSetError(nullptr, ALC_OUT_OF_MEMORY); + return nullptr; + } /* Set output format */ device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; - device->Frequency = DEFAULT_OUTPUT_RATE; - device->UpdateSize = DEFAULT_UPDATE_SIZE; - device->BufferSize = DEFAULT_UPDATE_SIZE * DEFAULT_NUM_UPDATES; + device->Frequency = DefaultOutputRate; + device->UpdateSize = DefaultUpdateSize; + device->BufferSize = DefaultUpdateSize * DefaultNumUpdates; device->SourcesMax = 256; device->NumStereoSources = 1; @@ -3566,8 +2940,8 @@ START_API_FUNC try { auto backend = PlaybackFactory->createBackend(device.get(), BackendType::Playback); - std::lock_guard _{ListLock}; - backend->open(deviceName); + std::lock_guard listlock{ListLock}; + backend->open(devname); device->Backend = std::move(backend); } catch(al::backend_exception &e) { @@ -3578,7 +2952,7 @@ START_API_FUNC } { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } @@ -3586,10 +2960,8 @@ START_API_FUNC TRACE("Created device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); return device.release(); } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); @@ -3611,13 +2983,13 @@ START_API_FUNC DeviceList.erase(iter); std::unique_lock statelock{dev->StateLock}; - al::vector orphanctxs; + std::vector orphanctxs; for(ContextBase *ctx : *dev->mContexts.load()) { auto ctxiter = std::lower_bound(ContextList.begin(), ContextList.end(), ctx); if(ctxiter != ContextList.end() && *ctxiter == ctx) { - orphanctxs.emplace_back(ContextRef{*ctxiter}); + orphanctxs.emplace_back(*ctxiter); ContextList.erase(ctxiter); } } @@ -3630,20 +3002,20 @@ START_API_FUNC } orphanctxs.clear(); - if(dev->Flags.test(DeviceRunning)) + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } return ALC_TRUE; } -END_API_FUNC /************************************************ * ALC capture functions ************************************************/ -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *deviceName, ALCuint frequency, ALCenum format, ALCsizei samples) noexcept { InitConfig(); @@ -3659,17 +3031,24 @@ START_API_FUNC return nullptr; } - if(deviceName) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - TRACE("Opening capture device \"%s\"\n", deviceName); - if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0 - || al::strcasecmp(deviceName, "openal-soft") == 0) - deviceName = nullptr; + TRACE("Opening capture device \"%.*s\"\n", al::sizei(devname), devname.data()); + if(al::case_compare(devname, GetDefaultName()) == 0 + || al::case_compare(devname, "openal-soft"sv) == 0) + devname = {}; } else TRACE("Opening default capture device\n"); - DeviceRef device{new ALCdevice{DeviceType::Capture}}; + DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Capture}}; + if(!device) + { + WARN("Failed to create capture device handle\n"); + alcSetError(nullptr, ALC_OUT_OF_MEMORY); + return nullptr; + } auto decompfmt = DecomposeDevFormat(format); if(!decompfmt) @@ -3688,14 +3067,14 @@ START_API_FUNC device->UpdateSize = static_cast(samples); device->BufferSize = static_cast(samples); - try { - TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", - DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + TRACE("Capture format: %s, %s, %uhz, %u / %u buffer\n", DevFmtChannelsString(device->FmtChans), + DevFmtTypeString(device->FmtType), device->Frequency, device->UpdateSize, + device->BufferSize); + try { auto backend = CaptureFactory->createBackend(device.get(), BackendType::Capture); - std::lock_guard _{ListLock}; - backend->open(deviceName); + std::lock_guard listlock{ListLock}; + backend->open(devname); device->Backend = std::move(backend); } catch(al::backend_exception &e) { @@ -3706,18 +3085,17 @@ START_API_FUNC } { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } + device->mDeviceState = DeviceState::Configured; TRACE("Created capture device %p, \"%s\"\n", voidp{device.get()}, device->DeviceName.c_str()); return device.release(); } -END_API_FUNC -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { std::unique_lock listlock{ListLock}; auto iter = std::lower_bound(DeviceList.begin(), DeviceList.end(), device); @@ -3736,17 +3114,17 @@ START_API_FUNC DeviceList.erase(iter); listlock.unlock(); - std::lock_guard _{dev->StateLock}; - if(dev->Flags.test(DeviceRunning)) + std::lock_guard statelock{dev->StateLock}; + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } return ALC_TRUE; } -END_API_FUNC -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) @@ -3755,15 +3133,16 @@ START_API_FUNC return; } - std::lock_guard _{dev->StateLock}; - if(!dev->Connected.load(std::memory_order_acquire)) + std::lock_guard statelock{dev->StateLock}; + if(!dev->Connected.load(std::memory_order_acquire) + || dev->mDeviceState < DeviceState::Configured) alcSetError(dev.get(), ALC_INVALID_DEVICE); - else if(!dev->Flags.test(DeviceRunning)) + else if(dev->mDeviceState != DeviceState::Playing) { try { auto backend = dev->Backend.get(); backend->start(); - dev->Flags.set(DeviceRunning); + dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("%s\n", e.what()); @@ -3772,26 +3151,24 @@ START_API_FUNC } } } -END_API_FUNC -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { - std::lock_guard _{dev->StateLock}; - if(dev->Flags.test(DeviceRunning)) + std::lock_guard statelock{dev->StateLock}; + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } } } -END_API_FUNC -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -START_API_FUNC +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Capture) @@ -3808,7 +3185,7 @@ START_API_FUNC if(samples < 1) return; - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; BackendBase *backend{dev->Backend.get()}; const auto usamples = static_cast(samples); @@ -3818,9 +3195,8 @@ START_API_FUNC return; } - backend->captureSamples(static_cast(buffer), usamples); + backend->captureSamples(static_cast(buffer), usamples); } -END_API_FUNC /************************************************ @@ -3828,13 +3204,12 @@ END_API_FUNC ************************************************/ /** Open a loopback device, for manual rendering. */ -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) -START_API_FUNC +ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) noexcept { InitConfig(); /* Make sure the device name, if specified, is us. */ - if(deviceName && strcmp(deviceName, alcDefaultName) != 0) + if(deviceName && strcmp(deviceName, GetDefaultName()) != 0) { alcSetError(nullptr, ALC_INVALID_VALUE); return nullptr; @@ -3844,10 +3219,16 @@ START_API_FUNC #ifdef ALSOFT_EAX eax_g_is_enabled ? uint{EAX_MAX_FXSLOTS} : #endif // ALSOFT_EAX - DEFAULT_SENDS + uint{DefaultSendCount} }; - DeviceRef device{new ALCdevice{DeviceType::Loopback}}; + DeviceRef device{new(std::nothrow) ALCdevice{DeviceType::Loopback}}; + if(!device) + { + WARN("Failed to create loopback device handle\n"); + alcSetError(nullptr, ALC_OUT_OF_MEMORY); + return nullptr; + } device->SourcesMax = 256; device->AuxiliaryEffectSlotMax = 64; @@ -3857,7 +3238,7 @@ START_API_FUNC device->BufferSize = 0; device->UpdateSize = 0; - device->Frequency = DEFAULT_OUTPUT_RATE; + device->Frequency = DefaultOutputRate; device->FmtChans = DevFmtChannelsDefault; device->FmtType = DevFmtTypeDefault; @@ -3878,7 +3259,7 @@ START_API_FUNC } { - std::lock_guard _{ListLock}; + std::lock_guard listlock{ListLock}; auto iter = std::lower_bound(DeviceList.cbegin(), DeviceList.cend(), device.get()); DeviceList.emplace(iter, device.get()); } @@ -3886,13 +3267,11 @@ START_API_FUNC TRACE("Created loopback device %p\n", voidp{device.get()}); return device.release(); } -END_API_FUNC /** * Determines if the loopback device supports the given format for rendering. */ -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Loopback) @@ -3902,29 +3281,32 @@ START_API_FUNC else { if(DevFmtTypeFromEnum(type).has_value() && DevFmtChannelsFromEnum(channels).has_value() - && freq >= MIN_OUTPUT_RATE && freq <= MAX_OUTPUT_RATE) + && freq >= int{MinOutputRate} && freq <= int{MaxOutputRate}) return ALC_TRUE; } return ALC_FALSE; } -END_API_FUNC /** * Renders some samples into a buffer, using the format last set by the * attributes given to alcCreateContext. */ -FORCE_ALIGN ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) -START_API_FUNC +#if defined(__GNUC__) && defined(__i386__) +/* Needed on x86-32 even without SSE codegen, since the mixer may still use SSE + * and GCC assumes the stack is aligned (x86-64 ABI guarantees alignment). + */ +[[gnu::force_align_arg_pointer]] +#endif +ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(!device || device->Type != DeviceType::Loopback) + if(!device || device->Type != DeviceType::Loopback) UNLIKELY alcSetError(device, ALC_INVALID_DEVICE); - else if(samples < 0 || (samples > 0 && buffer == nullptr)) + else if(samples < 0 || (samples > 0 && buffer == nullptr)) UNLIKELY alcSetError(device, ALC_INVALID_VALUE); else device->renderSamples(buffer, static_cast(samples), device->channelsFromFmt()); } -END_API_FUNC /************************************************ @@ -3932,26 +3314,25 @@ END_API_FUNC ************************************************/ /** Pause the DSP to stop audio processing. */ -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) alcSetError(dev.get(), ALC_INVALID_DEVICE); else { - std::lock_guard _{dev->StateLock}; - if(dev->Flags.test(DeviceRunning)) + std::lock_guard statelock{dev->StateLock}; + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } dev->Flags.set(DevicePaused); } } -END_API_FUNC /** Resume the DSP to restart audio processing. */ -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) -START_API_FUNC +ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) @@ -3960,9 +3341,21 @@ START_API_FUNC return; } - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; if(!dev->Flags.test(DevicePaused)) return; + if(dev->mDeviceState < DeviceState::Configured) + { + WARN("Cannot resume unconfigured device\n"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return; + } + if(!dev->Connected.load()) + { + WARN("Cannot resume a disconnected device\n"); + alcSetError(dev.get(), ALC_INVALID_DEVICE); + return; + } dev->Flags.reset(DevicePaused); if(dev->mContexts.load()->empty()) return; @@ -3970,7 +3363,7 @@ START_API_FUNC try { auto backend = dev->Backend.get(); backend->start(); - dev->Flags.set(DeviceRunning); + dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception& e) { ERR("%s\n", e.what()); @@ -3979,10 +3372,9 @@ START_API_FUNC return; } TRACE("Post-resume: %s, %s, %uhz, %u / %u buffer\n", - DevFmtChannelsString(device->FmtChans), DevFmtTypeString(device->FmtType), - device->Frequency, device->UpdateSize, device->BufferSize); + DevFmtChannelsString(dev->FmtChans), DevFmtTypeString(dev->FmtType), + dev->Frequency, dev->UpdateSize, dev->BufferSize); } -END_API_FUNC /************************************************ @@ -3990,8 +3382,7 @@ END_API_FUNC ************************************************/ /** Gets a string parameter at the given index. */ -ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) -START_API_FUNC +ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) noexcept { DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type == DeviceType::Capture) @@ -4011,11 +3402,9 @@ START_API_FUNC return nullptr; } -END_API_FUNC /** Resets the given device output, using the specified attribute list. */ -ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) -START_API_FUNC +ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) noexcept { std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; @@ -4025,19 +3414,20 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_DEVICE); return ALC_FALSE; } - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; listlock.unlock(); /* Force the backend to stop mixing first since we're resetting. Also reset * the connected state so lost devices can attempt recover. */ - if(dev->Flags.test(DeviceRunning)) + if(dev->mDeviceState == DeviceState::Playing) + { dev->Backend->stop(); - dev->Flags.reset(DeviceRunning); + dev->mDeviceState = DeviceState::Configured; + } - return ResetDeviceParams(dev.get(), attribs) ? ALC_TRUE : ALC_FALSE; + return ResetDeviceParams(dev.get(), SpanFromAttributeList(attribs)) ? ALC_TRUE : ALC_FALSE; } -END_API_FUNC /************************************************ @@ -4046,15 +3436,8 @@ END_API_FUNC /** Reopens the given device output, using the specified name and attribute list. */ FORCE_ALIGN ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, - const ALCchar *deviceName, const ALCint *attribs) -START_API_FUNC + const ALCchar *deviceName, const ALCint *attribs) noexcept { - if(deviceName) - { - if(!deviceName[0] || al::strcasecmp(deviceName, alcDefaultName) == 0) - deviceName = nullptr; - } - std::unique_lock listlock{ListLock}; DeviceRef dev{VerifyDevice(device)}; if(!dev || dev->Type != DeviceType::Playback) @@ -4063,20 +3446,34 @@ START_API_FUNC alcSetError(dev.get(), ALC_INVALID_DEVICE); return ALC_FALSE; } - std::lock_guard _{dev->StateLock}; + std::lock_guard statelock{dev->StateLock}; - /* Force the backend to stop mixing first since we're reopening. */ - if(dev->Flags.test(DeviceRunning)) + std::string_view devname{deviceName ? deviceName : ""}; + if(!devname.empty()) { - auto backend = dev->Backend.get(); - backend->stop(); - dev->Flags.reset(DeviceRunning); + if(devname.length() >= size_t{std::numeric_limits::max()}) + { + ERR("Device name too long (%zu >= %d)\n", devname.length(), + std::numeric_limits::max()); + alcSetError(dev.get(), ALC_INVALID_VALUE); + return ALC_FALSE; + } + if(al::case_compare(devname, GetDefaultName()) == 0) + devname = {}; + } + + /* Force the backend device to stop first since we're opening another one. */ + const bool wasPlaying{dev->mDeviceState == DeviceState::Playing}; + if(wasPlaying) + { + dev->Backend->stop(); + dev->mDeviceState = DeviceState::Configured; } BackendPtr newbackend; try { newbackend = PlaybackFactory->createBackend(dev.get(), BackendType::Playback); - newbackend->open(deviceName); + newbackend->open(devname); } catch(al::backend_exception &e) { listlock.unlock(); @@ -4086,16 +3483,12 @@ START_API_FUNC alcSetError(dev.get(), (e.errorCode() == al::backend_error::OutOfMemory) ? ALC_OUT_OF_MEMORY : ALC_INVALID_VALUE); - /* If the device is connected, not paused, and has contexts, ensure it - * continues playing. - */ - if(dev->Connected.load(std::memory_order_relaxed) && !dev->Flags.test(DevicePaused) - && !dev->mContexts.load(std::memory_order_relaxed)->empty()) + if(dev->Connected.load(std::memory_order_relaxed) && wasPlaying) { try { auto backend = dev->Backend.get(); backend->start(); - dev->Flags.set(DeviceRunning); + dev->mDeviceState = DeviceState::Playing; } catch(al::backend_exception &be) { ERR("%s\n", be.what()); @@ -4106,6 +3499,7 @@ START_API_FUNC } listlock.unlock(); dev->Backend = std::move(newbackend); + dev->mDeviceState = DeviceState::Unprepared; TRACE("Reopened device %p, \"%s\"\n", voidp{dev.get()}, dev->DeviceName.c_str()); /* Always return true even if resetting fails. It shouldn't fail, but this @@ -4119,7 +3513,40 @@ START_API_FUNC * In this way, we essentially act as if the function succeeded, but * immediately disconnects following it. */ - ResetDeviceParams(dev.get(), attribs); + ResetDeviceParams(dev.get(), SpanFromAttributeList(attribs)); return ALC_TRUE; } -END_API_FUNC + +/************************************************ + * ALC event query functions + ************************************************/ + +FORCE_ALIGN ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) noexcept +{ + auto etype = alc::GetEventType(eventType); + if(!etype) + { + WARN("Invalid event type: 0x%04x\n", eventType); + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_EVENT_NOT_SUPPORTED_SOFT; + } + + auto supported = alc::EventSupport::NoSupport; + switch(deviceType) + { + case ALC_PLAYBACK_DEVICE_SOFT: + if(PlaybackFactory) + supported = PlaybackFactory->queryEventSupport(*etype, BackendType::Playback); + break; + + case ALC_CAPTURE_DEVICE_SOFT: + if(CaptureFactory) + supported = CaptureFactory->queryEventSupport(*etype, BackendType::Capture); + break; + + default: + WARN("Invalid device type: 0x%04x\n", deviceType); + alcSetError(nullptr, ALC_INVALID_ENUM); + } + return al::to_underlying(supported); +} diff --git a/Engine/lib/openal-soft/alc/alconfig.cpp b/Engine/lib/openal-soft/alc/alconfig.cpp index b0544b890..8c6c7595a 100644 --- a/Engine/lib/openal-soft/alc/alconfig.cpp +++ b/Engine/lib/openal-soft/alc/alconfig.cpp @@ -22,9 +22,6 @@ #include "alconfig.h" -#include -#include -#include #ifdef _WIN32 #include #include @@ -34,25 +31,47 @@ #endif #include -#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include +#include -#include "alfstream.h" +#include "almalloc.h" #include "alstring.h" #include "core/helpers.h" #include "core/logging.h" #include "strutils.h" -#include "vector.h" +#if defined(ALSOFT_UWP) +#include // !!This is important!! +#include +#include +#include +using namespace winrt; +#endif namespace { +using namespace std::string_view_literals; + +#if defined(_WIN32) && !defined(_GAMING_XBOX) && !defined(ALSOFT_UWP) +struct CoTaskMemDeleter { + void operator()(void *mem) const { CoTaskMemFree(mem); } +}; +#endif + struct ConfigEntry { std::string key; std::string value; }; -al::vector ConfOpts; +std::vector ConfOpts; std::string &lstrip(std::string &line) @@ -72,57 +91,48 @@ bool readline(std::istream &f, std::string &output) return std::getline(f, output) && !output.empty(); } -std::string expdup(const char *str) +std::string expdup(std::string_view str) { std::string output; - std::string envval; - while(*str != '\0') + while(!str.empty()) { - const char *addstr; - size_t addstrlen; - - if(str[0] != '$') + if(auto nextpos = str.find('$')) { - const char *next = std::strchr(str, '$'); - addstr = str; - addstrlen = next ? static_cast(next-str) : std::strlen(str); + output += str.substr(0, nextpos); + if(nextpos == std::string_view::npos) + break; - str += addstrlen; + str.remove_prefix(nextpos); } - else + + str.remove_prefix(1); + if(str.empty()) { - str++; - if(*str == '$') - { - const char *next = std::strchr(str+1, '$'); - addstr = str; - addstrlen = next ? static_cast(next-str) : std::strlen(str); - - str += addstrlen; - } - else - { - const bool hasbraces{(*str == '{')}; - - if(hasbraces) str++; - const char *envstart = str; - while(std::isalnum(*str) || *str == '_') - ++str; - if(hasbraces && *str != '}') - continue; - const std::string envname{envstart, str}; - if(hasbraces) str++; - - envval = al::getenv(envname.c_str()).value_or(std::string{}); - addstr = envval.data(); - addstrlen = envval.length(); - } + output += '$'; + break; } - if(addstrlen == 0) + if(str.front() == '$') + { + output += '$'; + str.remove_prefix(1); continue; + } - output.append(addstr, addstrlen); + const bool hasbraces{str.front() == '{'}; + if(hasbraces) str.remove_prefix(1); + + size_t envend{0}; + while(envend < str.size() && (std::isalnum(str[envend]) || str[envend] == '_')) + ++envend; + if(hasbraces && (envend == str.size() || str[envend] != '}')) + continue; + const std::string envname{str.substr(0, envend)}; + if(hasbraces) ++envend; + str.remove_prefix(envend); + + if(auto envval = al::getenv(envname.c_str())) + output += *envval; } return output; @@ -140,44 +150,43 @@ void LoadConfigFromFile(std::istream &f) if(buffer[0] == '[') { - auto line = const_cast(buffer.data()); - char *section = line+1; - char *endsection; - - endsection = std::strchr(section, ']'); - if(!endsection || section == endsection) + auto endpos = buffer.find(']', 1); + if(endpos == 1 || endpos == std::string::npos) { - ERR(" config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); continue; } - if(endsection[1] != 0) + if(buffer[endpos+1] != '\0') { - char *end = endsection+1; - while(std::isspace(*end)) - ++end; - if(*end != 0 && *end != '#') + size_t last{endpos+1}; + while(last < buffer.size() && std::isspace(buffer[last])) + ++last; + + if(last < buffer.size() && buffer[last] != '#') { - ERR(" config parse error: bad line \"%s\"\n", line); + ERR(" config parse error: bad line \"%s\"\n", buffer.c_str()); continue; } } - *endsection = 0; + + auto section = std::string_view{buffer}.substr(1, endpos-1); curSection.clear(); - if(al::strcasecmp(section, "general") != 0) + if(al::case_compare(section, "general"sv) != 0) { do { - char *nextp = std::strchr(section, '%'); - if(!nextp) + auto nextp = section.find('%'); + if(nextp == std::string_view::npos) { curSection += section; break; } - curSection.append(section, nextp); - section = nextp; + curSection += section.substr(0, nextp); + section.remove_prefix(nextp); - if(((section[1] >= '0' && section[1] <= '9') || + if(section.size() > 2 && + ((section[1] >= '0' && section[1] <= '9') || (section[1] >= 'a' && section[1] <= 'f') || (section[1] >= 'A' && section[1] <= 'F')) && ((section[2] >= '0' && section[2] <= '9') || @@ -198,19 +207,19 @@ void LoadConfigFromFile(std::istream &f) else if(section[2] >= 'A' && section[2] <= 'F') b |= (section[2]-'A'+0x0a); curSection += static_cast(b); - section += 3; + section.remove_prefix(3); } - else if(section[1] == '%') + else if(section.size() > 1 && section[1] == '%') { curSection += '%'; - section += 2; + section.remove_prefix(2); } else { curSection += '%'; - section += 1; + section.remove_prefix(1); } - } while(*section != 0); + } while(!section.empty()); } continue; @@ -228,16 +237,17 @@ void LoadConfigFromFile(std::istream &f) ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } - auto keyend = sep++; - while(keyend > 0 && std::isspace(buffer[keyend-1])) - --keyend; - if(!keyend) + auto keypart = std::string_view{buffer}.substr(0, sep++); + while(!keypart.empty() && std::isspace(keypart.back())) + keypart.remove_suffix(1); + if(keypart.empty()) { ERR(" config parse error: malformed option line: \"%s\"\n", buffer.c_str()); continue; } - while(sep < buffer.size() && std::isspace(buffer[sep])) - sep++; + auto valpart = std::string_view{buffer}.substr(sep); + while(!valpart.empty() && std::isspace(valpart.front())) + valpart.remove_prefix(1); std::string fullKey; if(!curSection.empty()) @@ -245,20 +255,24 @@ void LoadConfigFromFile(std::istream &f) fullKey += curSection; fullKey += '/'; } - fullKey += buffer.substr(0u, keyend); + fullKey += keypart; - std::string value{(sep < buffer.size()) ? buffer.substr(sep) : std::string{}}; - if(value.size() > 1) + if(valpart.size() > size_t{std::numeric_limits::max()}) { - if((value.front() == '"' && value.back() == '"') - || (value.front() == '\'' && value.back() == '\'')) + ERR(" config parse error: value too long in line \"%s\"\n", buffer.c_str()); + continue; + } + if(valpart.size() > 1) + { + if((valpart.front() == '"' && valpart.back() == '"') + || (valpart.front() == '\'' && valpart.back() == '\'')) { - value.pop_back(); - value.erase(value.begin()); + valpart.remove_prefix(1); + valpart.remove_suffix(1); } } - TRACE(" found '%s' = '%s'\n", fullKey.c_str(), value.c_str()); + TRACE(" setting '%s' = '%.*s'\n", fullKey.c_str(), al::sizei(valpart), valpart.data()); /* Check if we already have this option set */ auto find_key = [&fullKey](const ConfigEntry &entry) -> bool @@ -266,61 +280,49 @@ void LoadConfigFromFile(std::istream &f) auto ent = std::find_if(ConfOpts.begin(), ConfOpts.end(), find_key); if(ent != ConfOpts.end()) { - if(!value.empty()) - ent->value = expdup(value.c_str()); + if(!valpart.empty()) + ent->value = expdup(valpart); else ConfOpts.erase(ent); } - else if(!value.empty()) - ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(value.c_str())}); + else if(!valpart.empty()) + ConfOpts.emplace_back(ConfigEntry{std::move(fullKey), expdup(valpart)}); } ConfOpts.shrink_to_fit(); } -const char *GetConfigValue(const char *devName, const char *blockName, const char *keyName) +const char *GetConfigValue(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) { - if(!keyName) + if(keyName.empty()) return nullptr; std::string key; - if(blockName && al::strcasecmp(blockName, "general") != 0) + if(!blockName.empty() && al::case_compare(blockName, "general"sv) != 0) { key = blockName; - if(devName) - { - key += '/'; - key += devName; - } key += '/'; - key += keyName; } - else + if(!devName.empty()) { - if(devName) - { - key = devName; - key += '/'; - } - key += keyName; + key += devName; + key += '/'; } + key += keyName; auto iter = std::find_if(ConfOpts.cbegin(), ConfOpts.cend(), - [&key](const ConfigEntry &entry) -> bool - { return entry.key == key; }); + [&key](const ConfigEntry &entry) -> bool { return entry.key == key; }); if(iter != ConfOpts.cend()) { - TRACE("Found %s = \"%s\"\n", key.c_str(), iter->value.c_str()); + TRACE("Found option %s = \"%s\"\n", key.c_str(), iter->value.c_str()); if(!iter->value.empty()) return iter->value.c_str(); return nullptr; } - if(!devName) - { - TRACE("Key %s not found\n", key.c_str()); + if(devName.empty()) return nullptr; - } - return GetConfigValue(nullptr, blockName, keyName); + return GetConfigValue({}, blockName, keyName); } } // namespace @@ -329,33 +331,48 @@ const char *GetConfigValue(const char *devName, const char *blockName, const cha #ifdef _WIN32 void ReadALConfig() { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) - { - std::string filepath{wstr_to_utf8(buffer)}; - filepath += "\\alsoft.ini"; + namespace fs = std::filesystem; + fs::path path; - TRACE("Loading config %s...\n", filepath.c_str()); - al::ifstream f{filepath}; - if(f.is_open()) - LoadConfigFromFile(f); +#if !defined(_GAMING_XBOX) + { +#if !defined(ALSOFT_UWP) + std::unique_ptr bufstore; + const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, + nullptr, al::out_ptr(bufstore))}; + if(SUCCEEDED(hr)) + { + const std::wstring_view buffer{bufstore.get()}; +#else + winrt::Windows::Storage::ApplicationDataContainer localSettings = winrt::Windows::Storage::ApplicationData::Current().LocalSettings(); + auto bufstore = Windows::Storage::ApplicationData::Current().RoamingFolder().Path(); + std::wstring_view buffer{bufstore}; + { +#endif + path = fs::path{buffer}; + path /= L"alsoft.ini"; + + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) + LoadConfigFromFile(f); + } } +#endif - std::string ppath{GetProcBinary().path}; - if(!ppath.empty()) + path = fs::u8path(GetProcBinary().path); + if(!path.empty()) { - ppath += "\\alsoft.ini"; - TRACE("Loading config %s...\n", ppath.c_str()); - al::ifstream f{ppath}; - if(f.is_open()) + path /= "alsoft.ini"; + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confpath = al::getenv(L"ALSOFT_CONF")) { - TRACE("Loading config %s...\n", wstr_to_utf8(confpath->c_str()).c_str()); - al::ifstream f{*confpath}; - if(f.is_open()) + path = *confpath; + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } } @@ -364,13 +381,12 @@ void ReadALConfig() void ReadALConfig() { - const char *str{"/etc/openal/alsoft.conf"}; + namespace fs = std::filesystem; + fs::path path{"/etc/openal/alsoft.conf"}; - TRACE("Loading config %s...\n", str); - al::ifstream f{str}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); - f.close(); std::string confpaths{al::getenv("XDG_CONFIG_DIRS").value_or("/etc/xdg")}; /* Go through the list in reverse, since "the order of base directories @@ -378,48 +394,43 @@ void ReadALConfig() * important". Ergo, we need to load the settings from the later dirs * first so that the settings in the earlier dirs override them. */ - std::string fname; while(!confpaths.empty()) { - auto next = confpaths.find_last_of(':'); + auto next = confpaths.rfind(':'); if(next < confpaths.length()) { - fname = confpaths.substr(next+1); + path = fs::path{std::string_view{confpaths}.substr(next+1)}.lexically_normal(); confpaths.erase(next); } else { - fname = confpaths; + path = fs::path{confpaths}.lexically_normal(); confpaths.clear(); } - if(fname.empty() || fname.front() != '/') - WARN("Ignoring XDG config dir: %s\n", fname.c_str()); + if(!path.is_absolute()) + WARN("Ignoring XDG config dir: %s\n", path.u8string().c_str()); else { - if(fname.back() != '/') fname += "/alsoft.conf"; - else fname += "alsoft.conf"; + path /= "alsoft.conf"; - TRACE("Loading config %s...\n", fname.c_str()); - f = al::ifstream{fname}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } - fname.clear(); } #ifdef __APPLE__ CFBundleRef mainBundle = CFBundleGetMainBundle(); if(mainBundle) { - unsigned char fileName[PATH_MAX]; - CFURLRef configURL; + CFURLRef configURL{CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), + nullptr)}; - if((configURL=CFBundleCopyResourceURL(mainBundle, CFSTR(".alsoftrc"), CFSTR(""), nullptr)) && - CFURLGetFileSystemRepresentation(configURL, true, fileName, sizeof(fileName))) + std::array fileName{}; + if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size())) { - f = al::ifstream{reinterpret_cast(fileName)}; - if(f.is_open()) + if(std::ifstream f{reinterpret_cast(fileName.data())}; f.is_open()) LoadConfigFromFile(f); } } @@ -427,102 +438,100 @@ void ReadALConfig() if(auto homedir = al::getenv("HOME")) { - fname = *homedir; - if(fname.back() != '/') fname += "/.alsoftrc"; - else fname += ".alsoftrc"; + path = *homedir; + path /= ".alsoftrc"; - TRACE("Loading config %s...\n", fname.c_str()); - f = al::ifstream{fname}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto configdir = al::getenv("XDG_CONFIG_HOME")) { - fname = *configdir; - if(fname.back() != '/') fname += "/alsoft.conf"; - else fname += "alsoft.conf"; + path = *configdir; + path /= "alsoft.conf"; } else { - fname.clear(); + path.clear(); if(auto homedir = al::getenv("HOME")) { - fname = *homedir; - if(fname.back() != '/') fname += "/.config/alsoft.conf"; - else fname += ".config/alsoft.conf"; + path = *homedir; + path /= ".config/alsoft.conf"; } } - if(!fname.empty()) + if(!path.empty()) { - TRACE("Loading config %s...\n", fname.c_str()); - f = al::ifstream{fname}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } - std::string ppath{GetProcBinary().path}; - if(!ppath.empty()) + path = GetProcBinary().path; + if(!path.empty()) { - if(ppath.back() != '/') ppath += "/alsoft.conf"; - else ppath += "alsoft.conf"; + path /= "alsoft.conf"; - TRACE("Loading config %s...\n", ppath.c_str()); - f = al::ifstream{ppath}; - if(f.is_open()) + TRACE("Loading config %s...\n", path.u8string().c_str()); + if(std::ifstream f{path}; f.is_open()) LoadConfigFromFile(f); } if(auto confname = al::getenv("ALSOFT_CONF")) { TRACE("Loading config %s...\n", confname->c_str()); - f = al::ifstream{*confname}; - if(f.is_open()) + if(std::ifstream f{*confname}; f.is_open()) LoadConfigFromFile(f); } } #endif -al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueStr(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return val; - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return static_cast(std::strtol(val, nullptr, 0)); - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueUInt(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return static_cast(std::strtoul(val, nullptr, 0)); - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueFloat(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return std::strtof(val, nullptr); - return al::nullopt; + return std::nullopt; } -al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName) +std::optional ConfigValueBool(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true")==0 || atoi(val) != 0; - return al::nullopt; + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; + return std::nullopt; } -bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def) +bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def) { if(const char *val{GetConfigValue(devName, blockName, keyName)}) - return (al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 - || al::strcasecmp(val, "true") == 0 || atoi(val) != 0); + return al::strcasecmp(val, "on") == 0 || al::strcasecmp(val, "yes") == 0 + || al::strcasecmp(val, "true") == 0 || atoi(val) != 0; return def; } diff --git a/Engine/lib/openal-soft/alc/alconfig.h b/Engine/lib/openal-soft/alc/alconfig.h index df2830ccc..e7daac28e 100644 --- a/Engine/lib/openal-soft/alc/alconfig.h +++ b/Engine/lib/openal-soft/alc/alconfig.h @@ -1,18 +1,25 @@ #ifndef ALCONFIG_H #define ALCONFIG_H +#include #include +#include -#include "aloptional.h" void ReadALConfig(); -bool GetConfigValueBool(const char *devName, const char *blockName, const char *keyName, bool def); +bool GetConfigValueBool(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName, bool def); -al::optional ConfigValueStr(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueInt(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueUInt(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueFloat(const char *devName, const char *blockName, const char *keyName); -al::optional ConfigValueBool(const char *devName, const char *blockName, const char *keyName); +std::optional ConfigValueStr(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); +std::optional ConfigValueInt(const std::string_view devName, const std::string_view blockName, + const std::string_view keyName); +std::optional ConfigValueUInt(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); +std::optional ConfigValueFloat(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); +std::optional ConfigValueBool(const std::string_view devName, + const std::string_view blockName, const std::string_view keyName); #endif /* ALCONFIG_H */ diff --git a/Engine/lib/openal-soft/alc/alu.cpp b/Engine/lib/openal-soft/alc/alu.cpp index e9ad68b10..7f8503dce 100644 --- a/Engine/lib/openal-soft/alc/alu.cpp +++ b/Engine/lib/openal-soft/alc/alu.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -36,7 +37,7 @@ #include #include #include -#include +#include #include #include "almalloc.h" @@ -76,7 +77,6 @@ #include "opthelpers.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" #include "vecmat.h" #include "vector.h" @@ -107,15 +107,14 @@ namespace { using uint = unsigned int; using namespace std::chrono; - -using namespace std::placeholders; +using namespace std::string_view_literals; float InitConeScale() { float ret{1.0f}; if(auto optval = al::getenv("__ALSOFT_HALF_ANGLE_CONES")) { - if(al::strcasecmp(optval->c_str(), "true") == 0 + if(al::case_compare(*optval, "true"sv) == 0 || strtol(optval->c_str(), nullptr, 0) == 1) ret *= 0.5f; } @@ -135,19 +134,14 @@ float ZScale{1.0f}; float NfcScale{1.0f}; -struct ChanMap { - Channel channel; - float angle; - float elevation; -}; - using HrtfDirectMixerFunc = void(*)(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, float *TempBuf, - HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo); HrtfDirectMixerFunc MixDirectHrtf{MixDirectHrtf_}; -inline HrtfDirectMixerFunc SelectHrtfMixer(void) +inline HrtfDirectMixerFunc SelectHrtfMixer() { #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) @@ -170,7 +164,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab if(increment > MixerFracOne) { sf = MixerFracOne/static_cast(increment) - table->scaleBase; - sf = maxf(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f); + sf = std::max(0.0f, BSincScaleCount*sf*table->scaleRange - 1.0f); si = float2uint(sf); /* The interpolation factor is fit to this diagonally-symmetric curve * to reduce the transition ripple caused by interpolating different @@ -182,7 +176,7 @@ inline void BsincPrepare(const uint increment, BsincState *state, const BSincTab state->sf = sf; state->m = table->m[si]; state->l = (state->m/2) - 1; - state->filter = table->Tab + table->filterOffset[si]; + state->filter = table->Tab.subspan(table->filterOffset[si]); } inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) @@ -205,11 +199,20 @@ inline ResamplerFunc SelectResampler(Resampler resampler, uint increment) return Resample_; #endif return Resample_; - case Resampler::Cubic: + case Resampler::Spline: + case Resampler::Gaussian: #ifdef HAVE_NEON if((CPUCapFlags&CPU_CAP_NEON)) return Resample_; #endif +#ifdef HAVE_SSE4_1 + if((CPUCapFlags&CPU_CAP_SSE4_1)) + return Resample_; +#endif +#ifdef HAVE_SSE2 + if((CPUCapFlags&CPU_CAP_SSE2)) + return Resample_; +#endif #ifdef HAVE_SSE if((CPUCapFlags&CPU_CAP_SSE)) return Resample_; @@ -255,7 +258,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale) YScale = flags.test(CompatFlags::ReverseY) ? -1.0f : 1.0f; ZScale = flags.test(CompatFlags::ReverseZ) ? -1.0f : 1.0f; - NfcScale = clampf(nfcscale, 0.0001f, 10000.0f); + NfcScale = std::clamp(nfcscale, 0.0001f, 10000.0f); } @@ -266,16 +269,19 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState case Resampler::Point: case Resampler::Linear: break; - case Resampler::Cubic: - state->cubic.filter = gCubicSpline.Tab.data(); + case Resampler::Spline: + state->emplace(al::span{gSplineFilter.mTable}); + break; + case Resampler::Gaussian: + state->emplace(al::span{gGaussianFilter.mTable}); break; case Resampler::FastBSinc12: case Resampler::BSinc12: - BsincPrepare(increment, &state->bsinc, &gBSinc12); + BsincPrepare(increment, &state->emplace(), &gBSinc12); break; case Resampler::FastBSinc24: case Resampler::BSinc24: - BsincPrepare(increment, &state->bsinc, &gBSinc24); + BsincPrepare(increment, &state->emplace(), &gBSinc24); break; } return SelectResampler(resampler, increment); @@ -285,34 +291,33 @@ ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState void DeviceBase::ProcessHrtf(const size_t SamplesToDo) { /* HRTF is stereo output only. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; MixDirectHrtf(RealOut.Buffer[lidx], RealOut.Buffer[ridx], Dry.Buffer, HrtfAccumData, - mHrtfState->mTemp.data(), mHrtfState->mChannels.data(), mHrtfState->mIrSize, SamplesToDo); + mHrtfState->mTemp, mHrtfState->mChannels, mHrtfState->mIrSize, SamplesToDo); } void DeviceBase::ProcessAmbiDec(const size_t SamplesToDo) { - AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); + AmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); } void DeviceBase::ProcessAmbiDecStablized(const size_t SamplesToDo) { /* Decode with front image stablization. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; - const uint cidx{RealOut.ChannelIndex[FrontCenter]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t cidx{RealOut.ChannelIndex[FrontCenter]}; - AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer.data(), lidx, ridx, cidx, - SamplesToDo); + AmbiDecoder->processStablize(RealOut.Buffer, Dry.Buffer, lidx, ridx, cidx, SamplesToDo); } void DeviceBase::ProcessUhj(const size_t SamplesToDo) { /* UHJ is stereo output only. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Encode to stereo-compatible 2-channel UHJ output. */ mUhjEncoder->encode(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), @@ -322,15 +327,14 @@ void DeviceBase::ProcessUhj(const size_t SamplesToDo) void DeviceBase::ProcessBs2b(const size_t SamplesToDo) { /* First, decode the ambisonic mix to the "real" output. */ - AmbiDecoder->process(RealOut.Buffer, Dry.Buffer.data(), SamplesToDo); + AmbiDecoder->process(RealOut.Buffer, Dry.Buffer, SamplesToDo); /* BS2B is stereo output only. */ - const uint lidx{RealOut.ChannelIndex[FrontLeft]}; - const uint ridx{RealOut.ChannelIndex[FrontRight]}; + const size_t lidx{RealOut.ChannelIndex[FrontLeft]}; + const size_t ridx{RealOut.ChannelIndex[FrontRight]}; /* Now apply the BS2B binaural/crossfeed filter. */ - bs2b_cross_feed(Bs2b.get(), RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), - SamplesToDo); + Bs2b->cross_feed(RealOut.Buffer[lidx].data(), RealOut.Buffer[ridx].data(), SamplesToDo); } @@ -356,48 +360,49 @@ inline uint dither_rng(uint *seed) noexcept void UpsampleBFormatTransform( const al::span,MaxAmbiChannels> output, const al::span> upsampler, - const al::span,MaxAmbiChannels> rotator, size_t coeffs_order) + const al::span,MaxAmbiChannels> rotator, + size_t ambi_order) { - const size_t num_chans{AmbiChannelsFromOrder(coeffs_order)}; + const size_t num_chans{AmbiChannelsFromOrder(ambi_order)}; for(size_t i{0};i < upsampler.size();++i) output[i].fill(0.0f); for(size_t i{0};i < upsampler.size();++i) { for(size_t k{0};k < num_chans;++k) { - float *RESTRICT out{output[i].data()}; + const float a{upsampler[i][k]}; /* Write the full number of channels. The compiler will have an * easier time optimizing if it has a fixed length. */ - for(size_t j{0};j < MaxAmbiChannels;++j) - out[j] += upsampler[i][k] * rotator[k][j]; + std::transform(rotator[k].cbegin(), rotator[k].cend(), output[i].cbegin(), + output[i].begin(), [a](float rot, float dst) noexcept { return rot*a + dst; }); } } } -inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { - case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); - case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); - case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa}; + case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D}; + case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } - return AmbiScale::FromN3D(); + return al::span{AmbiScale::FromN3D}; } -inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); - return AmbiIndex::FromACN(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; + return al::span{AmbiIndex::FromACN}; } -inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); - return AmbiIndex::FromACN2D(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D}; + return al::span{AmbiIndex::FromACN2D}; } @@ -457,14 +462,14 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa slot->Target = props->Target; slot->EffectType = props->Type; slot->mEffectProps = props->Props; - if(props->Type == EffectSlotType::Reverb || props->Type == EffectSlotType::EAXReverb) + if(auto *reverbprops = std::get_if(&props->Props)) { - slot->RoomRolloff = props->Props.Reverb.RoomRolloffFactor; - slot->DecayTime = props->Props.Reverb.DecayTime; - slot->DecayLFRatio = props->Props.Reverb.DecayLFRatio; - slot->DecayHFRatio = props->Props.Reverb.DecayHFRatio; - slot->DecayHFLimit = props->Props.Reverb.DecayHFLimit; - slot->AirAbsorptionGainHF = props->Props.Reverb.AirAbsorptionGainHF; + slot->RoomRolloff = reverbprops->RoomRolloffFactor; + slot->DecayTime = reverbprops->DecayTime; + slot->DecayLFRatio = reverbprops->DecayLFRatio; + slot->DecayHFRatio = reverbprops->DecayHFRatio; + slot->DecayHFLimit = reverbprops->DecayHFLimit; + slot->AirAbsorptionGainHF = reverbprops->AirAbsorptionGainHF; } else { @@ -490,9 +495,8 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len > 0) LIKELY { - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::ReleaseEffectState)}; - evt->u.mEffectState = oldstate; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mEffectState = oldstate; ring->writeAdvance(1); } else @@ -506,42 +510,90 @@ bool CalcEffectSlotParams(EffectSlot *slot, EffectSlot **sorted_slots, ContextBa } } - AtomicReplaceHead(context->mFreeEffectslotProps, props); + AtomicReplaceHead(context->mFreeEffectSlotProps, props); - EffectTarget output; - if(EffectSlot *target{slot->Target}) - output = EffectTarget{&target->Wet, nullptr}; - else + const auto output = [slot,context]() -> EffectTarget { + if(EffectSlot *target{slot->Target}) + return EffectTarget{&target->Wet, nullptr}; DeviceBase *device{context->mDevice}; - output = EffectTarget{&device->Dry, &device->RealOut}; - } + return EffectTarget{&device->Dry, &device->RealOut}; + }(); state->update(context, slot, &slot->mEffectProps, output); return true; } -/* Scales the given azimuth toward the side (+/- pi/2 radians) for positions in - * front. +/* Scales the azimuth of the given vector by 3 if it's in front. Effectively + * scales +/-30 degrees to +/-90 degrees, leaving > +90 and < -90 alone. */ -inline float ScaleAzimuthFront(float azimuth, float scale) +inline std::array ScaleAzimuthFront3(std::array pos) { - const float abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= al::numbers::pi_v*0.5f)) - return std::copysign(minf(abs_azi*scale, al::numbers::pi_v*0.5f), azimuth); - return azimuth; + if(pos[2] < 0.0f) + { + /* Normalize the length of the x,z components for a 2D vector of the + * azimuth angle. Negate Z since {0,0,-1} is angle 0. + */ + const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; + float x{pos[0] / len2d}; + float z{-pos[2] / len2d}; + + /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ + if(z > 0.866025403785f) + { + /* Triple the angle represented by x,z. */ + x = x*3.0f - x*x*x*4.0f; + z = z*z*z*4.0f - z*3.0f; + + /* Scale the vector back to fit in 3D. */ + pos[0] = x * len2d; + pos[2] = -z * len2d; + } + else + { + /* If azimuth >= 30 degrees, clamp to 90 degrees. */ + pos[0] = std::copysign(len2d, pos[0]); + pos[2] = 0.0f; + } + } + return pos; } -/* Wraps the given value in radians to stay between [-pi,+pi] */ -inline float WrapRadians(float r) +/* Scales the azimuth of the given vector by 1.5 (3/2) if it's in front. */ +inline std::array ScaleAzimuthFront3_2(std::array pos) { - static constexpr float Pi{al::numbers::pi_v}; - static constexpr float Pi2{Pi*2.0f}; - if(r > Pi) return std::fmod(Pi+r, Pi2) - Pi; - if(r < -Pi) return Pi - std::fmod(Pi-r, Pi2); - return r; + if(pos[2] < 0.0f) + { + const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; + float x{pos[0] / len2d}; + float z{-pos[2] / len2d}; + + /* Z > cos(pi/3) = -60 < azimuth < 60 degrees. */ + if(z > 0.5f) + { + /* Halve the angle represented by x,z. */ + x = std::copysign(std::sqrt((1.0f - z) * 0.5f), x); + z = std::sqrt((1.0f + z) * 0.5f); + + /* Triple the angle represented by x,z. */ + x = x*3.0f - x*x*x*4.0f; + z = z*z*z*4.0f - z*3.0f; + + /* Scale the vector back to fit in 3D. */ + pos[0] = x * len2d; + pos[2] = -z * len2d; + } + else + { + /* If azimuth >= 60 degrees, clamp to 90 degrees. */ + pos[0] = std::copysign(len2d, pos[0]); + pos[2] = 0.0f; + } + } + return pos; } + /* Begin ambisonic rotation helpers. * * Rotating first-order B-Format just needs a straight-forward X/Y/Z rotation @@ -561,19 +613,18 @@ inline float WrapRadians(float r) * precomputed since they're constant. The second-order coefficients are * followed by the third-order coefficients, etc. */ -template -constexpr size_t CalcRotatorSize() -{ return (L*2 + 1)*(L*2 + 1) + CalcRotatorSize(); } - -template<> constexpr size_t CalcRotatorSize<0>() = delete; -template<> constexpr size_t CalcRotatorSize<1>() = delete; -template<> constexpr size_t CalcRotatorSize<2>() { return 5*5; } +constexpr size_t CalcRotatorSize(size_t l) noexcept +{ + if(l >= 2) + return (l*2 + 1)*(l*2 + 1) + CalcRotatorSize(l-1); + return 0; +} struct RotatorCoeffs { struct CoeffValues { float u, v, w; }; - std::array()> mCoeffs{}; + std::array mCoeffs{}; RotatorCoeffs() { @@ -585,17 +636,38 @@ struct RotatorCoeffs { { for(int m{-l};m <= l;++m) { - // compute u,v,w terms of Eq.8.1 (Table I) - const bool d{m == 0}; // the delta function d_m0 - const float denom{static_cast((std::abs(n) == l) ? - (2*l) * (2*l - 1) : (l*l - n*n))}; + /* compute u,v,w terms of Eq.8.1 (Table I) + * + * const bool d{m == 0}; // the delta function d_m0 + * const double denom{(std::abs(n) == l) ? + * (2*l) * (2*l - 1) : (l*l - n*n)}; + * + * const int abs_m{std::abs(m)}; + * coeffs->u = std::sqrt((l*l - m*m) / denom); + * coeffs->v = std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * + * (1.0+d) * (1.0 - 2.0*d) * 0.5; + * coeffs->w = std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * + * (1.0-d) * -0.5; + */ - const int abs_m{std::abs(m)}; - coeffs->u = std::sqrt(static_cast(l*l - m*m)/denom); - coeffs->v = std::sqrt(static_cast(l+abs_m-1) * - static_cast(l+abs_m) / denom) * (1.0f+d) * (1.0f - 2.0f*d) * 0.5f; - coeffs->w = std::sqrt(static_cast(l-abs_m-1) * - static_cast(l-abs_m) / denom) * (1.0f-d) * -0.5f; + const double denom{static_cast((std::abs(n) == l) ? + (2*l) * (2*l - 1) : (l*l - n*n))}; + + if(m == 0) + { + coeffs->u = static_cast(std::sqrt(l * l / denom)); + coeffs->v = static_cast(std::sqrt((l-1) * l / denom) * -1.0); + coeffs->w = 0.0f; + } + else + { + const int abs_m{std::abs(m)}; + coeffs->u = static_cast(std::sqrt((l*l - m*m) / denom)); + coeffs->v = static_cast(std::sqrt((l+abs_m-1) * (l+abs_m) / denom) * + 0.5); + coeffs->w = static_cast(std::sqrt((l-abs_m-1) * (l-abs_m) / denom) * + -0.5); + } ++coeffs; } } @@ -617,16 +689,16 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) auto P = [](const int i, const int l, const int a, const int n, const size_t last_band, const AmbiRotateMatrix &R) { - const float ri1{ R[ 1+2][static_cast(i+2)]}; - const float rim1{R[-1+2][static_cast(i+2)]}; - const float ri0{ R[ 0+2][static_cast(i+2)]}; + const float ri1{ R[ 1+2][static_cast(i+2_z)]}; + const float rim1{R[-1+2][static_cast(i+2_z)]}; + const float ri0{ R[ 0+2][static_cast(i+2_z)]}; const size_t y{last_band + static_cast(a+l-1)}; if(n == -l) - return ri1*R[last_band][y] + rim1*R[last_band + static_cast(l-1)*2][y]; + return ri1*R[last_band][y] + rim1*R[last_band + static_cast(l-1_z)*2][y]; if(n == l) - return ri1*R[last_band + static_cast(l-1)*2][y] - rim1*R[last_band][y]; - return ri0*R[last_band + static_cast(n+l-1)][y]; + return ri1*R[last_band + static_cast(l-1_z)*2][y] - rim1*R[last_band][y]; + return ri0*R[last_band + static_cast(l-1_z+n)][y]; }; auto U = [P](const int l, const int m, const int n, const size_t last_band, @@ -679,73 +751,89 @@ void AmbiRotator(AmbiRotateMatrix &matrix, const int order) float r{0.0f}; // computes Eq.8.1 - const float u{coeffs->u}; - if(u != 0.0f) r += u * U(l, m, n, last_band, matrix); - const float v{coeffs->v}; - if(v != 0.0f) r += v * V(l, m, n, last_band, matrix); - const float w{coeffs->w}; - if(w != 0.0f) r += w * W(l, m, n, last_band, matrix); + if(const float u{coeffs->u}; u != 0.0f) + r += u * U(l, m, n, last_band, matrix); + if(const float v{coeffs->v}; v != 0.0f) + r += v * V(l, m, n, last_band, matrix); + if(const float w{coeffs->w}; w != 0.0f) + r += w * W(l, m, n, last_band, matrix); matrix[y][x] = r; ++coeffs; } } last_band = band_idx; - band_idx += static_cast(l)*size_t{2} + 1; + band_idx += static_cast(l)*2_uz + 1; } } /* End ambisonic rotation helpers. */ -constexpr float Deg2Rad(float x) noexcept -{ return static_cast(al::numbers::pi / 180.0 * x); } +constexpr float sin30{0.5f}; +constexpr float cos30{0.866025403785f}; +constexpr float sin45{al::numbers::sqrt2_v*0.5f}; +constexpr float cos45{al::numbers::sqrt2_v*0.5f}; +constexpr float sin110{ 0.939692620786f}; +constexpr float cos110{-0.342020143326f}; + +struct ChanPosMap { + Channel channel; + std::array pos; +}; + struct GainTriplet { float Base, HF, LF; }; void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, const float zpos, const float Distance, const float Spread, const GainTriplet &DryGain, - const al::span WetGain, EffectSlot *(&SendSlots)[MAX_SENDS], - const VoiceProps *props, const ContextParams &Context, DeviceBase *Device) + const al::span WetGain, + const al::span SendSlots, const VoiceProps *props, + const ContextParams &Context, DeviceBase *Device) { - static constexpr ChanMap MonoMap[1]{ - { FrontCenter, 0.0f, 0.0f } - }, RearMap[2]{ - { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) } - }, QuadMap[4]{ - { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }, - { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } - }, X51Map[6]{ - { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) } - }, X61Map[7]{ - { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) }, - { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } - }, X71Map[8]{ - { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }, - { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + static constexpr std::array MonoMap{ + ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} + }; + static constexpr std::array RearMap{ + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + }; + static constexpr std::array QuadMap{ + ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, + ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, + ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, + ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, + }; + static constexpr std::array X51Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, + ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, + }; + static constexpr std::array X61Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f}}, + ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, + }; + static constexpr std::array X71Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; - ChanMap StereoMap[2]{ - { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) } + std::array StereoMap{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, }; const auto Frequency = static_cast(Device->Frequency); @@ -762,47 +850,84 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con [](SendParams ¶ms) -> void { params.Gains.Target.fill(0.0f); }); } - DirectMode DirectChannels{props->DirectChannels}; - const ChanMap *chans{nullptr}; - switch(voice->mFmtChannels) + const auto getChans = [props,&StereoMap](FmtChannels chanfmt) noexcept + -> std::pair> { - case FmtMono: - chans = MonoMap; - /* Mono buffers are never played direct. */ - DirectChannels = DirectMode::Off; - break; - - case FmtStereo: - if(DirectChannels == DirectMode::Off) + switch(chanfmt) { - /* Convert counter-clockwise to clock-wise, and wrap between - * [-pi,+pi]. - */ - StereoMap[0].angle = WrapRadians(-props->StereoPan[0]); - StereoMap[1].angle = WrapRadians(-props->StereoPan[1]); + case FmtMono: + /* Mono buffers are never played direct. */ + return {DirectMode::Off, al::span{MonoMap}}; + + case FmtStereo: + case FmtMonoDup: + if(props->DirectChannels == DirectMode::Off) + { + for(size_t i{0};i < 2;++i) + { + /* StereoPan is counter-clockwise in radians. */ + const float a{props->StereoPan[i]}; + StereoMap[i].pos[0] = -std::sin(a); + StereoMap[i].pos[2] = -std::cos(a); + } + } + return {props->DirectChannels, al::span{StereoMap}}; + + case FmtRear: return {props->DirectChannels, al::span{RearMap}}; + case FmtQuad: return {props->DirectChannels, al::span{QuadMap}}; + case FmtX51: return {props->DirectChannels, al::span{X51Map}}; + case FmtX61: return {props->DirectChannels, al::span{X61Map}}; + case FmtX71: return {props->DirectChannels, al::span{X71Map}}; + + case FmtBFormat2D: + case FmtBFormat3D: + case FmtUHJ2: + case FmtUHJ3: + case FmtUHJ4: + case FmtSuperStereo: + return {DirectMode::Off, {}}; } - chans = StereoMap; - break; - - case FmtRear: chans = RearMap; break; - case FmtQuad: chans = QuadMap; break; - case FmtX51: chans = X51Map; break; - case FmtX61: chans = X61Map; break; - case FmtX71: chans = X71Map; break; - - case FmtBFormat2D: - case FmtBFormat3D: - case FmtUHJ2: - case FmtUHJ3: - case FmtUHJ4: - case FmtSuperStereo: - DirectChannels = DirectMode::Off; - break; - } + return {props->DirectChannels, {}}; + }; + const auto [DirectChannels,chans] = getChans(voice->mFmtChannels); voice->mFlags.reset(VoiceHasHrtf).reset(VoiceHasNfc); if(auto *decoder{voice->mDecoder.get()}) - decoder->mWidthControl = minf(props->EnhWidth, 0.7f); + decoder->mWidthControl = std::min(props->EnhWidth, 0.7f); + + const float lgain{std::min(1.0f-props->Panning, 1.0f)}; + const float rgain{std::min(1.0f+props->Panning, 1.0f)}; + const float mingain{std::min(lgain, rgain)}; + auto SelectChannelGain = [lgain,rgain,mingain](const Channel chan) noexcept + { + switch(chan) + { + case FrontLeft: return lgain; + case FrontRight: return rgain; + case FrontCenter: break; + case LFE: break; + case BackLeft: return lgain; + case BackRight: return rgain; + case BackCenter: break; + case SideLeft: return lgain; + case SideRight: return rgain; + case TopCenter: break; + case TopFrontLeft: return lgain; + case TopFrontCenter: break; + case TopFrontRight: return rgain; + case TopBackLeft: return lgain; + case TopBackCenter: break; + case TopBackRight: return rgain; + case BottomFrontLeft: return lgain; + case BottomFrontRight: return rgain; + case BottomBackLeft: return lgain; + case BottomBackRight: return rgain; + case Aux0: case Aux1: case Aux2: case Aux3: case Aux4: case Aux5: case Aux6: case Aux7: + case Aux8: case Aux9: case Aux10: case Aux11: case Aux12: case Aux13: case Aux14: + case Aux15: case MaxChannels: break; + } + return mingain; + }; if(IsAmbisonic(voice->mFmtChannels)) { @@ -824,7 +949,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; + const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Only need to adjust the first channel of a B-Format source. */ @@ -845,32 +970,21 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con auto calc_coeffs = [xpos,ypos,zpos](RenderMode mode) { if(mode != RenderMode::Pairwise) - return CalcDirectionCoeffs({xpos, ypos, zpos}); - - /* Clamp Y, in case rounding errors caused it to end up outside - * of -1...+1. - */ - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - /* Negate Z for right-handed coords with -Z in front. */ - const float az{std::atan2(xpos, -zpos)}; - - /* A scalar of 1.5 for plain stereo results in +/-60 degrees - * being moved to +/-90 degrees for direct right and left - * speaker responses. - */ - return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, 0.0f); + return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, 0.0f); + const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); + return CalcDirectionCoeffs(pos, 0.0f); }; - auto&& scales = GetAmbiScales(voice->mAmbiScaling); + const auto scales = GetAmbiScales(voice->mAmbiScaling); auto coeffs = calc_coeffs(Device->mRenderMode); if(!(coverage > 0.0f)) { - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base*scales[0], + ComputePanGains(&Device->Dry, coeffs, DryGain.Base*scales[0], voice->mChans[0].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base*scales[0], + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base*scales[0], voice->mChans[0].mWetParams[i].Gains.Target); } } @@ -922,25 +1036,25 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { if(voice->mAmbiOrder == 1) { - auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? - AmbiScale::FirstOrder2DUp : AmbiScale::FirstOrderUp; + const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + al::span{AmbiScale::FirstOrder2DUp} : al::span{AmbiScale::FirstOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 2) { - auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? - AmbiScale::SecondOrder2DUp : AmbiScale::SecondOrderUp; + const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + al::span{AmbiScale::SecondOrder2DUp} : al::span{AmbiScale::SecondOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 3) { - auto&& upsampler = Is2DAmbisonic(voice->mFmtChannels) ? - AmbiScale::ThirdOrder2DUp : AmbiScale::ThirdOrderUp; + const auto upsampler = Is2DAmbisonic(voice->mFmtChannels) ? + al::span{AmbiScale::ThirdOrder2DUp} : al::span{AmbiScale::ThirdOrderUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else if(voice->mAmbiOrder == 4) { - auto&& upsampler = AmbiScale::FourthOrder2DUp; + const auto upsampler = al::span{AmbiScale::FourthOrder2DUp}; UpsampleBFormatTransform(mixmatrix, upsampler, shrot, Device->mAmbiOrder); } else @@ -952,9 +1066,9 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Convert the rotation matrix for input ordering and scaling, and * whether input is 2D or 3D. */ - const uint8_t *index_map{Is2DAmbisonic(voice->mFmtChannels) ? - GetAmbi2DLayout(voice->mAmbiLayout).data() : - GetAmbiLayout(voice->mAmbiLayout).data()}; + const auto index_map = Is2DAmbisonic(voice->mFmtChannels) ? + GetAmbi2DLayout(voice->mAmbiLayout).subspan(0) : + GetAmbiLayout(voice->mAmbiLayout).subspan(0); /* Scale the panned W signal inversely to coverage (full coverage * means no panned signal), and according to the channel scaling. @@ -971,16 +1085,17 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * to the coverage amount) with the directional pan. For all * other channels, use just the (scaled) B-Format signal. */ - for(size_t x{0};x < MaxAmbiChannels;++x) - coeffs[x] += mixmatrix[acn][x] * scale; + std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(), + coeffs.begin(), [scale](const float in, const float coeff) noexcept + { return in*scale + coeff; }); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[c].mWetParams[i].Gains.Target); } @@ -997,13 +1112,13 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con for(size_t c{0};c < num_channels;c++) { - uint idx{Device->channelIdxByName(chans[c].channel)}; - if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + const float pangain{SelectChannelGain(chans[c].channel)}; + if(uint idx{Device->channelIdxByName(chans[c].channel)}; idx != InvalidChannelIndex) + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; else if(DirectChannels == DirectMode::RemixMismatch) { - auto match_channel = [chans,c](const InputRemixMap &map) noexcept -> bool - { return chans[c].channel == map.channel; }; + auto match_channel = [channel=chans[c].channel](const InputRemixMap &map) noexcept + { return channel == map.channel; }; auto remap = std::find_if(Device->RealOut.RemixMap.cbegin(), Device->RealOut.RemixMap.cend(), match_channel); if(remap != Device->RealOut.RemixMap.cend()) @@ -1012,8 +1127,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { idx = Device->channelIdxByName(target.channel); if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * - target.mix; + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain + * target.mix; } } } @@ -1028,12 +1143,13 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con if(chans[c].channel == LFE) continue; - const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, 0.0f); + const float pangain{SelectChannelGain(chans[c].channel)}; + const auto coeffs = CalcDirectionCoeffs(chans[c].pos, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1047,21 +1163,21 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con if(Distance > std::numeric_limits::epsilon()) { - const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float src_az{std::atan2(xpos, -zpos)}; - if(voice->mFmtChannels == FmtMono) { + const float src_ev{std::asin(std::clamp(ypos, -1.0f, 1.0f))}; + const float src_az{std::atan2(xpos, -zpos)}; + Device->mHrtf->getCoeffs(src_ev, src_az, Distance*NfcScale, Spread, voice->mChans[0].mDryParams.Hrtf.Target.Coeffs, voice->mChans[0].mDryParams.Hrtf.Target.Delay); voice->mChans[0].mDryParams.Hrtf.Target.Gain = DryGain.Base; - const auto coeffs = CalcAngleCoeffs(src_az, src_ev, Spread); + const auto coeffs = CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, Spread); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } } @@ -1071,34 +1187,39 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Skip LFE */ if(chans[c].channel == LFE) continue; + const float pangain{SelectChannelGain(chans[c].channel)}; /* Warp the channel position toward the source position as the * source spread decreases. With no spread, all channels are at * the source position, at full spread (pi*2), each channel is * left unchanged. */ - const float ev{lerpf(src_ev, chans[c].elevation, inv_pi_v/2.0f * Spread)}; + const float a{1.0f - (inv_pi_v/2.0f)*Spread}; + std::array pos{ + lerpf(chans[c].pos[0], xpos, a), + lerpf(chans[c].pos[1], ypos, a), + lerpf(chans[c].pos[2], zpos, a)}; + const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; + if(len < 1.0f) + { + pos[0] /= len; + pos[1] /= len; + pos[2] /= len; + } - float az{chans[c].angle - src_az}; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; - - az *= inv_pi_v/2.0f * Spread; - - az += src_az; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; + const float ev{std::asin(std::clamp(pos[1], -1.0f, 1.0f))}; + const float az{std::atan2(pos[0], -pos[2])}; Device->mHrtf->getCoeffs(ev, az, Distance*NfcScale, 0.0f, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); - voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base * pangain; - const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1109,7 +1230,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * where it can be 0 or full (non-mono sources are always full * spread here). */ - const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; /* Local sources on HRTF play with each channel panned to its * relative location around the listener, providing "virtual @@ -1120,23 +1241,26 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Skip LFE */ if(chans[c].channel == LFE) continue; + const float pangain{SelectChannelGain(chans[c].channel)}; /* Get the HRIR coefficients and delays for this channel * position. */ - Device->mHrtf->getCoeffs(chans[c].elevation, chans[c].angle, - std::numeric_limits::infinity(), spread, + const float ev{std::asin(chans[c].pos[1])}; + const float az{std::atan2(chans[c].pos[0], -chans[c].pos[2])}; + + Device->mHrtf->getCoeffs(ev, az, std::numeric_limits::infinity(), spread, voice->mChans[c].mDryParams.Hrtf.Target.Coeffs, voice->mChans[c].mDryParams.Hrtf.Target.Delay); - voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base; + voice->mChans[c].mDryParams.Hrtf.Target.Gain = DryGain.Base * pangain; /* Normal panning for auxiliary sends. */ - const auto coeffs = CalcAngleCoeffs(chans[c].angle, chans[c].elevation, spread); + const auto coeffs = CalcDirectionCoeffs(chans[c].pos, spread); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1156,7 +1280,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con /* Clamp the distance for really close sources, to prevent * excessive bass. */ - const float mdist{maxf(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; + const float mdist{std::max(Distance*NfcScale, Device->AvgSpeakerDist/4.0f)}; const float w0{SpeedOfSoundMetersPerSec / (mdist * Frequency)}; /* Adjust NFC filters. */ @@ -1171,19 +1295,18 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con auto calc_coeffs = [xpos,ypos,zpos,Spread](RenderMode mode) { if(mode != RenderMode::Pairwise) - return CalcDirectionCoeffs({xpos, ypos, zpos}, Spread); - const float ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float az{std::atan2(xpos, -zpos)}; - return CalcAngleCoeffs(ScaleAzimuthFront(az, 1.5f), ev, Spread); + return CalcDirectionCoeffs(std::array{xpos, ypos, zpos}, Spread); + const auto pos = ScaleAzimuthFront3_2(std::array{xpos, ypos, zpos}); + return CalcDirectionCoeffs(pos, Spread); }; const auto coeffs = calc_coeffs(Device->mRenderMode); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base, voice->mChans[0].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base, voice->mChans[0].mWetParams[i].Gains.Target); } } @@ -1191,11 +1314,10 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { using namespace al::numbers; - const float src_ev{std::asin(clampf(ypos, -1.0f, 1.0f))}; - const float src_az{std::atan2(xpos, -zpos)}; - for(size_t c{0};c < num_channels;c++) { + const float pangain{SelectChannelGain(chans[c].channel)}; + /* Special-case LFE */ if(chans[c].channel == LFE) { @@ -1203,7 +1325,8 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { const uint idx{Device->channelIdxByName(chans[c].channel)}; if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base + * pangain; } continue; } @@ -1213,29 +1336,29 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * at the source position, at full spread (pi*2), each * channel position is left unchanged. */ - const float ev{lerpf(src_ev, chans[c].elevation, - inv_pi_v/2.0f * Spread)}; - - float az{chans[c].angle - src_az}; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; - - az *= inv_pi_v/2.0f * Spread; - - az += src_az; - if(az < -pi_v) az += pi_v*2.0f; - else if(az > pi_v) az -= pi_v*2.0f; + const float a{1.0f - (inv_pi_v/2.0f)*Spread}; + std::array pos{ + lerpf(chans[c].pos[0], xpos, a), + lerpf(chans[c].pos[1], ypos, a), + lerpf(chans[c].pos[2], zpos, a)}; + const float len{std::sqrt(pos[0]*pos[0] + pos[1]*pos[1] + pos[2]*pos[2])}; + if(len < 1.0f) + { + pos[0] /= len; + pos[1] /= len; + pos[2] /= len; + } if(Device->mRenderMode == RenderMode::Pairwise) - az = ScaleAzimuthFront(az, 3.0f); - const auto coeffs = CalcAngleCoeffs(az, ev, 0.0f); + pos = ScaleAzimuthFront3(pos); + const auto coeffs = CalcDirectionCoeffs(pos, 0.0f); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1259,9 +1382,11 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con * where it can be 0 or full (non-mono sources are always full * spread here). */ - const float spread{Spread * (voice->mFmtChannels == FmtMono)}; + const float spread{Spread * float(voice->mFmtChannels == FmtMono)}; for(size_t c{0};c < num_channels;c++) { + const float pangain{SelectChannelGain(chans[c].channel)}; + /* Special-case LFE */ if(chans[c].channel == LFE) { @@ -1269,21 +1394,20 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con { const uint idx{Device->channelIdxByName(chans[c].channel)}; if(idx != InvalidChannelIndex) - voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base; + voice->mChans[c].mDryParams.Gains.Target[idx] = DryGain.Base * pangain; } continue; } - const auto coeffs = CalcAngleCoeffs((Device->mRenderMode == RenderMode::Pairwise) - ? ScaleAzimuthFront(chans[c].angle, 3.0f) : chans[c].angle, - chans[c].elevation, spread); + const auto coeffs = CalcDirectionCoeffs((Device->mRenderMode==RenderMode::Pairwise) + ? ScaleAzimuthFront3(chans[c].pos) : chans[c].pos, spread); - ComputePanGains(&Device->Dry, coeffs.data(), DryGain.Base, + ComputePanGains(&Device->Dry, coeffs, DryGain.Base * pangain, voice->mChans[c].mDryParams.Gains.Target); for(uint i{0};i < NumSends;i++) { if(const EffectSlot *Slot{SendSlots[i]}) - ComputePanGains(&Slot->Wet, coeffs.data(), WetGain[i].Base, + ComputePanGains(&Slot->Wet, coeffs, WetGain[i].Base * pangain, voice->mChans[c].mWetParams[i].Gains.Target); } } @@ -1332,7 +1456,7 @@ void CalcPanningAndFilters(Voice *voice, const float xpos, const float ypos, con void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBase *context) { DeviceBase *Device{context->mDevice}; - EffectSlot *SendSlots[MAX_SENDS]; + std::array SendSlots{}; voice->mDirect.Buffer = Device->Dry.Buffer; for(uint i{0};i < Device->NumAuxSends;i++) @@ -1353,19 +1477,20 @@ void CalcNonAttnSourceParams(Voice *voice, const VoiceProps *props, const Contex if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); + voice->mStep = std::max(fastf2u(Pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); /* Calculate gains */ - GainTriplet DryGain; - DryGain.Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * props->Direct.Gain * - context->mParams.Gain, GainMixMax); + GainTriplet DryGain{}; + DryGain.Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * + props->Direct.Gain * context->mParams.Gain, GainMixMax); DryGain.HF = props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - GainTriplet WetGain[MAX_SENDS]; + + std::array WetGain{}; for(uint i{0};i < Device->NumAuxSends;i++) { - WetGain[i].Base = minf(clampf(props->Gain, props->MinGain, props->MaxGain) * + WetGain[i].Base = std::min(std::clamp(props->Gain, props->MinGain, props->MaxGain) * props->Send[i].Gain * context->mParams.Gain, GainMixMax); WetGain[i].HF = props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; @@ -1382,20 +1507,30 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Set mixing buffers and get send parameters. */ voice->mDirect.Buffer = Device->Dry.Buffer; - EffectSlot *SendSlots[MAX_SENDS]; - uint UseDryAttnForRoom{0}; + std::array SendSlots{}; + std::array RoomRolloff{}; + std::bitset UseDryAttnForRoom{0}; for(uint i{0};i < NumSends;i++) { SendSlots[i] = props->Send[i].Slot; if(!SendSlots[i] || SendSlots[i]->EffectType == EffectSlotType::None) SendSlots[i] = nullptr; - else if(!SendSlots[i]->AuxSendAuto) + else if(SendSlots[i]->AuxSendAuto) { - /* If the slot's auxiliary send auto is off, the data sent to the - * effect slot is the same as the dry path, sans filter effects. + /* NOTE: Contrary to the EFX docs, the effect's room rolloff factor + * applies to the selected distance model along with the source's + * room rolloff factor, not necessarily the inverse distance model. + * + * Generic Software also applies these rolloff factors regardless + * of any setting. It doesn't seem to use the effect slot's send + * auto for anything, though as far as I understand, it's supposed + * to control whether the send gets the same gain/gainhf as the + * direct path (excluding the filter). */ - UseDryAttnForRoom |= 1u<RoomRolloffFactor + SendSlots[i]->RoomRolloff; } + else + UseDryAttnForRoom.set(i); if(!SendSlots[i]) voice->mSend[i].Buffer = {}; @@ -1427,62 +1562,77 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa /* Calculate distance attenuation */ float ClampedDist{Distance}; float DryGainBase{props->Gain}; - float WetGainBase{props->Gain}; + std::array WetGainBase{}; + WetGainBase.fill(props->Gain); + float DryAttnBase{1.0f}; switch(context->mParams.SourceDistanceModel ? props->mDistanceModel : context->mParams.mDistanceModel) { - case DistanceModel::InverseClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Inverse: - if(props->RefDistance > 0.0f) + case DistanceModel::InverseClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Inverse: + if(props->RefDistance > 0.0f) + { + float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; + if(dist > 0.0f) { - float dist{lerpf(props->RefDistance, ClampedDist, props->RolloffFactor)}; - if(dist > 0.0f) DryGainBase *= props->RefDistance / dist; - - dist = lerpf(props->RefDistance, ClampedDist, props->RoomRolloffFactor); - if(dist > 0.0f) WetGainBase *= props->RefDistance / dist; + DryAttnBase = props->RefDistance / dist; + DryGainBase *= DryAttnBase; } - break; - case DistanceModel::LinearClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Linear: - if(props->MaxDistance != props->RefDistance) + for(size_t i{0};i < NumSends;++i) { - float attn{(ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; - DryGainBase *= maxf(1.0f - attn, 0.0f); + dist = lerpf(props->RefDistance, ClampedDist, RoomRolloff[i]); + if(dist > 0.0f) WetGainBase[i] *= props->RefDistance / dist; + } + } + break; + case DistanceModel::LinearClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Linear: + if(props->MaxDistance != props->RefDistance) + { + float attn{(ClampedDist-props->RefDistance) / + (props->MaxDistance-props->RefDistance) * props->RolloffFactor}; + DryAttnBase = std::max(1.0f - attn, 0.0f); + DryGainBase *= DryAttnBase; + + for(size_t i{0};i < NumSends;++i) + { attn = (ClampedDist-props->RefDistance) / - (props->MaxDistance-props->RefDistance) * props->RoomRolloffFactor; - WetGainBase *= maxf(1.0f - attn, 0.0f); + (props->MaxDistance-props->RefDistance) * RoomRolloff[i]; + WetGainBase[i] *= std::max(1.0f - attn, 0.0f); } - break; + } + break; - case DistanceModel::ExponentClamped: - if(props->MaxDistance < props->RefDistance) break; - ClampedDist = clampf(ClampedDist, props->RefDistance, props->MaxDistance); - /*fall-through*/ - case DistanceModel::Exponent: - if(ClampedDist > 0.0f && props->RefDistance > 0.0f) - { - const float dist_ratio{ClampedDist/props->RefDistance}; - DryGainBase *= std::pow(dist_ratio, -props->RolloffFactor); - WetGainBase *= std::pow(dist_ratio, -props->RoomRolloffFactor); - } - break; + case DistanceModel::ExponentClamped: + if(props->MaxDistance < props->RefDistance) break; + ClampedDist = std::clamp(ClampedDist, props->RefDistance, props->MaxDistance); + /*fall-through*/ + case DistanceModel::Exponent: + if(ClampedDist > 0.0f && props->RefDistance > 0.0f) + { + const float dist_ratio{ClampedDist/props->RefDistance}; + DryAttnBase = std::pow(dist_ratio, -props->RolloffFactor); + DryGainBase *= DryAttnBase; + for(size_t i{0};i < NumSends;++i) + WetGainBase[i] *= std::pow(dist_ratio, -RoomRolloff[i]); + } + break; - case DistanceModel::Disable: - break; + case DistanceModel::Disable: + break; } /* Calculate directional soundcones */ - float ConeHF{1.0f}, WetConeHF{1.0f}; + float ConeHF{1.0f}, WetCone{1.0f}, WetConeHF{1.0f}; if(directional && props->InnerAngle < 360.0f) { static constexpr float Rad2Deg{static_cast(180.0 / al::numbers::pi)}; @@ -1492,40 +1642,44 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(Angle >= props->OuterAngle) { ConeGain = props->OuterGain; - ConeHF = lerpf(1.0f, props->OuterGainHF, props->DryGainHFAuto); + if(props->DryGainHFAuto) + ConeHF = props->OuterGainHF; } else if(Angle >= props->InnerAngle) { const float scale{(Angle-props->InnerAngle) / (props->OuterAngle-props->InnerAngle)}; ConeGain = lerpf(1.0f, props->OuterGain, scale); - ConeHF = lerpf(1.0f, props->OuterGainHF, scale * props->DryGainHFAuto); + if(props->DryGainHFAuto) + ConeHF = lerpf(1.0f, props->OuterGainHF, scale); } DryGainBase *= ConeGain; - WetGainBase *= lerpf(1.0f, ConeGain, props->WetGainAuto); - - WetConeHF = lerpf(1.0f, ConeHF, props->WetGainHFAuto); + if(props->WetGainAuto) + WetCone = ConeGain; + if(props->WetGainHFAuto) + WetConeHF = ConeHF; } /* Apply gain and frequency filters */ - DryGainBase = clampf(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; - WetGainBase = clampf(WetGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; - GainTriplet DryGain{}; - DryGain.Base = minf(DryGainBase * props->Direct.Gain, GainMixMax); + DryGainBase = std::clamp(DryGainBase, props->MinGain, props->MaxGain) * context->mParams.Gain; + DryGain.Base = std::min(DryGainBase * props->Direct.Gain, GainMixMax); DryGain.HF = ConeHF * props->Direct.GainHF; DryGain.LF = props->Direct.GainLF; - GainTriplet WetGain[MAX_SENDS]{}; + + std::array WetGain{}; for(uint i{0};i < NumSends;i++) { + WetGainBase[i] = std::clamp(WetGainBase[i]*WetCone, props->MinGain, props->MaxGain) * + context->mParams.Gain; /* If this effect slot's Auxiliary Send Auto is off, then use the dry * path distance and cone attenuation, otherwise use the wet (room) * path distance and cone attenuation. The send filter is used instead * of the direct filter, regardless. */ - const bool use_room{!(UseDryAttnForRoom&(1u<Send[i].Gain, GainMixMax); + const bool use_room{!UseDryAttnForRoom.test(i)}; + const float gain{use_room ? WetGainBase[i] : DryGainBase}; + WetGain[i].Base = std::min(gain * props->Send[i].Gain, GainMixMax); WetGain[i].HF = (use_room ? WetConeHF : ConeHF) * props->Send[i].GainHF; WetGain[i].LF = props->Send[i].GainLF; } @@ -1547,72 +1701,34 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(!SendSlots[i] || !(SendSlots[i]->DecayTime > 0.0f)) continue; - auto calc_attenuation = [](float distance, float refdist, float rolloff) noexcept - { - const float dist{lerpf(refdist, distance, rolloff)}; - if(dist > refdist) return refdist / dist; - return 1.0f; - }; - - /* The reverb effect's room rolloff factor always applies to an - * inverse distance rolloff model. - */ - WetGain[i].Base *= calc_attenuation(Distance, props->RefDistance, - SendSlots[i]->RoomRolloff); - if(distance_meters > std::numeric_limits::epsilon()) WetGain[i].HF *= std::pow(SendSlots[i]->AirAbsorptionGainHF, distance_meters); /* If this effect slot's Auxiliary Send Auto is off, don't apply - * the automatic initial reverb decay (should the reverb's room - * rolloff still apply?). + * the automatic initial reverb decay. + * + * NOTE: Generic Software applies the initial decay regardless of + * this setting. It doesn't seem to use it for anything, only the + * source's send filter gain auto flag affects this. */ if(!SendSlots[i]->AuxSendAuto) continue; - GainTriplet DecayDistance; - /* Calculate the distances to where this effect's decay reaches - * -60dB. - */ - DecayDistance.Base = SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec; - DecayDistance.LF = DecayDistance.Base * SendSlots[i]->DecayLFRatio; - DecayDistance.HF = DecayDistance.Base * SendSlots[i]->DecayHFRatio; - if(SendSlots[i]->DecayHFLimit) - { - const float airAbsorption{SendSlots[i]->AirAbsorptionGainHF}; - if(airAbsorption < 1.0f) - { - /* Calculate the distance to where this effect's air - * absorption reaches -60dB, and limit the effect's HF - * decay distance (so it doesn't take any longer to decay - * than the air would allow). - */ - static constexpr float log10_decaygain{-3.0f/*std::log10(ReverbDecayGain)*/}; - const float absorb_dist{log10_decaygain / std::log10(airAbsorption)}; - DecayDistance.HF = minf(absorb_dist, DecayDistance.HF); - } - } - - const float baseAttn = calc_attenuation(Distance, props->RefDistance, - props->RolloffFactor); + const float DecayDistance{SendSlots[i]->DecayTime * SpeedOfSoundMetersPerSec}; /* Apply a decay-time transformation to the wet path, based on the * source distance. The initial decay of the reverb effect is * calculated and applied to the wet path. + * + * FIXME: This is very likely not correct. It more likely should + * work by calculating a rolloff dynamically based on the reverb + * parameters (and source distance?) and add it to the room rolloff + * with the reverb and source rolloff parameters. */ - const float fact{distance_base / DecayDistance.Base}; + const float baseAttn{DryAttnBase}; + const float fact{distance_base / DecayDistance}; const float gain{std::pow(ReverbDecayGain, fact)*(1.0f-baseAttn) + baseAttn}; WetGain[i].Base *= gain; - - if(gain > 0.0f) - { - const float hffact{distance_base / DecayDistance.HF}; - const float gainhf{std::pow(ReverbDecayGain, hffact)*(1.0f-baseAttn) + baseAttn}; - WetGain[i].HF *= minf(gainhf/gain, 1.0f); - const float lffact{distance_base / DecayDistance.LF}; - const float gainlf{std::pow(ReverbDecayGain, lffact)*(1.0f-baseAttn) + baseAttn}; - WetGain[i].LF *= minf(gainlf/gain, 1.0f); - } } } @@ -1659,7 +1775,7 @@ void CalcAttnSourceParams(Voice *voice, const VoiceProps *props, const ContextBa if(Pitch > float{MaxPitch}) voice->mStep = MaxPitch<mStep = maxu(fastf2u(Pitch * MixerFracOne), 1); + voice->mStep = std::max(fastf2u(Pitch * MixerFracOne), 1u); voice->mResampler = PrepareResampler(props->mResampler, voice->mStep, &voice->mResampleState); float spread{0.0f}; @@ -1679,7 +1795,7 @@ void CalcSourceParams(Voice *voice, ContextBase *context, bool force) if(props) { - voice->mProps = *props; + voice->mProps = static_cast(*props); AtomicReplaceHead(context->mFreeVoiceProps, props); } @@ -1700,22 +1816,21 @@ void SendSourceStateEvent(ContextBase *context, uint id, VChangeState state) auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len < 1) return; - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::SourceStateChange)}; - evt->u.srcstate.id = id; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mId = id; switch(state) { case VChangeState::Reset: - evt->u.srcstate.state = AsyncEvent::SrcState::Reset; + evt.mState = AsyncSrcState::Reset; break; case VChangeState::Stop: - evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + evt.mState = AsyncSrcState::Stop; break; case VChangeState::Play: - evt->u.srcstate.state = AsyncEvent::SrcState::Play; + evt.mState = AsyncSrcState::Play; break; case VChangeState::Pause: - evt->u.srcstate.state = AsyncEvent::SrcState::Pause; + evt.mState = AsyncSrcState::Pause; break; /* Shouldn't happen. */ case VChangeState::Restart: @@ -1812,7 +1927,7 @@ void ProcessVoiceChanges(ContextBase *ctx) } oldvoice->mPendingChange.store(false, std::memory_order_release); } - if(sendevt && enabledevt.test(AsyncEvent::SourceStateChange)) + if(sendevt && enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStateEvent(ctx, cur->mSourceID, cur->mState); next = cur->mNext.load(std::memory_order_acquire); @@ -1820,8 +1935,8 @@ void ProcessVoiceChanges(ContextBase *ctx) ctx->mCurrentVoiceChange.store(cur, std::memory_order_release); } -void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, - const al::span voices) +void ProcessParamUpdates(ContextBase *ctx, const al::span slots, + const al::span sorted_slots, const al::span voices) { ProcessVoiceChanges(ctx); @@ -1829,9 +1944,9 @@ void ProcessParamUpdates(ContextBase *ctx, const EffectSlotArray &slots, if(!ctx->mHoldUpdates.load(std::memory_order_acquire)) LIKELY { bool force{CalcContextParams(ctx)}; - auto sorted_slots = const_cast(slots.data() + slots.size()); + auto sorted_slot_base = al::to_address(sorted_slots.begin()); for(EffectSlot *slot : slots) - force |= CalcEffectSlotParams(slot, sorted_slots, ctx); + force |= CalcEffectSlotParams(slot, sorted_slot_base, ctx); for(Voice *voice : voices) { @@ -1847,16 +1962,19 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) { ASSUME(SamplesToDo > 0); - const nanoseconds curtime{device->ClockBase + - nanoseconds{seconds{device->SamplesDone}}/device->Frequency}; + const nanoseconds curtime{device->mClockBase.load(std::memory_order_relaxed) + + nanoseconds{seconds{device->mSamplesDone.load(std::memory_order_relaxed)}}/ + device->Frequency}; for(ContextBase *ctx : *device->mContexts.load(std::memory_order_acquire)) { - const EffectSlotArray &auxslots = *ctx->mActiveAuxSlots.load(std::memory_order_acquire); + const auto auxslotspan = al::span{*ctx->mActiveAuxSlots.load(std::memory_order_acquire)}; + const auto auxslots = auxslotspan.first(auxslotspan.size()>>1); + const auto sorted_slots = auxslotspan.last(auxslotspan.size()>>1); const al::span voices{ctx->getVoicesSpanAcquired()}; - /* Process pending propery updates for objects on the context. */ - ProcessParamUpdates(ctx, auxslots, voices); + /* Process pending property updates for objects on the context. */ + ProcessParamUpdates(ctx, auxslots, sorted_slots, voices); /* Clear auxiliary effect slot mixing buffers. */ for(EffectSlot *slot : auxslots) @@ -1874,24 +1992,19 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) } /* Process effects. */ - if(const size_t num_slots{auxslots.size()}) + if(!auxslots.empty()) { - auto slots = auxslots.data(); - auto slots_end = slots + num_slots; - /* Sort the slots into extra storage, so that effect slots come - * before their effect slot target (or their targets' target). + * before their effect slot target (or their targets' target). Skip + * sorting if it has already been done. */ - const al::span sorted_slots{const_cast(slots_end), - num_slots}; - /* Skip sorting if it has already been done. */ if(!sorted_slots[0]) { /* First, copy the slots to the sorted list, then partition the * sorted list so that all slots without a target slot go to * the end. */ - std::copy(slots, slots_end, sorted_slots.begin()); + std::copy(auxslots.begin(), auxslots.end(), sorted_slots.begin()); auto split_point = std::partition(sorted_slots.begin(), sorted_slots.end(), [](const EffectSlot *slot) noexcept -> bool { return slot->Target != nullptr; }); @@ -1945,44 +2058,46 @@ void ProcessContexts(DeviceBase *device, const uint SamplesToDo) void ApplyDistanceComp(const al::span Samples, const size_t SamplesToDo, - const DistanceComp::ChanData *distcomp) + const al::span chandata) { ASSUME(SamplesToDo > 0); + auto distcomp = chandata.begin(); for(auto &chanbuffer : Samples) { const float gain{distcomp->Gain}; - const size_t base{distcomp->Length}; - float *distbuf{al::assume_aligned<16>(distcomp->Buffer)}; + auto distbuf = al::span{al::assume_aligned<16>(distcomp->Buffer.data()), + distcomp->Buffer.size()}; ++distcomp; - if(base < 1) - continue; + const size_t base{distbuf.size()}; + if(base < 1) continue; - float *inout{al::assume_aligned<16>(chanbuffer.data())}; - auto inout_end = inout + SamplesToDo; + const auto inout = al::span{al::assume_aligned<16>(chanbuffer.data()), SamplesToDo}; if(SamplesToDo >= base) LIKELY { - auto delay_end = std::rotate(inout, inout_end - base, inout_end); - std::swap_ranges(inout, delay_end, distbuf); + auto delay_end = std::rotate(inout.begin(), inout.end()-ptrdiff_t(base), inout.end()); + std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, distbuf); - std::rotate(distbuf, delay_start, distbuf + base); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); + std::rotate(distbuf.begin(), delay_start, distbuf.begin()+ptrdiff_t(base)); } - std::transform(inout, inout_end, inout, [gain](float s) { return s * gain; }); + std::transform(inout.begin(), inout.end(), inout.begin(), + [gain](float s) { return s*gain; }); } } void ApplyDither(const al::span Samples, uint *dither_seed, const float quant_scale, const size_t SamplesToDo) { + static constexpr double invRNGRange{1.0 / std::numeric_limits::max()}; ASSUME(SamplesToDo > 0); /* Dithering. Generate whitenoise (uniform distribution of random values * between -1 and +1) and add it to the sample values, after scaling up to - * the desired quantization depth amd before rounding. + * the desired quantization depth and before rounding. */ const float invscale{1.0f / quant_scale}; uint seed{*dither_seed}; @@ -1991,7 +2106,7 @@ void ApplyDither(const al::span Samples, uint *dither_seed, float val{sample * quant_scale}; uint rng0{dither_rng(&seed)}; uint rng1{dither_rng(&seed)}; - val += static_cast(rng0*(1.0/UINT_MAX) - rng1*(1.0/UINT_MAX)); + val += static_cast(rng0*invRNGRange - rng1*invRNGRange); return fast_roundf(val) * invscale; }; for(FloatBufferLine &inout : Samples) @@ -2015,12 +2130,12 @@ template<> inline int32_t SampleConv(float val) noexcept * When scaling and clamping for a signed 32-bit integer, these following * values are the best a float can give. */ - return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); + return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> inline int16_t SampleConv(float val) noexcept -{ return static_cast(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } +{ return static_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> inline int8_t SampleConv(float val) noexcept -{ return static_cast(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } +{ return static_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> inline uint32_t SampleConv(float val) noexcept @@ -2030,34 +2145,32 @@ template<> inline uint16_t SampleConv(float val) noexcept template<> inline uint8_t SampleConv(float val) noexcept { return static_cast(SampleConv(val) + 128); } -template +template void Write(const al::span InBuffer, void *OutBuffer, const size_t Offset, const size_t SamplesToDo, const size_t FrameStep) { ASSUME(FrameStep > 0); ASSUME(SamplesToDo > 0); - DevFmtType_t *outbase{static_cast*>(OutBuffer) + Offset*FrameStep}; + const auto output = al::span{static_cast(OutBuffer), (Offset+SamplesToDo)*FrameStep} + .subspan(Offset*FrameStep); size_t c{0}; for(const FloatBufferLine &inbuf : InBuffer) { - DevFmtType_t *out{outbase++}; - auto conv_sample = [FrameStep,&out](const float s) noexcept -> void + auto out = output.begin(); + auto conv_sample = [FrameStep,c,&out](const float s) noexcept { - *out = SampleConv>(s); - out += FrameStep; + out[c] = SampleConv(s); + out += ptrdiff_t(FrameStep); }; - std::for_each(inbuf.begin(), inbuf.begin()+SamplesToDo, conv_sample); + std::for_each_n(inbuf.cbegin(), SamplesToDo, conv_sample); ++c; } if(const size_t extra{FrameStep - c}) { - const auto silence = SampleConv>(0.0f); + const auto silence = SampleConv(0.0f); for(size_t i{0};i < SamplesToDo;++i) - { - std::fill_n(outbase, extra, silence); - outbase += FrameStep; - } + std::fill_n(&output[i*FrameStep + c], extra, silence); } } @@ -2065,28 +2178,28 @@ void Write(const al::span InBuffer, void *OutBuffer, cons uint DeviceBase::renderSamples(const uint numSamples) { - const uint samplesToDo{minu(numSamples, BufferLineSize)}; + const uint samplesToDo{std::min(numSamples, uint{BufferLineSize})}; /* Clear main mixing buffers. */ for(FloatBufferLine &buffer : MixBuffer) buffer.fill(0.0f); - /* Increment the mix count at the start (lsb should now be 1). */ - IncrementRef(MixCount); + { + const auto mixLock = getWriteMixLock(); - /* Process and mix each context's sources and effects. */ - ProcessContexts(this, samplesToDo); + /* Process and mix each context's sources and effects. */ + ProcessContexts(this, samplesToDo); - /* Increment the clock time. Every second's worth of samples is converted - * and added to clock base so that large sample counts don't overflow - * during conversion. This also guarantees a stable conversion. - */ - SamplesDone += samplesToDo; - ClockBase += std::chrono::seconds{SamplesDone / Frequency}; - SamplesDone %= Frequency; - - /* Increment the mix count at the end (lsb should now be 0). */ - IncrementRef(MixCount); + /* Every second's worth of samples is converted and added to clock base + * so that large sample counts don't overflow during conversion. This + * also guarantees a stable conversion. + */ + auto samplesDone = mSamplesDone.load(std::memory_order_relaxed) + samplesToDo; + auto clockBase = mClockBase.load(std::memory_order_relaxed) + + std::chrono::seconds{samplesDone/Frequency}; + mSamplesDone.store(samplesDone%Frequency, std::memory_order_relaxed); + mClockBase.store(clockBase, std::memory_order_relaxed); + } /* Apply any needed post-process for finalizing the Dry mix to the RealOut * (Ambisonic decode, UHJ encode, etc). @@ -2098,7 +2211,7 @@ uint DeviceBase::renderSamples(const uint numSamples) /* Apply delays and attenuation for mismatched speaker distances. */ if(ChannelDelays) - ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels.data()); + ApplyDistanceComp(RealOut.Buffer, samplesToDo, ChannelDelays->mChannels); /* Apply dithering. The compressor should have left enough headroom for the * dither noise to not saturate. @@ -2117,10 +2230,11 @@ void DeviceBase::renderSamples(const al::span outBuffers, const uint num { const uint samplesToDo{renderSamples(todo)}; - auto *srcbuf = RealOut.Buffer.data(); + auto srcbuf = RealOut.Buffer.cbegin(); for(auto *dstbuf : outBuffers) { - std::copy_n(srcbuf->data(), samplesToDo, dstbuf + total); + const auto dst = al::span{dstbuf, numSamples}.subspan(total); + std::copy_n(srcbuf->cbegin(), samplesToDo, dst.begin()); ++srcbuf; } @@ -2144,7 +2258,7 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz switch(FmtType) { #define HANDLE_WRITE(T) case T: \ - Write(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; + Write>(RealOut.Buffer, outBuffer, total, samplesToDo, frameStep); break; HANDLE_WRITE(DevFmtByte) HANDLE_WRITE(DevFmtUByte) HANDLE_WRITE(DevFmtShort) @@ -2162,34 +2276,43 @@ void DeviceBase::renderSamples(void *outBuffer, const uint numSamples, const siz void DeviceBase::handleDisconnect(const char *msg, ...) { - IncrementRef(MixCount); + const auto mixLock = getWriteMixLock(); + if(Connected.exchange(false, std::memory_order_acq_rel)) { - AsyncEvent evt{AsyncEvent::Disconnected}; + AsyncEvent evt{std::in_place_type}; + auto &disconnect = std::get(evt); - va_list args; + /* NOLINTBEGIN(*-array-to-pointer-decay) */ + va_list args, args2; va_start(args, msg); - int msglen{vsnprintf(evt.u.disconnect.msg, sizeof(evt.u.disconnect.msg), msg, args)}; + va_copy(args2, args); + if(int msglen{vsnprintf(nullptr, 0, msg, args)}; msglen > 0) + { + disconnect.msg.resize(static_cast(msglen)+1_uz); + vsnprintf(disconnect.msg.data(), disconnect.msg.size(), msg, args2); + } + else + disconnect.msg = ""; + va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ - if(msglen < 0 || static_cast(msglen) >= sizeof(evt.u.disconnect.msg)) - evt.u.disconnect.msg[sizeof(evt.u.disconnect.msg)-1] = 0; + while(!disconnect.msg.empty() && disconnect.msg.back() == '\0') + disconnect.msg.pop_back(); for(ContextBase *ctx : *mContexts.load()) { - if(ctx->mEnabledEvts.load(std::memory_order_acquire).test(AsyncEvent::Disconnected)) + RingBuffer *ring{ctx->mAsyncEvents.get()}; + auto evt_data = ring->getWriteVector().first; + if(evt_data.len > 0) { - RingBuffer *ring{ctx->mAsyncEvents.get()}; - auto evt_data = ring->getWriteVector().first; - if(evt_data.len > 0) - { - al::construct_at(reinterpret_cast(evt_data.buf), evt); - ring->writeAdvance(1); - ctx->mEventSem.post(); - } + al::construct_at(reinterpret_cast(evt_data.buf), evt); + ring->writeAdvance(1); + ctx->mEventSem.post(); } - if(!ctx->mStopVoicesOnDisconnect) + if(!ctx->mStopVoicesOnDisconnect.load()) { ProcessVoiceChanges(ctx); continue; @@ -2206,5 +2329,4 @@ void DeviceBase::handleDisconnect(const char *msg, ...) std::for_each(voicelist.begin(), voicelist.end(), stop_voice); } } - IncrementRef(MixCount); } diff --git a/Engine/lib/openal-soft/alc/alu.h b/Engine/lib/openal-soft/alc/alu.h index 67fd09e57..ef7ddd4c5 100644 --- a/Engine/lib/openal-soft/alc/alu.h +++ b/Engine/lib/openal-soft/alc/alu.h @@ -2,20 +2,20 @@ #define ALU_H #include - -#include "aloptional.h" +#include +#include struct ALCcontext; struct ALCdevice; struct EffectSlot; -enum class StereoEncoding : unsigned char; +enum class StereoEncoding : std::uint8_t; constexpr float GainMixMax{1000.0f}; /* +60dB */ -enum CompatFlags : uint8_t { +enum CompatFlags : std::uint8_t { ReverseX, ReverseY, ReverseZ, @@ -31,7 +31,7 @@ void aluInit(CompatFlagBitset flags, const float nfcscale); * Set up the appropriate panning method and mixing method given the device * properties. */ -void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode); +void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode); void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context); diff --git a/Engine/lib/openal-soft/alc/backends/alsa.cpp b/Engine/lib/openal-soft/alc/backends/alsa.cpp index d620a83cc..b0acf015d 100644 --- a/Engine/lib/openal-soft/alc/backends/alsa.cpp +++ b/Engine/lib/openal-soft/alc/backends/alsa.cpp @@ -31,29 +31,33 @@ #include #include #include +#include #include +#include #include #include +#include -#include "albyte.h" +#include "albit.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include namespace { -constexpr char alsaDevice[] = "ALSA Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "ALSA Default"sv; } #ifdef HAVE_DYNLOAD @@ -248,59 +252,97 @@ struct DevMap { { } }; -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; -const char *prefix_name(snd_pcm_stream_t stream) +std::string_view prefix_name(snd_pcm_stream_t stream) noexcept { - assert(stream == SND_PCM_STREAM_PLAYBACK || stream == SND_PCM_STREAM_CAPTURE); - return (stream==SND_PCM_STREAM_PLAYBACK) ? "device-prefix" : "capture-prefix"; + if(stream == SND_PCM_STREAM_PLAYBACK) + return "device-prefix"sv; + return "capture-prefix"sv; } -al::vector probe_devices(snd_pcm_stream_t stream) +struct SndCtlCardInfo { + snd_ctl_card_info_t *mInfo{}; + + SndCtlCardInfo() { snd_ctl_card_info_malloc(&mInfo); } + ~SndCtlCardInfo() { if(mInfo) snd_ctl_card_info_free(mInfo); } + SndCtlCardInfo(const SndCtlCardInfo&) = delete; + SndCtlCardInfo& operator=(const SndCtlCardInfo&) = delete; + + [[nodiscard]] + operator snd_ctl_card_info_t*() const noexcept { return mInfo; } +}; + +struct SndPcmInfo { + snd_pcm_info_t *mInfo{}; + + SndPcmInfo() { snd_pcm_info_malloc(&mInfo); } + ~SndPcmInfo() { if(mInfo) snd_pcm_info_free(mInfo); } + SndPcmInfo(const SndPcmInfo&) = delete; + SndPcmInfo& operator=(const SndPcmInfo&) = delete; + + [[nodiscard]] + operator snd_pcm_info_t*() const noexcept { return mInfo; } +}; + +struct SndCtl { + snd_ctl_t *mHandle{}; + + SndCtl() = default; + ~SndCtl() { if(mHandle) snd_ctl_close(mHandle); } + SndCtl(const SndCtl&) = delete; + SndCtl& operator=(const SndCtl&) = delete; + + [[nodiscard]] + auto open(const char *name, int mode) { return snd_ctl_open(&mHandle, name, mode); } + + [[nodiscard]] + operator snd_ctl_t*() const noexcept { return mHandle; } +}; + + +std::vector probe_devices(snd_pcm_stream_t stream) { - al::vector devlist; + std::vector devlist; - snd_ctl_card_info_t *info; - snd_ctl_card_info_malloc(&info); - snd_pcm_info_t *pcminfo; - snd_pcm_info_malloc(&pcminfo); + SndCtlCardInfo info; + SndPcmInfo pcminfo; - auto defname = ConfigValueStr(nullptr, "alsa", - (stream == SND_PCM_STREAM_PLAYBACK) ? "device" : "capture"); - devlist.emplace_back(alsaDevice, defname ? defname->c_str() : "default"); + auto defname = ConfigValueStr({}, "alsa"sv, + (stream == SND_PCM_STREAM_PLAYBACK) ? "device"sv : "capture"sv); + devlist.emplace_back(GetDefaultName(), defname ? std::string_view{*defname} : "default"sv); - if(auto customdevs = ConfigValueStr(nullptr, "alsa", - (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices" : "custom-captures")) + if(auto customdevs = ConfigValueStr({}, "alsa"sv, + (stream == SND_PCM_STREAM_PLAYBACK) ? "custom-devices"sv : "custom-captures"sv)) { - size_t nextpos{customdevs->find_first_not_of(';')}; - size_t curpos; - while((curpos=nextpos) < customdevs->length()) + size_t curpos{customdevs->find_first_not_of(';')}; + while(curpos < customdevs->length()) { - nextpos = customdevs->find_first_of(';', curpos+1); - - size_t seppos{customdevs->find_first_of('=', curpos)}; + size_t nextpos{customdevs->find(';', curpos+1)}; + const size_t seppos{customdevs->find('=', curpos)}; if(seppos == curpos || seppos >= nextpos) { - std::string spec{customdevs->substr(curpos, nextpos-curpos)}; + const std::string spec{customdevs->substr(curpos, nextpos-curpos)}; ERR("Invalid ALSA device specification \"%s\"\n", spec.c_str()); } else { - devlist.emplace_back(customdevs->substr(curpos, seppos-curpos), - customdevs->substr(seppos+1, nextpos-seppos-1)); - const auto &entry = devlist.back(); + const std::string_view strview{*customdevs}; + const auto &entry = devlist.emplace_back(strview.substr(curpos, seppos-curpos), + strview.substr(seppos+1, nextpos-seppos-1)); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } if(nextpos < customdevs->length()) nextpos = customdevs->find_first_not_of(';', nextpos+1); + curpos = nextpos; } } - const std::string main_prefix{ - ConfigValueStr(nullptr, "alsa", prefix_name(stream)).value_or("plughw:")}; + const std::string main_prefix{ConfigValueStr({}, "alsa"sv, prefix_name(stream)) + .value_or("plughw:")}; int card{-1}; int err{snd_card_next(&card)}; @@ -308,16 +350,17 @@ al::vector probe_devices(snd_pcm_stream_t stream) { std::string name{"hw:" + std::to_string(card)}; - snd_ctl_t *handle; - if((err=snd_ctl_open(&handle, name.c_str(), 0)) < 0) + SndCtl handle; + err = handle.open(name.c_str(), 0); + if(err < 0) { ERR("control open (hw:%d): %s\n", card, snd_strerror(err)); continue; } - if((err=snd_ctl_card_info(handle, info)) < 0) + err = snd_ctl_card_info(handle, info); + if(err < 0) { ERR("control hardware info (hw:%d): %s\n", card, snd_strerror(err)); - snd_ctl_close(handle); continue; } @@ -326,8 +369,7 @@ al::vector probe_devices(snd_pcm_stream_t stream) name = prefix_name(stream); name += '-'; name += cardid; - const std::string card_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(main_prefix)}; + const std::string card_prefix{ConfigValueStr({}, "alsa"sv, name).value_or(main_prefix)}; int dev{-1}; while(true) @@ -339,7 +381,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) snd_pcm_info_set_device(pcminfo, static_cast(dev)); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, stream); - if((err=snd_ctl_pcm_info(handle, pcminfo)) < 0) + err = snd_ctl_pcm_info(handle, pcminfo); + if(err < 0) { if(err != -ENOENT) ERR("control digital audio info (hw:%d): %s\n", card, snd_strerror(err)); @@ -352,8 +395,8 @@ al::vector probe_devices(snd_pcm_stream_t stream) name += cardid; name += '-'; name += std::to_string(dev); - const std::string device_prefix{ - ConfigValueStr(nullptr, "alsa", name.c_str()).value_or(card_prefix)}; + const std::string device_prefix{ConfigValueStr({}, "alsa"sv, name) + .value_or(card_prefix)}; /* "CardName, PcmName (CARD=cardid,DEV=dev)" */ name = cardname; @@ -372,18 +415,13 @@ al::vector probe_devices(snd_pcm_stream_t stream) device += ",DEV="; device += std::to_string(dev); - devlist.emplace_back(std::move(name), std::move(device)); - const auto &entry = devlist.back(); + const auto &entry = devlist.emplace_back(std::move(name), std::move(device)); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } - snd_ctl_close(handle); } if(err < 0) ERR("snd_card_next failed: %s\n", snd_strerror(err)); - snd_pcm_info_free(pcminfo); - snd_ctl_card_info_free(info); - return devlist; } @@ -392,7 +430,6 @@ int verify_state(snd_pcm_t *handle) { snd_pcm_state_t state{snd_pcm_state(handle)}; - int err; switch(state) { case SND_PCM_STATE_OPEN: @@ -405,15 +442,27 @@ int verify_state(snd_pcm_t *handle) break; case SND_PCM_STATE_XRUN: - if((err=snd_pcm_recover(handle, -EPIPE, 1)) < 0) + if(int err{snd_pcm_recover(handle, -EPIPE, 1)}; err < 0) return err; break; case SND_PCM_STATE_SUSPENDED: - if((err=snd_pcm_recover(handle, -ESTRPIPE, 1)) < 0) + if(int err{snd_pcm_recover(handle, -ESTRPIPE, 1)}; err < 0) return err; break; + case SND_PCM_STATE_DISCONNECTED: return -ENODEV; + + /* ALSA headers have made this enum public, leaving us in a bind: use + * the enum despite being private and internal to the libasound, or + * ignore when an enum value isn't handled. We can't rely on it being + * declared either, since older headers don't have it and it could be + * removed in the future. We can't even really rely on its value, since + * being private/internal means it's subject to change, but this is the + * best we can do. + */ + case 1024 /*SND_PCM_STATE_PRIVATE1*/: + assert(state != 1024); } return state; @@ -427,7 +476,7 @@ struct AlsaPlayback final : public BackendBase { int mixerProc(); int mixerNoMMapProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -439,12 +488,10 @@ struct AlsaPlayback final : public BackendBase { std::mutex mMutex; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(AlsaPlayback) }; AlsaPlayback::~AlsaPlayback() @@ -458,7 +505,7 @@ AlsaPlayback::~AlsaPlayback() int AlsaPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; @@ -506,7 +553,7 @@ int AlsaPlayback::mixerProc() avail -= avail%update_size; // it is possible that contiguous areas are smaller, thus we use a loop - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; while(avail > 0) { snd_pcm_uframes_t frames{avail}; @@ -520,6 +567,7 @@ int AlsaPlayback::mixerProc() break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ char *WritePtr{static_cast(areas->addr) + (offset * areas->step / 8)}; mDevice->renderSamples(WritePtr, static_cast(frames), mFrameStep); @@ -541,7 +589,7 @@ int AlsaPlayback::mixerProc() int AlsaPlayback::mixerNoMMapProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const snd_pcm_uframes_t update_size{mDevice->UpdateSize}; const snd_pcm_uframes_t buffer_size{mDevice->BufferSize}; @@ -585,13 +633,13 @@ int AlsaPlayback::mixerNoMMapProc() continue; } - al::byte *WritePtr{mBuffer.data()}; + auto WritePtr = mBuffer.begin(); avail = snd_pcm_bytes_to_frames(mPcmHandle, static_cast(mBuffer.size())); - std::lock_guard _{mMutex}; - mDevice->renderSamples(WritePtr, static_cast(avail), mFrameStep); + std::lock_guard dlock{mMutex}; + mDevice->renderSamples(al::to_address(WritePtr), static_cast(avail), mFrameStep); while(avail > 0) { - snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, WritePtr, + snd_pcm_sframes_t ret{snd_pcm_writei(mPcmHandle, al::to_address(WritePtr), static_cast(avail))}; switch(ret) { @@ -626,10 +674,10 @@ int AlsaPlayback::mixerNoMMapProc() } -void AlsaPlayback::open(const char *name) +void AlsaPlayback::open(std::string_view name) { std::string driver{"default"}; - if(name) + if(!name.empty()) { if(PlaybackDevices.empty()) PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); @@ -638,13 +686,13 @@ void AlsaPlayback::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; driver = iter->device_name; } else { - name = alsaDevice; - if(auto driveropt = ConfigValueStr(nullptr, "alsa", "device")) + name = GetDefaultName(); + if(auto driveropt = ConfigValueStr({}, "alsa"sv, "device"sv)) driver = std::move(driveropt).value(); } TRACE("Opening device \"%s\"\n", driver.c_str()); @@ -692,15 +740,14 @@ bool AlsaPlayback::reset() break; } - bool allowmmap{!!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "mmap", true)}; + bool allowmmap{GetConfigValueBool(mDevice->DeviceName, "alsa"sv, "mmap"sv, true)}; uint periodLen{static_cast(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)}; uint bufferLen{static_cast(mDevice->BufferSize * 1000000_u64 / mDevice->Frequency)}; uint rate{mDevice->Frequency}; - int err{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -715,17 +762,18 @@ bool AlsaPlayback::reset() /* test and set format (implicitly sets sample bits) */ if(snd_pcm_hw_params_test_format(mPcmHandle, hp.get(), format) < 0) { - static const struct { + struct FormatMap { snd_pcm_format_t format; DevFmtType fmttype; - } formatlist[] = { - { SND_PCM_FORMAT_FLOAT, DevFmtFloat }, - { SND_PCM_FORMAT_S32, DevFmtInt }, - { SND_PCM_FORMAT_U32, DevFmtUInt }, - { SND_PCM_FORMAT_S16, DevFmtShort }, - { SND_PCM_FORMAT_U16, DevFmtUShort }, - { SND_PCM_FORMAT_S8, DevFmtByte }, - { SND_PCM_FORMAT_U8, DevFmtUByte }, + }; + static constexpr std::array formatlist{ + FormatMap{SND_PCM_FORMAT_FLOAT, DevFmtFloat }, + FormatMap{SND_PCM_FORMAT_S32, DevFmtInt }, + FormatMap{SND_PCM_FORMAT_U32, DevFmtUInt }, + FormatMap{SND_PCM_FORMAT_S16, DevFmtShort }, + FormatMap{SND_PCM_FORMAT_U16, DevFmtUShort}, + FormatMap{SND_PCM_FORMAT_S8, DevFmtByte }, + FormatMap{SND_PCM_FORMAT_U8, DevFmtUByte }, }; for(const auto &fmt : formatlist) @@ -750,7 +798,7 @@ bool AlsaPlayback::reset() else mDevice->FmtChans = DevFmtStereo; } /* set rate (implicitly constrains period/buffer parameters) */ - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "alsa", "allow-resampler", false) + if(!GetConfigValueBool(mDevice->DeviceName, "alsa", "allow-resampler", false) || !mDevice->Flags.test(FrequencyRequest)) { if(snd_pcm_hw_params_set_rate_resample(mPcmHandle, hp.get(), 0) < 0) @@ -760,10 +808,10 @@ bool AlsaPlayback::reset() WARN("Failed to enable ALSA resampler\n"); CHECK(snd_pcm_hw_params_set_rate_near(mPcmHandle, hp.get(), &rate, nullptr)); /* set period time (implicitly constrains period/buffer parameters) */ - if((err=snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)) < 0) + if(int err{snd_pcm_hw_params_set_period_time_near(mPcmHandle, hp.get(), &periodLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_period_time_near failed: %s\n", snd_strerror(err)); /* set buffer time (implicitly sets buffer size/bytes/time and period size/bytes) */ - if((err=snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)) < 0) + if(int err{snd_pcm_hw_params_set_buffer_time_near(mPcmHandle, hp.get(), &bufferLen, nullptr)}; err < 0) ERR("snd_pcm_hw_params_set_buffer_time_near failed: %s\n", snd_strerror(err)); /* install and prepare hardware configuration */ CHECK(snd_pcm_hw_params(mPcmHandle, hp.get())); @@ -798,11 +846,10 @@ bool AlsaPlayback::reset() void AlsaPlayback::start() { - int err{}; snd_pcm_access_t access{}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -849,10 +896,9 @@ void AlsaPlayback::stop() ClockLatency AlsaPlayback::getClockLatency() { - ClockLatency ret; - - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) @@ -871,23 +917,21 @@ struct AlsaCapture final : public BackendBase { AlsaCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~AlsaCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; snd_pcm_t *mPcmHandle{nullptr}; - al::vector mBuffer; + std::vector mBuffer; bool mDoCapture{false}; RingBufferPtr mRing{nullptr}; snd_pcm_sframes_t mLastAvail{0}; - - DEF_NEWDEL(AlsaCapture) }; AlsaCapture::~AlsaCapture() @@ -898,10 +942,10 @@ AlsaCapture::~AlsaCapture() } -void AlsaCapture::open(const char *name) +void AlsaCapture::open(std::string_view name) { std::string driver{"default"}; - if(name) + if(!name.empty()) { if(CaptureDevices.empty()) CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); @@ -910,19 +954,18 @@ void AlsaCapture::open(const char *name) [name](const DevMap &entry) -> bool { return entry.name == name; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; driver = iter->device_name; } else { - name = alsaDevice; - if(auto driveropt = ConfigValueStr(nullptr, "alsa", "capture")) + name = GetDefaultName(); + if(auto driveropt = ConfigValueStr({}, "alsa"sv, "capture"sv)) driver = std::move(driveropt).value(); } TRACE("Opening device \"%s\"\n", driver.c_str()); - int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; - if(err < 0) + if(int err{snd_pcm_open(&mPcmHandle, driver.c_str(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)}; err < 0) throw al::backend_exception{al::backend_error::NoDevice, "Could not open ALSA device \"%s\"", driver.c_str()}; @@ -955,13 +998,15 @@ void AlsaCapture::open(const char *name) break; } - snd_pcm_uframes_t bufferSizeInFrames{maxu(mDevice->BufferSize, 100*mDevice->Frequency/1000)}; - snd_pcm_uframes_t periodSizeInFrames{minu(mDevice->BufferSize, 25*mDevice->Frequency/1000)}; + snd_pcm_uframes_t bufferSizeInFrames{std::max(mDevice->BufferSize, + 100u*mDevice->Frequency/1000u)}; + snd_pcm_uframes_t periodSizeInFrames{std::min(mDevice->BufferSize, + 25u*mDevice->Frequency/1000u)}; bool needring{false}; HwParamsPtr hp{CreateHwParams()}; #define CHECK(x) do { \ - if((err=(x)) < 0) \ + if(int err{x}; err < 0) \ throw al::backend_exception{al::backend_error::DeviceError, #x " failed: %s", \ snd_strerror(err)}; \ } while(0) @@ -999,13 +1044,11 @@ void AlsaCapture::open(const char *name) void AlsaCapture::start() { - int err{snd_pcm_prepare(mPcmHandle)}; - if(err < 0) + if(int err{snd_pcm_prepare(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_prepare failed: %s", snd_strerror(err)}; - err = snd_pcm_start(mPcmHandle); - if(err < 0) + if(int err{snd_pcm_start(mPcmHandle)}; err < 0) throw al::backend_exception{al::backend_error::DeviceError, "snd_pcm_start failed: %s", snd_strerror(err)}; @@ -1024,25 +1067,27 @@ void AlsaCapture::stop() /* The ring buffer implicitly captures when checking availability. * Direct access needs to explicitly capture it into temp storage. */ - auto temp = al::vector( + auto temp = std::vector( static_cast(snd_pcm_frames_to_bytes(mPcmHandle, avail))); captureSamples(temp.data(), avail); mBuffer = std::move(temp); } - int err{snd_pcm_drop(mPcmHandle)}; - if(err < 0) + if(int err{snd_pcm_drop(mPcmHandle)}; err < 0) ERR("drop failed: %s\n", snd_strerror(err)); mDoCapture = false; } -void AlsaCapture::captureSamples(al::byte *buffer, uint samples) +void AlsaCapture::captureSamples(std::byte *buffer, uint samples) { if(mRing) { - mRing->read(buffer, samples); + std::ignore = mRing->read(buffer, samples); return; } + const auto outspan = al::span{buffer, + static_cast(snd_pcm_frames_to_bytes(mPcmHandle, samples))}; + auto outiter = outspan.begin(); mLastAvail -= samples; while(mDevice->Connected.load(std::memory_order_acquire) && samples > 0) { @@ -1055,20 +1100,21 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples) if(static_cast(amt) > samples) amt = samples; amt = snd_pcm_frames_to_bytes(mPcmHandle, amt); - std::copy_n(mBuffer.begin(), amt, buffer); + std::copy_n(mBuffer.begin(), amt, outiter); mBuffer.erase(mBuffer.begin(), mBuffer.begin()+amt); amt = snd_pcm_bytes_to_frames(mPcmHandle, amt); } else if(mDoCapture) - amt = snd_pcm_readi(mPcmHandle, buffer, samples); + amt = snd_pcm_readi(mPcmHandle, al::to_address(outiter), samples); if(amt < 0) { ERR("read error: %s\n", snd_strerror(static_cast(amt))); if(amt == -EAGAIN) continue; - if((amt=snd_pcm_recover(mPcmHandle, static_cast(amt), 1)) >= 0) + amt = snd_pcm_recover(mPcmHandle, static_cast(amt), 1); + if(amt >= 0) { amt = snd_pcm_start(mPcmHandle); if(amt >= 0) @@ -1088,12 +1134,12 @@ void AlsaCapture::captureSamples(al::byte *buffer, uint samples) continue; } - buffer = buffer + amt; + outiter += amt; samples -= static_cast(amt); } if(samples > 0) - std::fill_n(buffer, snd_pcm_frames_to_bytes(mPcmHandle, samples), - al::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); + std::fill_n(outiter, snd_pcm_frames_to_bytes(mPcmHandle, samples), + std::byte((mDevice->FmtType == DevFmtUByte) ? 0x80 : 0)); } uint AlsaCapture::availableSamples() @@ -1105,7 +1151,8 @@ uint AlsaCapture::availableSamples() { ERR("avail update failed: %s\n", snd_strerror(static_cast(avail))); - if((avail=snd_pcm_recover(mPcmHandle, static_cast(avail), 1)) >= 0) + avail = snd_pcm_recover(mPcmHandle, static_cast(avail), 1); + if(avail >= 0) { if(mDoCapture) avail = snd_pcm_start(mPcmHandle); @@ -1141,7 +1188,8 @@ uint AlsaCapture::availableSamples() if(amt == -EAGAIN) continue; - if((amt=snd_pcm_recover(mPcmHandle, static_cast(amt), 1)) >= 0) + amt = snd_pcm_recover(mPcmHandle, static_cast(amt), 1); + if(amt >= 0) { if(mDoCapture) amt = snd_pcm_start(mPcmHandle); @@ -1168,9 +1216,8 @@ uint AlsaCapture::availableSamples() ClockLatency AlsaCapture::getClockLatency() { - ClockLatency ret; - - ret.ClockTime = GetDeviceClockTime(mDevice); + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); snd_pcm_sframes_t delay{}; int err{snd_pcm_delay(mPcmHandle, &delay)}; if(err < 0) @@ -1189,13 +1236,9 @@ ClockLatency AlsaCapture::getClockLatency() bool AlsaBackendFactory::init() { - bool error{false}; - #ifdef HAVE_DYNLOAD if(!alsa_handle) { - std::string missing_funcs; - alsa_handle = LoadLib("libasound.so.2"); if(!alsa_handle) { @@ -1203,52 +1246,47 @@ bool AlsaBackendFactory::init() return false; } - error = false; + std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(alsa_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) ALSA_FUNCS(LOAD_FUNC); #undef LOAD_FUNC - if(error) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(alsa_handle); alsa_handle = nullptr; + return false; } } #endif - return !error; + return true; } bool AlsaBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string AlsaBackendFactory::probe(BackendType type) +auto AlsaBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; - + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.emplace_back(entry.name); }; + switch(type) { case BackendType::Playback: PlaybackDevices = probe_devices(SND_PCM_STREAM_PLAYBACK); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices = probe_devices(SND_PCM_STREAM_CAPTURE); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/Engine/lib/openal-soft/alc/backends/alsa.h b/Engine/lib/openal-soft/alc/backends/alsa.h index b256dcf5a..1327db8a4 100644 --- a/Engine/lib/openal-soft/alc/backends/alsa.h +++ b/Engine/lib/openal-soft/alc/backends/alsa.h @@ -5,15 +5,15 @@ struct AlsaBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_ALSA_H */ diff --git a/Engine/lib/openal-soft/alc/backends/base.cpp b/Engine/lib/openal-soft/alc/backends/base.cpp index e5ad84943..4f90fef1f 100644 --- a/Engine/lib/openal-soft/alc/backends/base.cpp +++ b/Engine/lib/openal-soft/alc/backends/base.cpp @@ -7,17 +7,6 @@ #include #include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#include - -#include "albit.h" -#include "core/logging.h" -#include "aloptional.h" -#endif - -#include "atomic.h" #include "core/devformat.h" @@ -25,10 +14,12 @@ namespace al { backend_exception::backend_exception(backend_error code, const char *msg, ...) : mErrorCode{code} { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args; va_start(args, msg); setMessage(msg, args); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ } backend_exception::~backend_exception() = default; @@ -38,7 +29,7 @@ backend_exception::~backend_exception() = default; bool BackendBase::reset() { throw al::backend_exception{al::backend_error::DeviceError, "Invalid BackendBase call"}; } -void BackendBase::captureSamples(al::byte*, uint) +void BackendBase::captureSamples(std::byte*, uint) { } uint BackendBase::availableSamples() @@ -46,27 +37,26 @@ uint BackendBase::availableSamples() ClockLatency BackendBase::getClockLatency() { - ClockLatency ret; + ClockLatency ret{}; uint refcount; do { refcount = mDevice->waitForMix(); - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != ReadRef(mDevice->MixCount)); + } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* NOTE: The device will generally have about all but one periods filled at * any given time during playback. Without a more accurate measurement from * the output, this is an okay approximation. */ - ret.Latency = std::max(std::chrono::seconds{mDevice->BufferSize-mDevice->UpdateSize}, - std::chrono::seconds::zero()); + ret.Latency = std::chrono::seconds{mDevice->BufferSize - mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; return ret; } -void BackendBase::setDefaultWFXChannelOrder() +void BackendBase::setDefaultWFXChannelOrder() const { mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); @@ -126,6 +116,24 @@ void BackendBase::setDefaultWFXChannelOrder() mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; break; + case DevFmtX7144: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[FrontCenter] = 2; + mDevice->RealOut.ChannelIndex[LFE] = 3; + mDevice->RealOut.ChannelIndex[BackLeft] = 4; + mDevice->RealOut.ChannelIndex[BackRight] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12; + mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13; + mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14; + mDevice->RealOut.ChannelIndex[BottomBackRight] = 15; + break; case DevFmtX3D71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; @@ -141,7 +149,7 @@ void BackendBase::setDefaultWFXChannelOrder() } } -void BackendBase::setDefaultChannelOrder() +void BackendBase::setDefaultChannelOrder() const { mDevice->RealOut.ChannelIndex.fill(InvalidChannelIndex); @@ -179,6 +187,24 @@ void BackendBase::setDefaultChannelOrder() mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; mDevice->RealOut.ChannelIndex[TopBackRight] = 11; break; + case DevFmtX7144: + mDevice->RealOut.ChannelIndex[FrontLeft] = 0; + mDevice->RealOut.ChannelIndex[FrontRight] = 1; + mDevice->RealOut.ChannelIndex[BackLeft] = 2; + mDevice->RealOut.ChannelIndex[BackRight] = 3; + mDevice->RealOut.ChannelIndex[FrontCenter] = 4; + mDevice->RealOut.ChannelIndex[LFE] = 5; + mDevice->RealOut.ChannelIndex[SideLeft] = 6; + mDevice->RealOut.ChannelIndex[SideRight] = 7; + mDevice->RealOut.ChannelIndex[TopFrontLeft] = 8; + mDevice->RealOut.ChannelIndex[TopFrontRight] = 9; + mDevice->RealOut.ChannelIndex[TopBackLeft] = 10; + mDevice->RealOut.ChannelIndex[TopBackRight] = 11; + mDevice->RealOut.ChannelIndex[BottomFrontLeft] = 12; + mDevice->RealOut.ChannelIndex[BottomFrontRight] = 13; + mDevice->RealOut.ChannelIndex[BottomBackLeft] = 14; + mDevice->RealOut.ChannelIndex[BottomBackRight] = 15; + break; case DevFmtX3D71: mDevice->RealOut.ChannelIndex[FrontLeft] = 0; mDevice->RealOut.ChannelIndex[FrontRight] = 1; diff --git a/Engine/lib/openal-soft/alc/backends/base.h b/Engine/lib/openal-soft/alc/backends/base.h index b6b3d9227..2503c3df2 100644 --- a/Engine/lib/openal-soft/alc/backends/base.h +++ b/Engine/lib/openal-soft/alc/backends/base.h @@ -3,13 +3,16 @@ #include #include +#include #include #include #include +#include +#include -#include "albyte.h" #include "core/device.h" #include "core/except.h" +#include "alc/events.h" using uint = unsigned int; @@ -20,27 +23,33 @@ struct ClockLatency { }; struct BackendBase { - virtual void open(const char *name) = 0; + virtual void open(std::string_view name) = 0; virtual bool reset(); virtual void start() = 0; virtual void stop() = 0; - virtual void captureSamples(al::byte *buffer, uint samples); + virtual void captureSamples(std::byte *buffer, uint samples); virtual uint availableSamples(); virtual ClockLatency getClockLatency(); DeviceBase *const mDevice; + BackendBase() = delete; + BackendBase(const BackendBase&) = delete; + BackendBase(BackendBase&&) = delete; BackendBase(DeviceBase *device) noexcept : mDevice{device} { } virtual ~BackendBase() = default; + void operator=(const BackendBase&) = delete; + void operator=(BackendBase&&) = delete; + protected: /** Sets the default channel order used by most non-WaveFormatEx-based APIs. */ - void setDefaultChannelOrder(); + void setDefaultChannelOrder() const; /** Sets the default channel order used by WaveFormatEx. */ - void setDefaultWFXChannelOrder(); + void setDefaultWFXChannelOrder() const; }; using BackendPtr = std::unique_ptr; @@ -50,18 +59,6 @@ enum class BackendType { }; -/* Helper to get the current clock time from the device's ClockBase, and - * SamplesDone converted from the sample rate. - */ -inline std::chrono::nanoseconds GetDeviceClockTime(DeviceBase *device) -{ - using std::chrono::seconds; - using std::chrono::nanoseconds; - - auto ns = nanoseconds{seconds{device->SamplesDone}} / device->Frequency; - return device->ClockBase + ns; -} - /* Helper to get the device latency from the backend, including any fixed * latency from post-processing. */ @@ -74,16 +71,24 @@ inline ClockLatency GetClockLatency(DeviceBase *device, BackendBase *backend) struct BackendFactory { - virtual bool init() = 0; - - virtual bool querySupport(BackendType type) = 0; - - virtual std::string probe(BackendType type) = 0; - - virtual BackendPtr createBackend(DeviceBase *device, BackendType type) = 0; - -protected: + BackendFactory() = default; + BackendFactory(const BackendFactory&) = delete; + BackendFactory(BackendFactory&&) = delete; virtual ~BackendFactory() = default; + + void operator=(const BackendFactory&) = delete; + void operator=(BackendFactory&&) = delete; + + virtual auto init() -> bool = 0; + + virtual auto querySupport(BackendType type) -> bool = 0; + + virtual auto queryEventSupport(alc::EventType, BackendType) -> alc::EventSupport + { return alc::EventSupport::NoSupport; } + + virtual auto enumerate(BackendType type) -> std::vector = 0; + + virtual auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr = 0; }; namespace al { @@ -98,15 +103,15 @@ class backend_exception final : public base_exception { backend_error mErrorCode; public: -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] #else [[gnu::format(printf, 3, 4)]] #endif backend_exception(backend_error code, const char *msg, ...); ~backend_exception() override; - backend_error errorCode() const noexcept { return mErrorCode; } + [[nodiscard]] auto errorCode() const noexcept -> backend_error { return mErrorCode; } }; } // namespace al diff --git a/Engine/lib/openal-soft/alc/backends/coreaudio.cpp b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp index 8b0e75fdf..fd4abce04 100644 --- a/Engine/lib/openal-soft/alc/backends/coreaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.cpp @@ -22,18 +22,20 @@ #include "coreaudio.h" -#include +#include +#include +#include #include #include #include +#include #include #include - -#include -#include -#include +#include +#include #include "alnumeric.h" +#include "alstring.h" #include "core/converter.h" #include "core/device.h" #include "core/logging.h" @@ -42,18 +44,40 @@ #include #include - -namespace { - #if TARGET_OS_IOS || TARGET_OS_TV #define CAN_ENUMERATE 0 #else +#include #define CAN_ENUMERATE 1 #endif +namespace { + constexpr auto OutputElement = 0; constexpr auto InputElement = 1; +struct FourCCPrinter { + char mString[sizeof(UInt32) + 1]{}; + + constexpr FourCCPrinter(UInt32 code) noexcept + { + for(size_t i{0};i < sizeof(UInt32);++i) + { + const auto ch = static_cast(code & 0xff); + /* If this breaks early it'll leave the first byte null, to get + * read as a 0-length string. + */ + if(ch <= 0x1f || ch >= 0x7f) + break; + mString[sizeof(UInt32)-1-i] = ch; + code >>= 8; + } + } + constexpr FourCCPrinter(int code) noexcept : FourCCPrinter{static_cast(code)} { } + + constexpr const char *c_str() const noexcept { return mString; } +}; + #if CAN_ENUMERATE struct DeviceEntry { AudioDeviceID mId; @@ -147,7 +171,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) &propSize); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration size query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration size query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -158,7 +183,8 @@ UInt32 GetDeviceChannelCount(AudioDeviceID devId, bool isCapture) buflist); if(err) { - ERR("kAudioDevicePropertyStreamConfiguration query failed: %u\n", err); + ERR("kAudioDevicePropertyStreamConfiguration query failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return 0; } @@ -182,7 +208,7 @@ void EnumerateDevices(std::vector &list, bool isCapture) auto devIds = std::vector(propSize/sizeof(AudioDeviceID), kAudioDeviceUnknown); if(auto err = GetHwProperty(kAudioHardwarePropertyDevices, propSize, devIds.data())) { - ERR("Failed to get device list: %u\n", err); + ERR("Failed to get device list: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return; } @@ -247,6 +273,48 @@ void EnumerateDevices(std::vector &list, bool isCapture) newdevs.swap(list); } +struct DeviceHelper { + DeviceHelper() + { + AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + OSStatus status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); + if (status != noErr) + ERR("AudioObjectAddPropertyListener fail: %d", status); + } + ~DeviceHelper() + { + AudioObjectPropertyAddress addr{kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMain}; + OSStatus status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &addr, DeviceListenerProc, nil); + if (status != noErr) + ERR("AudioObjectRemovePropertyListener fail: %d", status); + } + + static OSStatus DeviceListenerProc(AudioObjectID /*inObjectID*/, UInt32 inNumberAddresses, + const AudioObjectPropertyAddress *inAddresses, void* /*inClientData*/) + { + for(UInt32 i = 0; i < inNumberAddresses; ++i) + { + switch(inAddresses[i].mSelector) + { + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultSystemOutputDevice: + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + "Default playback device changed: "+std::to_string(inAddresses[i].mSelector)); + break; + case kAudioHardwarePropertyDefaultInputDevice: + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + "Default capture device changed: "+std::to_string(inAddresses[i].mSelector)); + break; + } + } + return noErr; + } +}; + +static std::optional sDeviceHelper; + #else static constexpr char ca_device[] = "CoreAudio Default"; @@ -260,15 +328,8 @@ struct CoreAudioPlayback final : public BackendBase { OSStatus MixerProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus MixerProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -277,8 +338,6 @@ struct CoreAudioPlayback final : public BackendBase { uint mFrameSize{0u}; AudioStreamBasicDescription mFormat{}; // This is the OpenAL format as a CoreAudio ASBD - - DEF_NEWDEL(CoreAudioPlayback) }; CoreAudioPlayback::~CoreAudioPlayback() @@ -301,11 +360,11 @@ OSStatus CoreAudioPlayback::MixerProc(AudioUnitRenderActionFlags*, const AudioTi } -void CoreAudioPlayback::open(const char *name) +void CoreAudioPlayback::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultOutputDevice, sizeof(audioDevice), &audioDevice); else @@ -318,16 +377,16 @@ void CoreAudioPlayback::open(const char *name) auto devmatch = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), find_name); if(devmatch == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; #endif /* open the default output unit */ @@ -351,7 +410,7 @@ void CoreAudioPlayback::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &audioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -362,7 +421,7 @@ void CoreAudioPlayback::open(const char *name) err = AudioUnitInitialize(audioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* WARNING: I don't know if "valid" audio unit values are guaranteed to be * non-0. If not, this logic is broken. @@ -375,7 +434,7 @@ void CoreAudioPlayback::open(const char *name) mAudioUnit = audioUnit; #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { @@ -388,6 +447,21 @@ void CoreAudioPlayback::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else mDevice->DeviceName = "Unknown Device Name"; } + + if(audioDevice != kAudioDeviceUnknown) + { + UInt32 type{}; + err = GetDevProperty(audioDevice, kAudioDevicePropertyDataSource, false, + kAudioObjectPropertyElementMaster, sizeof(type), &type); + if(err != noErr) + ERR("Failed to get audio device type: %u\n", err); + else + { + TRACE("Got device type '%s'\n", FourCCPrinter{type}.c_str()); + mDevice->Flags.set(DirectEar, (type == kIOAudioOutputPortSubTypeHeadphones)); + } + } + #else mDevice->DeviceName = name; #endif @@ -397,7 +471,7 @@ bool CoreAudioPlayback::reset() { OSStatus err{AudioUnitUninitialize(mAudioUnit)}; if(err != noErr) - ERR("-- AudioUnitUninitialize failed.\n"); + ERR("AudioUnitUninitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); /* retrieve default output unit's properties (output side) */ AudioStreamBasicDescription streamFormat{}; @@ -406,7 +480,8 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, &size); if(err != noErr || size != sizeof(streamFormat)) { - ERR("AudioUnitGetProperty failed\n"); + ERR("AudioUnitGetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -473,7 +548,8 @@ bool CoreAudioPlayback::reset() OutputElement, &streamFormat, sizeof(streamFormat)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(StreamFormat) failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), + err); return false; } @@ -482,14 +558,16 @@ bool CoreAudioPlayback::reset() /* setup callback */ mFrameSize = mDevice->frameSizeFromFmt(); AURenderCallbackStruct input{}; - input.inputProc = CoreAudioPlayback::MixerProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast(inRefCon)->MixerProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, OutputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) { - ERR("AudioUnitSetProperty failed\n"); + ERR("AudioUnitSetProperty(SetRenderCallback) failed: '%s' (%u)\n", + FourCCPrinter{err}.c_str(), err); return false; } @@ -497,7 +575,7 @@ bool CoreAudioPlayback::reset() err = AudioUnitInitialize(mAudioUnit); if(err != noErr) { - ERR("AudioUnitInitialize failed\n"); + ERR("AudioUnitInitialize failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return false; } @@ -509,14 +587,14 @@ void CoreAudioPlayback::start() const OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioPlayback::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } @@ -527,18 +605,11 @@ struct CoreAudioCapture final : public BackendBase { OSStatus RecordProc(AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept; - static OSStatus RecordProcC(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, - const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, - AudioBufferList *ioData) noexcept - { - return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, - inBusNumber, inNumberFrames, ioData); - } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; AudioUnit mAudioUnit{0}; @@ -548,11 +619,9 @@ struct CoreAudioCapture final : public BackendBase { SampleConverterPtr mConverter; - al::vector mCaptureData; + std::vector mCaptureData; RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(CoreAudioCapture) }; CoreAudioCapture::~CoreAudioCapture() @@ -568,7 +637,7 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, AudioBufferList*) noexcept { union { - al::byte _[maxz(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; + std::byte buf[std::max(sizeof(AudioBufferList), offsetof(AudioBufferList, mBuffers[1]))]; AudioBufferList list; } audiobuf{}; @@ -581,20 +650,20 @@ OSStatus CoreAudioCapture::RecordProc(AudioUnitRenderActionFlags *ioActionFlags, inNumberFrames, &audiobuf.list)}; if(err != noErr) { - ERR("AudioUnitRender capture error: %d\n", err); + ERR("AudioUnitRender capture error: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); return err; } - mRing->write(mCaptureData.data(), inNumberFrames); + std::ignore = mRing->write(mCaptureData.data(), inNumberFrames); return noErr; } -void CoreAudioCapture::open(const char *name) +void CoreAudioCapture::open(std::string_view name) { #if CAN_ENUMERATE AudioDeviceID audioDevice{kAudioDeviceUnknown}; - if(!name) + if(name.empty()) GetHwProperty(kAudioHardwarePropertyDefaultInputDevice, sizeof(audioDevice), &audioDevice); else @@ -607,16 +676,16 @@ void CoreAudioCapture::open(const char *name) auto devmatch = std::find_if(CaptureList.cbegin(), CaptureList.cend(), find_name); if(devmatch == CaptureList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; audioDevice = devmatch->mId; } #else - if(!name) + if(name.empty()) name = ca_device; - else if(strcmp(name, ca_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + else if(name != ca_device) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; #endif AudioComponentDescription desc{}; @@ -640,7 +709,7 @@ void CoreAudioCapture::open(const char *name) OSStatus err{AudioComponentInstanceNew(comp, &mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::NoDevice, - "Could not create component instance: %u", err}; + "Could not create component instance: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Turn off AudioUnit output UInt32 enableIO{0}; @@ -648,7 +717,8 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Output, OutputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable audio unit output property: %u", err}; + "Could not disable audio unit output property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Turn on AudioUnit input enableIO = 1; @@ -656,7 +726,8 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Input, InputElement, &enableIO, sizeof(enableIO)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not enable audio unit input property: %u", err}; + "Could not enable audio unit input property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; #if CAN_ENUMERATE if(audioDevice != kAudioDeviceUnknown) @@ -666,14 +737,15 @@ void CoreAudioCapture::open(const char *name) // set capture callback AURenderCallbackStruct input{}; - input.inputProc = CoreAudioCapture::RecordProcC; + input.inputProc = [](void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) noexcept + { return static_cast(inRefCon)->RecordProc(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); }; input.inputProcRefCon = this; err = AudioUnitSetProperty(mAudioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, InputElement, &input, sizeof(AURenderCallbackStruct)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set capture callback: %u", err}; + "Could not set capture callback: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Disable buffer allocation for capture UInt32 flag{0}; @@ -681,13 +753,14 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Output, InputElement, &flag, sizeof(flag)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not disable buffer allocation property: %u", err}; + "Could not disable buffer allocation property: '%s' (%u)", FourCCPrinter{err}.c_str(), + err}; // Initialize the device err = AudioUnitInitialize(mAudioUnit); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not initialize audio unit: %u", err}; + "Could not initialize audio unit: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Get the hardware format AudioStreamBasicDescription hardwareFormat{}; @@ -696,7 +769,7 @@ void CoreAudioCapture::open(const char *name) InputElement, &hardwareFormat, &propertySize); if(err != noErr || propertySize != sizeof(hardwareFormat)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input format: %u", err}; + "Could not get input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; // Set up the requested format description AudioStreamBasicDescription requestedFormat{}; @@ -749,6 +822,7 @@ void CoreAudioCapture::open(const char *name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s not supported", @@ -777,14 +851,14 @@ void CoreAudioCapture::open(const char *name) InputElement, &outputFormat, sizeof(outputFormat)); if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "Could not set input format: %u", err}; + "Could not set input format: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; /* Calculate the minimum AudioUnit output format frame count for the pre- * conversion ring buffer. Ensure at least 100ms for the total buffer. */ double srateScale{outputFormat.mSampleRate / mDevice->Frequency}; - auto FrameCount64 = maxu64(static_cast(std::ceil(mDevice->BufferSize*srateScale)), - static_cast(outputFormat.mSampleRate)/10); + auto FrameCount64 = std::max(static_cast(std::ceil(mDevice->BufferSize*srateScale)), + static_cast(outputFormat.mSampleRate)/10_u64); FrameCount64 += MaxResamplerPadding; if(FrameCount64 > std::numeric_limits::max()) throw al::backend_exception{al::backend_error::DeviceError, @@ -796,11 +870,11 @@ void CoreAudioCapture::open(const char *name) kAudioUnitScope_Global, OutputElement, &outputFrameCount, &propertySize); if(err != noErr || propertySize != sizeof(outputFrameCount)) throw al::backend_exception{al::backend_error::DeviceError, - "Could not get input frame count: %u", err}; + "Could not get input frame count: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; mCaptureData.resize(outputFrameCount * mFrameSize); - outputFrameCount = static_cast(maxu64(outputFrameCount, FrameCount64)); + outputFrameCount = static_cast(std::max(uint64_t{outputFrameCount}, FrameCount64)); mRing = RingBuffer::Create(outputFrameCount, mFrameSize, false); /* Set up sample converter if needed */ @@ -810,7 +884,7 @@ void CoreAudioCapture::open(const char *name) mDevice->Frequency, Resampler::FastBSinc24); #if CAN_ENUMERATE - if(name) + if(!name.empty()) mDevice->DeviceName = name; else { @@ -834,21 +908,21 @@ void CoreAudioCapture::start() OSStatus err{AudioOutputUnitStart(mAudioUnit)}; if(err != noErr) throw al::backend_exception{al::backend_error::DeviceError, - "AudioOutputUnitStart failed: %d", err}; + "AudioOutputUnitStart failed: '%s' (%u)", FourCCPrinter{err}.c_str(), err}; } void CoreAudioCapture::stop() { OSStatus err{AudioOutputUnitStop(mAudioUnit)}; if(err != noErr) - ERR("AudioOutputUnitStop failed\n"); + ERR("AudioOutputUnitStop failed: '%s' (%u)\n", FourCCPrinter{err}.c_str(), err); } -void CoreAudioCapture::captureSamples(al::byte *buffer, uint samples) +void CoreAudioCapture::captureSamples(std::byte *buffer, uint samples) { if(!mConverter) { - mRing->read(buffer, samples); + std::ignore = mRing->read(buffer, samples); return; } @@ -882,28 +956,34 @@ BackendFactory &CoreAudioBackendFactory::getFactory() return factory; } -bool CoreAudioBackendFactory::init() { return true; } +bool CoreAudioBackendFactory::init() +{ +#if CAN_ENUMERATE + sDeviceHelper.emplace(); +#endif + return true; +} bool CoreAudioBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string CoreAudioBackendFactory::probe(BackendType type) +auto CoreAudioBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; #if CAN_ENUMERATE auto append_name = [&outnames](const DeviceEntry &entry) -> void - { - /* Includes null char. */ - outnames.append(entry.mName.c_str(), entry.mName.length()+1); - }; + { outnames.emplace_back(entry.mName); }; + switch(type) { case BackendType::Playback: EnumerateDevices(PlaybackList, false); + outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: EnumerateDevices(CaptureList, true); + outnames.reserve(CaptureList.size()); std::for_each(CaptureList.cbegin(), CaptureList.cend(), append_name); break; } @@ -914,8 +994,7 @@ std::string CoreAudioBackendFactory::probe(BackendType type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(ca_device, sizeof(ca_device)); + outnames.emplace_back(ca_device); break; } #endif @@ -930,3 +1009,18 @@ BackendPtr CoreAudioBackendFactory::createBackend(DeviceBase *device, BackendTyp return BackendPtr{new CoreAudioCapture{device}}; return nullptr; } + +alc::EventSupport CoreAudioBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/coreaudio.h b/Engine/lib/openal-soft/alc/backends/coreaudio.h index 1252edde3..26c2aaf98 100644 --- a/Engine/lib/openal-soft/alc/backends/coreaudio.h +++ b/Engine/lib/openal-soft/alc/backends/coreaudio.h @@ -5,15 +5,17 @@ struct CoreAudioBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_COREAUDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/dsound.cpp b/Engine/lib/openal-soft/alc/backends/dsound.cpp index f549c0fe6..0f5993c6d 100644 --- a/Engine/lib/openal-soft/alc/backends/dsound.cpp +++ b/Engine/lib/openal-soft/alc/backends/dsound.cpp @@ -25,10 +25,6 @@ #define WIN32_LEAN_AND_MEAN #include -#include -#include -#include - #include #include #ifndef _WAVEFORMATEXTENSIBLE_ @@ -36,15 +32,21 @@ #include #endif +#include #include #include -#include -#include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include -#include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/device.h" #include "core/helpers.h" @@ -52,7 +54,6 @@ #include "dynload.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" /* MinGW-w64 needs this for some unknown reason now. */ using LPCWAVEFORMATEX = const WAVEFORMATEX*; @@ -129,10 +130,10 @@ struct DevMap { { } }; -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; -bool checkName(const al::vector &list, const std::string &name) +bool checkName(const al::span list, const std::string &name) { auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; @@ -144,7 +145,7 @@ BOOL CALLBACK DSoundEnumDevices(GUID *guid, const WCHAR *desc, const WCHAR*, voi if(!guid) return TRUE; - auto& devices = *static_cast*>(data); + auto& devices = *static_cast*>(data); const std::string basename{DEVNAME_HEAD + wstr_to_utf8(desc)}; int count{1}; @@ -176,7 +177,7 @@ struct DSoundPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -189,8 +190,6 @@ struct DSoundPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(DSoundPlayback) }; DSoundPlayback::~DSoundPlayback() @@ -209,7 +208,7 @@ DSoundPlayback::~DSoundPlayback() FORCE_ALIGN int DSoundPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); DSBCAPS DSBCaps{}; DSBCaps.dwSize = sizeof(DSBCaps); @@ -299,24 +298,22 @@ FORCE_ALIGN int DSoundPlayback::mixerProc() return 0; } -void DSoundPlayback::open(const char *name) +void DSoundPlayback::open(std::string_view name) { HRESULT hr; if(PlaybackDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !PlaybackDevices.empty()) + if(name.empty() && !PlaybackDevices.empty()) { - name = PlaybackDevices[0].name.c_str(); + name = PlaybackDevices[0].name; guid = &PlaybackDevices[0].guid; } else @@ -332,7 +329,7 @@ void DSoundPlayback::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; } guid = &iter->guid; } @@ -347,7 +344,7 @@ void DSoundPlayback::open(const char *name) //DirectSound Init code ComPtr ds; if(SUCCEEDED(hr)) - hr = DirectSoundCreate(guid, ds.getPtr(), nullptr); + hr = DirectSoundCreate(guid, al::out_ptr(ds), nullptr); if(SUCCEEDED(hr)) hr = ds->SetCooperativeLevel(GetForegroundWindow(), DSSCL_PRIORITY); if(FAILED(hr)) @@ -425,49 +422,53 @@ bool DSoundPlayback::reset() case DevFmtX51: OutputType.dwChannelMask = isRear51 ? X5DOT1REAR : X5DOT1; break; case DevFmtX61: OutputType.dwChannelMask = X6DOT1; break; case DevFmtX71: OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX7144: mDevice->FmtChans = DevFmtX714; + /* fall-through */ case DevFmtX714: OutputType.dwChannelMask = X7DOT1DOT4; break; case DevFmtX3D71: OutputType.dwChannelMask = X7DOT1; break; } -retry_open: - hr = S_OK; - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); - OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); - OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * - OutputType.Format.wBitsPerSample / 8); - OutputType.Format.nSamplesPerSec = mDevice->Frequency; - OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * - OutputType.Format.nBlockAlign; - OutputType.Format.cbSize = 0; + do { + hr = S_OK; + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.nChannels = static_cast(mDevice->channelsFromFmt()); + OutputType.Format.wBitsPerSample = static_cast(mDevice->bytesFromFmt() * 8); + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * + OutputType.Format.wBitsPerSample / 8); + OutputType.Format.nSamplesPerSec = mDevice->Frequency; + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * + OutputType.Format.nBlockAlign; + OutputType.Format.cbSize = 0; - if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) - { - OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; - OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - if(mDevice->FmtType == DevFmtFloat) - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - - mPrimaryBuffer = nullptr; - } - else - { - if(SUCCEEDED(hr) && !mPrimaryBuffer) + if(OutputType.Format.nChannels > 2 || mDevice->FmtType == DevFmtFloat) { - DSBUFFERDESC DSBDescription{}; - DSBDescription.dwSize = sizeof(DSBDescription); - DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; - hr = mDS->CreateSoundBuffer(&DSBDescription, mPrimaryBuffer.getPtr(), nullptr); - } - if(SUCCEEDED(hr)) - hr = mPrimaryBuffer->SetFormat(&OutputType.Format); - } + OutputType.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + if(mDevice->FmtType == DevFmtFloat) + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + else + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + mPrimaryBuffer = nullptr; + } + else + { + if(SUCCEEDED(hr) && !mPrimaryBuffer) + { + DSBUFFERDESC DSBDescription{}; + DSBDescription.dwSize = sizeof(DSBDescription); + DSBDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mPrimaryBuffer), nullptr); + } + if(SUCCEEDED(hr)) + hr = mPrimaryBuffer->SetFormat(&OutputType.Format); + } + + if(FAILED(hr)) + break; - if(SUCCEEDED(hr)) - { uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; if(num_updates > MAX_UPDATES) num_updates = MAX_UPDATES; @@ -480,26 +481,21 @@ retry_open: DSBDescription.dwBufferBytes = mDevice->BufferSize * OutputType.Format.nBlockAlign; DSBDescription.lpwfxFormat = &OutputType.Format; - hr = mDS->CreateSoundBuffer(&DSBDescription, mBuffer.getPtr(), nullptr); - if(FAILED(hr) && mDevice->FmtType == DevFmtFloat) - { - mDevice->FmtType = DevFmtShort; - goto retry_open; - } - } + hr = mDS->CreateSoundBuffer(&DSBDescription, al::out_ptr(mBuffer), nullptr); + if(SUCCEEDED(hr) || mDevice->FmtType != DevFmtFloat) + break; + mDevice->FmtType = DevFmtShort; + } while(FAILED(hr)); if(SUCCEEDED(hr)) { - void *ptr; - hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, &ptr); + hr = mBuffer->QueryInterface(IID_IDirectSoundNotify, al::out_ptr(mNotifies)); if(SUCCEEDED(hr)) { - mNotifies = ComPtr{static_cast(ptr)}; - uint num_updates{mDevice->BufferSize / mDevice->UpdateSize}; assert(num_updates <= MAX_UPDATES); - std::array nots; + std::array nots{}; for(uint i{0};i < num_updates;++i) { nots[i].dwOffset = i * mDevice->UpdateSize * OutputType.Format.nBlockAlign; @@ -550,10 +546,10 @@ struct DSoundCapture final : public BackendBase { DSoundCapture(DeviceBase *device) noexcept : BackendBase{device} { } ~DSoundCapture() override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ComPtr mDSC; @@ -562,8 +558,6 @@ struct DSoundCapture final : public BackendBase { DWORD mCursor{0u}; RingBufferPtr mRing; - - DEF_NEWDEL(DSoundCapture) }; DSoundCapture::~DSoundCapture() @@ -577,24 +571,22 @@ DSoundCapture::~DSoundCapture() } -void DSoundCapture::open(const char *name) +void DSoundCapture::open(std::string_view name) { HRESULT hr; if(CaptureDevices.empty()) { /* Initialize COM to prevent name truncation */ - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); if(FAILED(hr)) ERR("Error enumerating DirectSound devices (0x%lx)!\n", hr); - if(SUCCEEDED(hrcom)) - CoUninitialize(); } const GUID *guid{nullptr}; - if(!name && !CaptureDevices.empty()) + if(name.empty() && !CaptureDevices.empty()) { - name = CaptureDevices[0].name.c_str(); + name = CaptureDevices[0].name; guid = &CaptureDevices[0].guid; } else @@ -610,7 +602,7 @@ void DSoundCapture::open(const char *name) [&id](const DevMap &entry) -> bool { return entry.guid == id; }); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; } guid = &iter->guid; } @@ -641,6 +633,7 @@ void DSoundCapture::open(const char *name) case DevFmtX61: InputType.dwChannelMask = X6DOT1; break; case DevFmtX71: InputType.dwChannelMask = X7DOT1; break; case DevFmtX714: InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: WARN("%s capture not supported\n", DevFmtChannelsString(mDevice->FmtChans)); @@ -657,6 +650,7 @@ void DSoundCapture::open(const char *name) InputType.Format.nAvgBytesPerSec = InputType.Format.nSamplesPerSec * InputType.Format.nBlockAlign; InputType.Format.cbSize = 0; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; if(mDevice->FmtType == DevFmtFloat) InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; @@ -669,8 +663,7 @@ void DSoundCapture::open(const char *name) InputType.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); } - uint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; DSCBUFFERDESC DSCBDescription{}; DSCBDescription.dwSize = sizeof(DSCBDescription); @@ -679,9 +672,9 @@ void DSoundCapture::open(const char *name) DSCBDescription.lpwfxFormat = &InputType.Format; //DirectSoundCapture Init code - hr = DirectSoundCaptureCreate(guid, mDSC.getPtr(), nullptr); + hr = DirectSoundCaptureCreate(guid, al::out_ptr(mDSC), nullptr); if(SUCCEEDED(hr)) - mDSC->CreateCaptureBuffer(&DSCBDescription, mDSCbuffer.getPtr(), nullptr); + mDSC->CreateCaptureBuffer(&DSCBDescription, al::out_ptr(mDSCbuffer), nullptr); if(SUCCEEDED(hr)) mRing = RingBuffer::Create(mDevice->BufferSize, InputType.Format.nBlockAlign, false); @@ -719,8 +712,8 @@ void DSoundCapture::stop() } } -void DSoundCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void DSoundCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint DSoundCapture::availableSamples() { @@ -743,9 +736,9 @@ uint DSoundCapture::availableSamples() } if(SUCCEEDED(hr)) { - mRing->write(ReadPtr1, ReadCnt1/FrameSize); + std::ignore = mRing->write(ReadPtr1, ReadCnt1/FrameSize); if(ReadPtr2 != nullptr && ReadCnt2 > 0) - mRing->write(ReadPtr2, ReadCnt2/FrameSize); + std::ignore = mRing->write(ReadPtr2, ReadCnt2/FrameSize); hr = mDSCbuffer->Unlock(ReadPtr1, ReadCnt1, ReadPtr2, ReadCnt2); mCursor = ReadCursor; } @@ -802,40 +795,32 @@ bool DSoundBackendFactory::init() bool DSoundBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string DSoundBackendFactory::probe(BackendType type) +auto DSoundBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.emplace_back(entry.name); }; /* Initialize COM to prevent name truncation */ - HRESULT hr; - HRESULT hrcom{CoInitialize(nullptr)}; + ComWrapper com{}; switch(type) { case BackendType::Playback: PlaybackDevices.clear(); - hr = DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices); - if(FAILED(hr)) + if(HRESULT hr{DirectSoundEnumerateW(DSoundEnumDevices, &PlaybackDevices)}; FAILED(hr)) ERR("Error enumerating DirectSound playback devices (0x%lx)!\n", hr); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); - hr = DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices); - if(FAILED(hr)) + if(HRESULT hr{DirectSoundCaptureEnumerateW(DSoundEnumDevices, &CaptureDevices)};FAILED(hr)) ERR("Error enumerating DirectSound capture devices (0x%lx)!\n", hr); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } - if(SUCCEEDED(hrcom)) - CoUninitialize(); return outnames; } diff --git a/Engine/lib/openal-soft/alc/backends/dsound.h b/Engine/lib/openal-soft/alc/backends/dsound.h index 787f227a0..33adbf297 100644 --- a/Engine/lib/openal-soft/alc/backends/dsound.h +++ b/Engine/lib/openal-soft/alc/backends/dsound.h @@ -5,15 +5,15 @@ struct DSoundBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_DSOUND_H */ diff --git a/Engine/lib/openal-soft/alc/backends/jack.cpp b/Engine/lib/openal-soft/alc/backends/jack.cpp index 791002ca9..39cfeb39c 100644 --- a/Engine/lib/openal-soft/alc/backends/jack.cpp +++ b/Engine/lib/openal-soft/alc/backends/jack.cpp @@ -22,23 +22,26 @@ #include "jack.h" +#include #include #include #include #include - -#include +#include #include #include +#include #include "alc/alconfig.h" #include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include "threads.h" #include #include @@ -46,6 +49,8 @@ namespace { +using namespace std::string_view_literals; + #ifdef HAVE_DYNLOAD #define JACK_FUNCS(MAGIC) \ MAGIC(jack_client_open); \ @@ -99,19 +104,13 @@ decltype(jack_error_callback) * pjack_error_callback; #endif -constexpr char JackDefaultAudioType[] = JACK_DEFAULT_AUDIO_TYPE; - jack_options_t ClientOptions = JackNullOption; bool jack_load() { - bool error{false}; - #ifdef HAVE_DYNLOAD if(!jack_handle) { - std::string missing_funcs; - #ifdef _WIN32 #define JACKLIB "libjack.dll" #else @@ -124,13 +123,10 @@ bool jack_load() return false; } - error = false; + std::string missing_funcs; #define LOAD_FUNC(f) do { \ p##f = reinterpret_cast(GetSymbol(jack_handle, #f)); \ - if(p##f == nullptr) { \ - error = true; \ - missing_funcs += "\n" #f; \ - } \ + if(p##f == nullptr) missing_funcs += "\n" #f; \ } while(0) JACK_FUNCS(LOAD_FUNC); #undef LOAD_FUNC @@ -139,61 +135,66 @@ bool jack_load() LOAD_SYM(jack_error_callback); #undef LOAD_SYM - if(error) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(jack_handle); jack_handle = nullptr; + return false; } } #endif - return !error; + return true; } struct JackDeleter { void operator()(void *ptr) { jack_free(ptr); } }; -using JackPortsPtr = std::unique_ptr; +using JackPortsPtr = std::unique_ptr; /* NOLINT(*-avoid-c-arrays) */ struct DeviceEntry { std::string mName; std::string mPattern; + DeviceEntry() = default; + DeviceEntry(const DeviceEntry&) = default; + DeviceEntry(DeviceEntry&&) = default; template DeviceEntry(T&& name, U&& pattern) : mName{std::forward(name)}, mPattern{std::forward(pattern)} { } + ~DeviceEntry(); + + DeviceEntry& operator=(const DeviceEntry&) = default; + DeviceEntry& operator=(DeviceEntry&&) = default; }; +DeviceEntry::~DeviceEntry() = default; -al::vector PlaybackList; +std::vector PlaybackList; -void EnumerateDevices(jack_client_t *client, al::vector &list) +void EnumerateDevices(jack_client_t *client, std::vector &list) { std::remove_reference_t{}.swap(list); - if(JackPortsPtr ports{jack_get_ports(client, nullptr, JackDefaultAudioType, JackPortIsInput)}) + if(JackPortsPtr ports{jack_get_ports(client, nullptr, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}) { for(size_t i{0};ports[i];++i) { - const char *sep{std::strchr(ports[i], ':')}; - if(!sep || ports[i] == sep) continue; + const std::string_view portname{ports[i]}; + const size_t seppos{portname.find(':')}; + if(seppos == 0 || seppos >= portname.size()) + continue; - const al::span portdev{ports[i], sep}; + const auto portdev = portname.substr(0, seppos); auto check_name = [portdev](const DeviceEntry &entry) -> bool - { - const size_t len{portdev.size()}; - return entry.mName.length() == len - && entry.mName.compare(0, len, portdev.data(), len) == 0; - }; + { return entry.mName == portdev; }; if(std::find_if(list.cbegin(), list.cend(), check_name) != list.cend()) continue; - std::string name{portdev.data(), portdev.size()}; - list.emplace_back(name, name+":"); - const auto &entry = list.back(); + const auto &entry = list.emplace_back(portdev, std::string{portdev}+":"); TRACE("Got device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } /* There are ports but couldn't get device names from them. Add a @@ -202,11 +203,11 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) if(ports[0] && list.empty()) { WARN("No device names found in available ports, adding a generic name.\n"); - list.emplace_back("JACK", ""); + list.emplace_back("JACK"sv, ""sv); } } - if(auto listopt = ConfigValueStr(nullptr, "jack", "custom-devices")) + if(auto listopt = ConfigValueStr({}, "jack", "custom-devices")) { for(size_t strpos{0};strpos < listopt->size();) { @@ -214,38 +215,32 @@ void EnumerateDevices(jack_client_t *client, al::vector &list) size_t seppos{listopt->find('=', strpos)}; if(seppos >= nextpos || seppos == strpos) { - const std::string entry{listopt->substr(strpos, nextpos-strpos)}; - ERR("Invalid device entry: \"%s\"\n", entry.c_str()); + const auto entry = std::string_view{*listopt}.substr(strpos, nextpos-strpos); + ERR("Invalid device entry: \"%.*s\"\n", al::sizei(entry), entry.data()); if(nextpos != std::string::npos) ++nextpos; strpos = nextpos; continue; } - const al::span name{listopt->data()+strpos, seppos-strpos}; - const al::span pattern{listopt->data()+(seppos+1), - std::min(nextpos, listopt->size())-(seppos+1)}; + const auto name = std::string_view{*listopt}.substr(strpos, seppos-strpos); + const auto pattern = std::string_view{*listopt}.substr(seppos+1, + std::min(nextpos, listopt->size())-(seppos+1)); /* Check if this custom pattern already exists in the list. */ auto check_pattern = [pattern](const DeviceEntry &entry) -> bool - { - const size_t len{pattern.size()}; - return entry.mPattern.length() == len - && entry.mPattern.compare(0, len, pattern.data(), len) == 0; - }; + { return entry.mPattern == pattern; }; auto itemmatch = std::find_if(list.begin(), list.end(), check_pattern); if(itemmatch != list.end()) { /* If so, replace the name with this custom one. */ - itemmatch->mName.assign(name.data(), name.size()); + itemmatch->mName = name; TRACE("Customized device name: %s = %s\n", itemmatch->mName.c_str(), itemmatch->mPattern.c_str()); } else { /* Otherwise, add a new device entry. */ - list.emplace_back(std::string{name.data(), name.size()}, - std::string{pattern.data(), pattern.size()}); - const auto &entry = list.back(); + const auto &entry = list.emplace_back(name, pattern); TRACE("Got custom device: %s = %s\n", entry.mName.c_str(), entry.mPattern.c_str()); } @@ -295,7 +290,7 @@ struct JackPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -304,7 +299,7 @@ struct JackPlayback final : public BackendBase { std::string mPortPattern; jack_client_t *mClient{nullptr}; - std::array mPort{}; + std::array mPort{}; std::mutex mMutex; @@ -315,8 +310,6 @@ struct JackPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(JackPlayback) }; JackPlayback::~JackPlayback() @@ -336,22 +329,22 @@ JackPlayback::~JackPlayback() int JackPlayback::processRt(jack_nframes_t numframes) noexcept { - std::array out; - size_t numchans{0}; + auto outptrs = std::array{}; + auto numchans = size_t{0}; for(auto port : mPort) { if(!port || numchans == mDevice->RealOut.Buffer.size()) break; - out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + outptrs[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); } + const auto dst = al::span{outptrs}.first(numchans); if(mPlaying.load(std::memory_order_acquire)) LIKELY - mDevice->renderSamples({out.data(), numchans}, static_cast(numframes)); + mDevice->renderSamples(dst, static_cast(numframes)); else { - auto clear_buf = [numframes](float *outbuf) -> void - { std::fill_n(outbuf, numframes, 0.0f); }; - std::for_each(out.begin(), out.begin()+numchans, clear_buf); + std::for_each(dst.begin(), dst.end(), [numframes](float *outbuf) -> void + { std::fill_n(outbuf, numframes, 0.0f); }); } return 0; @@ -360,53 +353,46 @@ int JackPlayback::processRt(jack_nframes_t numframes) noexcept int JackPlayback::process(jack_nframes_t numframes) noexcept { - std::array out; + std::array,MaxOutputChannels> out; size_t numchans{0}; for(auto port : mPort) { if(!port) break; - out[numchans++] = static_cast(jack_port_get_buffer(port, numframes)); + out[numchans++] = {static_cast(jack_port_get_buffer(port, numframes)), numframes}; } - jack_nframes_t total{0}; + size_t total{0}; if(mPlaying.load(std::memory_order_acquire)) LIKELY { auto data = mRing->getReadVector(); - jack_nframes_t todo{minu(numframes, static_cast(data.first.len))}; - auto write_first = [&data,numchans,todo](float *outbuf) -> float* - { - const float *RESTRICT in = reinterpret_cast(data.first.buf); - auto deinterlace_input = [&in,numchans]() noexcept -> float - { - float ret{*in}; - in += numchans; - return ret; - }; - std::generate_n(outbuf, todo, deinterlace_input); - data.first.buf += sizeof(float); - return outbuf + todo; - }; - std::transform(out.begin(), out.begin()+numchans, out.begin(), write_first); - total += todo; + const auto update_size = size_t{mDevice->UpdateSize}; - todo = minu(numframes-total, static_cast(data.second.len)); - if(todo > 0) + const auto outlen = size_t{numframes / update_size}; + const auto len1 = size_t{std::min(data.first.len/update_size, outlen)}; + const auto len2 = size_t{std::min(data.second.len/update_size, outlen-len1)}; + + auto src = al::span{reinterpret_cast(data.first.buf), update_size*len1*numchans}; + for(size_t i{0};i < len1;++i) { - auto write_second = [&data,numchans,todo](float *outbuf) -> float* + for(size_t c{0};c < numchans;++c) { - const float *RESTRICT in = reinterpret_cast(data.second.buf); - auto deinterlace_input = [&in,numchans]() noexcept -> float - { - float ret{*in}; - in += numchans; - return ret; - }; - std::generate_n(outbuf, todo, deinterlace_input); - data.second.buf += sizeof(float); - return outbuf + todo; - }; - std::transform(out.begin(), out.begin()+numchans, out.begin(), write_second); - total += todo; + const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); + out[c] = {iter, out[c].end()}; + src = src.subspan(update_size); + } + total += update_size; + } + + src = al::span{reinterpret_cast(data.second.buf), update_size*len2*numchans}; + for(size_t i{0};i < len2;++i) + { + for(size_t c{0};c < numchans;++c) + { + const auto iter = std::copy_n(src.begin(), update_size, out[c].begin()); + out[c] = {iter, out[c].end()}; + src = src.subspan(update_size); + } + total += update_size; } mRing->readAdvance(total); @@ -415,8 +401,8 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept if(numframes > total) { - const jack_nframes_t todo{numframes - total}; - auto clear_buf = [todo](float *outbuf) -> void { std::fill_n(outbuf, todo, 0.0f); }; + auto clear_buf = [](const al::span outbuf) -> void + { std::fill(outbuf.begin(), outbuf.end(), 0.0f); }; std::for_each(out.begin(), out.begin()+numchans, clear_buf); } @@ -426,45 +412,70 @@ int JackPlayback::process(jack_nframes_t numframes) noexcept int JackPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); - const size_t frame_step{mDevice->channelsFromFmt()}; + const auto update_size = uint{mDevice->UpdateSize}; + const auto num_channels = size_t{mDevice->channelsFromFmt()}; + auto outptrs = std::vector(num_channels); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - if(mRing->writeSpace() < mDevice->UpdateSize) + if(mRing->writeSpace() < update_size) { mSem.wait(); continue; } auto data = mRing->getWriteVector(); - size_t todo{data.first.len + data.second.len}; - todo -= todo%mDevice->UpdateSize; + const auto len1 = size_t{data.first.len / update_size}; + const auto len2 = size_t{data.second.len / update_size}; - const auto len1 = static_cast(minz(data.first.len, todo)); - const auto len2 = static_cast(minz(data.second.len, todo-len1)); - - std::lock_guard _{mMutex}; - mDevice->renderSamples(data.first.buf, len1, frame_step); + std::lock_guard dlock{mMutex}; + auto buffer = al::span{reinterpret_cast(data.first.buf), + data.first.len*num_channels}; + auto bufiter = buffer.begin(); + for(size_t i{0};i < len1;++i) + { + std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] + { + auto ret = al::to_address(bufiter); + bufiter += ptrdiff_t(update_size); + return ret; + }); + mDevice->renderSamples(outptrs, update_size); + } if(len2 > 0) - mDevice->renderSamples(data.second.buf, len2, frame_step); - mRing->writeAdvance(todo); + { + buffer = al::span{reinterpret_cast(data.second.buf), + data.second.len*num_channels}; + bufiter = buffer.begin(); + for(size_t i{0};i < len2;++i) + { + std::generate_n(outptrs.begin(), outptrs.size(), [&bufiter,update_size] + { + auto ret = al::to_address(bufiter); + bufiter += ptrdiff_t(update_size); + return ret; + }); + mDevice->renderSamples(outptrs, update_size); + } + } + mRing->writeAdvance((len1+len2) * update_size); } return 0; } -void JackPlayback::open(const char *name) +void JackPlayback::open(std::string_view name) { if(!mClient) { const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - jack_status_t status; + jack_status_t status{}; mClient = jack_client_open(client_name, ClientOptions, &status, nullptr); if(mClient == nullptr) throw al::backend_exception{al::backend_error::DeviceError, @@ -481,9 +492,9 @@ void JackPlayback::open(const char *name) if(PlaybackList.empty()) EnumerateDevices(mClient, PlaybackList); - if(!name && !PlaybackList.empty()) + if(name.empty() && !PlaybackList.empty()) { - name = PlaybackList[0].mName.c_str(); + name = PlaybackList[0].mName; mPortPattern = PlaybackList[0].mPattern; } else @@ -493,14 +504,10 @@ void JackPlayback::open(const char *name) auto iter = std::find_if(PlaybackList.cbegin(), PlaybackList.cend(), check_name); if(iter == PlaybackList.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name?name:""}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; mPortPattern = iter->mPattern; } - mRTMixing = GetConfigValueBool(name, "jack", "rt-mix", true); - jack_set_process_callback(mClient, - mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); - mDevice->DeviceName = name; } @@ -511,6 +518,10 @@ bool JackPlayback::reset() std::for_each(mPort.begin(), mPort.end(), unregister_port); mPort.fill(nullptr); + mRTMixing = GetConfigValueBool(mDevice->DeviceName, "jack", "rt-mix", true); + jack_set_process_callback(mClient, + mRTMixing ? &JackPlayback::processRtC : &JackPlayback::processC, this); + /* Ignore the requested buffer metrics and just keep one JACK-sized buffer * ready for when requested. */ @@ -525,9 +536,9 @@ bool JackPlayback::reset() } else { - const char *devname{mDevice->DeviceName.c_str()}; + const std::string_view devname{mDevice->DeviceName}; uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); mDevice->BufferSize = bufsize + mDevice->UpdateSize; } @@ -535,27 +546,27 @@ bool JackPlayback::reset() mDevice->FmtType = DevFmtFloat; int port_num{0}; - auto ports_end = mPort.begin() + mDevice->channelsFromFmt(); - auto bad_port = mPort.begin(); - while(bad_port != ports_end) + auto ports = al::span{mPort}.first(mDevice->channelsFromFmt()); + auto bad_port = ports.begin(); + while(bad_port != ports.end()) { std::string name{"channel_" + std::to_string(++port_num)}; - *bad_port = jack_port_register(mClient, name.c_str(), JackDefaultAudioType, + *bad_port = jack_port_register(mClient, name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); if(!*bad_port) break; ++bad_port; } - if(bad_port != ports_end) + if(bad_port != ports.end()) { ERR("Failed to register enough JACK ports for %s output\n", DevFmtChannelsString(mDevice->FmtChans)); - if(bad_port == mPort.begin()) return false; + if(bad_port == ports.begin()) return false; - if(bad_port == mPort.begin()+1) + if(bad_port == ports.begin()+1) mDevice->FmtChans = DevFmtMono; else { - ports_end = mPort.begin()+2; + const auto ports_end = ports.begin()+2; while(bad_port != ports_end) { jack_port_unregister(mClient, *(--bad_port)); @@ -575,10 +586,10 @@ void JackPlayback::start() if(jack_activate(mClient)) throw al::backend_exception{al::backend_error::DeviceError, "Failed to activate client"}; - const char *devname{mDevice->DeviceName.c_str()}; + const std::string_view devname{mDevice->DeviceName}; if(ConfigValueBool(devname, "jack", "connect-ports").value_or(true)) { - JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JackDefaultAudioType, + JackPortsPtr pnames{jack_get_ports(mClient, mPortPattern.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput)}; if(!pnames) { @@ -586,7 +597,7 @@ void JackPlayback::start() throw al::backend_exception{al::backend_error::DeviceError, "No playback ports found"}; } - for(size_t i{0};i < al::size(mPort) && mPort[i];++i) + for(size_t i{0};i < std::size(mPort) && mPort[i];++i) { if(!pnames[i]) { @@ -613,7 +624,7 @@ void JackPlayback::start() else { uint bufsize{ConfigValueUInt(devname, "jack", "buffer-size").value_or(mDevice->UpdateSize)}; - bufsize = maxu(NextPowerOf2(bufsize), mDevice->UpdateSize); + bufsize = std::max(NextPowerOf2(bufsize), mDevice->UpdateSize); mDevice->BufferSize = bufsize + mDevice->UpdateSize; mRing = RingBuffer::Create(bufsize, mDevice->frameSizeFromFmt(), true); @@ -651,10 +662,9 @@ void JackPlayback::stop() ClockLatency JackPlayback::getClockLatency() { - ClockLatency ret; - - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing ? mRing->readSpace() : mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; @@ -674,7 +684,7 @@ bool JackBackendFactory::init() if(!jack_load()) return false; - if(!GetConfigValueBool(nullptr, "jack", "spawn-server", false)) + if(!GetConfigValueBool({}, "jack", "spawn-server", false)) ClientOptions = static_cast(ClientOptions | JackNoStartServer); const PathNamePair &binname = GetProcBinary(); @@ -682,7 +692,7 @@ bool JackBackendFactory::init() void (*old_error_cb)(const char*){&jack_error_callback ? jack_error_callback : nullptr}; jack_set_error_function(jack_msg_handler); - jack_status_t status; + jack_status_t status{}; jack_client_t *client{jack_client_open(client_name, ClientOptions, &status, nullptr)}; jack_set_error_function(old_error_cb); if(!client) @@ -700,18 +710,15 @@ bool JackBackendFactory::init() bool JackBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -std::string JackBackendFactory::probe(BackendType type) +auto JackBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto append_name = [&outnames](const DeviceEntry &entry) -> void - { - /* Includes null char. */ - outnames.append(entry.mName.c_str(), entry.mName.length()+1); - }; + { outnames.emplace_back(entry.mName); }; const PathNamePair &binname = GetProcBinary(); const char *client_name{binname.fname.empty() ? "alsoft" : binname.fname.c_str()}; - jack_status_t status; + jack_status_t status{}; switch(type) { case BackendType::Playback: @@ -722,6 +729,7 @@ std::string JackBackendFactory::probe(BackendType type) } else WARN("jack_client_open() failed, 0x%02x\n", status); + outnames.reserve(PlaybackList.size()); std::for_each(PlaybackList.cbegin(), PlaybackList.cend(), append_name); break; case BackendType::Capture: diff --git a/Engine/lib/openal-soft/alc/backends/jack.h b/Engine/lib/openal-soft/alc/backends/jack.h index b83f24dda..1e4c9f05b 100644 --- a/Engine/lib/openal-soft/alc/backends/jack.h +++ b/Engine/lib/openal-soft/alc/backends/jack.h @@ -5,15 +5,15 @@ struct JackBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_JACK_H */ diff --git a/Engine/lib/openal-soft/alc/backends/loopback.cpp b/Engine/lib/openal-soft/alc/backends/loopback.cpp index bf4ab2467..e999ca514 100644 --- a/Engine/lib/openal-soft/alc/backends/loopback.cpp +++ b/Engine/lib/openal-soft/alc/backends/loopback.cpp @@ -30,16 +30,14 @@ namespace { struct LoopbackBackend final : public BackendBase { LoopbackBackend(DeviceBase *device) noexcept : BackendBase{device} { } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; - - DEF_NEWDEL(LoopbackBackend) }; -void LoopbackBackend::open(const char *name) +void LoopbackBackend::open(std::string_view name) { mDevice->DeviceName = name; } @@ -65,8 +63,8 @@ bool LoopbackBackendFactory::init() bool LoopbackBackendFactory::querySupport(BackendType) { return true; } -std::string LoopbackBackendFactory::probe(BackendType) -{ return std::string{}; } +auto LoopbackBackendFactory::enumerate(BackendType) -> std::vector +{ return {}; } BackendPtr LoopbackBackendFactory::createBackend(DeviceBase *device, BackendType) { return BackendPtr{new LoopbackBackend{device}}; } diff --git a/Engine/lib/openal-soft/alc/backends/loopback.h b/Engine/lib/openal-soft/alc/backends/loopback.h index cb42b3c8e..876a052cb 100644 --- a/Engine/lib/openal-soft/alc/backends/loopback.h +++ b/Engine/lib/openal-soft/alc/backends/loopback.h @@ -5,15 +5,15 @@ struct LoopbackBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_LOOPBACK_H */ diff --git a/Engine/lib/openal-soft/alc/backends/null.cpp b/Engine/lib/openal-soft/alc/backends/null.cpp index 5a8fc255c..d2e036ad9 100644 --- a/Engine/lib/openal-soft/alc/backends/null.cpp +++ b/Engine/lib/openal-soft/alc/backends/null.cpp @@ -30,10 +30,11 @@ #include #include -#include "core/device.h" #include "almalloc.h" +#include "alstring.h" +#include "althrd_setname.h" +#include "core/device.h" #include "core/helpers.h" -#include "threads.h" namespace { @@ -41,8 +42,9 @@ namespace { using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; +using namespace std::string_view_literals; -constexpr char nullDevice[] = "No Output"; +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "No Output"sv; } struct NullBackend final : public BackendBase { @@ -50,15 +52,13 @@ struct NullBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(NullBackend) }; int NullBackend::mixerProc() @@ -66,7 +66,7 @@ int NullBackend::mixerProc() const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); int64_t done{0}; auto start = std::chrono::steady_clock::now(); @@ -105,13 +105,13 @@ int NullBackend::mixerProc() } -void NullBackend::open(const char *name) +void NullBackend::open(std::string_view name) { - if(!name) - name = nullDevice; - else if(strcmp(name, nullDevice) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; mDevice->DeviceName = name; } @@ -150,19 +150,17 @@ bool NullBackendFactory::init() bool NullBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback); } -std::string NullBackendFactory::probe(BackendType type) +auto NullBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: - /* Includes null char. */ - outnames.append(nullDevice, sizeof(nullDevice)); - break; + /* Include null char. */ + return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } - return outnames; + return {}; } BackendPtr NullBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/null.h b/Engine/lib/openal-soft/alc/backends/null.h index 7048cad6f..213842af7 100644 --- a/Engine/lib/openal-soft/alc/backends/null.h +++ b/Engine/lib/openal-soft/alc/backends/null.h @@ -5,15 +5,15 @@ struct NullBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_NULL_H */ diff --git a/Engine/lib/openal-soft/alc/backends/oboe.cpp b/Engine/lib/openal-soft/alc/backends/oboe.cpp index 461f5a6ac..68acd0094 100644 --- a/Engine/lib/openal-soft/alc/backends/oboe.cpp +++ b/Engine/lib/openal-soft/alc/backends/oboe.cpp @@ -4,10 +4,11 @@ #include "oboe.h" #include +#include #include -#include #include "alnumeric.h" +#include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "ringbuffer.h" @@ -17,7 +18,9 @@ namespace { -constexpr char device_name[] = "Oboe Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Oboe Default"sv; } struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback { @@ -28,7 +31,9 @@ struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void onErrorAfterClose(oboe::AudioStream* /* audioStream */, oboe::Result /* error */) override; + + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -46,14 +51,20 @@ oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStrea return oboe::DataCallbackResult::Continue; } - -void OboePlayback::open(const char *name) +void OboePlayback::onErrorAfterClose(oboe::AudioStream*, oboe::Result error) { - if(!name) - name = device_name; - else if(std::strcmp(name, device_name) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(error == oboe::Result::ErrorDisconnected) + mDevice->handleDisconnect("Oboe AudioStream was disconnected: %s", oboe::convertToText(error)); + TRACE("Error was %s", oboe::convertToText(error)); +} + +void OboePlayback::open(std::string_view name) +{ + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* Open a basic output stream, just to ensure it can work. */ oboe::ManagedStream stream; @@ -72,6 +83,7 @@ bool OboePlayback::reset() oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Output); builder.setPerformanceMode(oboe::PerformanceMode::LowLatency); + builder.setUsage(oboe::Usage::Game); /* Don't let Oboe convert. We should be able to handle anything it gives * back. */ @@ -135,7 +147,7 @@ bool OboePlayback::reset() if(result != oboe::Result::OK) throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s", oboe::convertToText(result)}; - mStream->setBufferSizeInFrames(mini(static_cast(mDevice->BufferSize), + mStream->setBufferSizeInFrames(std::min(static_cast(mDevice->BufferSize), mStream->getBufferCapacityInFrames())); TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); @@ -164,6 +176,9 @@ bool OboePlayback::reset() mDevice->FmtType = DevFmtInt; break; case oboe::AudioFormat::I24: +#endif +#if OBOE_VERSION_MAJOR > 1 || (OBOE_VERSION_MAJOR == 1 && OBOE_VERSION_MINOR >= 8) + case oboe::AudioFormat::IEC61937: #endif case oboe::AudioFormat::Unspecified: case oboe::AudioFormat::Invalid: @@ -177,9 +192,9 @@ bool OboePlayback::reset() * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum * update size. */ - mDevice->UpdateSize = maxu(mDevice->Frequency / 100, + mDevice->UpdateSize = std::max(mDevice->Frequency/100u, static_cast(mStream->getFramesPerBurst())); - mDevice->BufferSize = maxu(mDevice->UpdateSize * 2, + mDevice->BufferSize = std::max(mDevice->UpdateSize*2u, static_cast(mStream->getBufferSizeInFrames())); return true; @@ -197,8 +212,7 @@ void OboePlayback::stop() { oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } @@ -212,28 +226,28 @@ struct OboeCapture final : public BackendBase, public oboe::AudioStreamCallback oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; }; oboe::DataCallbackResult OboeCapture::onAudioReady(oboe::AudioStream*, void *audioData, int32_t numFrames) { - mRing->write(audioData, static_cast(numFrames)); + std::ignore = mRing->write(audioData, static_cast(numFrames)); return oboe::DataCallbackResult::Continue; } -void OboeCapture::open(const char *name) +void OboeCapture::open(std::string_view name) { - if(!name) - name = device_name; - else if(std::strcmp(name, device_name) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; oboe::AudioStreamBuilder builder; builder.setDirection(oboe::Direction::Input) @@ -259,6 +273,7 @@ void OboeCapture::open(const char *name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -297,7 +312,7 @@ void OboeCapture::open(const char *name) TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get())); /* Ensure a minimum ringbuffer size of 100ms. */ - mRing = RingBuffer::Create(maxu(mDevice->BufferSize, mDevice->Frequency/10), + mRing = RingBuffer::Create(std::max(mDevice->BufferSize, mDevice->Frequency/10u), static_cast(mStream->getBytesPerFrame()), false); mDevice->DeviceName = name; @@ -315,15 +330,14 @@ void OboeCapture::stop() { const oboe::Result result{mStream->stop()}; if(result != oboe::Result::OK) - throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s", - oboe::convertToText(result)}; + ERR("Failed to stop stream: %s\n", oboe::convertToText(result)); } uint OboeCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void OboeCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void OboeCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace @@ -332,16 +346,15 @@ bool OboeBackendFactory::init() { return true; } bool OboeBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string OboeBackendFactory::probe(BackendType type) +auto OboeBackendFactory::enumerate(BackendType type) -> std::vector { switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - return std::string{device_name, sizeof(device_name)}; + return std::vector{std::string{GetDeviceName()}}; } - return std::string{}; + return {}; } BackendPtr OboeBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/oboe.h b/Engine/lib/openal-soft/alc/backends/oboe.h index a39c24454..d277cfe7c 100644 --- a/Engine/lib/openal-soft/alc/backends/oboe.h +++ b/Engine/lib/openal-soft/alc/backends/oboe.h @@ -5,15 +5,15 @@ struct OboeBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OBOE_H */ diff --git a/Engine/lib/openal-soft/alc/backends/opensl.cpp b/Engine/lib/openal-soft/alc/backends/opensl.cpp index f5b98fb88..26e690709 100644 --- a/Engine/lib/openal-soft/alc/backends/opensl.cpp +++ b/Engine/lib/openal-soft/alc/backends/opensl.cpp @@ -23,23 +23,26 @@ #include "opensl.h" -#include #include -#include #include +#include #include +#include +#include #include #include #include "albit.h" #include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "ringbuffer.h" -#include "threads.h" #include #include @@ -48,15 +51,17 @@ namespace { +using namespace std::string_view_literals; + /* Helper macros */ #define EXTRACT_VCALL_ARGS(...) __VA_ARGS__)) #define VCALL(obj, func) ((*(obj))->func((obj), EXTRACT_VCALL_ARGS #define VCALL0(obj, func) ((*(obj))->func((obj) EXTRACT_VCALL_ARGS -constexpr char opensl_device[] = "OpenSL"; - +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "OpenSL"sv; } +[[nodiscard]] constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept { switch(chans) @@ -80,6 +85,7 @@ constexpr SLuint32 GetChannelMask(DevFmtChannels chans) noexcept SL_SPEAKER_BACK_RIGHT | SL_SPEAKER_SIDE_LEFT | SL_SPEAKER_SIDE_RIGHT | SL_SPEAKER_TOP_FRONT_LEFT | SL_SPEAKER_TOP_FRONT_RIGHT | SL_SPEAKER_TOP_BACK_LEFT | SL_SPEAKER_TOP_BACK_RIGHT; + case DevFmtX7144: case DevFmtAmbi3D: break; } @@ -159,12 +165,10 @@ struct OpenSLPlayback final : public BackendBase { ~OpenSLPlayback() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast(context)->process(bq); } int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -189,8 +193,6 @@ struct OpenSLPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OpenSLPlayback) }; OpenSLPlayback::~OpenSLPlayback() @@ -229,7 +231,7 @@ void OpenSLPlayback::process(SLAndroidSimpleBufferQueueItf) noexcept int OpenSLPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); SLPlayItf player; SLAndroidSimpleBufferQueueItf bufferQueue; @@ -312,13 +314,13 @@ int OpenSLPlayback::mixerProc() } -void OpenSLPlayback::open(const char *name) +void OpenSLPlayback::open(std::string_view name) { - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* There's only one device, so if it's already open, there's nothing to do. */ if(mEngineObj) return; @@ -375,74 +377,6 @@ bool OpenSLPlayback::reset() mRing = nullptr; -#if 0 - if(!mDevice->Flags.get()) - { - /* FIXME: Disabled until I figure out how to get the Context needed for - * the getSystemService call. - */ - JNIEnv *env = Android_GetJNIEnv(); - jobject jctx = Android_GetContext(); - - /* Get necessary stuff for using java.lang.Integer, - * android.content.Context, and android.media.AudioManager. - */ - jclass int_cls = JCALL(env,FindClass)("java/lang/Integer"); - jmethodID int_parseint = JCALL(env,GetStaticMethodID)(int_cls, - "parseInt", "(Ljava/lang/String;)I" - ); - TRACE("Integer: %p, parseInt: %p\n", int_cls, int_parseint); - - jclass ctx_cls = JCALL(env,FindClass)("android/content/Context"); - jfieldID ctx_audsvc = JCALL(env,GetStaticFieldID)(ctx_cls, - "AUDIO_SERVICE", "Ljava/lang/String;" - ); - jmethodID ctx_getSysSvc = JCALL(env,GetMethodID)(ctx_cls, - "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" - ); - TRACE("Context: %p, AUDIO_SERVICE: %p, getSystemService: %p\n", - ctx_cls, ctx_audsvc, ctx_getSysSvc); - - jclass audmgr_cls = JCALL(env,FindClass)("android/media/AudioManager"); - jfieldID audmgr_prop_out_srate = JCALL(env,GetStaticFieldID)(audmgr_cls, - "PROPERTY_OUTPUT_SAMPLE_RATE", "Ljava/lang/String;" - ); - jmethodID audmgr_getproperty = JCALL(env,GetMethodID)(audmgr_cls, - "getProperty", "(Ljava/lang/String;)Ljava/lang/String;" - ); - TRACE("AudioManager: %p, PROPERTY_OUTPUT_SAMPLE_RATE: %p, getProperty: %p\n", - audmgr_cls, audmgr_prop_out_srate, audmgr_getproperty); - - const char *strchars; - jstring strobj; - - /* Now make the calls. */ - //AudioManager audMgr = (AudioManager)getSystemService(Context.AUDIO_SERVICE); - strobj = JCALL(env,GetStaticObjectField)(ctx_cls, ctx_audsvc); - jobject audMgr = JCALL(env,CallObjectMethod)(jctx, ctx_getSysSvc, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("Context.getSystemService(%s) = %p\n", strchars, audMgr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //String srateStr = audMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); - strobj = JCALL(env,GetStaticObjectField)(audmgr_cls, audmgr_prop_out_srate); - jstring srateStr = JCALL(env,CallObjectMethod)(audMgr, audmgr_getproperty, strobj); - strchars = JCALL(env,GetStringUTFChars)(strobj, nullptr); - TRACE("audMgr.getProperty(%s) = %p\n", strchars, srateStr); - JCALL(env,ReleaseStringUTFChars)(strobj, strchars); - - //int sampleRate = Integer.parseInt(srateStr); - sampleRate = JCALL(env,CallStaticIntMethod)(int_cls, int_parseint, srateStr); - - strchars = JCALL(env,GetStringUTFChars)(srateStr, nullptr); - TRACE("Got system sample rate %uhz (%s)\n", sampleRate, strchars); - JCALL(env,ReleaseStringUTFChars)(srateStr, strchars); - - if(!sampleRate) sampleRate = device->Frequency; - else sampleRate = maxu(sampleRate, MIN_OUTPUT_RATE); - } -#endif - mDevice->FmtChans = DevFmtStereo; mDevice->FmtType = DevFmtShort; @@ -564,7 +498,9 @@ void OpenSLPlayback::start() PrintErr(result, "bufferQueue->GetInterface"); if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLPlayback::processC, this); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS != result) @@ -628,8 +564,8 @@ ClockLatency OpenSLPlayback::getClockLatency() { ClockLatency ret; - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = std::chrono::seconds{mRing->readSpace() * mDevice->UpdateSize}; ret.Latency /= mDevice->Frequency; @@ -642,13 +578,11 @@ struct OpenSLCapture final : public BackendBase { ~OpenSLCapture() override; void process(SLAndroidSimpleBufferQueueItf bq) noexcept; - static void processC(SLAndroidSimpleBufferQueueItf bq, void *context) noexcept - { static_cast(context)->process(bq); } - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; /* engine interfaces */ @@ -662,8 +596,6 @@ struct OpenSLCapture final : public BackendBase { uint mSplOffset{0u}; uint mFrameSize{0}; - - DEF_NEWDEL(OpenSLCapture) }; OpenSLCapture::~OpenSLCapture() @@ -686,13 +618,13 @@ void OpenSLCapture::process(SLAndroidSimpleBufferQueueItf) noexcept } -void OpenSLCapture::open(const char* name) +void OpenSLCapture::open(std::string_view name) { - if(!name) - name = opensl_device; - else if(strcmp(name, opensl_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; SLresult result{slCreateEngine(&mEngineObj, 0, nullptr, 0, nullptr, nullptr)}; PrintErr(result, "slCreateEngine"); @@ -710,10 +642,10 @@ void OpenSLCapture::open(const char* name) { mFrameSize = mDevice->frameSizeFromFmt(); /* Ensure the total length is at least 100ms */ - uint length{maxu(mDevice->BufferSize, mDevice->Frequency/10)}; + uint length{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; /* Ensure the per-chunk length is at least 10ms, and no more than 50ms. */ - uint update_len{clampu(mDevice->BufferSize/3, mDevice->Frequency/100, - mDevice->Frequency/100*5)}; + uint update_len{std::clamp(mDevice->BufferSize/3u, mDevice->Frequency/100u, + mDevice->Frequency/100u*5u)}; uint num_updates{(length+update_len-1) / update_len}; mRing = RingBuffer::Create(num_updates, update_len*mFrameSize, false); @@ -813,13 +745,15 @@ void OpenSLCapture::open(const char* name) } if(SL_RESULT_SUCCESS == result) { - result = VCALL(bufferQueue,RegisterCallback)(&OpenSLCapture::processC, this); + result = VCALL(bufferQueue,RegisterCallback)( + [](SLAndroidSimpleBufferQueueItf bq, void *context) noexcept + { static_cast(context)->process(bq); }, this); PrintErr(result, "bufferQueue->RegisterCallback"); } if(SL_RESULT_SUCCESS == result) { const uint chunk_size{mDevice->UpdateSize * mFrameSize}; - const auto silence = (mDevice->FmtType == DevFmtUByte) ? al::byte{0x80} : al::byte{0}; + const auto silence = (mDevice->FmtType == DevFmtUByte) ? std::byte{0x80} : std::byte{0}; auto data = mRing->getWriteVector(); std::fill_n(data.first.buf, data.first.len*chunk_size, silence); @@ -883,7 +817,7 @@ void OpenSLCapture::stop() } } -void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) +void OpenSLCapture::captureSamples(std::byte *buffer, uint samples) { const uint update_size{mDevice->UpdateSize}; const uint chunk_size{update_size * mFrameSize}; @@ -895,7 +829,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) auto rdata = mRing->getReadVector(); for(uint i{0};i < samples;) { - const uint rem{minu(samples - i, update_size - mSplOffset)}; + const uint rem{std::min(samples - i, update_size - mSplOffset)}; std::copy_n(rdata.first.buf + mSplOffset*size_t{mFrameSize}, rem*size_t{mFrameSize}, buffer + i*size_t{mFrameSize}); @@ -932,7 +866,7 @@ void OpenSLCapture::captureSamples(al::byte *buffer, uint samples) return; /* For each buffer chunk that was fully read, queue another writable buffer - * chunk to keep the OpenSL queue full. This is rather convulated, as a + * chunk to keep the OpenSL queue full. This is rather convoluted, as a * result of the ring buffer holding more elements than are writable at a * given time. The end of the write vector increments when the read pointer * advances, which will "expose" a previously unwritable element. So for @@ -975,18 +909,15 @@ bool OSLBackendFactory::init() { return true; } bool OSLBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string OSLBackendFactory::probe(BackendType type) +auto OSLBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(opensl_device, sizeof(opensl_device)); - break; + return std::vector{std::string{GetDeviceName()}}; } - return outnames; + return {}; } BackendPtr OSLBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/opensl.h b/Engine/lib/openal-soft/alc/backends/opensl.h index b81624476..9f13dd71f 100644 --- a/Engine/lib/openal-soft/alc/backends/opensl.h +++ b/Engine/lib/openal-soft/alc/backends/opensl.h @@ -5,15 +5,15 @@ struct OSLBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSL_H */ diff --git a/Engine/lib/openal-soft/alc/backends/oss.cpp b/Engine/lib/openal-soft/alc/backends/oss.cpp index 6d4fa2615..bab99842c 100644 --- a/Engine/lib/openal-soft/alc/backends/oss.cpp +++ b/Engine/lib/openal-soft/alc/backends/oss.cpp @@ -31,27 +31,26 @@ #include #include #include -#include #include #include #include #include -#include #include +#include +#include #include #include +#include -#include "albyte.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" -#include "aloptional.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" #include @@ -83,117 +82,148 @@ namespace { -constexpr char DefaultName[] = "OSS Default"; -std::string DefaultPlayback{"/dev/dsp"}; -std::string DefaultCapture{"/dev/dsp"}; +using namespace std::string_literals; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "OSS Default"sv; } + +std::string DefaultPlayback{"/dev/dsp"s}; +std::string DefaultCapture{"/dev/dsp"s}; struct DevMap { std::string name; std::string device_name; + + template + DevMap(T&& name_, U&& devname_) + : name{std::forward(name_)}, device_name{std::forward(devname_)} + { } }; -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; #ifdef ALC_OSS_COMPAT #define DSP_CAP_OUTPUT 0x00020000 #define DSP_CAP_INPUT 0x00010000 -void ALCossListPopulate(al::vector &devlist, int type) +void ALCossListPopulate(std::vector &devlist, int type) { - devlist.emplace_back(DevMap{DefaultName, (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback}); + devlist.emplace_back(GetDefaultName(), (type==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback); } #else -void ALCossListAppend(al::vector &list, al::span handle, al::span path) +class FileHandle { + int mFd{-1}; + +public: + FileHandle() = default; + FileHandle(const FileHandle&) = delete; + FileHandle& operator=(const FileHandle&) = delete; + ~FileHandle() { if(mFd != -1) ::close(mFd); } + + template + [[nodiscard]] auto open(const char *fname, Args&& ...args) -> bool + { + close(); + mFd = ::open(fname, std::forward(args)...); + return mFd != -1; + } + void close() + { + if(mFd != -1) + ::close(mFd); + mFd = -1; + } + + [[nodiscard]] + auto get() const noexcept -> int { return mFd; } +}; + +void ALCossListAppend(std::vector &list, std::string_view handle, std::string_view path) { #ifdef ALC_OSS_DEVNODE_TRUC for(size_t i{0};i < path.size();++i) { - if(path[i] == '.' && handle.size() + i >= path.size()) + if(path[i] == '.' && handle.size() >= path.size() - i) { const size_t hoffset{handle.size() + i - path.size()}; if(strncmp(path.data() + i, handle.data() + hoffset, path.size() - i) == 0) - handle = handle.first(hoffset); - path = path.first(i); + handle = handle.substr(0, hoffset); + path = path.substr(0, i); } } #endif if(handle.empty()) handle = path; - std::string basename{handle.data(), handle.size()}; - std::string devname{path.data(), path.size()}; - - auto match_devname = [&devname](const DevMap &entry) -> bool - { return entry.device_name == devname; }; + auto match_devname = [path](const DevMap &entry) -> bool + { return entry.device_name == path; }; if(std::find_if(list.cbegin(), list.cend(), match_devname) != list.cend()) return; - auto checkName = [&list](const std::string &name) -> bool + auto checkName = [&list](const std::string_view name) -> bool { - auto match_name = [&name](const DevMap &entry) -> bool { return entry.name == name; }; + auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); }; int count{1}; - std::string newname{basename}; + std::string newname{handle}; while(checkName(newname)) { - newname = basename; + newname = handle; newname += " #"; newname += std::to_string(++count); } - list.emplace_back(DevMap{std::move(newname), std::move(devname)}); - const DevMap &entry = list.back(); + const DevMap &entry = list.emplace_back(std::move(newname), path); TRACE("Got device \"%s\", \"%s\"\n", entry.name.c_str(), entry.device_name.c_str()); } -void ALCossListPopulate(al::vector &devlist, int type_flag) +void ALCossListPopulate(std::vector &devlist, int type_flag) { - int fd{open("/dev/mixer", O_RDONLY)}; - if(fd < 0) + oss_sysinfo si{}; + FileHandle file; + if(!file.open("/dev/mixer", O_RDONLY)) { - TRACE("Could not open /dev/mixer: %s\n", strerror(errno)); + TRACE("Could not open /dev/mixer: %s\n", std::generic_category().message(errno).c_str()); goto done; } - oss_sysinfo si; - if(ioctl(fd, SNDCTL_SYSINFO, &si) == -1) + if(ioctl(file.get(), SNDCTL_SYSINFO, &si) == -1) { - TRACE("SNDCTL_SYSINFO failed: %s\n", strerror(errno)); + TRACE("SNDCTL_SYSINFO failed: %s\n", std::generic_category().message(errno).c_str()); goto done; } for(int i{0};i < si.numaudios;i++) { - oss_audioinfo ai; + oss_audioinfo ai{}; ai.dev = i; - if(ioctl(fd, SNDCTL_AUDIOINFO, &ai) == -1) + if(ioctl(file.get(), SNDCTL_AUDIOINFO, &ai) == -1) { - ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, strerror(errno)); + ERR("SNDCTL_AUDIOINFO (%d) failed: %s\n", i, + std::generic_category().message(errno).c_str()); continue; } if(!(ai.caps&type_flag) || ai.devnode[0] == '\0') continue; - al::span handle; + std::string_view handle; if(ai.handle[0] != '\0') handle = {ai.handle, strnlen(ai.handle, sizeof(ai.handle))}; else handle = {ai.name, strnlen(ai.name, sizeof(ai.name))}; - al::span devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; + const std::string_view devnode{ai.devnode, strnlen(ai.devnode, sizeof(ai.devnode))}; ALCossListAppend(devlist, handle, devnode); } done: - if(fd >= 0) - close(fd); - fd = -1; + file.close(); const char *defdev{((type_flag==DSP_CAP_INPUT) ? DefaultCapture : DefaultPlayback).c_str()}; auto iter = std::find_if(devlist.cbegin(), devlist.cend(), @@ -201,7 +231,7 @@ done: { return entry.device_name == defdev; } ); if(iter == devlist.cend()) - devlist.insert(devlist.begin(), DevMap{DefaultName, defdev}); + devlist.insert(devlist.begin(), DevMap{GetDefaultName(), defdev}); else { DevMap entry{std::move(*iter)}; @@ -231,19 +261,17 @@ struct OSSPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; int mFd{-1}; - al::vector mMixData; + std::vector mMixData; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OSSPlayback) }; OSSPlayback::~OSSPlayback() @@ -257,7 +285,7 @@ OSSPlayback::~OSSPlayback() int OSSPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; const size_t frame_size{mDevice->frameSizeFromFmt()}; @@ -269,38 +297,38 @@ int OSSPlayback::mixerProc() pollitem.fd = mFd; pollitem.events = POLLOUT; - int pret{poll(&pollitem, 1, 1000)}; - if(pret < 0) + if(int pret{poll(&pollitem, 1, 1000)}; pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; - ERR("poll failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed waiting for playback buffer: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("poll failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed waiting for playback buffer: %s", errstr.c_str()); break; } - else if(pret == 0) + else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { WARN("poll timeout\n"); continue; } - al::byte *write_ptr{mMixData.data()}; - size_t to_write{mMixData.size()}; - mDevice->renderSamples(write_ptr, static_cast(to_write/frame_size), frame_step); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) + al::span write_buf{mMixData}; + mDevice->renderSamples(write_buf.data(), static_cast(write_buf.size()/frame_size), + frame_step); + while(!write_buf.empty() && !mKillNow.load(std::memory_order_acquire)) { - ssize_t wrote{write(mFd, write_ptr, to_write)}; + ssize_t wrote{write(mFd, write_buf.data(), write_buf.size())}; if(wrote < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) continue; - ERR("write failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed writing playback samples: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("write failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed writing playback samples: %s", errstr.c_str()); break; } - to_write -= static_cast(wrote); - write_ptr += wrote; + write_buf = write_buf.subspan(static_cast(wrote)); } } @@ -308,11 +336,11 @@ int OSSPlayback::mixerProc() } -void OSSPlayback::open(const char *name) +void OSSPlayback::open(std::string_view name) { const char *devname{DefaultPlayback.c_str()}; - if(!name) - name = DefaultName; + if(name.empty()) + name = GetDefaultName(); else { if(PlaybackDevices.empty()) @@ -324,14 +352,14 @@ void OSSPlayback::open(const char *name) ); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; devname = iter->device_name.c_str(); } int fd{::open(devname, O_WRONLY)}; if(fd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - strerror(errno)}; + std::generic_category().message(errno).c_str()}; if(mFd != -1) ::close(mFd); @@ -367,15 +395,14 @@ bool OSSPlayback::reset() uint ossSpeed{mDevice->Frequency}; uint frameSize{numChannels * mDevice->bytesFromFmt()}; /* According to the OSS spec, 16 bytes (log2(16)) is the minimum. */ - uint log2FragmentSize{maxu(log2i(mDevice->UpdateSize*frameSize), 4)}; + uint log2FragmentSize{std::max(log2i(mDevice->UpdateSize*frameSize), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; - const char *err; -#define CHECKERR(func) if((func) < 0) { \ - err = #func; \ - goto err; \ -} +#define CHECKERR(func) if((func) < 0) \ + throw al::backend_exception{al::backend_error::DeviceError, "%s failed: %s\n", #func, \ + std::generic_category().message(errno).c_str()}; + /* Don't fail if SETFRAGMENT fails. We can handle just about anything * that's reported back via GETOSPACE */ ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize); @@ -383,12 +410,6 @@ bool OSSPlayback::reset() CHECKERR(ioctl(mFd, SNDCTL_DSP_CHANNELS, &numChannels)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SPEED, &ossSpeed)); CHECKERR(ioctl(mFd, SNDCTL_DSP_GETOSPACE, &info)); - if(0) - { - err: - ERR("%s failed: %s\n", err, strerror(errno)); - return false; - } #undef CHECKERR if(mDevice->channelsFromFmt() != numChannels) @@ -413,7 +434,7 @@ bool OSSPlayback::reset() setDefaultChannelOrder(); - mMixData.resize(mDevice->UpdateSize * mDevice->frameSizeFromFmt()); + mMixData.resize(size_t{mDevice->UpdateSize} * mDevice->frameSizeFromFmt()); return true; } @@ -437,7 +458,7 @@ void OSSPlayback::stop() mThread.join(); if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); + ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); } @@ -447,10 +468,10 @@ struct OSScapture final : public BackendBase { int recordProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; int mFd{-1}; @@ -459,8 +480,6 @@ struct OSScapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(OSScapture) }; OSScapture::~OSScapture() @@ -474,7 +493,7 @@ OSScapture::~OSScapture() int OSScapture::recordProc() { SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire)) @@ -483,16 +502,16 @@ int OSScapture::recordProc() pollitem.fd = mFd; pollitem.events = POLLIN; - int sret{poll(&pollitem, 1, 1000)}; - if(sret < 0) + if(int pret{poll(&pollitem, 1, 1000)}; pret < 0) { if(errno == EINTR || errno == EAGAIN) continue; - ERR("poll failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed to check capture samples: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("poll failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed to check capture samples: %s", errstr.c_str()); break; } - else if(sret == 0) + else if(pret == 0) /* NOLINT(*-else-after-return) 'pret' is local to the if/else blocks */ { WARN("poll timeout\n"); continue; @@ -504,8 +523,9 @@ int OSScapture::recordProc() ssize_t amt{read(mFd, vec.first.buf, vec.first.len*frame_size)}; if(amt < 0) { - ERR("read failed: %s\n", strerror(errno)); - mDevice->handleDisconnect("Failed reading capture samples: %s", strerror(errno)); + const auto errstr = std::generic_category().message(errno); + ERR("read failed: %s\n", errstr.c_str()); + mDevice->handleDisconnect("Failed reading capture samples: %s", errstr.c_str()); break; } mRing->writeAdvance(static_cast(amt)/frame_size); @@ -516,11 +536,11 @@ int OSScapture::recordProc() } -void OSScapture::open(const char *name) +void OSScapture::open(std::string_view name) { const char *devname{DefaultCapture.c_str()}; - if(!name) - name = DefaultName; + if(name.empty()) + name = GetDefaultName(); else { if(CaptureDevices.empty()) @@ -532,14 +552,14 @@ void OSScapture::open(const char *name) ); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; devname = iter->device_name.c_str(); } mFd = ::open(devname, O_RDONLY); if(mFd == -1) throw al::backend_exception{al::backend_error::NoDevice, "Could not open %s: %s", devname, - strerror(errno)}; + std::generic_category().message(errno).c_str()}; int ossFormat{}; switch(mDevice->FmtType) @@ -566,13 +586,13 @@ void OSScapture::open(const char *name) uint frameSize{numChannels * mDevice->bytesFromFmt()}; uint ossSpeed{mDevice->Frequency}; /* according to the OSS spec, 16 bytes are the minimum */ - uint log2FragmentSize{maxu(log2i(mDevice->BufferSize * frameSize / periods), 4)}; + uint log2FragmentSize{std::max(log2i(mDevice->BufferSize * frameSize / periods), 4u)}; uint numFragmentsLogSize{(periods << 16) | log2FragmentSize}; audio_buf_info info{}; #define CHECKERR(func) if((func) < 0) { \ throw al::backend_exception{al::backend_error::DeviceError, #func " failed: %s", \ - strerror(errno)}; \ + std::generic_category().message(errno).c_str()}; \ } CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFRAGMENT, &numFragmentsLogSize)); CHECKERR(ioctl(mFd, SNDCTL_DSP_SETFMT, &ossFormat)); @@ -617,11 +637,11 @@ void OSScapture::stop() mThread.join(); if(ioctl(mFd, SNDCTL_DSP_RESET) != 0) - ERR("Error resetting device: %s\n", strerror(errno)); + ERR("Error resetting device: %s\n", std::generic_category().message(errno).c_str()); } -void OSScapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void OSScapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint OSScapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -637,9 +657,9 @@ BackendFactory &OSSBackendFactory::getFactory() bool OSSBackendFactory::init() { - if(auto devopt = ConfigValueStr(nullptr, "oss", "device")) + if(auto devopt = ConfigValueStr({}, "oss", "device")) DefaultPlayback = std::move(*devopt); - if(auto capopt = ConfigValueStr(nullptr, "oss", "capture")) + if(auto capopt = ConfigValueStr({}, "oss", "capture")) DefaultCapture = std::move(*capopt); return true; @@ -648,18 +668,13 @@ bool OSSBackendFactory::init() bool OSSBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string OSSBackendFactory::probe(BackendType type) +auto OSSBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; - + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void { - struct stat buf; - if(stat(entry.device_name.c_str(), &buf) == 0) - { - /* Includes null char. */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - } + if(struct stat buf{}; stat(entry.device_name.c_str(), &buf) == 0) + outnames.emplace_back(entry.name); }; switch(type) @@ -667,12 +682,14 @@ std::string OSSBackendFactory::probe(BackendType type) case BackendType::Playback: PlaybackDevices.clear(); ALCossListPopulate(PlaybackDevices, DSP_CAP_OUTPUT); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: CaptureDevices.clear(); ALCossListPopulate(CaptureDevices, DSP_CAP_INPUT); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/Engine/lib/openal-soft/alc/backends/oss.h b/Engine/lib/openal-soft/alc/backends/oss.h index 4f2c00b96..b5faf96a5 100644 --- a/Engine/lib/openal-soft/alc/backends/oss.h +++ b/Engine/lib/openal-soft/alc/backends/oss.h @@ -5,15 +5,15 @@ struct OSSBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_OSS_H */ diff --git a/Engine/lib/openal-soft/alc/backends/pipewire.cpp b/Engine/lib/openal-soft/alc/backends/pipewire.cpp index c6569a74a..23589dc6a 100644 --- a/Engine/lib/openal-soft/alc/backends/pipewire.cpp +++ b/Engine/lib/openal-soft/alc/backends/pipewire.cpp @@ -23,24 +23,30 @@ #include "pipewire.h" #include +#include #include +#include +#include +#include +#include +#include +#include #include #include #include #include -#include +#include #include #include -#include +#include +#include #include -#include +#include #include -#include "albyte.h" #include "alc/alconfig.h" +#include "alc/backends/base.h" #include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "core/devformat.h" @@ -71,10 +77,15 @@ _Pragma("GCC diagnostic ignored \"-Weverything\"") #include "spa/buffer/buffer.h" #include "spa/param/audio/format-utils.h" #include "spa/param/audio/raw.h" +#include "spa/param/format.h" #include "spa/param/param.h" #include "spa/pod/builder.h" #include "spa/utils/json.h" +/* NOLINTBEGIN : All kinds of unsafe C stuff here from PipeWire headers + * (function-like macros, C style casts in macros, etc), which we can't do + * anything about except wrap into inline functions. + */ namespace { /* Wrap some nasty macros here too... */ template @@ -107,32 +118,69 @@ template constexpr auto get_pod_body(const spa_pod *pod) noexcept { return al::span{static_cast(SPA_POD_BODY(pod)), N}; } -constexpr auto make_pod_builder(void *data, uint32_t size) noexcept -{ return SPA_POD_BUILDER_INIT(data, size); } - constexpr auto get_array_value_type(const spa_pod *pod) noexcept { return SPA_POD_ARRAY_VALUE_TYPE(pod); } +constexpr auto make_pod_builder(void *data, uint32_t size) noexcept +{ return SPA_POD_BUILDER_INIT(data, size); } + constexpr auto PwIdAny = PW_ID_ANY; } // namespace +/* NOLINTEND */ _Pragma("GCC diagnostic pop") namespace { +struct PodDynamicBuilder { +private: + std::vector mStorage; + spa_pod_builder mPod{}; + + int overflow(uint32_t size) noexcept + { + try { + mStorage.resize(size); + } + catch(...) { + ERR("Failed to resize POD storage\n"); + return -ENOMEM; + } + mPod.data = mStorage.data(); + mPod.size = size; + return 0; + } + +public: + PodDynamicBuilder(uint32_t initSize=0) : mStorage(initSize) + , mPod{make_pod_builder(mStorage.data(), initSize)} + { + static constexpr auto callbacks{[] + { + spa_pod_builder_callbacks cb{}; + cb.version = SPA_VERSION_POD_BUILDER_CALLBACKS; + cb.overflow = [](void *data, uint32_t size) noexcept + { return static_cast(data)->overflow(size); }; + return cb; + }()}; + + spa_pod_builder_set_callbacks(&mPod, &callbacks, this); + } + + spa_pod_builder *get() noexcept { return &mPod; } +}; + /* Added in 0.3.33, but we currently only require 0.3.23. */ #ifndef PW_KEY_NODE_RATE #define PW_KEY_NODE_RATE "node.rate" #endif +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; using uint = unsigned int; -constexpr char pwireDevice[] = "PipeWire Output"; -constexpr char pwireInput[] = "PipeWire Input"; - bool check_version(const char *version) { @@ -142,10 +190,8 @@ bool check_version(const char *version) */ int major{0}, minor{0}, revision{0}; int ret{sscanf(version, "%d.%d.%d", &major, &minor, &revision)}; - if(ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) - || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO))) - return true; - return false; + return ret == 3 && (major > PW_MAJOR || (major == PW_MAJOR && minor > PW_MINOR) + || (major == PW_MAJOR && minor == PW_MINOR && revision >= PW_MICRO)); } #ifdef HAVE_DYNLOAD @@ -199,7 +245,7 @@ bool pwire_load() if(pwire_handle) return true; - static constexpr char pwire_library[] = "libpipewire-0.3.so.0"; + const char *pwire_library{"libpipewire-0.3.so.0"}; std::string missing_funcs; pwire_handle = LoadLib(pwire_library); @@ -294,7 +340,7 @@ using Pod_t = typename PodInfo::Type; template al::span> get_array_span(const spa_pod *pod) { - uint32_t nvals; + uint32_t nvals{}; if(void *v{spa_pod_get_array(pod, &nvals)}) { if(get_array_value_type(pod) == T) @@ -304,12 +350,12 @@ al::span> get_array_span(const spa_pod *pod) } template -al::optional> get_value(const spa_pod *value) +std::optional> get_value(const spa_pod *value) { Pod_t val{}; if(PodInfo::get_value(value, &val) == 0) return val; - return al::nullopt; + return std::nullopt; } /* Internally, PipeWire types "inherit" from each other, but this is hidden @@ -395,9 +441,11 @@ public: explicit operator bool() const noexcept { return mLoop != nullptr; } + [[nodiscard]] auto start() const { return pw_thread_loop_start(mLoop); } auto stop() const { return pw_thread_loop_stop(mLoop); } + [[nodiscard]] auto getLoop() const { return pw_thread_loop_get_loop(mLoop); } auto lock() const { return pw_thread_loop_lock(mLoop); } @@ -405,7 +453,7 @@ public: auto signal(bool wait) const { return pw_thread_loop_signal(mLoop, wait); } - auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) + auto newContext(pw_properties *props=nullptr, size_t user_data_size=0) const { return PwContextPtr{pw_context_new(getLoop(), props, user_data_size)}; } static auto Create(const char *name, spa_dict *props=nullptr) @@ -433,8 +481,73 @@ using MainloopLockGuard = std::lock_guard; * devices provided by the server. */ -struct NodeProxy; -struct MetadataProxy; +/* A generic PipeWire node proxy object used to track changes to sink and + * source nodes. + */ +struct NodeProxy { + static constexpr pw_node_events CreateNodeEvents() + { + pw_node_events ret{}; + ret.version = PW_VERSION_NODE_EVENTS; + ret.info = infoCallback; + ret.param = [](void *object, int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) noexcept + { static_cast(object)->paramCallback(seq, id, index, next, param); }; + return ret; + } + + uint32_t mId{}; + + PwNodePtr mNode{}; + spa_hook mListener{}; + + NodeProxy(uint32_t id, PwNodePtr node) + : mId{id}, mNode{std::move(node)} + { + static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; + ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); + + /* Track changes to the enumerable and current formats (indicates the + * default and active format, which is what we're interested in). + */ + std::array fmtids{{SPA_PARAM_EnumFormat, SPA_PARAM_Format}}; + ppw_node_subscribe_params(mNode.get(), fmtids.data(), fmtids.size()); + } + ~NodeProxy() + { spa_hook_remove(&mListener); } + + + static void infoCallback(void *object, const pw_node_info *info) noexcept; + void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param) const noexcept; +}; + +/* A metadata proxy object used to query the default sink and source. */ +struct MetadataProxy { + static constexpr pw_metadata_events CreateMetadataEvents() + { + pw_metadata_events ret{}; + ret.version = PW_VERSION_METADATA_EVENTS; + ret.property = propertyCallback; + return ret; + } + + uint32_t mId{}; + + PwMetadataPtr mMetadata{}; + spa_hook mListener{}; + + MetadataProxy(uint32_t id, PwMetadataPtr mdata) + : mId{id}, mMetadata{std::move(mdata)} + { + static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; + ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); + } + ~MetadataProxy() + { spa_hook_remove(&mListener); } + + static auto propertyCallback(void *object, uint32_t id, const char *key, const char *type, + const char *value) noexcept -> int; +}; + /* The global thread watching for global events. This particular class responds * to objects being added to or removed from the registry. @@ -450,8 +563,8 @@ struct EventManager { /* A list of proxy objects watching for events about changes to objects in * the registry. */ - std::vector mNodeList; - MetadataProxy *mDefaultMetadata{nullptr}; + std::vector> mNodeList; + std::optional mDefaultMetadata; /* Initialization handling. When init() is called, mInitSeq is set to a * SequenceID that marks the end of populating the registry. As objects of @@ -463,24 +576,29 @@ struct EventManager { std::atomic mHasAudio{false}; int mInitSeq{}; + ~EventManager() { if(mLoop) mLoop.stop(); } + bool init(); - ~EventManager(); void kill(); auto lock() const { return mLoop.lock(); } auto unlock() const { return mLoop.unlock(); } + [[nodiscard]] inline + bool initIsDone(std::memory_order m=std::memory_order_seq_cst) const noexcept + { return mInitDone.load(m); } + /** * Waits for initialization to finish. The event manager must *NOT* be * locked when calling this. */ void waitForInit() { - if(!mInitDone.load(std::memory_order_acquire)) UNLIKELY + if(!initIsDone(std::memory_order_acquire)) UNLIKELY { MainloopUniqueLock plock{mLoop}; - plock.wait([this](){ return mInitDone.load(std::memory_order_acquire); }); + plock.wait([this](){ return initIsDone(std::memory_order_acquire); }); } } @@ -496,7 +614,7 @@ struct EventManager { plock.wait([this,&has_audio]() { has_audio = mHasAudio.load(std::memory_order_acquire); - return has_audio || mInitDone.load(std::memory_order_acquire); + return has_audio || initIsDone(std::memory_order_acquire); }); return has_audio; } @@ -506,38 +624,34 @@ struct EventManager { /* If initialization isn't done, update the sequence ID so it won't * complete until after currently scheduled events. */ - if(!mInitDone.load(std::memory_order_relaxed)) + if(!initIsDone(std::memory_order_relaxed)) mInitSeq = ppw_core_sync(mCore.get(), PW_ID_CORE, mInitSeq); } void addCallback(uint32_t id, uint32_t permissions, const char *type, uint32_t version, - const spa_dict *props); - static void addCallbackC(void *object, uint32_t id, uint32_t permissions, const char *type, - uint32_t version, const spa_dict *props) - { static_cast(object)->addCallback(id, permissions, type, version, props); } + const spa_dict *props) noexcept; - void removeCallback(uint32_t id); - static void removeCallbackC(void *object, uint32_t id) - { static_cast(object)->removeCallback(id); } + void removeCallback(uint32_t id) noexcept; static constexpr pw_registry_events CreateRegistryEvents() { pw_registry_events ret{}; ret.version = PW_VERSION_REGISTRY_EVENTS; - ret.global = &EventManager::addCallbackC; - ret.global_remove = &EventManager::removeCallbackC; + ret.global = [](void *object, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const spa_dict *props) noexcept + { static_cast(object)->addCallback(id, permissions, type, version, props); }; + ret.global_remove = [](void *object, uint32_t id) noexcept + { static_cast(object)->removeCallback(id); }; return ret; } - void coreCallback(uint32_t id, int seq); - static void coreCallbackC(void *object, uint32_t id, int seq) - { static_cast(object)->coreCallback(id, seq); } + void coreCallback(uint32_t id, int seq) noexcept; static constexpr pw_core_events CreateCoreEvents() { pw_core_events ret{}; ret.version = PW_VERSION_CORE_EVENTS; - ret.done = &EventManager::coreCallbackC; + ret.done = [](void *object, uint32_t id, int seq) noexcept + { static_cast(object)->coreCallback(id, seq); }; return ret; } }; @@ -570,12 +684,23 @@ struct DeviceNode { static std::vector sList; static DeviceNode &Add(uint32_t id); static DeviceNode *Find(uint32_t id); + static DeviceNode *FindByDevName(std::string_view devname); static void Remove(uint32_t id); - static std::vector &GetList() noexcept { return sList; } + static auto GetList() noexcept { return al::span{sList}; } - void parseSampleRate(const spa_pod *value) noexcept; - void parsePositions(const spa_pod *value) noexcept; - void parseChannelCount(const spa_pod *value) noexcept; + void parseSampleRate(const spa_pod *value, bool force_update) noexcept; + void parsePositions(const spa_pod *value, bool force_update) noexcept; + void parseChannelCount(const spa_pod *value, bool force_update) noexcept; + + void callEvent(alc::EventType type, std::string_view message) const + { + /* Source nodes aren't recognized for playback, only Sink and Duplex + * nodes are. All node types are recognized for capture. + */ + if(mType != NodeType::Source) + alc::Event(type, alc::DeviceType::Playback, message); + alc::Event(type, alc::DeviceType::Capture, message); + } }; std::vector DeviceNode::sList; std::string DefaultSinkDevice; @@ -601,8 +726,7 @@ DeviceNode &DeviceNode::Add(uint32_t id) auto match = std::find_if(sList.begin(), sList.end(), match_id); if(match != sList.end()) return *match; - sList.emplace_back(); - auto &n = sList.back(); + auto &n = sList.emplace_back(); n.mId = id; return n; } @@ -618,6 +742,17 @@ DeviceNode *DeviceNode::Find(uint32_t id) return nullptr; } +DeviceNode *DeviceNode::FindByDevName(std::string_view devname) +{ + auto match_id = [devname](DeviceNode &n) noexcept -> bool + { return n.mDevName == devname; }; + + auto match = std::find_if(sList.begin(), sList.end(), match_id); + if(match != sList.end()) return al::to_address(match); + + return nullptr; +} + void DeviceNode::Remove(uint32_t id) { auto match_id = [id](DeviceNode &n) noexcept -> bool @@ -625,6 +760,11 @@ void DeviceNode::Remove(uint32_t id) if(n.mId != id) return false; TRACE("Removing device \"%s\"\n", n.mDevName.c_str()); + if(gEventHandler.initIsDone(std::memory_order_relaxed)) + { + const std::string msg{"Device removed: "+n.mName}; + n.callEvent(alc::EventType::DeviceRemoved, msg); + } return true; }; @@ -633,25 +773,32 @@ void DeviceNode::Remove(uint32_t id) } -const spa_audio_channel MonoMap[]{ +constexpr std::array MonoMap{ SPA_AUDIO_CHANNEL_MONO -}, StereoMap[] { +}; +constexpr std::array StereoMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR -}, QuadMap[]{ +}; +constexpr std::array QuadMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR -}, X51Map[]{ +}; +constexpr std::array X51Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR -}, X51RearMap[]{ +}; +constexpr std::array X51RearMap{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR -}, X61Map[]{ +}; +constexpr std::array X61Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RC, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR -}, X71Map[]{ +}; +constexpr std::array X71Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR -}, X714Map[]{ +}; +constexpr std::array X714Map{ SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_LFE, SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR @@ -661,20 +808,18 @@ const spa_audio_channel MonoMap[]{ * Checks if every channel in 'map1' exists in 'map0' (that is, map0 is equal * to or a superset of map1). */ -template -bool MatchChannelMap(const al::span map0, const spa_audio_channel (&map1)[N]) +bool MatchChannelMap(const al::span map0, + const al::span map1) { - if(map0.size() < N) + if(map0.size() < map1.size()) return false; - for(const spa_audio_channel chid : map1) - { - if(std::find(map0.begin(), map0.end(), chid) == map0.end()) - return false; - } - return true; + + auto find_channel = [map0](const spa_audio_channel chid) -> bool + { return std::find(map0.begin(), map0.end(), chid) != map0.end(); }; + return std::all_of(map1.cbegin(), map1.cend(), find_channel); } -void DeviceNode::parseSampleRate(const spa_pod *value) noexcept +void DeviceNode::parseSampleRate(const spa_pod *value, bool force_update) noexcept { /* TODO: Can this be anything else? Long, Float, Double? */ uint32_t nvals{}, choiceType{}; @@ -683,7 +828,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept const uint podType{get_pod_type(value)}; if(podType != SPA_TYPE_Int) { - WARN("Unhandled sample rate POD type: %u\n", podType); + WARN(" Unhandled sample rate POD type: %u\n", podType); return; } @@ -691,15 +836,16 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals != 3) { - WARN("Unexpected SPA_CHOICE_Range count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Range count: %u\n", nvals); return; } auto srates = get_pod_body(value); /* [0] is the default, [1] is the min, and [2] is the max. */ - TRACE("Device ID %" PRIu64 " sample rate: %d (range: %d -> %d)\n", mSerial, srates[0], - srates[1], srates[2]); - mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + TRACE(" sample rate: %d (range: %d -> %d)\n", srates[0], srates[1], srates[2]); + if(!mSampleRate || force_update) + mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, + MaxOutputRate)); return; } @@ -707,7 +853,7 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals == 0) { - WARN("Unexpected SPA_CHOICE_Enum count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_Enum count: %u\n", nvals); return; } auto srates = get_pod_body(value, nvals); @@ -719,15 +865,16 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept others += ", "; others += std::to_string(srates[i]); } - TRACE("Device ID %" PRIu64 " sample rate: %d (%s)\n", mSerial, srates[0], others.c_str()); + TRACE(" sample rate: %d (%s)\n", srates[0], others.c_str()); /* Pick the first rate listed that's within the allowed range (default * rate if possible). */ for(const auto &rate : srates) { - if(rate >= MIN_OUTPUT_RATE && rate <= MAX_OUTPUT_RATE) + if(rate >= int{MinOutputRate} && rate <= int{MaxOutputRate}) { - mSampleRate = static_cast(rate); + if(!mSampleRate || force_update) + mSampleRate = static_cast(rate); break; } } @@ -738,119 +885,102 @@ void DeviceNode::parseSampleRate(const spa_pod *value) noexcept { if(nvals != 1) { - WARN("Unexpected SPA_CHOICE_None count: %u\n", nvals); + WARN(" Unexpected SPA_CHOICE_None count: %u\n", nvals); return; } auto srates = get_pod_body(value); - TRACE("Device ID %" PRIu64 " sample rate: %d\n", mSerial, srates[0]); - mSampleRate = static_cast(clampi(srates[0], MIN_OUTPUT_RATE, MAX_OUTPUT_RATE)); + TRACE(" sample rate: %d\n", srates[0]); + if(!mSampleRate || force_update) + mSampleRate = static_cast(std::clamp(srates[0], MinOutputRate, + MaxOutputRate)); return; } - WARN("Unhandled sample rate choice type: %u\n", choiceType); + WARN(" Unhandled sample rate choice type: %u\n", choiceType); } -void DeviceNode::parsePositions(const spa_pod *value) noexcept +void DeviceNode::parsePositions(const spa_pod *value, bool force_update) noexcept { + uint32_t choiceCount{}, choiceType{}; + value = spa_pod_get_values(value, &choiceCount, &choiceType); + + if(choiceType != SPA_CHOICE_None || choiceCount != 1) + { + ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + return; + } + const auto chanmap = get_array_span(value); if(chanmap.empty()) return; - mIs51Rear = false; - - if(MatchChannelMap(chanmap, X714Map)) - mChannels = DevFmtX714; - else if(MatchChannelMap(chanmap, X71Map)) - mChannels = DevFmtX71; - else if(MatchChannelMap(chanmap, X61Map)) - mChannels = DevFmtX61; - else if(MatchChannelMap(chanmap, X51Map)) - mChannels = DevFmtX51; - else if(MatchChannelMap(chanmap, X51RearMap)) + if(mChannels == InvalidChannelConfig || force_update) { - mChannels = DevFmtX51; - mIs51Rear = true; + mIs51Rear = false; + + if(MatchChannelMap(chanmap, X714Map)) + mChannels = DevFmtX714; + else if(MatchChannelMap(chanmap, X71Map)) + mChannels = DevFmtX71; + else if(MatchChannelMap(chanmap, X61Map)) + mChannels = DevFmtX61; + else if(MatchChannelMap(chanmap, X51Map)) + mChannels = DevFmtX51; + else if(MatchChannelMap(chanmap, X51RearMap)) + { + mChannels = DevFmtX51; + mIs51Rear = true; + } + else if(MatchChannelMap(chanmap, QuadMap)) + mChannels = DevFmtQuad; + else if(MatchChannelMap(chanmap, StereoMap)) + mChannels = DevFmtStereo; + else + mChannels = DevFmtMono; } - else if(MatchChannelMap(chanmap, QuadMap)) - mChannels = DevFmtQuad; - else if(MatchChannelMap(chanmap, StereoMap)) - mChannels = DevFmtStereo; - else - mChannels = DevFmtMono; - TRACE("Device ID %" PRIu64 " got %zu position%s for %s%s\n", mSerial, chanmap.size(), - (chanmap.size()==1)?"":"s", DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); + TRACE(" %zu position%s for %s%s\n", chanmap.size(), (chanmap.size()==1)?"":"s", + DevFmtChannelsString(mChannels), mIs51Rear?"(rear)":""); } -void DeviceNode::parseChannelCount(const spa_pod *value) noexcept +void DeviceNode::parseChannelCount(const spa_pod *value, bool force_update) noexcept { /* As a fallback with just a channel count, just assume mono or stereo. */ + uint32_t choiceCount{}, choiceType{}; + value = spa_pod_get_values(value, &choiceCount, &choiceType); + + if(choiceType != SPA_CHOICE_None || choiceCount != 1) + { + ERR(" Unexpected positions choice: type=%u, count=%u\n", choiceType, choiceCount); + return; + } + const auto chancount = get_value(value); if(!chancount) return; - mIs51Rear = false; + if(mChannels == InvalidChannelConfig || force_update) + { + mIs51Rear = false; - if(*chancount >= 2) - mChannels = DevFmtStereo; - else if(*chancount >= 1) - mChannels = DevFmtMono; - TRACE("Device ID %" PRIu64 " got %d channel%s for %s\n", mSerial, *chancount, - (*chancount==1)?"":"s", DevFmtChannelsString(mChannels)); + if(*chancount >= 2) + mChannels = DevFmtStereo; + else if(*chancount >= 1) + mChannels = DevFmtMono; + } + TRACE(" %d channel%s for %s\n", *chancount, (*chancount==1)?"":"s", + DevFmtChannelsString(mChannels)); } -constexpr char MonitorPrefix[]{"Monitor of "}; -constexpr auto MonitorPrefixLen = al::size(MonitorPrefix) - 1; -constexpr char AudioSinkClass[]{"Audio/Sink"}; -constexpr char AudioSourceClass[]{"Audio/Source"}; -constexpr char AudioSourceVirtualClass[]{"Audio/Source/Virtual"}; -constexpr char AudioDuplexClass[]{"Audio/Duplex"}; -constexpr char StreamClass[]{"Stream/"}; - -/* A generic PipeWire node proxy object used to track changes to sink and - * source nodes. - */ -struct NodeProxy { - static constexpr pw_node_events CreateNodeEvents() - { - pw_node_events ret{}; - ret.version = PW_VERSION_NODE_EVENTS; - ret.info = &NodeProxy::infoCallbackC; - ret.param = &NodeProxy::paramCallbackC; - return ret; - } - - uint32_t mId{}; - - PwNodePtr mNode{}; - spa_hook mListener{}; - - NodeProxy(uint32_t id, PwNodePtr node) - : mId{id}, mNode{std::move(node)} - { - static constexpr pw_node_events nodeEvents{CreateNodeEvents()}; - ppw_node_add_listener(mNode.get(), &mListener, &nodeEvents, this); - - /* Track changes to the enumerable formats (indicates the default - * format, which is what we're interested in). - */ - uint32_t fmtids[]{SPA_PARAM_EnumFormat}; - ppw_node_subscribe_params(mNode.get(), al::data(fmtids), al::size(fmtids)); - } - ~NodeProxy() - { spa_hook_remove(&mListener); } +[[nodiscard]] constexpr auto GetMonitorPrefix() noexcept { return "Monitor of "sv; } +[[nodiscard]] constexpr auto GetMonitorSuffix() noexcept { return ".monitor"sv; } +[[nodiscard]] constexpr auto GetAudioSinkClassName() noexcept { return "Audio/Sink"sv; } +[[nodiscard]] constexpr auto GetAudioSourceClassName() noexcept { return "Audio/Source"sv; } +[[nodiscard]] constexpr auto GetAudioDuplexClassName() noexcept { return "Audio/Duplex"sv; } +[[nodiscard]] constexpr auto GetAudioSourceVirtualClassName() noexcept +{ return "Audio/Source/Virtual"sv; } - void infoCallback(const pw_node_info *info); - static void infoCallbackC(void *object, const pw_node_info *info) - { static_cast(object)->infoCallback(info); } - - void paramCallback(int seq, uint32_t id, uint32_t index, uint32_t next, const spa_pod *param); - static void paramCallbackC(void *object, int seq, uint32_t id, uint32_t index, uint32_t next, - const spa_pod *param) - { static_cast(object)->paramCallback(seq, id, index, next, param); } -}; - -void NodeProxy::infoCallback(const pw_node_info *info) +void NodeProxy::infoCallback(void*, const pw_node_info *info) noexcept { /* We only care about property changes here (media class, name/desc). * Format changes will automatically invoke the param callback. @@ -863,14 +993,15 @@ void NodeProxy::infoCallback(const pw_node_info *info) /* Can this actually change? */ const char *media_class{spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS)}; if(!media_class) UNLIKELY return; + const std::string_view className{media_class}; NodeType ntype{}; - if(al::strcasecmp(media_class, AudioSinkClass) == 0) + if(al::case_compare(className, GetAudioSinkClassName()) == 0) ntype = NodeType::Sink; - else if(al::strcasecmp(media_class, AudioSourceClass) == 0 - || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0) + else if(al::case_compare(className, GetAudioSourceClassName()) == 0 + || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0) ntype = NodeType::Source; - else if(al::strcasecmp(media_class, AudioDuplexClass) == 0) + else if(al::case_compare(className, GetAudioDuplexClassName()) == 0) ntype = NodeType::Duplex; else { @@ -888,6 +1019,7 @@ void NodeProxy::infoCallback(const pw_node_info *info) #ifdef PW_KEY_OBJECT_SERIAL if(const char *serial_str{spa_dict_lookup(info->props, PW_KEY_OBJECT_SERIAL)}) { + errno = 0; char *serial_end{}; serial_id = std::strtoull(serial_str, &serial_end, 0); if(*serial_end != '\0' || errno == ERANGE) @@ -898,81 +1030,92 @@ void NodeProxy::infoCallback(const pw_node_info *info) } #endif + std::string name; + if(nodeName && *nodeName) name = nodeName; + else name = "PipeWire node #"+std::to_string(info->id); + const char *form_factor{spa_dict_lookup(info->props, PW_KEY_DEVICE_FORM_FACTOR)}; TRACE("Got %s device \"%s\"%s%s%s\n", AsString(ntype), devName ? devName : "(nil)", form_factor?" (":"", form_factor?form_factor:"", form_factor?")":""); - TRACE(" \"%s\" = ID %" PRIu64 "\n", nodeName ? nodeName : "(nil)", serial_id); + TRACE(" \"%s\" = ID %" PRIu64 "\n", name.c_str(), serial_id); DeviceNode &node = DeviceNode::Add(info->id); node.mSerial = serial_id; - if(nodeName && *nodeName) node.mName = nodeName; - else node.mName = "PipeWire node #"+std::to_string(info->id); + /* This method is called both to notify about a new sink/source node, + * and update properties for the node. It's unclear what properties can + * change for an existing node without being removed first, so err on + * the side of caution: send a DeviceRemoved event if it had a name + * that's being changed, and send a DeviceAdded event when the name + * differs or it didn't have one. + * + * The DeviceRemoved event needs to be called before the potentially + * new NodeType is set, so the removal event is called for the previous + * device type, while the DeviceAdded event needs to be called after. + * + * This is overkill if the node type, name, and devname can't change. + */ + bool notifyAdd{false}; + if(node.mName != name) + { + if(gEventHandler.initIsDone(std::memory_order_relaxed)) + { + if(!node.mName.empty()) + { + const std::string msg{"Device removed: "+node.mName}; + node.callEvent(alc::EventType::DeviceRemoved, msg); + } + notifyAdd = true; + } + node.mName = std::move(name); + } node.mDevName = devName ? devName : ""; node.mType = ntype; - node.mIsHeadphones = form_factor && (al::strcasecmp(form_factor, "headphones") == 0 - || al::strcasecmp(form_factor, "headset") == 0); + node.mIsHeadphones = form_factor && (al::case_compare(form_factor, "headphones"sv) == 0 + || al::case_compare(form_factor, "headset"sv) == 0); + if(notifyAdd) + { + const std::string msg{"Device added: "+node.mName}; + node.callEvent(alc::EventType::DeviceAdded, msg); + } } } -void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) +void NodeProxy::paramCallback(int, uint32_t id, uint32_t, uint32_t, const spa_pod *param) const noexcept { - if(id == SPA_PARAM_EnumFormat) + if(id == SPA_PARAM_EnumFormat || id == SPA_PARAM_Format) { DeviceNode *node{DeviceNode::Find(mId)}; if(!node) UNLIKELY return; + TRACE("Device ID %" PRIu64 " %s format%s:\n", node->mSerial, + (id == SPA_PARAM_EnumFormat) ? "available" : "current", + (id == SPA_PARAM_EnumFormat) ? "s" : ""); + + const bool force_update{id == SPA_PARAM_Format}; if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_rate)}) - node->parseSampleRate(&prop->value); + node->parseSampleRate(&prop->value, force_update); if(const spa_pod_prop *prop{spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_position)}) - node->parsePositions(&prop->value); - else if((prop=spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels)) != nullptr) - node->parseChannelCount(&prop->value); + node->parsePositions(&prop->value, force_update); + else + { + prop = spa_pod_find_prop(param, nullptr, SPA_FORMAT_AUDIO_channels); + if(prop) node->parseChannelCount(&prop->value, force_update); + } } } -/* A metadata proxy object used to query the default sink and source. */ -struct MetadataProxy { - static constexpr pw_metadata_events CreateMetadataEvents() - { - pw_metadata_events ret{}; - ret.version = PW_VERSION_METADATA_EVENTS; - ret.property = &MetadataProxy::propertyCallbackC; - return ret; - } - - uint32_t mId{}; - - PwMetadataPtr mMetadata{}; - spa_hook mListener{}; - - MetadataProxy(uint32_t id, PwMetadataPtr mdata) - : mId{id}, mMetadata{std::move(mdata)} - { - static constexpr pw_metadata_events metadataEvents{CreateMetadataEvents()}; - ppw_metadata_add_listener(mMetadata.get(), &mListener, &metadataEvents, this); - } - ~MetadataProxy() - { spa_hook_remove(&mListener); } - - - int propertyCallback(uint32_t id, const char *key, const char *type, const char *value); - static int propertyCallbackC(void *object, uint32_t id, const char *key, const char *type, - const char *value) - { return static_cast(object)->propertyCallback(id, key, type, value); } -}; - -int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *type, - const char *value) +auto MetadataProxy::propertyCallback(void*, uint32_t id, const char *key, const char *type, + const char *value) noexcept -> int { if(id != PW_ID_CORE) return 0; bool isCapture{}; - if(std::strcmp(key, "default.audio.sink") == 0) + if("default.audio.sink"sv == key) isCapture = false; - else if(std::strcmp(key, "default.audio.source") == 0) + else if("default.audio.source"sv == key) isCapture = true; else return 0; @@ -984,50 +1127,70 @@ int MetadataProxy::propertyCallback(uint32_t id, const char *key, const char *ty else DefaultSourceDevice.clear(); return 0; } - if(std::strcmp(type, "Spa:String:JSON") != 0) + if("Spa:String:JSON"sv != type) { ERR("Unexpected %s property type: %s\n", key, type); return 0; } - spa_json it[2]{}; - spa_json_init(&it[0], value, strlen(value)); - if(spa_json_enter_object(&it[0], &it[1]) <= 0) + std::array it{}; + spa_json_init(it.data(), value, strlen(value)); + if(spa_json_enter_object(&std::get<0>(it), &std::get<1>(it)) <= 0) return 0; auto get_json_string = [](spa_json *iter) { - al::optional str; + std::optional str; const char *val{}; int len{spa_json_next(iter, &val)}; if(len <= 0) return str; - str.emplace().resize(static_cast(len), '\0'); - if(spa_json_parse_string(val, len, &str->front()) <= 0) + str.emplace(static_cast(len), '\0'); + if(spa_json_parse_string(val, len, str->data()) <= 0) str.reset(); else while(!str->empty() && str->back() == '\0') str->pop_back(); return str; }; - while(auto propKey = get_json_string(&it[1])) + while(auto propKey = get_json_string(&std::get<1>(it))) { - if(*propKey == "name") + if("name"sv == *propKey) { - auto propValue = get_json_string(&it[1]); + auto propValue = get_json_string(&std::get<1>(it)); if(!propValue) break; TRACE("Got default %s device \"%s\"\n", isCapture ? "capture" : "playback", propValue->c_str()); - if(!isCapture) + if(!isCapture && DefaultSinkDevice != *propValue) + { + if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) + { + auto entry = DeviceNode::FindByDevName(*propValue); + const std::string message{"Default playback device changed: "+ + (entry ? entry->mName : std::string{})}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + message); + } DefaultSinkDevice = std::move(*propValue); - else + } + else if(isCapture && DefaultSourceDevice != *propValue) + { + if(gEventHandler.mInitDone.load(std::memory_order_relaxed)) + { + auto entry = DeviceNode::FindByDevName(*propValue); + const std::string message{"Default capture device changed: "+ + (entry ? entry->mName : std::string{})}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + message); + } DefaultSourceDevice = std::move(*propValue); + } } else { const char *v{}; - if(spa_json_next(&it[1], &v) <= 0) + if(spa_json_next(&std::get<1>(it), &v) <= 0) break; } } @@ -1044,7 +1207,7 @@ bool EventManager::init() return false; } - mContext = mLoop.newContext(pw_properties_new(PW_KEY_CONFIG_NAME, "client-rt.conf", nullptr)); + mContext = mLoop.newContext(); if(!mContext) { ERR("Failed to create PipeWire event context (errno: %d)\n", errno); @@ -1085,26 +1248,12 @@ bool EventManager::init() return true; } -EventManager::~EventManager() -{ - if(mLoop) mLoop.stop(); - - for(NodeProxy *node : mNodeList) - al::destroy_at(node); - if(mDefaultMetadata) - al::destroy_at(mDefaultMetadata); -} - void EventManager::kill() { if(mLoop) mLoop.stop(); - for(NodeProxy *node : mNodeList) - al::destroy_at(node); + mDefaultMetadata.reset(); mNodeList.clear(); - if(mDefaultMetadata) - al::destroy_at(mDefaultMetadata); - mDefaultMetadata = nullptr; mRegistry = nullptr; mCore = nullptr; @@ -1113,30 +1262,30 @@ void EventManager::kill() } void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t version, - const spa_dict *props) + const spa_dict *props) noexcept { /* We're only interested in interface nodes. */ if(std::strcmp(type, PW_TYPE_INTERFACE_Node) == 0) { const char *media_class{spa_dict_lookup(props, PW_KEY_MEDIA_CLASS)}; if(!media_class) return; + const std::string_view className{media_class}; /* Specifically, audio sinks and sources (and duplexes). */ - const bool isGood{al::strcasecmp(media_class, AudioSinkClass) == 0 - || al::strcasecmp(media_class, AudioSourceClass) == 0 - || al::strcasecmp(media_class, AudioSourceVirtualClass) == 0 - || al::strcasecmp(media_class, AudioDuplexClass) == 0}; + const bool isGood{al::case_compare(className, GetAudioSinkClassName()) == 0 + || al::case_compare(className, GetAudioSourceClassName()) == 0 + || al::case_compare(className, GetAudioSourceVirtualClassName()) == 0 + || al::case_compare(className, GetAudioDuplexClassName()) == 0}; if(!isGood) { - if(std::strstr(media_class, "/Video") == nullptr - && std::strncmp(media_class, StreamClass, sizeof(StreamClass)-1) != 0) + if(!al::contains(className, "/Video"sv) && !al::starts_with(className, "Stream/"sv)) TRACE("Ignoring node class %s\n", media_class); return; } /* Create the proxy object. */ auto node = PwNodePtr{static_cast(pw_registry_bind(mRegistry.get(), id, type, - version, sizeof(NodeProxy)))}; + version, 0))}; if(!node) { ERR("Failed to create node proxy object (errno: %d)\n", errno); @@ -1146,8 +1295,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t /* Initialize the NodeProxy to hold the node object, add it to the * active node list, and update the sync point. */ - auto *proxy = static_cast(pw_proxy_get_user_data(as(node.get()))); - mNodeList.emplace_back(al::construct_at(proxy, id, std::move(node))); + mNodeList.emplace_back(std::make_unique(id, std::move(node))); syncInit(); /* Signal any waiters that we have found a source or sink for audio @@ -1161,7 +1309,7 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t const char *data_class{spa_dict_lookup(props, PW_KEY_METADATA_NAME)}; if(!data_class) return; - if(std::strcmp(data_class, "default") != 0) + if("default"sv != data_class) { TRACE("Ignoring metadata \"%s\"\n", data_class); return; @@ -1174,42 +1322,32 @@ void EventManager::addCallback(uint32_t id, uint32_t, const char *type, uint32_t } auto mdata = PwMetadataPtr{static_cast(pw_registry_bind(mRegistry.get(), id, - type, version, sizeof(MetadataProxy)))}; + type, version, 0))}; if(!mdata) { ERR("Failed to create metadata proxy object (errno: %d)\n", errno); return; } - auto *proxy = static_cast( - pw_proxy_get_user_data(as(mdata.get()))); - mDefaultMetadata = al::construct_at(proxy, id, std::move(mdata)); + mDefaultMetadata.emplace(id, std::move(mdata)); syncInit(); } } -void EventManager::removeCallback(uint32_t id) +void EventManager::removeCallback(uint32_t id) noexcept { DeviceNode::Remove(id); - auto clear_node = [id](NodeProxy *node) noexcept - { - if(node->mId != id) - return false; - al::destroy_at(node); - return true; - }; + auto clear_node = [id](std::unique_ptr &node) noexcept + { return node->mId == id; }; auto node_end = std::remove_if(mNodeList.begin(), mNodeList.end(), clear_node); mNodeList.erase(node_end, mNodeList.end()); if(mDefaultMetadata && mDefaultMetadata->mId == id) - { - al::destroy_at(mDefaultMetadata); - mDefaultMetadata = nullptr; - } + mDefaultMetadata.reset(); } -void EventManager::coreCallback(uint32_t id, int seq) +void EventManager::coreCallback(uint32_t id, int seq) noexcept { if(id == PW_ID_CORE && seq == mInitSeq) { @@ -1260,6 +1398,7 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u case DevFmtX71: map = X71Map; break; case DevFmtX714: map = X714Map; break; case DevFmtX3D71: map = X71Map; break; + case DevFmtX7144: case DevFmtAmbi3D: info.flags |= SPA_AUDIO_FLAG_UNPOSITIONED; info.channels = device->channelsFromFmt(); @@ -1268,27 +1407,18 @@ spa_audio_info_raw make_spa_info(DeviceBase *device, bool is51rear, use_f32p_e u if(!map.empty()) { info.channels = static_cast(map.size()); - std::copy(map.begin(), map.end(), info.position); + std::copy(map.begin(), map.end(), std::begin(info.position)); } return info; } class PipeWirePlayback final : public BackendBase { - void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); - static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, - const char *error) - { static_cast(data)->stateChangedCallback(old, state, error); } + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; + void ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept; + void outputCallback() noexcept; - void ioChangedCallback(uint32_t id, void *area, uint32_t size); - static void ioChangedCallbackC(void *data, uint32_t id, void *area, uint32_t size) - { static_cast(data)->ioChangedCallback(id, area, size); } - - void outputCallback(); - static void outputCallbackC(void *data) - { static_cast(data)->outputCallback(); } - - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -1302,52 +1432,54 @@ class PipeWirePlayback final : public BackendBase { PwStreamPtr mStream; spa_hook mStreamListener{}; spa_io_rate_match *mRateMatch{}; - std::unique_ptr mChannelPtrs; - uint mNumChannels{}; + std::vector mChannelPtrs; static constexpr pw_stream_events CreateEvents() { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; - ret.state_changed = &PipeWirePlayback::stateChangedCallbackC; - ret.io_changed = &PipeWirePlayback::ioChangedCallbackC; - ret.process = &PipeWirePlayback::outputCallbackC; + ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept + { static_cast(data)->stateChangedCallback(old, state, error); }; + ret.io_changed = [](void *data, uint32_t id, void *area, uint32_t size) noexcept + { static_cast(data)->ioChangedCallback(id, area, size); }; + ret.process = [](void *data) noexcept + { static_cast(data)->outputCallback(); }; return ret; } public: PipeWirePlayback(DeviceBase *device) noexcept : BackendBase{device} { } - ~PipeWirePlayback() + ~PipeWirePlayback() final { /* Stop the mainloop so the stream can be properly destroyed. */ if(mLoop) mLoop.stop(); } - - DEF_NEWDEL(PipeWirePlayback) }; -void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +void PipeWirePlayback::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } -void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) +void PipeWirePlayback::ioChangedCallback(uint32_t id, void *area, uint32_t size) noexcept { switch(id) { case SPA_IO_RateMatch: if(size >= sizeof(spa_io_rate_match)) mRateMatch = static_cast(area); + else + mRateMatch = nullptr; break; } } -void PipeWirePlayback::outputCallback() +void PipeWirePlayback::outputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; const al::span datas{pw_buf->buffer->datas, - minu(mNumChannels, pw_buf->buffer->n_datas)}; + std::min(mChannelPtrs.size(), size_t{pw_buf->buffer->n_datas})}; #if PW_CHECK_VERSION(0,3,49) /* In 0.3.49, pw_buffer::requested specifies the number of samples needed * by the resampler/graph for this audio update. @@ -1367,37 +1499,35 @@ void PipeWirePlayback::outputCallback() * buffer length in any one channel is smaller than we wanted (shouldn't * be, but just in case). */ - float **chanptr_end{mChannelPtrs.get()}; + auto chanptr_end = mChannelPtrs.begin(); for(const auto &data : datas) { - length = minu(length, data.maxsize/sizeof(float)); + length = std::min(length, data.maxsize/uint{sizeof(float)}); *chanptr_end = static_cast(data.data); ++chanptr_end; - } - mDevice->renderSamples({mChannelPtrs.get(), chanptr_end}, length); - - for(const auto &data : datas) - { data.chunk->offset = 0; data.chunk->stride = sizeof(float); data.chunk->size = length * sizeof(float); } + + mDevice->renderSamples(mChannelPtrs, length); + pw_buf->size = length; pw_stream_queue_buffer(mStream.get(), pw_buf); } -void PipeWirePlayback::open(const char *name) +void PipeWirePlayback::open(std::string_view name) { static std::atomic OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); - if(!name) + if(name.empty()) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match = devlist.cend(); @@ -1422,15 +1552,15 @@ void PipeWirePlayback::open(const char *name) } else { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_name = [name](const DeviceNode &n) -> bool - { return n.mType != NodeType::Source && n.mName == name; }; + { return n.mType != NodeType::Source && (n.mName == name || n.mDevName == name); }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; targetid = match->mSerial; devname = match->mName; @@ -1472,19 +1602,19 @@ void PipeWirePlayback::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else - mDevice->DeviceName = pwireDevice; + mDevice->DeviceName = "PipeWire Output"sv; } bool PipeWirePlayback::reset() { if(mStream) { - MainloopLockGuard _{mLoop}; + MainloopLockGuard looplock{mLoop}; mStream = nullptr; } mStreamListener = {}; mRateMatch = nullptr; - mTimeBase = GetDeviceClockTime(mDevice); + mTimeBase = mDevice->getClockTime(); /* If connecting to a specific device, update various device parameters to * match its format. @@ -1493,7 +1623,7 @@ bool PipeWirePlayback::reset() mDevice->Flags.reset(DirectEar); if(mTargetId != PwIdAny) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool @@ -1505,11 +1635,12 @@ bool PipeWirePlayback::reset() { /* Scale the update size if the sample rate changes. */ const double scale{static_cast(match->mSampleRate) / mDevice->Frequency}; - const double numbufs{static_cast(mDevice->BufferSize)/mDevice->UpdateSize}; + const double updatesize{std::round(mDevice->UpdateSize * scale)}; + const double buffersize{std::round(mDevice->BufferSize * scale)}; + mDevice->Frequency = match->mSampleRate; - mDevice->UpdateSize = static_cast(clampd(mDevice->UpdateSize*scale + 0.5, - 64.0, 8192.0)); - mDevice->BufferSize = static_cast(numbufs*mDevice->UpdateSize + 0.5); + mDevice->UpdateSize = static_cast(std::clamp(updatesize, 64.0, 8192.0)); + mDevice->BufferSize = static_cast(std::max(buffersize, 128.0)); } if(!mDevice->Flags.test(ChannelsRequest) && match->mChannels != InvalidChannelConfig) mDevice->FmtChans = match->mChannels; @@ -1523,14 +1654,10 @@ bool PipeWirePlayback::reset() */ spa_audio_info_raw info{make_spa_info(mDevice, is51rear, ForceF32Planar)}; - /* TODO: How to tell what an appropriate size is? Examples just use this - * magic value. - */ - constexpr uint32_t pod_buffer_size{1024}; - auto pod_buffer = std::make_unique(pod_buffer_size); - spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + static constexpr uint32_t pod_buffer_size{1024}; + PodDynamicBuilder b(pod_buffer_size); - const spa_pod *params{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + const spa_pod *params{spa_format_audio_raw_build(b.get(), SPA_PARAM_EnumFormat, &info)}; if(!params) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -1571,7 +1698,7 @@ bool PipeWirePlayback::reset() pw_stream_flags flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS}; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pipewire", "rt-mix", true)) + if(GetConfigValueBool(mDevice->DeviceName, "pipewire", "rt-mix", false)) flags |= PW_STREAM_FLAG_RT_PROCESS; if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_OUTPUT, PwIdAny, flags, ¶ms, 1)}) throw al::backend_exception{al::backend_error::DeviceError, @@ -1596,8 +1723,7 @@ bool PipeWirePlayback::reset() */ plock.unlock(); - mNumChannels = mDevice->channelsFromFmt(); - mChannelPtrs = std::make_unique(mNumChannels); + mChannelPtrs.resize(mDevice->channelsFromFmt()); setDefaultWFXChannelOrder(); @@ -1655,7 +1781,7 @@ void PipeWirePlayback::start() mDevice->UpdateSize = updatesize; mDevice->BufferSize = static_cast(ptime.buffered + delay + - totalbuffers*updatesize); + uint64_t{totalbuffers}*updatesize); break; } #else @@ -1674,7 +1800,10 @@ void PipeWirePlayback::start() } #endif if(!--wait_count) + { + ERR("Timeout getting PipeWire stream buffering info\n"); break; + } plock.unlock(); std::this_thread::sleep_for(milliseconds{20}); @@ -1686,8 +1815,7 @@ void PipeWirePlayback::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to stop PipeWire stream (res: %d)", res}; + ERR("Failed to stop PipeWire stream (res: %d)\n", res); /* Wait for the stream to stop playing. */ plock.wait([stream=mStream.get()]() @@ -1706,7 +1834,7 @@ ClockLatency PipeWirePlayback::getClockLatency() pw_time ptime{}; if(mStream) { - MainloopLockGuard _{mLoop}; + MainloopLockGuard looplock{mLoop}; if(int res{pw_stream_get_time_n(mStream.get(), &ptime, sizeof(ptime))}) ERR("Failed to get PipeWire stream time (res: %d)\n", res); } @@ -1719,10 +1847,10 @@ ClockLatency PipeWirePlayback::getClockLatency() uint refcount; do { refcount = mDevice->waitForMix(); - mixtime = GetDeviceClockTime(mDevice); + mixtime = mDevice->getClockTime(); clock_gettime(CLOCK_MONOTONIC, &tspec); std::atomic_thread_fence(std::memory_order_acquire); - } while(refcount != ReadRef(mDevice->MixCount)); + } while(refcount != mDevice->mMixCount.load(std::memory_order_relaxed)); /* Convert the monotonic clock, stream ticks, and stream delay to * nanoseconds. @@ -1769,7 +1897,7 @@ ClockLatency PipeWirePlayback::getClockLatency() delay -= monoclock - nanoseconds{ptime.now}; /* Return the mixer time and delay. Clamp the delay to no less than 0, - * incase timer drift got that severe. + * in case timer drift got that severe. */ ClockLatency ret{}; ret.ClockTime = mixtime; @@ -1780,19 +1908,13 @@ ClockLatency PipeWirePlayback::getClockLatency() class PipeWireCapture final : public BackendBase { - void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error); - static void stateChangedCallbackC(void *data, pw_stream_state old, pw_stream_state state, - const char *error) - { static_cast(data)->stateChangedCallback(old, state, error); } + void stateChangedCallback(pw_stream_state old, pw_stream_state state, const char *error) noexcept; + void inputCallback() noexcept; - void inputCallback(); - static void inputCallbackC(void *data) - { static_cast(data)->inputCallback(); } - - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; uint64_t mTargetId{PwIdAny}; @@ -1808,47 +1930,48 @@ class PipeWireCapture final : public BackendBase { { pw_stream_events ret{}; ret.version = PW_VERSION_STREAM_EVENTS; - ret.state_changed = &PipeWireCapture::stateChangedCallbackC; - ret.process = &PipeWireCapture::inputCallbackC; + ret.state_changed = [](void *data, pw_stream_state old, pw_stream_state state, const char *error) noexcept + { static_cast(data)->stateChangedCallback(old, state, error); }; + ret.process = [](void *data) noexcept + { static_cast(data)->inputCallback(); }; return ret; } public: PipeWireCapture(DeviceBase *device) noexcept : BackendBase{device} { } - ~PipeWireCapture() { if(mLoop) mLoop.stop(); } - - DEF_NEWDEL(PipeWireCapture) + ~PipeWireCapture() final { if(mLoop) mLoop.stop(); } }; -void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) +void PipeWireCapture::stateChangedCallback(pw_stream_state, pw_stream_state, const char*) noexcept { mLoop.signal(false); } -void PipeWireCapture::inputCallback() +void PipeWireCapture::inputCallback() noexcept { pw_buffer *pw_buf{pw_stream_dequeue_buffer(mStream.get())}; if(!pw_buf) UNLIKELY return; spa_data *bufdata{pw_buf->buffer->datas}; - const uint offset{minu(bufdata->chunk->offset, bufdata->maxsize)}; - const uint size{minu(bufdata->chunk->size, bufdata->maxsize - offset)}; + const uint offset{bufdata->chunk->offset % bufdata->maxsize}; + const auto input = al::span{static_cast(bufdata->data), bufdata->maxsize} + .subspan(offset, std::min(bufdata->chunk->size, bufdata->maxsize - offset)); - mRing->write(static_cast(bufdata->data) + offset, size / mRing->getElemSize()); + std::ignore = mRing->write(input.data(), input.size() / mRing->getElemSize()); pw_stream_queue_buffer(mStream.get(), pw_buf); } -void PipeWireCapture::open(const char *name) +void PipeWireCapture::open(std::string_view name) { static std::atomic OpenCount{0}; uint64_t targetid{PwIdAny}; std::string devname{}; gEventHandler.waitForInit(); - if(!name) + if(name.empty()) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match = devlist.cend(); @@ -1874,29 +1997,39 @@ void PipeWireCapture::open(const char *name) targetid = match->mSerial; if(match->mType != NodeType::Sink) devname = match->mName; - else devname = MonitorPrefix+match->mName; + else devname = std::string{GetMonitorPrefix()}+match->mName; } else { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); + const std::string_view prefix{GetMonitorPrefix()}; + const std::string_view suffix{GetMonitorSuffix()}; auto match_name = [name](const DeviceNode &n) -> bool { return n.mType != NodeType::Sink && n.mName == name; }; auto match = std::find_if(devlist.cbegin(), devlist.cend(), match_name); - if(match == devlist.cend() && std::strncmp(name, MonitorPrefix, MonitorPrefixLen) == 0) + if(match == devlist.cend() && al::starts_with(name, prefix)) { - const char *sinkname{name + MonitorPrefixLen}; + const std::string_view sinkname{name.substr(prefix.length())}; auto match_sinkname = [sinkname](const DeviceNode &n) -> bool { return n.mType == NodeType::Sink && n.mName == sinkname; }; match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); } + else if(match == devlist.cend() && al::ends_with(name, suffix)) + { + const std::string_view sinkname{name.substr(0, name.size()-suffix.size())}; + auto match_sinkname = [sinkname](const DeviceNode &n) -> bool + { return n.mType == NodeType::Sink && n.mDevName == sinkname; }; + match = std::find_if(devlist.cbegin(), devlist.cend(), match_sinkname); + } if(match == devlist.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; targetid = match->mSerial; - devname = name; + if(match->mType != NodeType::Sink) devname = match->mName; + else devname = std::string{GetMonitorPrefix()}+match->mName; } if(!mLoop) @@ -1935,13 +2068,13 @@ void PipeWireCapture::open(const char *name) if(!devname.empty()) mDevice->DeviceName = std::move(devname); else - mDevice->DeviceName = pwireInput; + mDevice->DeviceName = "PipeWire Input"sv; bool is51rear{false}; if(mTargetId != PwIdAny) { - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_id = [targetid=mTargetId](const DeviceNode &n) -> bool @@ -1952,11 +2085,11 @@ void PipeWireCapture::open(const char *name) } spa_audio_info_raw info{make_spa_info(mDevice, is51rear, UseDevType)}; - constexpr uint32_t pod_buffer_size{1024}; - auto pod_buffer = std::make_unique(pod_buffer_size); - spa_pod_builder b{make_pod_builder(pod_buffer.get(), pod_buffer_size)}; + static constexpr uint32_t pod_buffer_size{1024}; + PodDynamicBuilder b(pod_buffer_size); - const spa_pod *params[]{spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info)}; + std::array params{static_cast(spa_format_audio_raw_build(b.get(), + SPA_PARAM_EnumFormat, &info))}; if(!params[0]) throw al::backend_exception{al::backend_error::DeviceError, "Failed to set PipeWire audio format parameters"}; @@ -1998,7 +2131,7 @@ void PipeWireCapture::open(const char *name) constexpr pw_stream_flags Flags{PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS}; - if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params, 1)}) + if(int res{pw_stream_connect(mStream.get(), PW_DIRECTION_INPUT, PwIdAny, Flags, params.data(), 1)}) throw al::backend_exception{al::backend_error::DeviceError, "Error connecting PipeWire stream (res: %d)", res}; @@ -2017,7 +2150,7 @@ void PipeWireCapture::open(const char *name) setDefaultWFXChannelOrder(); /* Ensure at least a 100ms capture buffer. */ - mRing = RingBuffer::Create(maxu(mDevice->Frequency/10, mDevice->BufferSize), + mRing = RingBuffer::Create(std::max(mDevice->Frequency/10u, mDevice->BufferSize), mDevice->frameSizeFromFmt(), false); } @@ -2044,8 +2177,7 @@ void PipeWireCapture::stop() { MainloopUniqueLock plock{mLoop}; if(int res{pw_stream_set_active(mStream.get(), false)}) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to stop PipeWire stream (res: %d)", res}; + ERR("Failed to stop PipeWire stream (res: %d)\n", res); plock.wait([stream=mStream.get()]() { return pw_stream_get_state(stream, nullptr) != PW_STREAM_STATE_STREAMING; }); @@ -2054,8 +2186,8 @@ void PipeWireCapture::stop() uint PipeWireCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void PipeWireCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void PipeWireCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace @@ -2074,11 +2206,11 @@ bool PipeWireBackendFactory::init() } TRACE("Found PipeWire version \"%s\" (%s or newer)\n", version, pw_get_headers_version()); - pw_init(0, nullptr); + pw_init(nullptr, nullptr); if(!gEventHandler.init()) return false; - if(!GetConfigValueBool(nullptr, "pipewire", "assume-audio", false) + if(!GetConfigValueBool({}, "pipewire", "assume-audio", false) && !gEventHandler.waitForAudio()) { gEventHandler.kill(); @@ -2094,12 +2226,12 @@ bool PipeWireBackendFactory::init() bool PipeWireBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string PipeWireBackendFactory::probe(BackendType type) +auto PipeWireBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; gEventHandler.waitForInit(); - EventWatcherLockGuard _{gEventHandler}; + EventWatcherLockGuard evtlock{gEventHandler}; auto&& devlist = DeviceNode::GetList(); auto match_defsink = [](const DeviceNode &n) -> bool @@ -2117,31 +2249,31 @@ std::string PipeWireBackendFactory::probe(BackendType type) case BackendType::Playback: defmatch = std::find_if(defmatch, devlist.cend(), match_defsink); if(defmatch != devlist.cend()) - { - /* Includes null char. */ - outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); - } + outnames.emplace_back(defmatch->mName); for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch && iter->mType != NodeType::Source) - outnames.append(iter->mName.c_str(), iter->mName.length()+1); + outnames.emplace_back(iter->mName); } break; case BackendType::Capture: + outnames.reserve(devlist.size()); defmatch = std::find_if(defmatch, devlist.cend(), match_defsource); if(defmatch != devlist.cend()) { if(defmatch->mType == NodeType::Sink) - outnames.append(MonitorPrefix); - outnames.append(defmatch->mName.c_str(), defmatch->mName.length()+1); + outnames.emplace_back(std::string{GetMonitorPrefix()}+defmatch->mName); + else + outnames.emplace_back(defmatch->mName); } for(auto iter = devlist.cbegin();iter != devlist.cend();++iter) { if(iter != defmatch) { if(iter->mType == NodeType::Sink) - outnames.append(MonitorPrefix); - outnames.append(iter->mName.c_str(), iter->mName.length()+1); + outnames.emplace_back(std::string{GetMonitorPrefix()}+iter->mName); + else + outnames.emplace_back(iter->mName); } } break; @@ -2150,6 +2282,7 @@ std::string PipeWireBackendFactory::probe(BackendType type) return outnames; } + BackendPtr PipeWireBackendFactory::createBackend(DeviceBase *device, BackendType type) { if(type == BackendType::Playback) @@ -2164,3 +2297,18 @@ BackendFactory &PipeWireBackendFactory::getFactory() static PipeWireBackendFactory factory{}; return factory; } + +alc::EventSupport PipeWireBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + return alc::EventSupport::FullSupport; + + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/pipewire.h b/Engine/lib/openal-soft/alc/backends/pipewire.h index 5f930239c..567d18edd 100644 --- a/Engine/lib/openal-soft/alc/backends/pipewire.h +++ b/Engine/lib/openal-soft/alc/backends/pipewire.h @@ -2,22 +2,26 @@ #define BACKENDS_PIPEWIRE_H #include +#include +#include "alc/events.h" #include "base.h" struct DeviceBase; struct PipeWireBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PIPEWIRE_H */ diff --git a/Engine/lib/openal-soft/alc/backends/portaudio.cpp b/Engine/lib/openal-soft/alc/backends/portaudio.cpp index 9c94587da..eb1c289f3 100644 --- a/Engine/lib/openal-soft/alc/backends/portaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/portaudio.cpp @@ -26,21 +26,20 @@ #include #include +#include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alstring.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "ringbuffer.h" -#include +#include /* NOLINT(*-duplicate-include) Not the same header. */ namespace { -constexpr char pa_device[] = "PortAudio Default"; - - #ifdef HAVE_DYNLOAD void *pa_handle; #define MAKE_FUNC(x) decltype(x) * p##x @@ -51,6 +50,8 @@ MAKE_FUNC(Pa_StartStream); MAKE_FUNC(Pa_StopStream); MAKE_FUNC(Pa_OpenStream); MAKE_FUNC(Pa_CloseStream); +MAKE_FUNC(Pa_GetDeviceCount); +MAKE_FUNC(Pa_GetDeviceInfo); MAKE_FUNC(Pa_GetDefaultOutputDevice); MAKE_FUNC(Pa_GetDefaultInputDevice); MAKE_FUNC(Pa_GetStreamInfo); @@ -64,12 +65,49 @@ MAKE_FUNC(Pa_GetStreamInfo); #define Pa_StopStream pPa_StopStream #define Pa_OpenStream pPa_OpenStream #define Pa_CloseStream pPa_CloseStream +#define Pa_GetDeviceCount pPa_GetDeviceCount +#define Pa_GetDeviceInfo pPa_GetDeviceInfo #define Pa_GetDefaultOutputDevice pPa_GetDefaultOutputDevice #define Pa_GetDefaultInputDevice pPa_GetDefaultInputDevice #define Pa_GetStreamInfo pPa_GetStreamInfo #endif #endif +struct DeviceEntry { + std::string mName; + bool mIsPlayback{}; + bool mIsCapture{}; +}; +std::vector DeviceNames; + +void EnumerateDevices() +{ + const auto devcount = Pa_GetDeviceCount(); + if(devcount < 0) + { + ERR("Error getting device count: %s\n", Pa_GetErrorText(devcount)); + return; + } + + std::vector(static_cast(devcount)).swap(DeviceNames); + PaDeviceIndex idx{0}; + for(auto &entry : DeviceNames) + { + if(auto info = Pa_GetDeviceInfo(idx); info && info->name) + { +#ifdef _WIN32 + entry.mName = "OpenAL Soft on "+std::string{info->name}; +#else + entry.mName = info->name; +#endif + entry.mIsPlayback = (info->maxOutputChannels > 0); + entry.mIsCapture = (info->maxInputChannels > 0); + TRACE("Device %d \"%s\": %d playback, %d capture channels\n", idx, entry.mName.c_str(), + info->maxOutputChannels, info->maxInputChannels); + } + ++idx; + } +} struct PortPlayback final : public BackendBase { PortPlayback(DeviceBase *device) noexcept : BackendBase{device} { } @@ -77,24 +115,17 @@ struct PortPlayback final : public BackendBase { int writeCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; - static int writeCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; PaStream *mStream{nullptr}; PaStreamParameters mParams{}; + DevFmtChannels mChannelConfig{}; + uint mAmbiOrder{}; uint mUpdateSize{0u}; - - DEF_NEWDEL(PortPlayback) }; PortPlayback::~PortPlayback() @@ -115,22 +146,38 @@ int PortPlayback::writeCallback(const void*, void *outputBuffer, unsigned long f } -void PortPlayback::open(const char *name) +void PortPlayback::open(std::string_view name) { - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(DeviceNames.empty()) + EnumerateDevices(); + + int deviceid{-1}; + if(name.empty()) + { + if(auto devidopt = ConfigValueInt({}, "port", "device")) + deviceid = *devidopt; + if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) + deviceid = Pa_GetDefaultOutputDevice(); + name = DeviceNames.at(static_cast(deviceid)).mName; + } + else + { + auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), + [name](const DeviceEntry &entry) { return entry.mIsPlayback && name == entry.mName; }); + if(iter == DeviceNames.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); + } PaStreamParameters params{}; - auto devidopt = ConfigValueInt(nullptr, "port", "device"); - if(devidopt && *devidopt >= 0) params.device = *devidopt; - else params.device = Pa_GetDefaultOutputDevice(); + params.device = deviceid; params.suggestedLatency = mDevice->BufferSize / static_cast(mDevice->Frequency); params.hostApiSpecificStreamInfo = nullptr; - params.channelCount = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + mChannelConfig = mDevice->FmtChans; + mAmbiOrder = mDevice->mAmbiOrder; + params.channelCount = static_cast(mDevice->channelsFromFmt()); switch(mDevice->FmtType) { @@ -155,19 +202,21 @@ void PortPlayback::open(const char *name) break; } -retry_open: - PaStream *stream{}; - PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, mDevice->UpdateSize, - paNoFlag, &PortPlayback::writeCallbackC, this)}; - if(err != paNoError) + static constexpr auto writeCallback = [](const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) noexcept { - if(params.sampleFormat == paFloat32) - { - params.sampleFormat = paInt16; - goto retry_open; - } - throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", - Pa_GetErrorText(err)}; + return static_cast(userData)->writeCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + }; + PaStream *stream{}; + while(PaError err{Pa_OpenStream(&stream, nullptr, ¶ms, mDevice->Frequency, + mDevice->UpdateSize, paNoFlag, writeCallback, this)}) + { + if(params.sampleFormat != paFloat32) + throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", + Pa_GetErrorText(err)}; + params.sampleFormat = paInt16; } Pa_CloseStream(mStream); @@ -182,7 +231,18 @@ bool PortPlayback::reset() { const PaStreamInfo *streamInfo{Pa_GetStreamInfo(mStream)}; mDevice->Frequency = static_cast(streamInfo->sampleRate); + mDevice->FmtChans = mChannelConfig; + mDevice->mAmbiOrder = mAmbiOrder; mDevice->UpdateSize = mUpdateSize; + mDevice->BufferSize = mUpdateSize * 2; + if(streamInfo->outputLatency > 0.0f) + { + const double sampleLatency{streamInfo->outputLatency * streamInfo->sampleRate}; + TRACE("Reported stream latency: %f sec (%f samples)\n", streamInfo->outputLatency, + sampleLatency); + mDevice->BufferSize = static_cast(std::clamp(sampleLatency, + double(mDevice->BufferSize), double{std::numeric_limits::max()})); + } if(mParams.sampleFormat == paInt8) mDevice->FmtType = DevFmtByte; @@ -200,15 +260,6 @@ bool PortPlayback::reset() return false; } - if(mParams.channelCount >= 2) - mDevice->FmtChans = DevFmtStereo; - else if(mParams.channelCount == 1) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unexpected channel count: %u\n", mParams.channelCount); - return false; - } setDefaultChannelOrder(); return true; @@ -216,16 +267,14 @@ bool PortPlayback::reset() void PortPlayback::start() { - const PaError err{Pa_StartStream(mStream)}; - if(err == paNoError) + if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start playback: %s", Pa_GetErrorText(err)}; } void PortPlayback::stop() { - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) + if(PaError err{Pa_StopStream(mStream)}; err != paNoError) ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); } @@ -235,27 +284,18 @@ struct PortCapture final : public BackendBase { ~PortCapture() override; int readCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) noexcept; - static int readCallbackC(const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, - const PaStreamCallbackFlags statusFlags, void *userData) noexcept - { - return static_cast(userData)->readCallback(inputBuffer, outputBuffer, - framesPerBuffer, timeInfo, statusFlags); - } + const PaStreamCallbackTimeInfo *timeInfo, const PaStreamCallbackFlags statusFlags) const noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; PaStream *mStream{nullptr}; - PaStreamParameters mParams; + PaStreamParameters mParams{}; RingBufferPtr mRing{nullptr}; - - DEF_NEWDEL(PortCapture) }; PortCapture::~PortCapture() @@ -268,30 +308,43 @@ PortCapture::~PortCapture() int PortCapture::readCallback(const void *inputBuffer, void*, unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) noexcept + const PaStreamCallbackTimeInfo*, const PaStreamCallbackFlags) const noexcept { - mRing->write(inputBuffer, framesPerBuffer); + std::ignore = mRing->write(inputBuffer, framesPerBuffer); return 0; } -void PortCapture::open(const char *name) +void PortCapture::open(std::string_view name) { - if(!name) - name = pa_device; - else if(strcmp(name, pa_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(DeviceNames.empty()) + EnumerateDevices(); - uint samples{mDevice->BufferSize}; - samples = maxu(samples, 100 * mDevice->Frequency / 1000); - uint frame_size{mDevice->frameSizeFromFmt()}; + int deviceid{}; + if(name.empty()) + { + if(auto devidopt = ConfigValueInt({}, "port", "capture")) + deviceid = *devidopt; + if(deviceid < 0 || static_cast(deviceid) >= DeviceNames.size()) + deviceid = Pa_GetDefaultInputDevice(); + name = DeviceNames.at(static_cast(deviceid)).mName; + } + else + { + auto iter = std::find_if(DeviceNames.cbegin(), DeviceNames.cend(), + [name](const DeviceEntry &entry) { return entry.mIsCapture && name == entry.mName; }); + if(iter == DeviceNames.cend()) + throw al::backend_exception{al::backend_error::NoDevice, + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + deviceid = static_cast(std::distance(DeviceNames.cbegin(), iter)); + } + + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency/10u)}; + const uint frame_size{mDevice->frameSizeFromFmt()}; mRing = RingBuffer::Create(samples, frame_size, false); - auto devidopt = ConfigValueInt(nullptr, "port", "capture"); - if(devidopt && *devidopt >= 0) mParams.device = *devidopt; - else mParams.device = Pa_GetDefaultOutputDevice(); + mParams.device = deviceid; mParams.suggestedLatency = 0.0f; mParams.hostApiSpecificStreamInfo = nullptr; @@ -319,8 +372,15 @@ void PortCapture::open(const char *name) } mParams.channelCount = static_cast(mDevice->channelsFromFmt()); + static constexpr auto readCallback = [](const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, + const PaStreamCallbackFlags statusFlags, void *userData) noexcept + { + return static_cast(userData)->readCallback(inputBuffer, outputBuffer, + framesPerBuffer, timeInfo, statusFlags); + }; PaError err{Pa_OpenStream(&mStream, &mParams, nullptr, mDevice->Frequency, - paFramesPerBufferUnspecified, paNoFlag, &PortCapture::readCallbackC, this)}; + paFramesPerBufferUnspecified, paNoFlag, readCallback, this)}; if(err != paNoError) throw al::backend_exception{al::backend_error::NoDevice, "Failed to open stream: %s", Pa_GetErrorText(err)}; @@ -331,16 +391,14 @@ void PortCapture::open(const char *name) void PortCapture::start() { - const PaError err{Pa_StartStream(mStream)}; - if(err != paNoError) + if(const PaError err{Pa_StartStream(mStream)}; err != paNoError) throw al::backend_exception{al::backend_error::DeviceError, "Failed to start recording: %s", Pa_GetErrorText(err)}; } void PortCapture::stop() { - PaError err{Pa_StopStream(mStream)}; - if(err != paNoError) + if(PaError err{Pa_StopStream(mStream)}; err != paNoError) ERR("Error stopping stream: %s\n", Pa_GetErrorText(err)); } @@ -348,16 +406,14 @@ void PortCapture::stop() uint PortCapture::availableSamples() { return static_cast(mRing->readSpace()); } -void PortCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void PortCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } } // namespace bool PortBackendFactory::init() { - PaError err; - #ifdef HAVE_DYNLOAD if(!pa_handle) { @@ -391,12 +447,15 @@ bool PortBackendFactory::init() LOAD_FUNC(Pa_StopStream); LOAD_FUNC(Pa_OpenStream); LOAD_FUNC(Pa_CloseStream); + LOAD_FUNC(Pa_GetDeviceCount); + LOAD_FUNC(Pa_GetDeviceInfo); LOAD_FUNC(Pa_GetDefaultOutputDevice); LOAD_FUNC(Pa_GetDefaultInputDevice); LOAD_FUNC(Pa_GetStreamInfo); #undef LOAD_FUNC - if((err=Pa_Initialize()) != paNoError) + const PaError err{Pa_Initialize()}; + if(err != paNoError) { ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); CloseLib(pa_handle); @@ -405,7 +464,8 @@ bool PortBackendFactory::init() } } #else - if((err=Pa_Initialize()) != paNoError) + const PaError err{Pa_Initialize()}; + if(err != paNoError) { ERR("Pa_Initialize() returned an error: %s\n", Pa_GetErrorText(err)); return false; @@ -417,18 +477,52 @@ bool PortBackendFactory::init() bool PortBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string PortBackendFactory::probe(BackendType type) +auto PortBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector devices; + + EnumerateDevices(); + int defaultid{-1}; switch(type) { case BackendType::Playback: + defaultid = Pa_GetDefaultOutputDevice(); + if(auto devidopt = ConfigValueInt({}, "port", "device"); devidopt && *devidopt >= 0 + && static_cast(*devidopt) < DeviceNames.size()) + defaultid = *devidopt; + + for(size_t i{0};i < DeviceNames.size();++i) + { + if(DeviceNames[i].mIsPlayback) + { + if(defaultid >= 0 && static_cast(defaultid) == i) + devices.emplace(devices.cbegin(), DeviceNames[i].mName); + else + devices.emplace_back(DeviceNames[i].mName); + } + } + break; + case BackendType::Capture: - /* Includes null char. */ - outnames.append(pa_device, sizeof(pa_device)); + defaultid = Pa_GetDefaultInputDevice(); + if(auto devidopt = ConfigValueInt({}, "port", "capture"); devidopt && *devidopt >= 0 + && static_cast(*devidopt) < DeviceNames.size()) + defaultid = *devidopt; + + for(size_t i{0};i < DeviceNames.size();++i) + { + if(DeviceNames[i].mIsCapture) + { + if(defaultid >= 0 && static_cast(defaultid) == i) + devices.emplace(devices.cbegin(), DeviceNames[i].mName); + else + devices.emplace_back(DeviceNames[i].mName); + } + } break; } - return outnames; + + return devices; } BackendPtr PortBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/portaudio.h b/Engine/lib/openal-soft/alc/backends/portaudio.h index c35ccff2d..f5abe8e4c 100644 --- a/Engine/lib/openal-soft/alc/backends/portaudio.h +++ b/Engine/lib/openal-soft/alc/backends/portaudio.h @@ -5,15 +5,15 @@ struct PortBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PORTAUDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp index 4b0e316f4..91fa4b6c1 100644 --- a/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp +++ b/Engine/lib/openal-soft/alc/backends/pulseaudio.cpp @@ -28,28 +28,30 @@ #include #include #include +#include +#include +#include #include +#include #include #include -#include -#include +#include #include +#include #include #include +#include -#include "albyte.h" #include "alc/alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" +#include "alstring.h" +#include "base.h" #include "core/devformat.h" #include "core/device.h" #include "core/logging.h" #include "dynload.h" #include "opthelpers.h" #include "strutils.h" -#include "vector.h" #include @@ -65,6 +67,8 @@ using uint = unsigned int; MAGIC(pa_context_get_state); \ MAGIC(pa_context_disconnect); \ MAGIC(pa_context_set_state_callback); \ + MAGIC(pa_context_set_subscribe_callback); \ + MAGIC(pa_context_subscribe); \ MAGIC(pa_context_errno); \ MAGIC(pa_context_connect); \ MAGIC(pa_context_get_server_info); \ @@ -136,6 +140,8 @@ PULSE_FUNCS(MAKE_FUNC) #define pa_context_get_state ppa_context_get_state #define pa_context_disconnect ppa_context_disconnect #define pa_context_set_state_callback ppa_context_set_state_callback +#define pa_context_set_subscribe_callback ppa_context_set_subscribe_callback +#define pa_context_subscribe ppa_context_subscribe #define pa_context_errno ppa_context_errno #define pa_context_connect ppa_context_connect #define pa_context_get_server_info ppa_context_get_server_info @@ -270,6 +276,9 @@ constexpr pa_context_flags_t& operator|=(pa_context_flags_t &lhs, pa_context_fla return lhs; } +constexpr pa_subscription_mask_t operator|(pa_subscription_mask_t lhs, pa_subscription_mask_t rhs) +{ return pa_subscription_mask_t(lhs | al::to_underlying(rhs)); } + struct DevMap { std::string name; @@ -282,8 +291,8 @@ bool checkName(const al::span list, const std::string &name) return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; /* Global flags and properties */ @@ -291,13 +300,14 @@ pa_context_flags_t pulse_ctx_flags; class PulseMainloop { pa_threaded_mainloop *mLoop{}; + pa_context *mContext{}; public: PulseMainloop() = default; PulseMainloop(const PulseMainloop&) = delete; PulseMainloop(PulseMainloop&& rhs) noexcept : mLoop{rhs.mLoop} { rhs.mLoop = nullptr; } explicit PulseMainloop(pa_threaded_mainloop *loop) noexcept : mLoop{loop} { } - ~PulseMainloop() { if(mLoop) pa_threaded_mainloop_free(mLoop); } + ~PulseMainloop(); PulseMainloop& operator=(const PulseMainloop&) = delete; PulseMainloop& operator=(PulseMainloop&& rhs) noexcept @@ -312,10 +322,12 @@ public: explicit operator bool() const noexcept { return mLoop != nullptr; } + [[nodiscard]] auto start() const { return pa_threaded_mainloop_start(mLoop); } auto stop() const { return pa_threaded_mainloop_stop(mLoop); } - auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } + [[nodiscard]] auto getApi() const { return pa_threaded_mainloop_get_api(mLoop); } + [[nodiscard]] auto getContext() const noexcept { return mContext; } auto lock() const { return pa_threaded_mainloop_lock(mLoop); } auto unlock() const { return pa_threaded_mainloop_unlock(mLoop); } @@ -325,14 +337,14 @@ public: static auto Create() { return PulseMainloop{pa_threaded_mainloop_new()}; } - void streamSuccessCallback(pa_stream*, int) noexcept { signal(); } + void streamSuccessCallback(pa_stream*, int) const noexcept { signal(); } static void streamSuccessCallbackC(pa_stream *stream, int success, void *pdata) noexcept { static_cast(pdata)->streamSuccessCallback(stream, success); } - void close(pa_context *context, pa_stream *stream=nullptr); + void close(pa_stream *stream=nullptr); - void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) noexcept + void deviceSinkCallback(pa_context*, const pa_sink_info *info, int eol) const noexcept { if(eol) { @@ -363,7 +375,7 @@ public: TRACE("Got device \"%s\", \"%s\"\n", newentry.name.c_str(), newentry.device_name.c_str()); } - void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) noexcept + void deviceSourceCallback(pa_context*, const pa_source_info *info, int eol) const noexcept { if(eol) { @@ -410,7 +422,7 @@ struct MainloopUniqueLock : public std::unique_lock { auto wait(Predicate done_waiting) const -> void { while(!done_waiting()) wait(); } - void waitForOperation(pa_operation *op) + void waitForOperation(pa_operation *op) const { if(op) { @@ -420,6 +432,41 @@ struct MainloopUniqueLock : public std::unique_lock { } + void setEventHandler() + { + pa_operation *op{pa_context_subscribe(mutex()->mContext, + PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, + [](pa_context*, int, void *pdata) noexcept + { static_cast(pdata)->signal(); }, + mutex())}; + waitForOperation(op); + + /* Watch for device added/removed events. + * + * TODO: Also track the "default" device, in as much as PulseAudio has + * the concept of a default device (whatever device is opened when not + * specifying a specific sink or source name). There doesn't seem to be + * an event for this. + */ + auto handler = [](pa_context*, pa_subscription_event_type_t t, uint32_t, void*) noexcept + { + const auto eventFacility = (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK); + if(eventFacility == PA_SUBSCRIPTION_EVENT_SINK + || eventFacility == PA_SUBSCRIPTION_EVENT_SOURCE) + { + const auto deviceType = (eventFacility == PA_SUBSCRIPTION_EVENT_SINK) + ? alc::DeviceType::Playback : alc::DeviceType::Capture; + const auto eventType = (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK); + if(eventType == PA_SUBSCRIPTION_EVENT_NEW) + alc::Event(alc::EventType::DeviceAdded, deviceType, "Device added"); + else if(eventType == PA_SUBSCRIPTION_EVENT_REMOVE) + alc::Event(alc::EventType::DeviceRemoved, deviceType, "Device removed"); + } + }; + pa_context_set_subscribe_callback(mutex()->mContext, handler, nullptr); + } + + void contextStateCallback(pa_context *context) noexcept { pa_context_state_t state{pa_context_get_state(context)}; @@ -434,59 +481,71 @@ struct MainloopUniqueLock : public std::unique_lock { mutex()->signal(); } - pa_context *connectContext(); - pa_stream *connectStream(const char *device_name, pa_context *context, pa_stream_flags_t flags, + void connectContext(); + pa_stream *connectStream(const char *device_name, pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type); }; using MainloopLockGuard = std::lock_guard; - -pa_context *MainloopUniqueLock::connectContext() +PulseMainloop::~PulseMainloop() { - pa_context *context{pa_context_new(mutex()->getApi(), nullptr)}; - if(!context) throw al::backend_exception{al::backend_error::OutOfMemory, + if(mContext) + { + MainloopUniqueLock looplock{*this}; + pa_context_disconnect(mContext); + pa_context_unref(mContext); + } + if(mLoop) + pa_threaded_mainloop_free(mLoop); +} + + +void MainloopUniqueLock::connectContext() +{ + if(mutex()->mContext) + return; + + mutex()->mContext = pa_context_new(mutex()->getApi(), nullptr); + if(!mutex()->mContext) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_context_new() failed"}; - pa_context_set_state_callback(context, [](pa_context *ctx, void *pdata) noexcept + pa_context_set_state_callback(mutex()->mContext, [](pa_context *ctx, void *pdata) noexcept { return static_cast(pdata)->contextStateCallback(ctx); }, this); - int err; - if((err=pa_context_connect(context, nullptr, pulse_ctx_flags, nullptr)) >= 0) + int err{pa_context_connect(mutex()->mContext, nullptr, pulse_ctx_flags, nullptr)}; + if(err >= 0) { - pa_context_state_t state; - while((state=pa_context_get_state(context)) != PA_CONTEXT_READY) + wait([&err,this]() { + pa_context_state_t state{pa_context_get_state(mutex()->mContext)}; if(!PA_CONTEXT_IS_GOOD(state)) { - err = pa_context_errno(context); - if(err > 0) err = -err; - break; + err = pa_context_errno(mutex()->mContext); + if(err > 0) err = -err; + return true; } - - wait(); - } + return state == PA_CONTEXT_READY; + }); } - pa_context_set_state_callback(context, nullptr, nullptr); + pa_context_set_state_callback(mutex()->mContext, nullptr, nullptr); if(err < 0) { - pa_context_unref(context); + pa_context_unref(mutex()->mContext); + mutex()->mContext = nullptr; throw al::backend_exception{al::backend_error::DeviceError, "Context did not connect (%s)", pa_strerror(err)}; } - - return context; } -pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context *context, - pa_stream_flags_t flags, pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, - BackendType type) +pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_stream_flags_t flags, + pa_buffer_attr *attr, pa_sample_spec *spec, pa_channel_map *chanmap, BackendType type) { const char *stream_id{(type==BackendType::Playback) ? "Playback Stream" : "Capture Stream"}; - pa_stream *stream{pa_stream_new(context, stream_id, spec, chanmap)}; + pa_stream *stream{pa_stream_new(mutex()->mContext, stream_id, spec, chanmap)}; if(!stream) throw al::backend_exception{al::backend_error::OutOfMemory, "pa_stream_new() failed (%s)", - pa_strerror(pa_context_errno(context))}; + pa_strerror(pa_context_errno(mutex()->mContext))}; pa_stream_set_state_callback(stream, [](pa_stream *strm, void *pdata) noexcept { return static_cast(pdata)->streamStateCallback(strm); }, this); @@ -501,93 +560,79 @@ pa_stream *MainloopUniqueLock::connectStream(const char *device_name, pa_context stream_id, pa_strerror(err)}; } - pa_stream_state_t state; - while((state=pa_stream_get_state(stream)) != PA_STREAM_READY) + wait([&err,stream,stream_id,this]() { + pa_stream_state_t state{pa_stream_get_state(stream)}; if(!PA_STREAM_IS_GOOD(state)) { - err = pa_context_errno(context); + err = pa_context_errno(mutex()->mContext); pa_stream_unref(stream); throw al::backend_exception{al::backend_error::DeviceError, "%s did not get ready (%s)", stream_id, pa_strerror(err)}; } + return state == PA_STREAM_READY; + }); - wait(); - } pa_stream_set_state_callback(stream, nullptr, nullptr); return stream; } -void PulseMainloop::close(pa_context *context, pa_stream *stream) +void PulseMainloop::close(pa_stream *stream) { - MainloopUniqueLock _{*this}; - if(stream) - { - pa_stream_set_state_callback(stream, nullptr, nullptr); - pa_stream_set_moved_callback(stream, nullptr, nullptr); - pa_stream_set_write_callback(stream, nullptr, nullptr); - pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); - pa_stream_disconnect(stream); - pa_stream_unref(stream); - } + if(!stream) + return; - pa_context_disconnect(context); - pa_context_unref(context); + MainloopUniqueLock looplock{*this}; + pa_stream_set_state_callback(stream, nullptr, nullptr); + pa_stream_set_moved_callback(stream, nullptr, nullptr); + pa_stream_set_write_callback(stream, nullptr, nullptr); + pa_stream_set_buffer_attr_callback(stream, nullptr, nullptr); + pa_stream_disconnect(stream); + pa_stream_unref(stream); } void PulseMainloop::probePlaybackDevices() { - pa_context *context{}; - PlaybackDevices.clear(); try { MainloopUniqueLock plock{*this}; + plock.connectContext(); + auto sink_callback = [](pa_context *ctx, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSinkCallback(ctx, info, eol); }; - context = plock.connectContext(); - pa_operation *op{pa_context_get_sink_info_by_name(context, nullptr, sink_callback, this)}; + pa_operation *op{pa_context_get_sink_info_by_name(mContext, nullptr, sink_callback, this)}; plock.waitForOperation(op); - op = pa_context_get_sink_info_list(context, sink_callback, this); + op = pa_context_get_sink_info_list(mContext, sink_callback, this); plock.waitForOperation(op); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context); } } void PulseMainloop::probeCaptureDevices() { - pa_context *context{}; - CaptureDevices.clear(); try { MainloopUniqueLock plock{*this}; + plock.connectContext(); + auto src_callback = [](pa_context *ctx, const pa_source_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->deviceSourceCallback(ctx, info, eol); }; - context = plock.connectContext(); - pa_operation *op{pa_context_get_source_info_by_name(context, nullptr, src_callback, this)}; + pa_operation *op{pa_context_get_source_info_by_name(mContext, nullptr, src_callback, + this)}; plock.waitForOperation(op); - op = pa_context_get_source_info_list(context, src_callback, this); + op = pa_context_get_source_info_list(mContext, src_callback, this); plock.waitForOperation(op); - - pa_context_disconnect(context); - pa_context_unref(context); - context = nullptr; } catch(std::exception &e) { ERR("Error enumerating devices: %s\n", e.what()); - if(context) close(context); } } @@ -607,7 +652,7 @@ struct PulsePlayback final : public BackendBase { void sinkNameCallback(pa_context *context, const pa_sink_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -615,29 +660,19 @@ struct PulsePlayback final : public BackendBase { PulseMainloop mMainloop; - al::optional mDeviceName{al::nullopt}; + std::optional mDeviceName{std::nullopt}; bool mIs51Rear{false}; - pa_buffer_attr mAttr; - pa_sample_spec mSpec; + pa_buffer_attr mAttr{}; + pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; uint mFrameSize{0u}; - - DEF_NEWDEL(PulsePlayback) }; PulsePlayback::~PulsePlayback() -{ - if(!mContext) - return; - - mMainloop.close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} +{ if(mStream) mMainloop.close(mStream); } void PulsePlayback::bufferAttrCallback(pa_stream *stream) noexcept @@ -674,7 +709,7 @@ void PulsePlayback::streamWriteCallback(pa_stream *stream, size_t nbytes) noexce free_func = pa_xfree; } else - buflen = minz(buflen, nbytes); + buflen = std::min(buflen, nbytes); nbytes -= buflen; mDevice->renderSamples(buf, static_cast(buflen/mFrameSize), mSpec.channels); @@ -722,9 +757,9 @@ void PulsePlayback::sinkInfoCallback(pa_context*, const pa_sink_info *info, int else { mIs51Rear = false; - char chanmap_str[PA_CHANNEL_MAP_SNPRINT_MAX]{}; - pa_channel_map_snprint(chanmap_str, sizeof(chanmap_str), &info->channel_map); - WARN("Failed to find format for channel map:\n %s\n", chanmap_str); + std::array chanmap_str{}; + pa_channel_map_snprint(chanmap_str.data(), chanmap_str.size(), &info->channel_map); + WARN("Failed to find format for channel map:\n %s\n", chanmap_str.data()); } if(info->active_port) @@ -750,33 +785,37 @@ void PulsePlayback::streamMovedCallback(pa_stream *stream) noexcept } -void PulsePlayback::open(const char *name) +void PulsePlayback::open(std::string_view name) { mMainloop = PulseMainloop::Create(); - mMainloop.start(); + if(mMainloop.start() != 0) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start device mainloop"}; const char *pulse_name{nullptr}; - const char *dev_name{nullptr}; - if(name) + std::string_view display_name; + if(!name.empty()) { if(PlaybackDevices.empty()) mMainloop.probePlaybackDevices(); - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), - [name](const DevMap &entry) -> bool { return entry.name == name; }); + auto match_name = [name](const DevMap &entry) -> bool + { return entry.name == name || entry.device_name == name; }; + auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), match_name); if(iter == PlaybackDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + pulse_name = iter->device_name.c_str(); - dev_name = iter->name.c_str(); + display_name = iter->name; } MainloopUniqueLock plock{mMainloop}; - mContext = plock.connectContext(); + plock.connectContext(); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_FIX_FORMAT | PA_STREAM_FIX_RATE | PA_STREAM_FIX_CHANNELS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; pa_sample_spec spec{}; @@ -790,25 +829,26 @@ void PulsePlayback::open(const char *name) if(defname) pulse_name = defname->c_str(); } TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = plock.connectStream(pulse_name, mContext, flags, nullptr, &spec, nullptr, + mStream = plock.connectStream(pulse_name, flags, nullptr, &spec, nullptr, BackendType::Playback); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); mFrameSize = static_cast(pa_frame_size(pa_stream_get_sample_spec(mStream))); if(pulse_name) mDeviceName.emplace(pulse_name); else mDeviceName.reset(); - if(!dev_name) + if(display_name.empty()) { auto name_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkNameCallback(context, info, eol); }; - pa_operation *op{pa_context_get_sink_info_by_name(mContext, + pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } else - mDevice->DeviceName = dev_name; + mDevice->DeviceName = display_name; } bool PulsePlayback::reset() @@ -827,16 +867,17 @@ bool PulsePlayback::reset() mStream = nullptr; } - auto info_callback = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept + auto info_cb = [](pa_context *context, const pa_sink_info *info, int eol, void *pdata) noexcept { return static_cast(pdata)->sinkInfoCallback(context, info, eol); }; - pa_operation *op{pa_context_get_sink_info_by_name(mContext, deviceName, info_callback, this)}; + pa_operation *op{pa_context_get_sink_info_by_name(mMainloop.getContext(), deviceName, info_cb, + this)}; plock.waitForOperation(op); pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_EARLY_REQUESTS}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "adjust-latency", false)) + if(GetConfigValueBool(mDevice->DeviceName, "pulse", "adjust-latency", false)) { /* ADJUST_LATENCY can't be specified with EARLY_REQUESTS, for some * reason. So if the user wants to adjust the overall device latency, @@ -845,7 +886,7 @@ bool PulsePlayback::reset() flags &= ~PA_STREAM_EARLY_REQUESTS; flags |= PA_STREAM_ADJUST_LATENCY; } - if(GetConfigValueBool(mDevice->DeviceName.c_str(), "pulse", "fix-rate", false) + if(GetConfigValueBool(mDevice->DeviceName, "pulse", "fix-rate", false) || !mDevice->Flags.test(FrequencyRequest)) flags |= PA_STREAM_FIX_RATE; @@ -874,6 +915,9 @@ bool PulsePlayback::reset() case DevFmtX3D71: chanmap = X71ChanMap; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + /*fall-through*/ case DevFmtX714: chanmap = X714ChanMap; break; @@ -916,13 +960,16 @@ bool PulsePlayback::reset() mAttr.minreq = mDevice->UpdateSize * frame_size; mAttr.fragsize = ~0u; - mStream = plock.connectStream(deviceName, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(deviceName, flags, &mAttr, &mSpec, &chanmap, BackendType::Playback); - pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamStateCallback(stream); }, this); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); + constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(stream); }; + pa_stream_set_state_callback(mStream, state_callback, this); + + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); mSpec = *(pa_stream_get_sample_spec(mStream)); mFrameSize = static_cast(pa_frame_size(&mSpec)); @@ -933,15 +980,15 @@ bool PulsePlayback::reset() * accordingly. */ const auto scale = static_cast(mSpec.rate) / mDevice->Frequency; - const auto perlen = static_cast(clampd(scale*mDevice->UpdateSize + 0.5, 64.0, - 8192.0)); - const auto buflen = static_cast(clampd(scale*mDevice->BufferSize + 0.5, perlen*2, - std::numeric_limits::max()/mFrameSize)); + const auto perlen = std::clamp(std::round(scale*mDevice->UpdateSize), 64.0, 8192.0); + const auto bufmax = uint{std::numeric_limits::max()} / mFrameSize; + const auto buflen = std::clamp(std::round(scale*mDevice->BufferSize), perlen*2.0, + static_cast(bufmax)); mAttr.maxlength = ~0u; - mAttr.tlength = buflen * mFrameSize; + mAttr.tlength = static_cast(buflen) * mFrameSize; mAttr.prebuf = 0u; - mAttr.minreq = perlen * mFrameSize; + mAttr.minreq = static_cast(perlen) * mFrameSize; op = pa_stream_set_buffer_attr(mStream, &mAttr, &PulseMainloop::streamSuccessCallbackC, &mMainloop); @@ -950,7 +997,7 @@ bool PulsePlayback::reset() mDevice->Frequency = mSpec.rate; } - auto attr_callback = [](pa_stream *stream, void *pdata) noexcept + constexpr auto attr_callback = [](pa_stream *stream, void *pdata) noexcept { return static_cast(pdata)->bufferAttrCallback(stream); }; pa_stream_set_buffer_attr_callback(mStream, attr_callback, this); bufferAttrCallback(mStream); @@ -975,8 +1022,9 @@ void PulsePlayback::start() pa_stream_write(mStream, buf, todo, pa_xfree, 0, PA_SEEK_RELATIVE); } - pa_stream_set_write_callback(mStream, [](pa_stream *stream, size_t nbytes, void *pdata)noexcept - { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }, this); + constexpr auto stream_write = [](pa_stream *stream, size_t nbytes, void *pdata) noexcept + { return static_cast(pdata)->streamWriteCallback(stream, nbytes); }; + pa_stream_set_write_callback(mStream, stream_write, this); pa_operation *op{pa_stream_cork(mStream, 0, &PulseMainloop::streamSuccessCallbackC, &mMainloop)}; @@ -996,13 +1044,13 @@ void PulsePlayback::stop() ClockLatency PulsePlayback::getClockLatency() { - ClockLatency ret; - pa_usec_t latency; - int neg, err; + ClockLatency ret{}; + pa_usec_t latency{}; + int neg{}, err{}; { MainloopUniqueLock plock{mMainloop}; - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } @@ -1033,42 +1081,32 @@ struct PulseCapture final : public BackendBase { void sourceNameCallback(pa_context *context, const pa_source_info *info, int eol) noexcept; void streamMovedCallback(pa_stream *stream) noexcept; - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; ClockLatency getClockLatency() override; PulseMainloop mMainloop; - al::optional mDeviceName{al::nullopt}; + std::optional mDeviceName{std::nullopt}; - al::span mCapBuffer; + al::span mCapBuffer; size_t mHoleLength{0}; size_t mPacketLength{0}; uint mLastReadable{0u}; - al::byte mSilentVal{}; + std::byte mSilentVal{}; pa_buffer_attr mAttr{}; pa_sample_spec mSpec{}; pa_stream *mStream{nullptr}; - pa_context *mContext{nullptr}; - - DEF_NEWDEL(PulseCapture) }; PulseCapture::~PulseCapture() -{ - if(!mContext) - return; - - mMainloop.close(mContext, mStream); - mContext = nullptr; - mStream = nullptr; -} +{ if(mStream) mMainloop.close(mStream); } void PulseCapture::streamStateCallback(pa_stream *stream) noexcept @@ -1098,56 +1136,47 @@ void PulseCapture::streamMovedCallback(pa_stream *stream) noexcept } -void PulseCapture::open(const char *name) +void PulseCapture::open(std::string_view name) { if(!mMainloop) { mMainloop = PulseMainloop::Create(); - mMainloop.start(); + if(mMainloop.start() != 0) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to start device mainloop"}; } const char *pulse_name{nullptr}; - if(name) + if(!name.empty()) { if(CaptureDevices.empty()) mMainloop.probeCaptureDevices(); - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), - [name](const DevMap &entry) -> bool { return entry.name == name; }); + auto match_name = [name](const DevMap &entry) -> bool + { return entry.name == name || entry.device_name == name; }; + auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), match_name); if(iter == CaptureDevices.cend()) throw al::backend_exception{al::backend_error::NoDevice, - "Device name \"%s\" not found", name}; + "Device name \"%.*s\" not found", al::sizei(name), name.data()}; + pulse_name = iter->device_name.c_str(); mDevice->DeviceName = iter->name; } MainloopUniqueLock plock{mMainloop}; - mContext = plock.connectContext(); + plock.connectContext(); pa_channel_map chanmap{}; switch(mDevice->FmtChans) { - case DevFmtMono: - chanmap = MonoChanMap; - break; - case DevFmtStereo: - chanmap = StereoChanMap; - break; - case DevFmtQuad: - chanmap = QuadChanMap; - break; - case DevFmtX51: - chanmap = X51ChanMap; - break; - case DevFmtX61: - chanmap = X61ChanMap; - break; - case DevFmtX71: - chanmap = X71ChanMap; - break; - case DevFmtX714: - chanmap = X714ChanMap; - break; + case DevFmtMono: chanmap = MonoChanMap; break; + case DevFmtStereo: chanmap = StereoChanMap; break; + case DevFmtQuad: chanmap = QuadChanMap; break; + case DevFmtX51: chanmap = X51ChanMap; break; + case DevFmtX61: chanmap = X61ChanMap; break; + case DevFmtX71: chanmap = X71ChanMap; break; + case DevFmtX714: chanmap = X714ChanMap; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -1158,7 +1187,7 @@ void PulseCapture::open(const char *name) switch(mDevice->FmtType) { case DevFmtUByte: - mSilentVal = al::byte(0x80); + mSilentVal = std::byte(0x80); mSpec.format = PA_SAMPLE_U8; break; case DevFmtShort: @@ -1182,33 +1211,37 @@ void PulseCapture::open(const char *name) throw al::backend_exception{al::backend_error::DeviceError, "Invalid sample format"}; const auto frame_size = static_cast(pa_frame_size(&mSpec)); - const uint samples{maxu(mDevice->BufferSize, 100 * mDevice->Frequency / 1000)}; + const uint samples{std::max(mDevice->BufferSize, mDevice->Frequency*100u/1000u)}; mAttr.minreq = ~0u; mAttr.prebuf = ~0u; mAttr.maxlength = samples * frame_size; mAttr.tlength = ~0u; - mAttr.fragsize = minu(samples, 50*mDevice->Frequency/1000) * frame_size; + mAttr.fragsize = std::min(samples, mDevice->Frequency*50u/1000u) * frame_size; pa_stream_flags_t flags{PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY}; - if(!GetConfigValueBool(nullptr, "pulse", "allow-moves", true)) + if(!GetConfigValueBool({}, "pulse", "allow-moves", true)) flags |= PA_STREAM_DONT_MOVE; TRACE("Connecting to \"%s\"\n", pulse_name ? pulse_name : "(default)"); - mStream = plock.connectStream(pulse_name, mContext, flags, &mAttr, &mSpec, &chanmap, + mStream = plock.connectStream(pulse_name, flags, &mAttr, &mSpec, &chanmap, BackendType::Capture); - pa_stream_set_moved_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamMovedCallback(stream); }, this); - pa_stream_set_state_callback(mStream, [](pa_stream *stream, void *pdata) noexcept - { return static_cast(pdata)->streamStateCallback(stream); }, this); + constexpr auto move_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamMovedCallback(stream); }; + pa_stream_set_moved_callback(mStream, move_callback, this); + + constexpr auto state_callback = [](pa_stream *stream, void *pdata) noexcept + { return static_cast(pdata)->streamStateCallback(stream); }; + pa_stream_set_state_callback(mStream, state_callback, this); if(pulse_name) mDeviceName.emplace(pulse_name); else mDeviceName.reset(); if(mDevice->DeviceName.empty()) { - auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, void *pdata) noexcept + constexpr auto name_callback = [](pa_context *context, const pa_source_info *info, int eol, + void *pdata) noexcept { return static_cast(pdata)->sourceNameCallback(context, info, eol); }; - pa_operation *op{pa_context_get_source_info_by_name(mContext, + pa_operation *op{pa_context_get_source_info_by_name(mMainloop.getContext(), pa_stream_get_device_name(mStream), name_callback, this)}; plock.waitForOperation(op); } @@ -1230,9 +1263,9 @@ void PulseCapture::stop() plock.waitForOperation(op); } -void PulseCapture::captureSamples(al::byte *buffer, uint samples) +void PulseCapture::captureSamples(std::byte *buffer, uint samples) { - al::span dstbuf{buffer, samples * pa_frame_size(&mSpec)}; + al::span dstbuf{buffer, samples * pa_frame_size(&mSpec)}; /* Capture is done in fragment-sized chunks, so we loop until we get all * that's available. @@ -1242,7 +1275,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) { if(mHoleLength > 0) UNLIKELY { - const size_t rem{minz(dstbuf.size(), mHoleLength)}; + const size_t rem{std::min(dstbuf.size(), mHoleLength)}; std::fill_n(dstbuf.begin(), rem, mSilentVal); dstbuf = dstbuf.subspan(rem); mHoleLength -= rem; @@ -1251,7 +1284,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) } if(!mCapBuffer.empty()) { - const size_t rem{minz(dstbuf.size(), mCapBuffer.size())}; + const size_t rem{std::min(dstbuf.size(), mCapBuffer.size())}; std::copy_n(mCapBuffer.begin(), rem, dstbuf.begin()); dstbuf = dstbuf.subspan(rem); mCapBuffer = mCapBuffer.subspan(rem); @@ -1276,12 +1309,12 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) break; } - const void *capbuf; - size_t caplen; + const void *capbuf{}; + size_t caplen{}; if(pa_stream_peek(mStream, &capbuf, &caplen) < 0) UNLIKELY { mDevice->handleDisconnect("Failed retrieving capture samples: %s", - pa_strerror(pa_context_errno(mContext))); + pa_strerror(pa_context_errno(mMainloop.getContext()))); break; } plock.unlock(); @@ -1290,7 +1323,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) if(!capbuf) UNLIKELY mHoleLength = caplen; else - mCapBuffer = {static_cast(capbuf), caplen}; + mCapBuffer = {static_cast(capbuf), caplen}; mPacketLength = caplen; } if(!dstbuf.empty()) @@ -1299,7 +1332,7 @@ void PulseCapture::captureSamples(al::byte *buffer, uint samples) uint PulseCapture::availableSamples() { - size_t readable{maxz(mCapBuffer.size(), mHoleLength)}; + size_t readable{std::max(mCapBuffer.size(), mHoleLength)}; if(mDevice->Connected.load(std::memory_order_acquire)) { @@ -1332,13 +1365,13 @@ uint PulseCapture::availableSamples() ClockLatency PulseCapture::getClockLatency() { - ClockLatency ret; - pa_usec_t latency; - int neg, err; + ClockLatency ret{}; + pa_usec_t latency{}; + int neg{}, err{}; { MainloopUniqueLock plock{mMainloop}; - ret.ClockTime = GetDeviceClockTime(mDevice); + ret.ClockTime = mDevice->getClockTime(); err = pa_stream_get_latency(mStream, &latency, &neg); } @@ -1363,9 +1396,6 @@ bool PulseBackendFactory::init() #ifdef HAVE_DYNLOAD if(!pulse_handle) { - bool ret{true}; - std::string missing_funcs; - #ifdef _WIN32 #define PALIB "libpulse-0.dll" #elif defined(__APPLE__) && defined(__MACH__) @@ -1380,17 +1410,15 @@ bool PulseBackendFactory::init() return false; } + std::string missing_funcs; #define LOAD_FUNC(x) do { \ p##x = reinterpret_cast(GetSymbol(pulse_handle, #x)); \ - if(!(p##x)) { \ - ret = false; \ - missing_funcs += "\n" #x; \ - } \ + if(!(p##x)) missing_funcs += "\n" #x; \ } while(0) PULSE_FUNCS(LOAD_FUNC) #undef LOAD_FUNC - if(!ret) + if(!missing_funcs.empty()) { WARN("Missing expected functions:%s\n", missing_funcs.c_str()); CloseLib(pulse_handle); @@ -1401,20 +1429,23 @@ bool PulseBackendFactory::init() #endif /* HAVE_DYNLOAD */ pulse_ctx_flags = PA_CONTEXT_NOFLAGS; - if(!GetConfigValueBool(nullptr, "pulse", "spawn-server", false)) + if(!GetConfigValueBool({}, "pulse", "spawn-server", false)) pulse_ctx_flags |= PA_CONTEXT_NOAUTOSPAWN; try { if(!gGlobalMainloop) { gGlobalMainloop = PulseMainloop::Create(); - gGlobalMainloop.start(); + if(gGlobalMainloop.start() != 0) + { + gGlobalMainloop = nullptr; + return false; + } } MainloopUniqueLock plock{gGlobalMainloop}; - pa_context *context{plock.connectContext()}; - pa_context_disconnect(context); - pa_context_unref(context); + plock.connectContext(); + plock.setEventHandler(); return true; } catch(...) { @@ -1425,27 +1456,24 @@ bool PulseBackendFactory::init() bool PulseBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string PulseBackendFactory::probe(BackendType type) +auto PulseBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const DevMap &entry) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - outnames.append(entry.name.c_str(), entry.name.length()+1); - }; + { outnames.push_back(entry.name); }; switch(type) { case BackendType::Playback: gGlobalMainloop.probePlaybackDevices(); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: gGlobalMainloop.probeCaptureDevices(); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } @@ -1467,3 +1495,18 @@ BackendFactory &PulseBackendFactory::getFactory() static PulseBackendFactory factory{}; return factory; } + +alc::EventSupport PulseBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: + return alc::EventSupport::FullSupport; + + case alc::EventType::DefaultDeviceChanged: + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/pulseaudio.h b/Engine/lib/openal-soft/alc/backends/pulseaudio.h index 6690fe8a9..c183d4794 100644 --- a/Engine/lib/openal-soft/alc/backends/pulseaudio.h +++ b/Engine/lib/openal-soft/alc/backends/pulseaudio.h @@ -1,19 +1,27 @@ #ifndef BACKENDS_PULSEAUDIO_H #define BACKENDS_PULSEAUDIO_H +#include +#include + +#include "alc/events.h" #include "base.h" +struct DeviceBase; + class PulseBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_PULSEAUDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/sdl2.cpp b/Engine/lib/openal-soft/alc/backends/sdl2.cpp index a4a5a9ac1..ec3be6b0e 100644 --- a/Engine/lib/openal-soft/alc/backends/sdl2.cpp +++ b/Engine/lib/openal-soft/alc/backends/sdl2.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "almalloc.h" #include "alnumeric.h" @@ -46,17 +47,17 @@ namespace { #define DEVNAME_PREFIX "" #endif -constexpr char defaultDeviceName[] = DEVNAME_PREFIX "Default Device"; +constexpr auto getDevicePrefix() noexcept -> std::string_view { return DEVNAME_PREFIX; } +constexpr auto getDefaultDeviceName() noexcept -> std::string_view +{ return DEVNAME_PREFIX "Default Device"; } struct Sdl2Backend final : public BackendBase { Sdl2Backend(DeviceBase *device) noexcept : BackendBase{device} { } ~Sdl2Backend() override; void audioCallback(Uint8 *stream, int len) noexcept; - static void audioCallbackC(void *ptr, Uint8 *stream, int len) noexcept - { static_cast(ptr)->audioCallback(stream, len); } - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -68,8 +69,6 @@ struct Sdl2Backend final : public BackendBase { DevFmtChannels mFmtChans{}; DevFmtType mFmtType{}; uint mUpdateSize{0u}; - - DEF_NEWDEL(Sdl2Backend) }; Sdl2Backend::~Sdl2Backend() @@ -86,7 +85,7 @@ void Sdl2Backend::audioCallback(Uint8 *stream, int len) noexcept mDevice->renderSamples(stream, ulen / mFrameSize, mDevice->channelsFromFmt()); } -void Sdl2Backend::open(const char *name) +void Sdl2Backend::open(std::string_view name) { SDL_AudioSpec want{}, have{}; @@ -102,24 +101,39 @@ void Sdl2Backend::open(const char *name) case DevFmtFloat: want.format = AUDIO_F32; break; } want.channels = (mDevice->FmtChans == DevFmtMono) ? 1 : 2; - want.samples = static_cast(minu(mDevice->UpdateSize, 8192)); - want.callback = &Sdl2Backend::audioCallbackC; + want.samples = static_cast(std::min(mDevice->UpdateSize, 8192u)); + want.callback = [](void *ptr, Uint8 *stream, int len) noexcept + { return static_cast(ptr)->audioCallback(stream, len); }; want.userdata = this; /* Passing nullptr to SDL_OpenAudioDevice opens a default, which isn't * necessarily the first in the list. */ + const auto defaultDeviceName = getDefaultDeviceName(); SDL_AudioDeviceID devid; - if(!name || strcmp(name, defaultDeviceName) == 0) + if(name.empty() || name == defaultDeviceName) + { + name = defaultDeviceName; devid = SDL_OpenAudioDevice(nullptr, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + } else { - const size_t prefix_len = strlen(DEVNAME_PREFIX); - if(strncmp(name, DEVNAME_PREFIX, prefix_len) == 0) - devid = SDL_OpenAudioDevice(name+prefix_len, SDL_FALSE, &want, &have, + const auto namePrefix = getDevicePrefix(); + if(name.size() >= namePrefix.size() && name.substr(0, namePrefix.size()) == namePrefix) + { + /* Copy the string_view to a string to ensure it's null terminated + * for this call. + */ + const std::string devname{name.substr(namePrefix.size())}; + devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + } else - devid = SDL_OpenAudioDevice(name, SDL_FALSE, &want, &have, SDL_AUDIO_ALLOW_ANY_CHANGE); + { + const std::string devname{name}; + devid = SDL_OpenAudioDevice(devname.c_str(), SDL_FALSE, &want, &have, + SDL_AUDIO_ALLOW_ANY_CHANGE); + } } if(!devid) throw al::backend_exception{al::backend_error::NoDevice, "%s", SDL_GetError()}; @@ -161,7 +175,7 @@ void Sdl2Backend::open(const char *name) mFmtType = devtype; mUpdateSize = have.samples; - mDevice->DeviceName = name ? name : defaultDeviceName; + mDevice->DeviceName = name; } bool Sdl2Backend::reset() @@ -195,23 +209,27 @@ bool SDL2BackendFactory::init() bool SDL2BackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string SDL2BackendFactory::probe(BackendType type) +auto SDL2BackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; if(type != BackendType::Playback) return outnames; int num_devices{SDL_GetNumAudioDevices(SDL_FALSE)}; + if(num_devices <= 0) + return outnames; - /* Includes null char. */ - outnames.append(defaultDeviceName, sizeof(defaultDeviceName)); + outnames.reserve(static_cast(num_devices)); + outnames.emplace_back(getDefaultDeviceName()); for(int i{0};i < num_devices;++i) { - std::string name{DEVNAME_PREFIX}; - name += SDL_GetAudioDeviceName(i, SDL_FALSE); - if(!name.empty()) - outnames.append(name.c_str(), name.length()+1); + std::string outname{getDevicePrefix()}; + if(const char *name = SDL_GetAudioDeviceName(i, SDL_FALSE)) + outname += name; + else + outname += "Unknown Device Name #"+std::to_string(i); + outnames.emplace_back(std::move(outname)); } return outnames; } diff --git a/Engine/lib/openal-soft/alc/backends/sdl2.h b/Engine/lib/openal-soft/alc/backends/sdl2.h index 3bd8df863..8c5829121 100644 --- a/Engine/lib/openal-soft/alc/backends/sdl2.h +++ b/Engine/lib/openal-soft/alc/backends/sdl2.h @@ -5,15 +5,15 @@ struct SDL2BackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SDL2_H */ diff --git a/Engine/lib/openal-soft/alc/backends/sndio.cpp b/Engine/lib/openal-soft/alc/backends/sndio.cpp index 077e77f20..8d0693af7 100644 --- a/Engine/lib/openal-soft/alc/backends/sndio.cpp +++ b/Engine/lib/openal-soft/alc/backends/sndio.cpp @@ -22,31 +22,35 @@ #include "sndio.h" +#include +#include +#include +#include #include -#include #include -#include -#include -#include +#include #include +#include #include "alnumeric.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" -#include "threads.h" -#include "vector.h" -#include +#include /* NOLINT(*-duplicate-include) Not the same header. */ namespace { -static const char sndio_device[] = "SndIO Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "SndIO Default"sv; } struct SioPar : public sio_par { - SioPar() { sio_initpar(this); } + SioPar() : sio_par{} { sio_initpar(this); } void clear() { sio_initpar(this); } }; @@ -57,7 +61,7 @@ struct SndioPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -65,12 +69,10 @@ struct SndioPlayback final : public BackendBase { sio_hdl *mSndHandle{nullptr}; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(SndioPlayback) }; SndioPlayback::~SndioPlayback() @@ -86,12 +88,12 @@ int SndioPlayback::mixerProc() const size_t frameSize{frameStep * mDevice->bytesFromFmt()}; SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { - al::span buffer{mBuffer}; + al::span buffer{mBuffer}; mDevice->renderSamples(buffer.data(), static_cast(buffer.size() / frameSize), frameStep); @@ -112,13 +114,13 @@ int SndioPlayback::mixerProc() } -void SndioPlayback::open(const char *name) +void SndioPlayback::open(std::string_view name) { - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; sio_hdl *sndHandle{sio_open(nullptr, SIO_PLAY, 0)}; if(!sndHandle) @@ -136,72 +138,75 @@ bool SndioPlayback::reset() SioPar par; auto tryfmt = mDevice->FmtType; -retry_params: - switch(tryfmt) + while(true) { - case DevFmtByte: - par.bits = 8; - par.sig = 1; - break; - case DevFmtUByte: - par.bits = 8; - par.sig = 0; - break; - case DevFmtShort: - par.bits = 16; - par.sig = 1; - break; - case DevFmtUShort: - par.bits = 16; - par.sig = 0; - break; - case DevFmtFloat: - case DevFmtInt: - par.bits = 32; - par.sig = 1; - break; - case DevFmtUInt: - par.bits = 32; - par.sig = 0; - break; - } - par.bps = SIO_BPS(par.bits); - par.le = SIO_LE_NATIVE; - par.msb = 1; + switch(tryfmt) + { + case DevFmtByte: + par.bits = 8; + par.sig = 1; + break; + case DevFmtUByte: + par.bits = 8; + par.sig = 0; + break; + case DevFmtShort: + par.bits = 16; + par.sig = 1; + break; + case DevFmtUShort: + par.bits = 16; + par.sig = 0; + break; + case DevFmtFloat: + case DevFmtInt: + par.bits = 32; + par.sig = 1; + break; + case DevFmtUInt: + par.bits = 32; + par.sig = 0; + break; + } + par.bps = SIO_BPS(par.bits); + par.le = SIO_LE_NATIVE; + par.msb = 1; - par.rate = mDevice->Frequency; - par.pchan = mDevice->channelsFromFmt(); + par.rate = mDevice->Frequency; + par.pchan = mDevice->channelsFromFmt(); - par.round = mDevice->UpdateSize; - par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; - if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; + par.round = mDevice->UpdateSize; + par.appbufsz = mDevice->BufferSize - mDevice->UpdateSize; + if(!par.appbufsz) par.appbufsz = mDevice->UpdateSize; - try { - if(!sio_setpar(mSndHandle, &par)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set device parameters"}; + try { + if(!sio_setpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to set device parameters"}; - par.clear(); - if(!sio_getpar(mSndHandle, &par)) - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to get device parameters"}; + par.clear(); + if(!sio_getpar(mSndHandle, &par)) + throw al::backend_exception{al::backend_error::DeviceError, + "Failed to get device parameters"}; - if(par.bps > 1 && par.le != SIO_LE_NATIVE) - throw al::backend_exception{al::backend_error::DeviceError, - "%s-endian samples not supported", par.le ? "Little" : "Big"}; - if(par.bits < par.bps*8 && !par.msb) - throw al::backend_exception{al::backend_error::DeviceError, - "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; - if(par.pchan < 1) - throw al::backend_exception{al::backend_error::DeviceError, - "No playback channels on device"}; - } - catch(al::backend_exception &e) { - if(tryfmt == DevFmtShort) - throw; - par.clear(); - tryfmt = DevFmtShort; - goto retry_params; + if(par.bps > 1 && par.le != SIO_LE_NATIVE) + throw al::backend_exception{al::backend_error::DeviceError, + "%s-endian samples not supported", par.le ? "Little" : "Big"}; + if(par.bits < par.bps*8 && !par.msb) + throw al::backend_exception{al::backend_error::DeviceError, + "MSB-padded samples not supported (%u of %u bits)", par.bits, par.bps*8}; + if(par.pchan < 1) + throw al::backend_exception{al::backend_error::DeviceError, + "No playback channels on device"}; + + break; + } + catch(al::backend_exception &e) { + if(tryfmt == DevFmtShort) + throw; + par.clear(); + tryfmt = DevFmtShort; + } } if(par.bps == 1) @@ -229,11 +234,11 @@ retry_params: mDevice->UpdateSize = par.round; mDevice->BufferSize = par.bufsz + par.round; - mBuffer.resize(mDevice->UpdateSize * par.pchan*par.bps); + mBuffer.resize(size_t{mDevice->UpdateSize} * par.pchan*par.bps); if(par.sig == 1) - std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); else if(par.bits == 8) - std::fill_n(mBuffer.data(), mBuffer.size(), al::byte(0x80)); + std::fill_n(mBuffer.data(), mBuffer.size(), std::byte(0x80)); else if(par.bits == 16) std::fill_n(reinterpret_cast(mBuffer.data()), mBuffer.size()/2, 0x8000); else if(par.bits == 32) @@ -280,10 +285,10 @@ struct SndioCapture final : public BackendBase { int recordProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; sio_hdl *mSndHandle{nullptr}; @@ -292,8 +297,6 @@ struct SndioCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(SndioCapture) }; SndioCapture::~SndioCapture() @@ -306,7 +309,7 @@ SndioCapture::~SndioCapture() int SndioCapture::recordProc() { SetRTPriority(); - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); const uint frameSize{mDevice->frameSizeFromFmt()}; @@ -317,29 +320,30 @@ int SndioCapture::recordProc() return 1; } - auto fds = std::make_unique(static_cast(nfds_pre)); + auto fds = std::vector(static_cast(nfds_pre)); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) { /* Wait until there's some samples to read. */ - const int nfds{sio_pollfd(mSndHandle, fds.get(), POLLIN)}; + const int nfds{sio_pollfd(mSndHandle, fds.data(), POLLIN)}; if(nfds <= 0) { mDevice->handleDisconnect("Failed to get polling fds: %d", nfds); break; } - int pollres{::poll(fds.get(), static_cast(nfds), 2000)}; + int pollres{::poll(fds.data(), fds.size(), 2000)}; if(pollres < 0) { if(errno == EINTR) continue; - mDevice->handleDisconnect("Poll error: %s", strerror(errno)); + mDevice->handleDisconnect("Poll error: %s", + std::generic_category().message(errno).c_str()); break; } if(pollres == 0) continue; - const int revents{sio_revents(mSndHandle, fds.get())}; + const int revents{sio_revents(mSndHandle, fds.data())}; if((revents&POLLHUP)) { mDevice->handleDisconnect("Got POLLHUP from poll events"); @@ -349,7 +353,7 @@ int SndioCapture::recordProc() continue; auto data = mRing->getWriteVector(); - al::span buffer{data.first.buf, data.first.len*frameSize}; + al::span buffer{data.first.buf, data.first.len*frameSize}; while(!buffer.empty()) { size_t got{sio_read(mSndHandle, buffer.data(), buffer.size())}; @@ -373,8 +377,8 @@ int SndioCapture::recordProc() if(buffer.empty()) { /* Got samples to read, but no place to store it. Drop it. */ - static char junk[4096]; - sio_read(mSndHandle, junk, sizeof(junk) - (sizeof(junk)%frameSize)); + static std::array junk; + sio_read(mSndHandle, junk.data(), junk.size() - (junk.size()%frameSize)); } } @@ -382,13 +386,13 @@ int SndioCapture::recordProc() } -void SndioCapture::open(const char *name) +void SndioCapture::open(std::string_view name) { - if(!name) - name = sndio_device; - else if(strcmp(name, sndio_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; mSndHandle = sio_open(nullptr, SIO_REC, true); if(mSndHandle == nullptr) @@ -431,12 +435,12 @@ void SndioCapture::open(const char *name) par.rchan = mDevice->channelsFromFmt(); par.rate = mDevice->Frequency; - par.appbufsz = maxu(mDevice->BufferSize, mDevice->Frequency/10); - par.round = minu(par.appbufsz/2, mDevice->Frequency/40); + par.appbufsz = std::max(mDevice->BufferSize, mDevice->Frequency/10u); + par.round = std::min(par.appbufsz/2u, mDevice->Frequency/40u); if(!sio_setpar(mSndHandle, &par) || !sio_getpar(mSndHandle, &par)) throw al::backend_exception{al::backend_error::DeviceError, - "Failed to set device praameters"}; + "Failed to set device parameters"}; if(par.bps > 1 && par.le != SIO_LE_NATIVE) throw al::backend_exception{al::backend_error::DeviceError, @@ -461,7 +465,7 @@ void SndioCapture::open(const char *name) DevFmtTypeString(mDevice->FmtType), DevFmtChannelsString(mDevice->FmtChans), mDevice->Frequency, par.sig?'s':'u', par.bps*8, par.rchan, par.rate}; - mRing = RingBuffer::Create(mDevice->BufferSize, par.bps*par.rchan, false); + mRing = RingBuffer::Create(mDevice->BufferSize, size_t{par.bps}*par.rchan, false); mDevice->BufferSize = static_cast(mRing->writeSpace()); mDevice->UpdateSize = par.round; @@ -496,8 +500,8 @@ void SndioCapture::stop() ERR("Error stopping device\n"); } -void SndioCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void SndioCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint SndioCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -516,18 +520,15 @@ bool SndIOBackendFactory::init() bool SndIOBackendFactory::querySupport(BackendType type) { return (type == BackendType::Playback || type == BackendType::Capture); } -std::string SndIOBackendFactory::probe(BackendType type) +auto SndIOBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: case BackendType::Capture: - /* Includes null char. */ - outnames.append(sndio_device, sizeof(sndio_device)); - break; + return std::vector{std::string{GetDefaultName()}}; } - return outnames; + return {}; } BackendPtr SndIOBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/sndio.h b/Engine/lib/openal-soft/alc/backends/sndio.h index d94331911..a4496c92a 100644 --- a/Engine/lib/openal-soft/alc/backends/sndio.h +++ b/Engine/lib/openal-soft/alc/backends/sndio.h @@ -5,15 +5,15 @@ struct SndIOBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SNDIO_H */ diff --git a/Engine/lib/openal-soft/alc/backends/solaris.cpp b/Engine/lib/openal-soft/alc/backends/solaris.cpp index 791609ce7..047736c6a 100644 --- a/Engine/lib/openal-soft/alc/backends/solaris.cpp +++ b/Engine/lib/openal-soft/alc/backends/solaris.cpp @@ -35,24 +35,26 @@ #include #include #include +#include #include #include -#include "albyte.h" #include "alc/alconfig.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" -#include "threads.h" -#include "vector.h" #include namespace { -constexpr char solaris_device[] = "Solaris Default"; +using namespace std::string_view_literals; + +[[nodiscard]] constexpr auto GetDefaultName() noexcept { return "Solaris Default"sv; } std::string solaris_driver{"/dev/audio"}; @@ -63,7 +65,7 @@ struct SolarisBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -71,12 +73,10 @@ struct SolarisBackend final : public BackendBase { int mFd{-1}; uint mFrameStep{}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(SolarisBackend) }; SolarisBackend::~SolarisBackend() @@ -89,10 +89,10 @@ SolarisBackend::~SolarisBackend() int SolarisBackend::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frame_step{mDevice->channelsFromFmt()}; - const uint frame_size{mDevice->frameSizeFromFmt()}; + const size_t frame_size{mDevice->frameSizeFromFmt()}; while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -116,12 +116,12 @@ int SolarisBackend::mixerProc() continue; } - al::byte *write_ptr{mBuffer.data()}; - size_t to_write{mBuffer.size()}; - mDevice->renderSamples(write_ptr, static_cast(to_write/frame_size), frame_step); - while(to_write > 0 && !mKillNow.load(std::memory_order_acquire)) + al::span buffer{mBuffer}; + mDevice->renderSamples(buffer.data(), static_cast(buffer.size()/frame_size), + frame_step); + while(!buffer.empty() && !mKillNow.load(std::memory_order_acquire)) { - ssize_t wrote{write(mFd, write_ptr, to_write)}; + ssize_t wrote{write(mFd, buffer.data(), buffer.size())}; if(wrote < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) @@ -131,8 +131,7 @@ int SolarisBackend::mixerProc() break; } - to_write -= static_cast(wrote); - write_ptr += wrote; + buffer = buffer.subspan(static_cast(wrote)); } } @@ -140,13 +139,13 @@ int SolarisBackend::mixerProc() } -void SolarisBackend::open(const char *name) +void SolarisBackend::open(std::string_view name) { - if(!name) - name = solaris_device; - else if(strcmp(name, solaris_device) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDefaultName(); + else if(name != GetDefaultName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; int fd{::open(solaris_driver.c_str(), O_WRONLY)}; if(fd == -1) @@ -231,7 +230,7 @@ bool SolarisBackend::reset() setDefaultChannelOrder(); mBuffer.resize(mDevice->UpdateSize * size_t{frame_size}); - std::fill(mBuffer.begin(), mBuffer.end(), al::byte{}); + std::fill(mBuffer.begin(), mBuffer.end(), std::byte{}); return true; } @@ -268,7 +267,7 @@ BackendFactory &SolarisBackendFactory::getFactory() bool SolarisBackendFactory::init() { - if(auto devopt = ConfigValueStr(nullptr, "solaris", "device")) + if(auto devopt = ConfigValueStr({}, "solaris", "device")) solaris_driver = std::move(*devopt); return true; } @@ -276,23 +275,19 @@ bool SolarisBackendFactory::init() bool SolarisBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string SolarisBackendFactory::probe(BackendType type) +auto SolarisBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: - { - struct stat buf; - if(stat(solaris_driver.c_str(), &buf) == 0) - outnames.append(solaris_device, sizeof(solaris_device)); - } - break; + if(struct stat buf{}; stat(solaris_driver.c_str(), &buf) == 0) + return std::vector{std::string{GetDefaultName()}}; + break; case BackendType::Capture: break; } - return outnames; + return {}; } BackendPtr SolarisBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/solaris.h b/Engine/lib/openal-soft/alc/backends/solaris.h index 5da6ad3a9..8415c362b 100644 --- a/Engine/lib/openal-soft/alc/backends/solaris.h +++ b/Engine/lib/openal-soft/alc/backends/solaris.h @@ -5,15 +5,15 @@ struct SolarisBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_SOLARIS_H */ diff --git a/Engine/lib/openal-soft/alc/backends/wasapi.cpp b/Engine/lib/openal-soft/alc/backends/wasapi.cpp index e834eef42..d640e1d41 100644 --- a/Engine/lib/openal-soft/alc/backends/wasapi.cpp +++ b/Engine/lib/openal-soft/alc/backends/wasapi.cpp @@ -25,13 +25,15 @@ #define WIN32_LEAN_AND_MEAN #include -#include -#include +#include +#include #include #include #include +#include #include +#include #include #include #include @@ -53,12 +55,16 @@ #include #include #include +#include #include #include #include "albit.h" #include "alc/alconfig.h" #include "alnumeric.h" +#include "alspan.h" +#include "alstring.h" +#include "althrd_setname.h" #include "comptr.h" #include "core/converter.h" #include "core/device.h" @@ -66,8 +72,22 @@ #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" +#if defined(ALSOFT_UWP) + +#include // !!This is important!! +#include +#include +#include +#include +#include + +using namespace winrt; +using namespace Windows::Foundation; +using namespace Windows::Media::Devices; +using namespace Windows::Devices::Enumeration; +using namespace Windows::Media::Devices; +#endif /* Some headers seem to define these as macros for __uuidof, which is annoying * since some headers don't declare them at all. Hopefully the ifdef is enough @@ -79,22 +99,22 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x #ifndef KSDATAFORMAT_SUBTYPE_IEEE_FLOAT DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71); #endif - +#if !defined(ALSOFT_UWP) DEFINE_DEVPROPKEY(DEVPKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,0x20, 0x67,0xd1,0x46,0xa8,0x50,0xe0, 14); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_FormFactor, 0x1da5d803, 0xd492, 0x4edd, 0x8c,0x23, 0xe0,0xc0,0xff,0xee,0x7f,0x0e, 0); DEFINE_PROPERTYKEY(PKEY_AudioEndpoint_GUID, 0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23,0xe0, 0xc0,0xff,0xee,0x7f,0x0e, 4 ); - +#endif namespace { +using namespace std::string_view_literals; using std::chrono::nanoseconds; using std::chrono::milliseconds; using std::chrono::seconds; -using ReferenceTime = std::chrono::duration>; +[[nodiscard]] constexpr auto GetDevicePrefix() noexcept { return "OpenAL Soft on "sv; } -inline constexpr ReferenceTime operator "" _reftime(unsigned long long int n) noexcept -{ return ReferenceTime{static_cast(n)}; } +using ReferenceTime = std::chrono::duration>; #define MONO SPEAKER_FRONT_CENTER @@ -124,47 +144,127 @@ constexpr DWORD X61Mask{MaskFromTopBits(X6DOT1)}; constexpr DWORD X71Mask{MaskFromTopBits(X7DOT1)}; constexpr DWORD X714Mask{MaskFromTopBits(X7DOT1DOT4)}; -constexpr char DevNameHead[] = "OpenAL Soft on "; -constexpr size_t DevNameHeadLen{al::size(DevNameHead) - 1}; + +#ifndef _MSC_VER +constexpr AudioObjectType operator|(AudioObjectType lhs, AudioObjectType rhs) noexcept +{ return static_cast(lhs | al::to_underlying(rhs)); } +#endif + +constexpr AudioObjectType ChannelMask_Mono{AudioObjectType_FrontCenter}; +constexpr AudioObjectType ChannelMask_Stereo{AudioObjectType_FrontLeft + | AudioObjectType_FrontRight}; +constexpr AudioObjectType ChannelMask_Quad{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X51{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight}; +constexpr AudioObjectType ChannelMask_X51Rear{AudioObjectType_FrontLeft + | AudioObjectType_FrontRight | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency + | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X61{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackCenter}; +constexpr AudioObjectType ChannelMask_X71{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight}; +constexpr AudioObjectType ChannelMask_X714{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight + | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft + | AudioObjectType_TopBackRight}; +constexpr AudioObjectType ChannelMask_X7144{AudioObjectType_FrontLeft | AudioObjectType_FrontRight + | AudioObjectType_FrontCenter | AudioObjectType_LowFrequency | AudioObjectType_SideLeft + | AudioObjectType_SideRight | AudioObjectType_BackLeft | AudioObjectType_BackRight + | AudioObjectType_TopFrontLeft | AudioObjectType_TopFrontRight | AudioObjectType_TopBackLeft + | AudioObjectType_TopBackRight | AudioObjectType_BottomFrontLeft + | AudioObjectType_BottomFrontRight | AudioObjectType_BottomBackLeft + | AudioObjectType_BottomBackRight}; + + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + + +template +constexpr auto as_unsigned(T value) noexcept +{ + using UT = std::make_unsigned_t; + return static_cast(value); +} /* Scales the given reftime value, rounding the result. */ -inline uint RefTime2Samples(const ReferenceTime &val, uint srate) +template +constexpr uint RefTime2Samples(const ReferenceTime &val, T srate) noexcept { const auto retval = (val*srate + ReferenceTime{seconds{1}}/2) / seconds{1}; - return static_cast(mini64(retval, std::numeric_limits::max())); + return static_cast(std::min(retval, std::numeric_limits::max())); } class GuidPrinter { - char mMsg[64]; + std::array mMsg{}; public: GuidPrinter(const GUID &guid) { - std::snprintf(mMsg, al::size(mMsg), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", + std::snprintf(mMsg.data(), mMsg.size(), "{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}", DWORD{guid.Data1}, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); } - const char *c_str() const { return mMsg; } + [[nodiscard]] auto c_str() const -> const char* { return mMsg.data(); } }; struct PropVariant { - PROPVARIANT mProp; + PROPVARIANT mProp{}; public: PropVariant() { PropVariantInit(&mProp); } + PropVariant(const PropVariant &rhs) : PropVariant{} { PropVariantCopy(&mProp, &rhs.mProp); } ~PropVariant() { clear(); } + auto operator=(const PropVariant &rhs) -> PropVariant& + { + if(this != &rhs) + PropVariantCopy(&mProp, &rhs.mProp); + return *this; + } + void clear() { PropVariantClear(&mProp); } PROPVARIANT* get() noexcept { return &mProp; } - PROPVARIANT& operator*() noexcept { return mProp; } - const PROPVARIANT& operator*() const noexcept { return mProp; } + /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ + [[nodiscard]] + auto type() const noexcept -> VARTYPE { return mProp.vt; } - PROPVARIANT* operator->() noexcept { return &mProp; } - const PROPVARIANT* operator->() const noexcept { return &mProp; } + template [[nodiscard]] + auto value() const -> T + { + if constexpr(std::is_same_v) + { + alassert(mProp.vt == VT_UI4); + return mProp.uiVal; + } + else if constexpr(std::is_same_v || std::is_same_v) + { + alassert(mProp.vt == VT_LPWSTR); + return mProp.pwszVal; + } + } + + void setBlob(const al::span data) + { + if constexpr(sizeof(size_t) > sizeof(ULONG)) + alassert(data.size() <= std::numeric_limits::max()); + mProp.vt = VT_BLOB; + mProp.blob.cbSize = static_cast(data.size()); + mProp.blob.pBlobData = data.data(); + } + /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ }; struct DevMap { @@ -178,71 +278,123 @@ struct DevMap { , endpoint_guid{std::forward(guid_)} , devid{std::forward(devid_)} { } + /* To prevent GCC from complaining it doesn't want to inline this. */ + ~DevMap(); }; +DevMap::~DevMap() = default; -bool checkName(const al::vector &list, const std::string &name) +bool checkName(const al::span list, const std::string_view name) { - auto match_name = [&name](const DevMap &entry) -> bool - { return entry.name == name; }; + auto match_name = [name](const DevMap &entry) -> bool { return entry.name == name; }; return std::find_if(list.cbegin(), list.cend(), match_name) != list.cend(); } -al::vector PlaybackDevices; -al::vector CaptureDevices; + +struct DeviceList { + auto lock() noexcept(noexcept(mMutex.lock())) { return mMutex.lock(); } + auto unlock() noexcept(noexcept(mMutex.unlock())) { return mMutex.unlock(); } + +private: + std::mutex mMutex; + std::vector mPlayback; + std::vector mCapture; + std::wstring mPlaybackDefaultId; + std::wstring mCaptureDefaultId; + + friend struct DeviceListLock; +}; +struct DeviceListLock : public std::unique_lock { + using std::unique_lock::unique_lock; + + [[nodiscard]] auto& getPlaybackList() const noexcept { return mutex()->mPlayback; } + [[nodiscard]] auto& getCaptureList() const noexcept { return mutex()->mCapture; } + + void setPlaybackDefaultId(std::wstring_view devid) const { mutex()->mPlaybackDefaultId = devid; } + [[nodiscard]] auto getPlaybackDefaultId() const noexcept -> std::wstring_view { return mutex()->mPlaybackDefaultId; } + void setCaptureDefaultId(std::wstring_view devid) const { mutex()->mCaptureDefaultId = devid; } + [[nodiscard]] auto getCaptureDefaultId() const noexcept -> std::wstring_view { return mutex()->mCaptureDefaultId; } +}; + +DeviceList gDeviceList; + + +#if defined(ALSOFT_UWP) +enum EDataFlow { + eRender = 0, + eCapture = (eRender + 1), + eAll = (eCapture + 1), + EDataFlow_enum_count = (eAll + 1) +}; +#endif + +#if defined(ALSOFT_UWP) +using DeviceHandle = Windows::Devices::Enumeration::DeviceInformation; +using EventRegistrationToken = winrt::event_token; +#else +using DeviceHandle = ComPtr; +#endif using NameGUIDPair = std::pair; -NameGUIDPair get_device_name_and_guid(IMMDevice *device) +NameGUIDPair GetDeviceNameAndGuid(const DeviceHandle &device) { - static constexpr char UnknownName[]{"Unknown Device Name"}; - static constexpr char UnknownGuid[]{"Unknown Device GUID"}; + constexpr auto UnknownName = "Unknown Device Name"sv; + constexpr auto UnknownGuid = "Unknown Device GUID"sv; + +#if !defined(ALSOFT_UWP) std::string name, guid; ComPtr ps; - HRESULT hr = device->OpenPropertyStore(STGM_READ, ps.getPtr()); + HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); - return std::make_pair(UnknownName, UnknownGuid); + return {std::string{UnknownName}, std::string{UnknownGuid}}; } PropVariant pvprop; - hr = ps->GetValue(reinterpret_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); + hr = ps->GetValue(al::bit_cast(DEVPKEY_Device_FriendlyName), pvprop.get()); if(FAILED(hr)) - { WARN("GetValue Device_FriendlyName failed: 0x%08lx\n", hr); - name += UnknownName; - } - else if(pvprop->vt == VT_LPWSTR) - name += wstr_to_utf8(pvprop->pwszVal); + else if(pvprop.type() == VT_LPWSTR) + name = wstr_to_utf8(pvprop.value()); else - { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - name += UnknownName; - } + WARN("Unexpected Device_FriendlyName PROPVARIANT type: 0x%04x\n", pvprop.type()); pvprop.clear(); - hr = ps->GetValue(reinterpret_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); + hr = ps->GetValue(al::bit_cast(PKEY_AudioEndpoint_GUID), pvprop.get()); if(FAILED(hr)) - { WARN("GetValue AudioEndpoint_GUID failed: 0x%08lx\n", hr); - guid = UnknownGuid; - } - else if(pvprop->vt == VT_LPWSTR) - guid = wstr_to_utf8(pvprop->pwszVal); + else if(pvprop.type() == VT_LPWSTR) + guid = wstr_to_utf8(pvprop.value()); else + WARN("Unexpected AudioEndpoint_GUID PROPVARIANT type: 0x%04x\n", pvprop.type()); +#else + std::string name{wstr_to_utf8(device.Name())}; + std::string guid; + // device->Id is DeviceInterfacePath: \\?\SWD#MMDEVAPI#{0.0.0.00000000}.{a21c17a0-fc1d-405e-ab5a-b513422b57d1}#{e6327cad-dcec-4949-ae8a-991e976a79d2} + auto devIfPath = device.Id(); + if(auto devIdStart = wcsstr(devIfPath.data(), L"}.")) { - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvprop->vt); - guid = UnknownGuid; + devIdStart += 2; // L"}." + if(auto devIdStartEnd = wcschr(devIdStart, L'#')) + { + std::wstring wDevId{devIdStart, static_cast(devIdStartEnd - devIdStart)}; + guid = wstr_to_utf8(wDevId.c_str()); + std::transform(guid.begin(), guid.end(), guid.begin(), + [](char ch) { return static_cast(std::toupper(ch)); }); + } } - - return std::make_pair(std::move(name), std::move(guid)); +#endif + if(name.empty()) name = UnknownName; + if(guid.empty()) guid = UnknownGuid; + return {std::move(name), std::move(guid)}; } - -EndpointFormFactor get_device_formfactor(IMMDevice *device) +#if !defined(ALSOFT_UWP) +EndpointFormFactor GetDeviceFormfactor(IMMDevice *device) { ComPtr ps; - HRESULT hr{device->OpenPropertyStore(STGM_READ, ps.getPtr())}; + HRESULT hr{device->OpenPropertyStore(STGM_READ, al::out_ptr(ps))}; if(FAILED(hr)) { WARN("OpenPropertyStore failed: 0x%08lx\n", hr); @@ -254,96 +406,430 @@ EndpointFormFactor get_device_formfactor(IMMDevice *device) hr = ps->GetValue(PKEY_AudioEndpoint_FormFactor, pvform.get()); if(FAILED(hr)) WARN("GetValue AudioEndpoint_FormFactor failed: 0x%08lx\n", hr); - else if(pvform->vt == VT_UI4) - formfactor = static_cast(pvform->ulVal); - else if(pvform->vt != VT_EMPTY) - WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform->vt); + else if(pvform.type() == VT_UI4) + formfactor = static_cast(pvform.value()); + else if(pvform.type() != VT_EMPTY) + WARN("Unexpected PROPVARIANT type: 0x%04x\n", pvform.type()); return formfactor; } +#endif -void add_device(IMMDevice *device, const WCHAR *devid, al::vector &list) +#if defined(ALSOFT_UWP) +struct DeviceHelper final : public IActivateAudioInterfaceCompletionHandler +#else +struct DeviceHelper final : private IMMNotificationClient +#endif { - for(auto &entry : list) +#if defined(ALSOFT_UWP) + DeviceHelper() { - if(entry.devid == devid) - return; + /* TODO: UWP also needs to watch for device added/removed events and + * dynamically add/remove devices from the lists. + */ + mActiveClientEvent = CreateEventW(nullptr, FALSE, FALSE, nullptr); + + mRenderDeviceChangedToken = MediaDevice::DefaultAudioRenderDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioRenderDeviceChangedEventArgs& args) { + if (args.Role() == AudioDeviceRole::Default) + { + const std::string msg{ "Default playback device changed: " + + wstr_to_utf8(args.Id())}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, + msg); + } + }); + + mCaptureDeviceChangedToken = MediaDevice::DefaultAudioCaptureDeviceChanged([this](const IInspectable& /*sender*/, const DefaultAudioCaptureDeviceChangedEventArgs& args) { + if (args.Role() == AudioDeviceRole::Default) + { + const std::string msg{ "Default capture device changed: " + + wstr_to_utf8(args.Id()) }; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, + msg); + } + }); + } +#else + DeviceHelper() = default; +#endif + ~DeviceHelper() + { +#if defined(ALSOFT_UWP) + MediaDevice::DefaultAudioRenderDeviceChanged(mRenderDeviceChangedToken); + MediaDevice::DefaultAudioCaptureDeviceChanged(mCaptureDeviceChangedToken); + + if(mActiveClientEvent != nullptr) + CloseHandle(mActiveClientEvent); + mActiveClientEvent = nullptr; +#else + if(mEnumerator) + mEnumerator->UnregisterEndpointNotificationCallback(this); + mEnumerator = nullptr; +#endif } - auto name_guid = get_device_name_and_guid(device); + /** -------------------------- IUnknown ----------------------------- */ + std::atomic mRefCount{1}; + STDMETHODIMP_(ULONG) AddRef() noexcept override { return mRefCount.fetch_add(1u) + 1u; } + STDMETHODIMP_(ULONG) Release() noexcept override { return mRefCount.fetch_sub(1u) - 1u; } - int count{1}; - std::string newname{name_guid.first}; - while(checkName(list, newname)) + STDMETHODIMP QueryInterface(const IID& IId, void **UnknownPtrPtr) noexcept override { - newname = name_guid.first; - newname += " #"; - newname += std::to_string(++count); - } - list.emplace_back(std::move(newname), std::move(name_guid.second), devid); - const DevMap &newentry = list.back(); + // Three rules of QueryInterface: + // https://docs.microsoft.com/en-us/windows/win32/com/rules-for-implementing-queryinterface + // 1. Objects must have identity. + // 2. The set of interfaces on an object instance must be static. + // 3. It must be possible to query successfully for any interface on an object from any other interface. - TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), - newentry.endpoint_guid.c_str(), newentry.devid.c_str()); -} + // If ppvObject(the address) is nullptr, then this method returns E_POINTER. + if(!UnknownPtrPtr) + return E_POINTER; -WCHAR *get_device_id(IMMDevice *device) -{ - WCHAR *devid; - - const HRESULT hr{device->GetId(&devid)}; - if(FAILED(hr)) - { - ERR("Failed to get device id: %lx\n", hr); - return nullptr; - } - - return devid; -} - -void probe_devices(IMMDeviceEnumerator *devenum, EDataFlow flowdir, al::vector &list) -{ - al::vector{}.swap(list); - - ComPtr coll; - HRESULT hr{devenum->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, coll.getPtr())}; - if(FAILED(hr)) - { - ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); - return; - } - - UINT count{0}; - hr = coll->GetCount(&count); - if(SUCCEEDED(hr) && count > 0) - list.reserve(count); - - ComPtr device; - hr = devenum->GetDefaultAudioEndpoint(flowdir, eMultimedia, device.getPtr()); - if(SUCCEEDED(hr)) - { - if(WCHAR *devid{get_device_id(device.get())}) + // https://docs.microsoft.com/en-us/windows/win32/com/implementing-reference-counting + // Whenever a client calls a method(or API function), such as QueryInterface, that returns a new interface + // pointer, the method being called is responsible for incrementing the reference count through the returned + // pointer. For example, when a client first creates an object, it receives an interface pointer to an object + // that, from the client's point of view, has a reference count of one. If the client then calls AddRef on the + // interface pointer, the reference count becomes two. The client must call Release twice on the interface + // pointer to drop all of its references to the object. +#if defined(ALSOFT_UWP) + if(IId == __uuidof(IActivateAudioInterfaceCompletionHandler)) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + *UnknownPtrPtr = static_cast(this); + AddRef(); + return S_OK; } - device = nullptr; - } - - for(UINT i{0};i < count;++i) - { - hr = coll->Item(i, device.getPtr()); - if(FAILED(hr)) continue; - - if(WCHAR *devid{get_device_id(device.get())}) +#else + if(IId == __uuidof(IMMNotificationClient)) { - add_device(device.get(), devid, list); - CoTaskMemFree(devid); + *UnknownPtrPtr = static_cast(this); + AddRef(); + return S_OK; + } +#endif + else if(IId == __uuidof(IAgileObject) || IId == __uuidof(IUnknown)) + { + *UnknownPtrPtr = static_cast(this); + AddRef(); + return S_OK; } - device = nullptr; - } -} + // This method returns S_OK if the interface is supported, and E_NOINTERFACE otherwise. + *UnknownPtrPtr = nullptr; + return E_NOINTERFACE; + } + +#if defined(ALSOFT_UWP) + /** ----------------------- IActivateAudioInterfaceCompletionHandler ------------ */ + HRESULT ActivateCompleted(IActivateAudioInterfaceAsyncOperation*) override + { + SetEvent(mActiveClientEvent); + + // Need to return S_OK + return S_OK; + } +#else + /** ----------------------- IMMNotificationClient ------------ */ + STDMETHODIMP OnDeviceStateChanged(LPCWSTR /*pwstrDeviceId*/, DWORD /*dwNewState*/) noexcept override { return S_OK; } + + STDMETHODIMP OnDeviceAdded(LPCWSTR pwstrDeviceId) noexcept override + { + ComPtr device; + HRESULT hr{mEnumerator->GetDevice(pwstrDeviceId, al::out_ptr(device))}; + if(FAILED(hr)) + { + ERR("Failed to get device: 0x%08lx\n", hr); + return S_OK; + } + + ComPtr endpoint; + hr = device->QueryInterface(__uuidof(IMMEndpoint), al::out_ptr(endpoint)); + if(FAILED(hr)) + { + ERR("Failed to get device endpoint: 0x%08lx\n", hr); + return S_OK; + } + + EDataFlow flowdir{}; + hr = endpoint->GetDataFlow(&flowdir); + if(FAILED(hr)) + { + ERR("Failed to get endpoint data flow: 0x%08lx\n", hr); + return S_OK; + } + + auto devlock = DeviceListLock{gDeviceList}; + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + + if(AddDevice(device, pwstrDeviceId, list)) + { + const auto devtype = (flowdir==eRender) ? alc::DeviceType::Playback + : alc::DeviceType::Capture; + const std::string msg{"Device added: "+list.back().name}; + alc::Event(alc::EventType::DeviceAdded, devtype, msg); + } + + return S_OK; + } + + STDMETHODIMP OnDeviceRemoved(LPCWSTR pwstrDeviceId) noexcept override + { + auto devlock = DeviceListLock{gDeviceList}; + for(auto flowdir : std::array{eRender, eCapture}) + { + auto &list = (flowdir==eRender) ? devlock.getPlaybackList() : devlock.getCaptureList(); + auto devtype = (flowdir==eRender)?alc::DeviceType::Playback : alc::DeviceType::Capture; + + /* Find the ID in the list to remove. */ + auto iter = std::find_if(list.begin(), list.end(), + [pwstrDeviceId](const DevMap &entry) noexcept + { return pwstrDeviceId == entry.devid; }); + if(iter == list.end()) continue; + + TRACE("Removing device \"%s\", \"%s\", \"%ls\"\n", iter->name.c_str(), + iter->endpoint_guid.c_str(), iter->devid.c_str()); + + std::string msg{"Device removed: "+std::move(iter->name)}; + list.erase(iter); + + alc::Event(alc::EventType::DeviceRemoved, devtype, msg); + } + return S_OK; + } + + STDMETHODIMP OnPropertyValueChanged(LPCWSTR /*pwstrDeviceId*/, const PROPERTYKEY /*key*/) noexcept override { return S_OK; } + + STDMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDefaultDeviceId) noexcept override + { + if(role != eMultimedia) + return S_OK; + + const std::wstring_view devid{pwstrDefaultDeviceId ? pwstrDefaultDeviceId + : std::wstring_view{}}; + if(flow == eRender) + { + DeviceListLock{gDeviceList}.setPlaybackDefaultId(devid); + const std::string msg{"Default playback device changed: " + wstr_to_utf8(devid)}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Playback, msg); + } + else if(flow == eCapture) + { + DeviceListLock{gDeviceList}.setCaptureDefaultId(devid); + const std::string msg{"Default capture device changed: " + wstr_to_utf8(devid)}; + alc::Event(alc::EventType::DefaultDeviceChanged, alc::DeviceType::Capture, msg); + } + return S_OK; + } +#endif + + /** -------------------------- DeviceHelper ----------------------------- */ + HRESULT init() + { +#if !defined(ALSOFT_UWP) + HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), al::out_ptr(mEnumerator))}; + if(SUCCEEDED(hr)) + mEnumerator->RegisterEndpointNotificationCallback(this); + else + WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); + return hr; +#else + return S_OK; +#endif + } + + HRESULT openDevice(std::wstring_view devid, EDataFlow flow, DeviceHandle& device) + { +#if !defined(ALSOFT_UWP) + HRESULT hr{E_FAIL}; + if(mEnumerator) + { + if(devid.empty()) + hr = mEnumerator->GetDefaultAudioEndpoint(flow, eMultimedia, al::out_ptr(device)); + else + hr = mEnumerator->GetDevice(devid.data(), al::out_ptr(device)); + } + return hr; +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto devIfPath = + devid.empty() ? (flow == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) : MediaDevice::GetDefaultAudioCaptureId(deviceRole)) + : winrt::hstring(devid.data()); + if (devIfPath.empty()) + return E_POINTER; + + auto&& deviceInfo = DeviceInformation::CreateFromIdAsync(devIfPath, nullptr, DeviceInformationKind::DeviceInterface).get(); + if (!deviceInfo) + return E_NOINTERFACE; + device = deviceInfo; + return S_OK; +#endif + } + +#if !defined(ALSOFT_UWP) + static HRESULT activateAudioClient(_In_ DeviceHandle &device, REFIID iid, void **ppv) + { return device->Activate(iid, CLSCTX_INPROC_SERVER, nullptr, ppv); } +#else + HRESULT activateAudioClient(_In_ DeviceHandle &device, _In_ REFIID iid, void **ppv) + { + ComPtr asyncOp; + HRESULT hr{ActivateAudioInterfaceAsync(device.Id().data(), iid, nullptr, this, + al::out_ptr(asyncOp))}; + if(FAILED(hr)) + return hr; + + /* I don't like waiting for INFINITE time, but the activate operation + * can take an indefinite amount of time since it can require user + * input. + */ + DWORD res{WaitForSingleObjectEx(mActiveClientEvent, INFINITE, FALSE)}; + if(res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + return E_FAIL; + } + + HRESULT hrActivateRes{E_FAIL}; + ComPtr punkAudioIface; + hr = asyncOp->GetActivateResult(&hrActivateRes, al::out_ptr(punkAudioIface)); + if(SUCCEEDED(hr)) hr = hrActivateRes; + if(FAILED(hr)) return hr; + + return punkAudioIface->QueryInterface(iid, ppv); + } +#endif + + std::wstring probeDevices(EDataFlow flowdir, std::vector &list) + { + std::wstring defaultId; + std::vector{}.swap(list); + +#if !defined(ALSOFT_UWP) + ComPtr coll; + HRESULT hr{mEnumerator->EnumAudioEndpoints(flowdir, DEVICE_STATE_ACTIVE, + al::out_ptr(coll))}; + if(FAILED(hr)) + { + ERR("Failed to enumerate audio endpoints: 0x%08lx\n", hr); + return defaultId; + } + + UINT count{0}; + hr = coll->GetCount(&count); + if(SUCCEEDED(hr) && count > 0) + list.reserve(count); + + ComPtr device; + hr = mEnumerator->GetDefaultAudioEndpoint(flowdir, eMultimedia, al::out_ptr(device)); + if(SUCCEEDED(hr)) + { + if(WCHAR *devid{GetDeviceId(device.get())}) + { + defaultId = devid; + CoTaskMemFree(devid); + } + device = nullptr; + } + + for(UINT i{0};i < count;++i) + { + hr = coll->Item(i, al::out_ptr(device)); + if(FAILED(hr)) + continue; + + if(WCHAR *devid{GetDeviceId(device.get())}) + { + std::ignore = AddDevice(device, devid, list); + CoTaskMemFree(devid); + } + device = nullptr; + } +#else + const auto deviceRole = Windows::Media::Devices::AudioDeviceRole::Default; + auto DefaultAudioId = flowdir == eRender ? MediaDevice::GetDefaultAudioRenderId(deviceRole) + : MediaDevice::GetDefaultAudioCaptureId(deviceRole); + if(!DefaultAudioId.empty()) + { + auto deviceInfo = DeviceInformation::CreateFromIdAsync(DefaultAudioId, nullptr, + DeviceInformationKind::DeviceInterface).get(); + if(deviceInfo) + defaultId = deviceInfo.Id().data(); + } + + // Get the string identifier of the audio renderer + auto AudioSelector = flowdir == eRender ? MediaDevice::GetAudioRenderSelector() : MediaDevice::GetAudioCaptureSelector(); + + // Setup the asynchronous callback + auto&& DeviceInfoCollection = DeviceInformation::FindAllAsync(AudioSelector, /*PropertyList*/nullptr, DeviceInformationKind::DeviceInterface).get(); + if(DeviceInfoCollection) + { + try { + auto deviceCount = DeviceInfoCollection.Size(); + for(unsigned int i{0};i < deviceCount;++i) + { + auto deviceInfo = DeviceInfoCollection.GetAt(i); + if(deviceInfo) + std::ignore = AddDevice(deviceInfo, deviceInfo.Id().data(), list); + } + } + catch (const winrt::hresult_error& /*ex*/) { + } + } +#endif + + return defaultId; + } + +private: + static bool AddDevice(const DeviceHandle &device, const WCHAR *devid, std::vector &list) + { + for(auto &entry : list) + { + if(entry.devid == devid) + return false; + } + + auto name_guid = GetDeviceNameAndGuid(device); + int count{1}; + std::string newname{name_guid.first}; + while(checkName(list, newname)) + { + newname = name_guid.first; + newname += " #"; + newname += std::to_string(++count); + } + list.emplace_back(std::move(newname), std::move(name_guid.second), devid); + const DevMap &newentry = list.back(); + + TRACE("Got device \"%s\", \"%s\", \"%ls\"\n", newentry.name.c_str(), + newentry.endpoint_guid.c_str(), newentry.devid.c_str()); + return true; + } + +#if !defined(ALSOFT_UWP) + static WCHAR *GetDeviceId(IMMDevice *device) + { + WCHAR *devid; + + const HRESULT hr{device->GetId(&devid)}; + if(FAILED(hr)) + { + ERR("Failed to get device id: %lx\n", hr); + return nullptr; + } + + return devid; + } + ComPtr mEnumerator{nullptr}; + +#else + + HANDLE mActiveClientEvent{nullptr}; + + EventRegistrationToken mRenderDeviceChangedToken; + EventRegistrationToken mCaptureDeviceChangedToken; +#endif +}; bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { @@ -357,6 +843,7 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { out->Format = *in; out->Format.cbSize = 0; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; if(out->Format.nChannels == 1) out->dwChannelMask = MONO; @@ -370,6 +857,7 @@ bool MakeExtensible(WAVEFORMATEXTENSIBLE *out, const WAVEFORMATEX *in) { out->Format = *in; out->Format.cbSize = 0; + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ out->Samples.wValidBitsPerSample = out->Format.wBitsPerSample; if(out->Format.nChannels == 1) out->dwChannelMask = MONO; @@ -394,6 +882,7 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) { const WAVEFORMATEXTENSIBLE *fmtex{ CONTAINING_RECORD(format, const WAVEFORMATEXTENSIBLE, Format)}; + /* NOLINTBEGIN(cppcoreguidelines-pro-type-union-access) */ TRACE("%s:\n" " FormatTag = 0x%04x\n" " Channels = %d\n" @@ -409,6 +898,7 @@ void TraceFormat(const char *msg, const WAVEFORMATEX *format) fmtex->Format.nAvgBytesPerSec, fmtex->Format.nBlockAlign, fmtex->Format.wBitsPerSample, fmtex->Format.cbSize, fmtex->Samples.wReserved, fmtex->dwChannelMask, GuidPrinter{fmtex->SubFormat}.c_str()); + /* NOLINTEND(cppcoreguidelines-pro-type-union-access) */ } else TRACE("%s:\n" @@ -430,56 +920,62 @@ enum class MsgType { StartDevice, StopDevice, CloseDevice, - EnumeratePlayback, - EnumerateCapture, - Count, - QuitThread = Count + QuitThread }; -constexpr char MessageStr[static_cast(MsgType::Count)][20]{ - "Open Device", - "Reset Device", - "Start Device", - "Stop Device", - "Close Device", - "Enumerate Playback", - "Enumerate Capture" -}; +constexpr const char *GetMessageTypeName(MsgType type) noexcept +{ + switch(type) + { + case MsgType::OpenDevice: return "Open Device"; + case MsgType::ResetDevice: return "Reset Device"; + case MsgType::StartDevice: return "Start Device"; + case MsgType::StopDevice: return "Stop Device"; + case MsgType::CloseDevice: return "Close Device"; + case MsgType::QuitThread: break; + } + return ""; +} /* Proxy interface used by the message handler. */ struct WasapiProxy { + WasapiProxy() = default; + WasapiProxy(const WasapiProxy&) = delete; + WasapiProxy(WasapiProxy&&) = delete; virtual ~WasapiProxy() = default; - virtual HRESULT openProxy(const char *name) = 0; + void operator=(const WasapiProxy&) = delete; + void operator=(WasapiProxy&&) = delete; + + virtual HRESULT openProxy(std::string_view name) = 0; virtual void closeProxy() = 0; virtual HRESULT resetProxy() = 0; virtual HRESULT startProxy() = 0; - virtual void stopProxy() = 0; + virtual void stopProxy() = 0; struct Msg { MsgType mType; WasapiProxy *mProxy; - const char *mParam; + std::string_view mParam; std::promise mPromise; explicit operator bool() const noexcept { return mType != MsgType::QuitThread; } }; - static std::thread sThread; - static std::deque mMsgQueue; - static std::mutex mMsgQueueLock; - static std::condition_variable mMsgQueueCond; - static std::mutex sThreadLock; - static size_t sInitCount; + static inline std::deque mMsgQueue; + static inline std::mutex mMsgQueueLock; + static inline std::condition_variable mMsgQueueCond; - std::future pushMessage(MsgType type, const char *param=nullptr) + static inline std::optional sDeviceHelper; + + std::future pushMessage(MsgType type, std::string_view param={}) { std::promise promise; std::future future{promise.get_future()}; { - std::lock_guard _{mMsgQueueLock}; + std::lock_guard msglock{mMsgQueueLock}; mMsgQueue.emplace_back(Msg{type, this, param, std::move(promise)}); } mMsgQueueCond.notify_one(); @@ -491,8 +987,8 @@ struct WasapiProxy { std::promise promise; std::future future{promise.get_future()}; { - std::lock_guard _{mMsgQueueLock}; - mMsgQueue.emplace_back(Msg{type, nullptr, nullptr, std::move(promise)}); + std::lock_guard msglock{mMsgQueueLock}; + mMsgQueue.emplace_back(Msg{type, nullptr, {}, std::move(promise)}); } mMsgQueueCond.notify_one(); return future; @@ -508,65 +1004,45 @@ struct WasapiProxy { } static int messageHandler(std::promise *promise); - - static HRESULT InitThread() - { - std::lock_guard _{sThreadLock}; - HRESULT res{S_OK}; - if(!sThread.joinable()) - { - std::promise promise; - auto future = promise.get_future(); - - sThread = std::thread{&WasapiProxy::messageHandler, &promise}; - res = future.get(); - if(FAILED(res)) - { - sThread.join(); - return res; - } - } - ++sInitCount; - return res; - } - - static void DeinitThread() - { - std::lock_guard _{sThreadLock}; - if(!--sInitCount && sThread.joinable()) - { - pushMessageStatic(MsgType::QuitThread); - sThread.join(); - } - } }; -std::thread WasapiProxy::sThread; -std::deque WasapiProxy::mMsgQueue; -std::mutex WasapiProxy::mMsgQueueLock; -std::condition_variable WasapiProxy::mMsgQueueCond; -std::mutex WasapiProxy::sThreadLock; -size_t WasapiProxy::sInitCount{0}; int WasapiProxy::messageHandler(std::promise *promise) { TRACE("Starting message thread\n"); - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - WARN("Failed to initialize COM: 0x%08lx\n", hr); - promise->set_value(hr); + WARN("Failed to initialize COM: 0x%08lx\n", com.status()); + promise->set_value(com.status()); return 0; } - promise->set_value(S_OK); + + struct HelperResetter { + ~HelperResetter() { sDeviceHelper.reset(); } + }; + HelperResetter scoped_watcher; + + HRESULT hr{sDeviceHelper.emplace().init()}; + promise->set_value(hr); promise = nullptr; + if(FAILED(hr)) + return 0; + + { + auto devlock = DeviceListLock{gDeviceList}; + auto defaultId = sDeviceHelper->probeDevices(eRender, devlock.getPlaybackList()); + if(!defaultId.empty()) devlock.setPlaybackDefaultId(defaultId); + defaultId = sDeviceHelper->probeDevices(eCapture, devlock.getCaptureList()); + if(!defaultId.empty()) devlock.setCaptureDefaultId(defaultId); + } TRACE("Starting message loop\n"); while(Msg msg{popMessage()}) { - TRACE("Got message \"%s\" (0x%04x, this=%p, param=%p)\n", - MessageStr[static_cast(msg.mType)], static_cast(msg.mType), - static_cast(msg.mProxy), static_cast(msg.mParam)); + TRACE("Got message \"%s\" (0x%04x, this=%p, param=\"%.*s\")\n", + GetMessageTypeName(msg.mType), static_cast(msg.mType), + static_cast(msg.mProxy), al::sizei(msg.mParam), msg.mParam.data()); switch(msg.mType) { @@ -595,27 +1071,6 @@ int WasapiProxy::messageHandler(std::promise *promise) msg.mPromise.set_value(S_OK); continue; - case MsgType::EnumeratePlayback: - case MsgType::EnumerateCapture: - { - void *ptr{}; - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - msg.mPromise.set_value(hr); - else - { - ComPtr devenum{static_cast(ptr)}; - - if(msg.mType == MsgType::EnumeratePlayback) - probe_devices(devenum.get(), eRender, PlaybackDevices); - else if(msg.mType == MsgType::EnumerateCapture) - probe_devices(devenum.get(), eCapture, CaptureDevices); - msg.mPromise.set_value(S_OK); - } - continue; - } - case MsgType::QuitThread: break; } @@ -623,20 +1078,19 @@ int WasapiProxy::messageHandler(std::promise *promise) msg.mPromise.set_value(E_FAIL); } TRACE("Message loop finished\n"); - CoUninitialize(); return 0; } - struct WasapiPlayback final : public BackendBase, WasapiProxy { WasapiPlayback(DeviceBase *device) noexcept : BackendBase{device} { } ~WasapiPlayback() override; int mixerProc(); + int mixerSpatialProc(); - void open(const char *name) override; - HRESULT openProxy(const char *name) override; + void open(std::string_view name) override; + HRESULT openProxy(std::string_view name) override; void closeProxy() override; bool reset() override; @@ -648,14 +1102,28 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { ClockLatency getClockLatency() override; + void prepareFormat(WAVEFORMATEXTENSIBLE &OutputType); + void finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType); + + auto initSpatial() -> bool; + HRESULT mOpenStatus{E_FAIL}; - ComPtr mMMDev{nullptr}; - ComPtr mClient{nullptr}; - ComPtr mRender{nullptr}; + DeviceHandle mMMDev{nullptr}; + + struct PlainDevice { + ComPtr mClient{nullptr}; + ComPtr mRender{nullptr}; + }; + struct SpatialDevice { + ComPtr mClient{nullptr}; + ComPtr mRender{nullptr}; + AudioObjectType mStaticMask{}; + }; + std::variant mAudio; HANDLE mNotifyEvent{nullptr}; UINT32 mOrigBufferSize{}, mOrigUpdateSize{}; - std::unique_ptr mResampleBuffer{}; + std::vector mResampleBuffer{}; uint mBufferFilled{0}; SampleConverterPtr mResampler; @@ -666,17 +1134,12 @@ struct WasapiPlayback final : public BackendBase, WasapiProxy { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WasapiPlayback) }; WasapiPlayback::~WasapiPlayback() { if(SUCCEEDED(mOpenStatus)) - { pushMessage(MsgType::CloseDevice).wait(); - DeinitThread(); - } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -687,24 +1150,29 @@ WasapiPlayback::~WasapiPlayback() FORCE_ALIGN int WasapiPlayback::mixerProc() { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); return 1; } + auto &audio = std::get(mAudio); + SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const uint frame_size{mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8u}; const uint update_size{mOrigUpdateSize}; const UINT32 buffer_len{mOrigBufferSize}; + const void *resbufferptr{}; + + mBufferFilled = 0; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 written; - hr = mClient->GetCurrentPadding(&written); + HRESULT hr{audio.mClient->GetCurrentPadding(&written)}; if(FAILED(hr)) { ERR("Failed to get padding: 0x%08lx\n", hr); @@ -723,43 +1191,38 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } BYTE *buffer; - hr = mRender->GetBuffer(len, &buffer); + hr = audio.mRender->GetBuffer(len, &buffer); if(SUCCEEDED(hr)) { if(mResampler) { - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; + auto dst = al::span{buffer, size_t{len}*frame_size}; for(UINT32 done{0};done < len;) { if(mBufferFilled == 0) { - mDevice->renderSamples(mResampleBuffer.get(), mDevice->UpdateSize, + mDevice->renderSamples(mResampleBuffer.data(), mDevice->UpdateSize, mFormat.Format.nChannels); + resbufferptr = mResampleBuffer.data(); mBufferFilled = mDevice->UpdateSize; } - const void *src{mResampleBuffer.get()}; - uint srclen{mBufferFilled}; - uint got{mResampler->convert(&src, &srclen, buffer, len-done)}; - buffer += got*frame_size; + uint got{mResampler->convert(&resbufferptr, &mBufferFilled, dst.data(), + len-done)}; + dst = dst.subspan(size_t{got}*frame_size); done += got; mPadding.store(written + done, std::memory_order_relaxed); - if(srclen) - { - const char *bsrc{static_cast(src)}; - std::copy(bsrc, bsrc + srclen*frame_size, mResampleBuffer.get()); - } - mBufferFilled = srclen; } } else { - std::lock_guard _{mMutex}; + std::lock_guard dlock{mMutex}; mDevice->renderSamples(buffer, len, mFormat.Format.nChannels); mPadding.store(written + len, std::memory_order_relaxed); } - hr = mRender->ReleaseBuffer(len, 0); + hr = audio.mRender->ReleaseBuffer(len, 0); } if(FAILED(hr)) { @@ -770,12 +1233,129 @@ FORCE_ALIGN int WasapiPlayback::mixerProc() } mPadding.store(0u, std::memory_order_release); - CoUninitialize(); + return 0; +} + +FORCE_ALIGN int WasapiPlayback::mixerSpatialProc() +{ + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) + { + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); + return 1; + } + + auto &audio = std::get(mAudio); + + SetRTPriority(); + althrd_setname(GetMixerThreadName()); + + std::vector> channels; + std::vector buffers; + std::vector resbuffers; + std::vector tmpbuffers; + + /* TODO: Set mPadding appropriately. There doesn't seem to be a way to + * update it dynamically based on the stream, so a fixed size may be the + * best we can do. + */ + mPadding.store(mOrigBufferSize-mOrigUpdateSize, std::memory_order_release); + + mBufferFilled = 0; + while(!mKillNow.load(std::memory_order_relaxed)) + { + if(DWORD res{WaitForSingleObjectEx(mNotifyEvent, 1000, FALSE)}; res != WAIT_OBJECT_0) + { + ERR("WaitForSingleObjectEx error: 0x%lx\n", res); + + HRESULT hr{audio.mRender->Reset()}; + if(FAILED(hr)) + { + ERR("ISpatialAudioObjectRenderStream::Reset failed: 0x%08lx\n", hr); + mDevice->handleDisconnect("Device lost: 0x%08lx", hr); + break; + } + } + + UINT32 dynamicCount{}, framesToDo{}; + HRESULT hr{audio.mRender->BeginUpdatingAudioObjects(&dynamicCount, &framesToDo)}; + if(SUCCEEDED(hr)) + { + if(channels.empty()) UNLIKELY + { + auto flags = as_unsigned(audio.mStaticMask); + channels.reserve(as_unsigned(al::popcount(flags))); + while(flags) + { + auto id = decltype(flags){1} << al::countr_zero(flags); + flags &= ~id; + + channels.emplace_back(); + audio.mRender->ActivateSpatialAudioObject(static_cast(id), + al::out_ptr(channels.back())); + } + buffers.resize(channels.size()); + if(mResampler) + { + tmpbuffers.resize(buffers.size()); + resbuffers.resize(buffers.size()); + auto bufptr = mResampleBuffer.begin(); + for(size_t i{0};i < tmpbuffers.size();++i) + { + resbuffers[i] = reinterpret_cast(al::to_address(bufptr)); + bufptr += ptrdiff_t(mDevice->UpdateSize*sizeof(float)); + } + } + } + + /* We have to call to get each channel's buffer individually every + * update, unfortunately. + */ + std::transform(channels.cbegin(), channels.cend(), buffers.begin(), + [](const ComPtr &obj) -> float* + { + BYTE *buffer{}; + UINT32 size{}; + obj->GetBuffer(&buffer, &size); + return reinterpret_cast(buffer); + }); + + if(!mResampler) + mDevice->renderSamples(buffers, framesToDo); + else + { + std::lock_guard dlock{mMutex}; + for(UINT32 pos{0};pos < framesToDo;) + { + if(mBufferFilled == 0) + { + mDevice->renderSamples(resbuffers, mDevice->UpdateSize); + std::copy(resbuffers.cbegin(), resbuffers.cend(), tmpbuffers.begin()); + mBufferFilled = mDevice->UpdateSize; + } + + const uint got{mResampler->convertPlanar(tmpbuffers.data(), &mBufferFilled, + reinterpret_cast(buffers.data()), framesToDo-pos)}; + for(auto &buf : buffers) + buf += got; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + pos += got; + } + } + + hr = audio.mRender->EndUpdatingAudioObjects(); + } + + if(FAILED(hr)) + ERR("Failed to update playback objects: 0x%08lx\n", hr); + } + mPadding.store(0u, std::memory_order_release); + return 0; } -void WasapiPlayback::open(const char *name) +void WasapiPlayback::open(std::string_view name) { if(SUCCEEDED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, @@ -789,132 +1369,65 @@ void WasapiPlayback::open(const char *name) "Failed to create notify events"}; } - HRESULT hr{InitThread()}; - if(FAILED(hr)) - { - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to init COM thread: 0x%08lx", hr}; - } - - if(name) - { - if(PlaybackDevices.empty()) - pushMessage(MsgType::EnumeratePlayback); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } - } + if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) + name = name.substr(prefix.size()); mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) - { - DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", mOpenStatus}; - } } -HRESULT WasapiPlayback::openProxy(const char *name) +HRESULT WasapiPlayback::openProxy(std::string_view name) { - const wchar_t *devid{nullptr}; - if(name) + std::string devname; + std::wstring devid; + if(!name.empty()) { - auto iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + auto devlock = DeviceListLock{gDeviceList}; + auto list = al::span{devlock.getPlaybackList()}; + auto iter = std::find_if(list.cbegin(), list.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == PlaybackDevices.cend()) + if(iter == list.cend()) { const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(PlaybackDevices.cbegin(), PlaybackDevices.cend(), + iter = std::find_if(list.cbegin(), list.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } - if(iter == PlaybackDevices.cend()) + if(iter == list.cend()) { - WARN("Failed to find device name matching \"%s\"\n", name); + WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); return E_FAIL; } - name = iter->name.c_str(); - devid = iter->devid.c_str(); + devname = iter->name; + devid = iter->devid; } - void *ptr; - ComPtr mmdev; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr enumerator{static_cast(ptr)}; - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, mmdev.getPtr()); - else - hr = enumerator->GetDevice(devid, mmdev.getPtr()); - } + HRESULT hr{sDeviceHelper->openDevice(devid, eRender, mMMDev)}; if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); return hr; } + if(!devname.empty()) + mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + else + mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; - mClient = nullptr; - mMMDev = std::move(mmdev); - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; - - return hr; + return S_OK; } void WasapiPlayback::closeProxy() { - mClient = nullptr; + mAudio.emplace(); mMMDev = nullptr; } -bool WasapiPlayback::reset() +void WasapiPlayback::prepareFormat(WAVEFORMATEXTENSIBLE &OutputType) { - HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; - if(FAILED(hr)) - throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; - return true; -} - -HRESULT WasapiPlayback::resetProxy() -{ - mClient = nullptr; - - void *ptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; - if(FAILED(hr)) - { - ERR("Failed to reactivate audio client: 0x%08lx\n", hr); - return hr; - } - mClient = ComPtr{static_cast(ptr)}; - - WAVEFORMATEX *wfx; - hr = mClient->GetMixFormat(&wfx); - if(FAILED(hr)) - { - ERR("Failed to get mix format: 0x%08lx\n", hr); - return hr; - } - TraceFormat("Device mix format", wfx); - - WAVEFORMATEXTENSIBLE OutputType; - if(!MakeExtensible(&OutputType, wfx)) - { - CoTaskMemFree(wfx); - return E_FAIL; - } - CoTaskMemFree(wfx); - wfx = nullptr; - - const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; - const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; bool isRear51{false}; if(!mDevice->Flags.test(FrequencyRequest)) @@ -928,7 +1441,7 @@ HRESULT WasapiPlayback::resetProxy() const uint32_t chancount{OutputType.Format.nChannels}; const DWORD chanmask{OutputType.dwChannelMask}; if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX71; + mDevice->FmtChans = DevFmtX714; else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) mDevice->FmtChans = DevFmtX71; else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) @@ -987,6 +1500,9 @@ HRESULT WasapiPlayback::resetProxy() OutputType.Format.nChannels = 8; OutputType.dwChannelMask = X7DOT1; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + /*fall-through*/ case DevFmtX714: OutputType.Format.nChannels = 12; OutputType.dwChannelMask = X7DOT1DOT4; @@ -999,7 +1515,6 @@ HRESULT WasapiPlayback::resetProxy() /* fall-through */ case DevFmtUByte: OutputType.Format.wBitsPerSample = 8; - OutputType.Samples.wValidBitsPerSample = 8; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtUShort: @@ -1007,7 +1522,6 @@ HRESULT WasapiPlayback::resetProxy() /* fall-through */ case DevFmtShort: OutputType.Format.wBitsPerSample = 16; - OutputType.Samples.wValidBitsPerSample = 16; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtUInt: @@ -1015,28 +1529,417 @@ HRESULT WasapiPlayback::resetProxy() /* fall-through */ case DevFmtInt: OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break; case DevFmtFloat: OutputType.Format.wBitsPerSample = 32; - OutputType.Samples.wValidBitsPerSample = 32; OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; OutputType.Format.nSamplesPerSec = mDevice->Frequency; OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nChannels * OutputType.Format.wBitsPerSample / 8); OutputType.Format.nAvgBytesPerSec = OutputType.Format.nSamplesPerSec * OutputType.Format.nBlockAlign; +} + +void WasapiPlayback::finalizeFormat(WAVEFORMATEXTENSIBLE &OutputType) +{ + if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) + mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + else + mDevice->Frequency = std::min(mDevice->Frequency, uint(OutputType.Format.nSamplesPerSec)); + + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + /* Don't update the channel format if the requested format fits what's + * supported. + */ + bool chansok{false}; + if(mDevice->Flags.test(ChannelsRequest)) + { + /* When requesting a channel configuration, make sure it fits the + * mask's lsb (to ensure no gaps in the output channels). If there's no + * mask, assume the request fits with enough channels. + */ + switch(mDevice->FmtChans) + { + case DevFmtMono: + chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); + break; + case DevFmtStereo: + chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); + break; + case DevFmtQuad: + chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask)); + break; + case DevFmtX51: + chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask)); + break; + case DevFmtX61: + chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask)); + break; + case DevFmtX71: + case DevFmtX3D71: + chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask)); + break; + case DevFmtX714: + chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); + case DevFmtX7144: + case DevFmtAmbi3D: + break; + } + } + if(!chansok) + { + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 + || (chanmask&X51RearMask) == X5DOT1REAR)) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) + mDevice->FmtChans = DevFmtMono; + else + { + ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, + OutputType.dwChannelMask); + mDevice->FmtChans = DevFmtStereo; + OutputType.Format.nChannels = 2; + OutputType.dwChannelMask = STEREO; + } + } + + if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) + { + if(OutputType.Format.wBitsPerSample == 8) + mDevice->FmtType = DevFmtUByte; + else if(OutputType.Format.wBitsPerSample == 16) + mDevice->FmtType = DevFmtShort; + else if(OutputType.Format.wBitsPerSample == 32) + mDevice->FmtType = DevFmtInt; + else + { + mDevice->FmtType = DevFmtShort; + OutputType.Format.wBitsPerSample = 16; + } + } + else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + mDevice->FmtType = DevFmtFloat; + OutputType.Format.wBitsPerSample = 32; + } + else + { + ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); + mDevice->FmtType = DevFmtShort; + if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) + OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; + OutputType.Format.wBitsPerSample = 16; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; +} + + +auto WasapiPlayback::initSpatial() -> bool +{ + auto &audio = mAudio.emplace(); + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(ISpatialAudioClient), + al::out_ptr(audio.mClient))}; + if(FAILED(hr)) + { + ERR("Failed to activate spatial audio client: 0x%08lx\n", hr); + return false; + } + + ComPtr fmtenum; + hr = audio.mClient->GetSupportedAudioObjectFormatEnumerator(al::out_ptr(fmtenum)); + if(FAILED(hr)) + { + ERR("Failed to get format enumerator: 0x%08lx\n", hr); + return false; + } + + UINT32 fmtcount{}; + hr = fmtenum->GetCount(&fmtcount); + if(FAILED(hr) || fmtcount == 0) + { + ERR("Failed to get format count: 0x%08lx\n", hr); + return false; + } + + WAVEFORMATEX *preferredFormat{}; + hr = fmtenum->GetFormat(0, &preferredFormat); + if(FAILED(hr)) + { + ERR("Failed to get preferred format: 0x%08lx\n", hr); + return false; + } + TraceFormat("Preferred mix format", preferredFormat); + + UINT32 maxFrames{}; + hr = audio.mClient->GetMaxFrameCount(preferredFormat, &maxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: 0x%08lx\n", hr); + else + TRACE("Max sample frames: %u\n", maxFrames); + for(UINT32 i{1};i < fmtcount;++i) + { + WAVEFORMATEX *otherFormat{}; + hr = fmtenum->GetFormat(i, &otherFormat); + if(FAILED(hr)) + ERR("Failed to format %u: 0x%08lx\n", i+1, hr); + else + { + TraceFormat("Other mix format", otherFormat); + UINT32 otherMaxFrames{}; + hr = audio.mClient->GetMaxFrameCount(otherFormat, &otherMaxFrames); + if(FAILED(hr)) + ERR("Failed to get max frames: 0x%08lx\n", hr); + else + TRACE("Max sample frames: %u\n", otherMaxFrames); + } + } + + WAVEFORMATEXTENSIBLE OutputType; + if(!MakeExtensible(&OutputType, preferredFormat)) + return false; + + /* Force 32-bit float. This is currently required for planar output. */ + if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE + && OutputType.Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT) + { + OutputType.Format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + OutputType.Format.cbSize = 0; + } + if(OutputType.Format.wBitsPerSample != 32) + { + OutputType.Format.nAvgBytesPerSec = OutputType.Format.nAvgBytesPerSec * 32u + / OutputType.Format.wBitsPerSample; + OutputType.Format.nBlockAlign = static_cast(OutputType.Format.nBlockAlign * 32 + / OutputType.Format.wBitsPerSample); + OutputType.Format.wBitsPerSample = 32; + } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ + OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + + /* Match the output rate if not requesting anything specific. */ + if(!mDevice->Flags.test(FrequencyRequest)) + mDevice->Frequency = OutputType.Format.nSamplesPerSec; + + bool isRear51{false}; + if(!mDevice->Flags.test(ChannelsRequest)) + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) + mDevice->FmtChans = DevFmtX714; + else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) + mDevice->FmtChans = DevFmtX71; + else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) + mDevice->FmtChans = DevFmtX61; + else if(chancount >= 6 && (chanmask&X51Mask) == X5DOT1) + mDevice->FmtChans = DevFmtX51; + else if(chancount >= 6 && (chanmask&X51RearMask) == X5DOT1REAR) + { + mDevice->FmtChans = DevFmtX51; + isRear51 = true; + } + else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) + mDevice->FmtChans = DevFmtQuad; + else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) + mDevice->FmtChans = DevFmtStereo; + /* HACK: Don't autoselect mono. Wine returns this and makes the audio + * terrible. + */ + else if(!(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask))) + ERR("Unhandled channel config: %d -- 0x%08lx\n", chancount, chanmask); + } + else + { + const uint32_t chancount{OutputType.Format.nChannels}; + const DWORD chanmask{OutputType.dwChannelMask}; + isRear51 = (chancount == 6 && (chanmask&X51RearMask) == X5DOT1REAR); + } + + auto getTypeMask = [isRear51](DevFmtChannels chans) noexcept + { + switch(chans) + { + case DevFmtMono: return ChannelMask_Mono; + case DevFmtStereo: return ChannelMask_Stereo; + case DevFmtQuad: return ChannelMask_Quad; + case DevFmtX51: return isRear51 ? ChannelMask_X51Rear : ChannelMask_X51; + case DevFmtX61: return ChannelMask_X61; + case DevFmtX3D71: + case DevFmtX71: return ChannelMask_X71; + case DevFmtX714: return ChannelMask_X714; + case DevFmtX7144: return ChannelMask_X7144; + case DevFmtAmbi3D: + break; + } + return ChannelMask_Stereo; + }; + + SpatialAudioObjectRenderStreamActivationParams streamParams{}; + streamParams.ObjectFormat = &OutputType.Format; + streamParams.StaticObjectTypeMask = getTypeMask(mDevice->FmtChans); + streamParams.Category = AudioCategory_Media; + streamParams.EventHandle = mNotifyEvent; + + PropVariant paramProp{}; + paramProp.setBlob({reinterpret_cast(&streamParams), sizeof(streamParams)}); + + hr = audio.mClient->ActivateSpatialAudioStream(paramProp.get(), + __uuidof(ISpatialAudioObjectRenderStream), al::out_ptr(audio.mRender)); + if(FAILED(hr)) + { + ERR("Failed to activate spatial audio stream: 0x%08lx\n", hr); + return false; + } + + audio.mStaticMask = streamParams.StaticObjectTypeMask; + mFormat = OutputType; + + mDevice->FmtType = DevFmtFloat; + mDevice->Flags.reset(DirectEar).set(Virtualization); + if(streamParams.StaticObjectTypeMask == ChannelMask_Stereo) + mDevice->FmtChans = DevFmtStereo; + if(!GetConfigValueBool(mDevice->DeviceName, "wasapi", "allow-resampler", true)) + mDevice->Frequency = uint(OutputType.Format.nSamplesPerSec); + else + mDevice->Frequency = std::min(mDevice->Frequency, + uint(OutputType.Format.nSamplesPerSec)); + + setDefaultWFXChannelOrder(); + + /* FIXME: Get the real update and buffer size. Presumably the actual device + * is configured once ActivateSpatialAudioStream succeeds, and an + * IAudioClient from the same IMMDevice accesses the same device + * configuration. This isn't obviously correct, but for now assume + * IAudioClient::GetDevicePeriod returns the current device period time + * that ISpatialAudioObjectRenderStream will try to wake up at. + * + * Unfortunately this won't get the buffer size of the + * ISpatialAudioObjectRenderStream, so we only assume there's two periods. + */ + mOrigUpdateSize = mDevice->UpdateSize; + mOrigBufferSize = mOrigUpdateSize*2; + ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + + ComPtr tmpClient; + hr = sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(tmpClient)); + if(FAILED(hr)) + ERR("Failed to activate audio client: 0x%08lx\n", hr); + else + { + hr = tmpClient->GetDevicePeriod(&reinterpret_cast(per_time), nullptr); + if(FAILED(hr)) + ERR("Failed to get device period: 0x%08lx\n", hr); + else + { + mOrigUpdateSize = RefTime2Samples(per_time, mFormat.Format.nSamplesPerSec); + mOrigBufferSize = mOrigUpdateSize*2; + } + } + tmpClient = nullptr; + + mDevice->UpdateSize = RefTime2Samples(per_time, mDevice->Frequency); + mDevice->BufferSize = mDevice->UpdateSize*2; + + mResampler = nullptr; + mResampleBuffer.clear(); + mResampleBuffer.shrink_to_fit(); + mBufferFilled = 0; + if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) + { + const auto flags = as_unsigned(streamParams.StaticObjectTypeMask); + const auto channelCount = as_unsigned(al::popcount(flags)); + mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, channelCount, + mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); + mResampleBuffer.resize(size_t{mDevice->UpdateSize} * channelCount * + mFormat.Format.wBitsPerSample / 8); + + TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", + DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), + mFormat.Format.nSamplesPerSec, mOrigUpdateSize, mDevice->Frequency, + mDevice->UpdateSize); + } + + return true; +} + +bool WasapiPlayback::reset() +{ + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; + if(FAILED(hr)) + throw al::backend_exception{al::backend_error::DeviceError, "0x%08lx", hr}; + return true; +} + +HRESULT WasapiPlayback::resetProxy() +{ + if(GetConfigValueBool(mDevice->DeviceName, "wasapi", "spatial-api", false)) + { + if(initSpatial()) + return S_OK; + } + + mDevice->Flags.reset(Virtualization); + + auto &audio = mAudio.emplace(); + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(audio.mClient))}; + if(FAILED(hr)) + { + ERR("Failed to reactivate audio client: 0x%08lx\n", hr); + return hr; + } + + WAVEFORMATEX *wfx; + hr = audio.mClient->GetMixFormat(&wfx); + if(FAILED(hr)) + { + ERR("Failed to get mix format: 0x%08lx\n", hr); + return hr; + } + TraceFormat("Device mix format", wfx); + + WAVEFORMATEXTENSIBLE OutputType; + if(!MakeExtensible(&OutputType, wfx)) + { + CoTaskMemFree(wfx); + return E_FAIL; + } + CoTaskMemFree(wfx); + wfx = nullptr; + + const ReferenceTime per_time{ReferenceTime{seconds{mDevice->UpdateSize}} / mDevice->Frequency}; + const ReferenceTime buf_time{ReferenceTime{seconds{mDevice->BufferSize}} / mDevice->Frequency}; + + prepareFormat(OutputType); TraceFormat("Requesting playback format", &OutputType.Format); - hr = mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); + hr = audio.mClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, &OutputType.Format, &wfx); if(FAILED(hr)) { WARN("Failed to check format support: 0x%08lx\n", hr); - hr = mClient->GetMixFormat(&wfx); + hr = audio.mClient->GetMixFormat(&wfx); } if(FAILED(hr)) { @@ -1055,116 +1958,19 @@ HRESULT WasapiPlayback::resetProxy() CoTaskMemFree(wfx); wfx = nullptr; - if(!GetConfigValueBool(mDevice->DeviceName.c_str(), "wasapi", "allow-resampler", true)) - mDevice->Frequency = OutputType.Format.nSamplesPerSec; - else - mDevice->Frequency = minu(mDevice->Frequency, OutputType.Format.nSamplesPerSec); - - const uint32_t chancount{OutputType.Format.nChannels}; - const DWORD chanmask{OutputType.dwChannelMask}; - /* Don't update the channel format if the requested format fits what's - * supported. - */ - bool chansok{false}; - if(mDevice->Flags.test(ChannelsRequest)) - { - /* When requesting a channel configuration, make sure it fits the - * mask's lsb (to ensure no gaps in the output channels). If - * there's no mask, assume the request fits with enough channels. - */ - switch(mDevice->FmtChans) - { - case DevFmtMono: - chansok = (chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)); - break; - case DevFmtStereo: - chansok = (chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)); - break; - case DevFmtQuad: - chansok = (chancount >= 4 && ((chanmask&QuadMask) == QUAD || !chanmask)); - break; - case DevFmtX51: - chansok = (chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 - || (chanmask&X51RearMask) == X5DOT1REAR || !chanmask)); - break; - case DevFmtX61: - chansok = (chancount >= 7 && ((chanmask&X61Mask) == X6DOT1 || !chanmask)); - break; - case DevFmtX71: - case DevFmtX3D71: - chansok = (chancount >= 8 && ((chanmask&X71Mask) == X7DOT1 || !chanmask)); - break; - case DevFmtX714: - chansok = (chancount >= 12 && ((chanmask&X714Mask) == X7DOT1DOT4 || !chanmask)); - case DevFmtAmbi3D: - break; - } - } - if(!chansok) - { - if(chancount >= 12 && (chanmask&X714Mask) == X7DOT1DOT4) - mDevice->FmtChans = DevFmtX714; - else if(chancount >= 8 && (chanmask&X71Mask) == X7DOT1) - mDevice->FmtChans = DevFmtX71; - else if(chancount >= 7 && (chanmask&X61Mask) == X6DOT1) - mDevice->FmtChans = DevFmtX61; - else if(chancount >= 6 && ((chanmask&X51Mask) == X5DOT1 - || (chanmask&X51RearMask) == X5DOT1REAR)) - mDevice->FmtChans = DevFmtX51; - else if(chancount >= 4 && (chanmask&QuadMask) == QUAD) - mDevice->FmtChans = DevFmtQuad; - else if(chancount >= 2 && ((chanmask&StereoMask) == STEREO || !chanmask)) - mDevice->FmtChans = DevFmtStereo; - else if(chancount >= 1 && ((chanmask&MonoMask) == MONO || !chanmask)) - mDevice->FmtChans = DevFmtMono; - else - { - ERR("Unhandled extensible channels: %d -- 0x%08lx\n", OutputType.Format.nChannels, - OutputType.dwChannelMask); - mDevice->FmtChans = DevFmtStereo; - OutputType.Format.nChannels = 2; - OutputType.dwChannelMask = STEREO; - } - } - - if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) - { - if(OutputType.Format.wBitsPerSample == 8) - mDevice->FmtType = DevFmtUByte; - else if(OutputType.Format.wBitsPerSample == 16) - mDevice->FmtType = DevFmtShort; - else if(OutputType.Format.wBitsPerSample == 32) - mDevice->FmtType = DevFmtInt; - else - { - mDevice->FmtType = DevFmtShort; - OutputType.Format.wBitsPerSample = 16; - } - } - else if(IsEqualGUID(OutputType.SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - mDevice->FmtType = DevFmtFloat; - OutputType.Format.wBitsPerSample = 32; - } - else - { - ERR("Unhandled format sub-type: %s\n", GuidPrinter{OutputType.SubFormat}.c_str()); - mDevice->FmtType = DevFmtShort; - if(OutputType.Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE) - OutputType.Format.wFormatTag = WAVE_FORMAT_PCM; - OutputType.Format.wBitsPerSample = 16; - OutputType.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; - } - OutputType.Samples.wValidBitsPerSample = OutputType.Format.wBitsPerSample; + finalizeFormat(OutputType); } mFormat = OutputType; - const EndpointFormFactor formfactor{get_device_formfactor(mMMDev.get())}; +#if !defined(ALSOFT_UWP) + const EndpointFormFactor formfactor{GetDeviceFormfactor(mMMDev.get())}; mDevice->Flags.set(DirectEar, (formfactor == Headphones || formfactor == Headset)); - +#else + mDevice->Flags.set(DirectEar, false); +#endif setDefaultWFXChannelOrder(); - hr = mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + hr = audio.mClient->Initialize(AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buf_time.count(), 0, &OutputType.Format, nullptr); if(FAILED(hr)) { @@ -1174,37 +1980,46 @@ HRESULT WasapiPlayback::resetProxy() UINT32 buffer_len{}; ReferenceTime min_per{}; - hr = mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); + hr = audio.mClient->GetDevicePeriod(&reinterpret_cast(min_per), nullptr); if(SUCCEEDED(hr)) - hr = mClient->GetBufferSize(&buffer_len); + hr = audio.mClient->GetBufferSize(&buffer_len); if(FAILED(hr)) { ERR("Failed to get audio buffer info: 0x%08lx\n", hr); return hr; } + hr = audio.mClient->SetEventHandle(mNotifyEvent); + if(FAILED(hr)) + { + ERR("Failed to set event handle: 0x%08lx\n", hr); + return hr; + } + /* Find the nearest multiple of the period size to the update size */ if(min_per < per_time) - min_per *= maxi64((per_time + min_per/2) / min_per, 1); + min_per *= std::max((per_time + min_per/2) / min_per, 1_i64); mOrigBufferSize = buffer_len; - mOrigUpdateSize = minu(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), buffer_len/2); + mOrigUpdateSize = std::min(RefTime2Samples(min_per, mFormat.Format.nSamplesPerSec), + buffer_len/2u); mDevice->BufferSize = static_cast(uint64_t{buffer_len} * mDevice->Frequency / mFormat.Format.nSamplesPerSec); - mDevice->UpdateSize = minu(RefTime2Samples(min_per, mDevice->Frequency), - mDevice->BufferSize/2); + mDevice->UpdateSize = std::min(RefTime2Samples(min_per, mDevice->Frequency), + mDevice->BufferSize/2u); mResampler = nullptr; - mResampleBuffer = nullptr; + mResampleBuffer.clear(); + mResampleBuffer.shrink_to_fit(); mBufferFilled = 0; if(mDevice->Frequency != mFormat.Format.nSamplesPerSec) { mResampler = SampleConverter::Create(mDevice->FmtType, mDevice->FmtType, mFormat.Format.nChannels, mDevice->Frequency, mFormat.Format.nSamplesPerSec, Resampler::FastBSinc24); - mResampleBuffer = std::make_unique(size_t{mDevice->UpdateSize} * - mFormat.Format.nChannels * mFormat.Format.wBitsPerSample / 8); + mResampleBuffer.resize(size_t{mDevice->UpdateSize} * mFormat.Format.nChannels * + mFormat.Format.wBitsPerSample / 8); TRACE("Created converter for %s/%s format, dst: %luhz (%u), src: %uhz (%u)\n", DevFmtChannelsString(mDevice->FmtChans), DevFmtTypeString(mDevice->FmtType), @@ -1212,13 +2027,6 @@ HRESULT WasapiPlayback::resetProxy() mDevice->UpdateSize); } - hr = mClient->SetEventHandle(mNotifyEvent); - if(FAILED(hr)) - { - ERR("Failed to set event handle: 0x%08lx\n", hr); - return hr; - } - return hr; } @@ -1235,33 +2043,61 @@ HRESULT WasapiPlayback::startProxy() { ResetEvent(mNotifyEvent); - HRESULT hr{mClient->Start()}; - if(FAILED(hr)) + auto mstate_fallback = [](std::monostate) -> HRESULT + { return E_FAIL; }; + auto start_plain = [&](PlainDevice &audio) -> HRESULT { - ERR("Failed to start audio client: 0x%08lx\n", hr); - return hr; - } + HRESULT hr{audio.mClient->Start()}; + if(FAILED(hr)) + { + ERR("Failed to start audio client: 0x%08lx\n", hr); + return hr; + } - void *ptr; - hr = mClient->GetService(IID_IAudioRenderClient, &ptr); - if(SUCCEEDED(hr)) + hr = audio.mClient->GetService(__uuidof(IAudioRenderClient), al::out_ptr(audio.mRender)); + if(SUCCEEDED(hr)) + { + try { + mKillNow.store(false, std::memory_order_release); + mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; + } + catch(...) { + audio.mRender = nullptr; + ERR("Failed to start thread\n"); + hr = E_FAIL; + } + } + if(FAILED(hr)) + audio.mClient->Stop(); + return hr; + }; + auto start_spatial = [&](SpatialDevice &audio) -> HRESULT { - mRender = ComPtr{static_cast(ptr)}; + HRESULT hr{audio.mRender->Start()}; + if(FAILED(hr)) + { + ERR("Failed to start spatial audio stream: 0x%08lx\n", hr); + return hr; + } + try { mKillNow.store(false, std::memory_order_release); - mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerProc), this}; + mThread = std::thread{std::mem_fn(&WasapiPlayback::mixerSpatialProc), this}; } catch(...) { - mRender = nullptr; ERR("Failed to start thread\n"); hr = E_FAIL; } - } - if(FAILED(hr)) - mClient->Stop(); + if(FAILED(hr)) + { + audio.mRender->Stop(); + audio.mRender->Reset(); + } + return hr; + }; - return hr; + return std::visit(overloaded{mstate_fallback, start_plain, start_spatial}, mAudio); } @@ -1270,23 +2106,33 @@ void WasapiPlayback::stop() void WasapiPlayback::stopProxy() { - if(!mRender || !mThread.joinable()) + if(!mThread.joinable()) return; mKillNow.store(true, std::memory_order_release); mThread.join(); - mRender = nullptr; - mClient->Stop(); + auto mstate_fallback = [](std::monostate) -> void + { }; + auto stop_plain = [](PlainDevice &audio) -> void + { + audio.mRender = nullptr; + audio.mClient->Stop(); + }; + auto stop_spatial = [](SpatialDevice &audio) -> void + { + audio.mRender->Stop(); + audio.mRender->Reset(); + }; + std::visit(overloaded{mstate_fallback, stop_plain, stop_spatial}, mAudio); } ClockLatency WasapiPlayback::getClockLatency() { - ClockLatency ret; - - std::lock_guard _{mMutex}; - ret.ClockTime = GetDeviceClockTime(mDevice); + std::lock_guard dlock{mMutex}; + ClockLatency ret{}; + ret.ClockTime = mDevice->getClockTime(); ret.Latency = seconds{mPadding.load(std::memory_order_relaxed)}; ret.Latency /= mFormat.Format.nSamplesPerSec; if(mResampler) @@ -1306,8 +2152,8 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { int recordProc(); - void open(const char *name) override; - HRESULT openProxy(const char *name) override; + void open(std::string_view name) override; + HRESULT openProxy(std::string_view name) override; void closeProxy() override; HRESULT resetProxy() override; @@ -1316,11 +2162,11 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { void stop() override; void stopProxy() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; HRESULT mOpenStatus{E_FAIL}; - ComPtr mMMDev{nullptr}; + DeviceHandle mMMDev{nullptr}; ComPtr mClient{nullptr}; ComPtr mCapture{nullptr}; HANDLE mNotifyEvent{nullptr}; @@ -1331,17 +2177,12 @@ struct WasapiCapture final : public BackendBase, WasapiProxy { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WasapiCapture) }; WasapiCapture::~WasapiCapture() { if(SUCCEEDED(mOpenStatus)) - { pushMessage(MsgType::CloseDevice).wait(); - DeinitThread(); - } mOpenStatus = E_FAIL; if(mNotifyEvent != nullptr) @@ -1352,21 +2193,21 @@ WasapiCapture::~WasapiCapture() FORCE_ALIGN int WasapiCapture::recordProc() { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) + ComWrapper com{COINIT_MULTITHREADED}; + if(!com) { - ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", hr); - mDevice->handleDisconnect("COM init failed: 0x%08lx", hr); + ERR("CoInitializeEx(nullptr, COINIT_MULTITHREADED) failed: 0x%08lx\n", com.status()); + mDevice->handleDisconnect("COM init failed: 0x%08lx", com.status()); return 1; } - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); - al::vector samples; + std::vector samples; while(!mKillNow.load(std::memory_order_relaxed)) { UINT32 avail; - hr = mCapture->GetNextPacketSize(&avail); + HRESULT hr{mCapture->GetNextPacketSize(&avail)}; if(FAILED(hr)) ERR("Failed to get next packet size: 0x%08lx\n", hr); else if(avail > 0) @@ -1382,7 +2223,7 @@ FORCE_ALIGN int WasapiCapture::recordProc() { if(mChannelConv.is_active()) { - samples.resize(numsamples*2); + samples.resize(numsamples*2_uz); mChannelConv.convert(rdata, samples.data(), numsamples); rdata = reinterpret_cast(samples.data()); } @@ -1392,11 +2233,12 @@ FORCE_ALIGN int WasapiCapture::recordProc() size_t dstframes; if(mSampleConv) { + static constexpr auto lenlimit = size_t{std::numeric_limits::max()}; const void *srcdata{rdata}; uint srcframes{numsamples}; dstframes = mSampleConv->convert(&srcdata, &srcframes, data.first.buf, - static_cast(minz(data.first.len, INT_MAX))); + static_cast(std::min(data.first.len, lenlimit))); if(srcframes > 0 && dstframes == data.first.len && data.second.len > 0) { /* If some source samples remain, all of the first dest @@ -1404,18 +2246,22 @@ FORCE_ALIGN int WasapiCapture::recordProc() * dest block, do another run for the second block. */ dstframes += mSampleConv->convert(&srcdata, &srcframes, data.second.buf, - static_cast(minz(data.second.len, INT_MAX))); + static_cast(std::min(data.second.len, lenlimit))); } } else { const uint framesize{mDevice->frameSizeFromFmt()}; - size_t len1{minz(data.first.len, numsamples)}; - size_t len2{minz(data.second.len, numsamples-len1)}; + auto dst = al::span{rdata, size_t{numsamples}*framesize}; + size_t len1{std::min(data.first.len, size_t{numsamples})}; + size_t len2{std::min(data.second.len, numsamples-len1)}; - memcpy(data.first.buf, rdata, len1*framesize); + memcpy(data.first.buf, dst.data(), len1*framesize); if(len2 > 0) - memcpy(data.second.buf, rdata+len1*framesize, len2*framesize); + { + dst = dst.subspan(len1*framesize); + memcpy(data.second.buf, dst.data(), len2*framesize); + } dstframes = len1 + len2; } @@ -1437,12 +2283,11 @@ FORCE_ALIGN int WasapiCapture::recordProc() ERR("WaitForSingleObjectEx error: 0x%lx\n", res); } - CoUninitialize(); return 0; } -void WasapiCapture::open(const char *name) +void WasapiCapture::open(std::string_view name) { if(SUCCEEDED(mOpenStatus)) throw al::backend_exception{al::backend_error::DeviceError, @@ -1456,34 +2301,15 @@ void WasapiCapture::open(const char *name) "Failed to create notify events"}; } - HRESULT hr{InitThread()}; - if(FAILED(hr)) - { - throw al::backend_exception{al::backend_error::DeviceError, - "Failed to init COM thread: 0x%08lx", hr}; - } - - if(name) - { - if(CaptureDevices.empty()) - pushMessage(MsgType::EnumerateCapture); - if(std::strncmp(name, DevNameHead, DevNameHeadLen) == 0) - { - name += DevNameHeadLen; - if(*name == '\0') - name = nullptr; - } - } + if(const auto prefix = GetDevicePrefix(); al::starts_with(name, prefix)) + name = name.substr(prefix.size()); mOpenStatus = pushMessage(MsgType::OpenDevice, name).get(); if(FAILED(mOpenStatus)) - { - DeinitThread(); throw al::backend_exception{al::backend_error::DeviceError, "Device init failed: 0x%08lx", mOpenStatus}; - } - hr = pushMessage(MsgType::ResetDevice).get(); + HRESULT hr{pushMessage(MsgType::ResetDevice).get()}; if(FAILED(hr)) { if(hr == E_OUTOFMEMORY) @@ -1492,52 +2318,46 @@ void WasapiCapture::open(const char *name) } } -HRESULT WasapiCapture::openProxy(const char *name) +HRESULT WasapiCapture::openProxy(std::string_view name) { - const wchar_t *devid{nullptr}; - if(name) + std::string devname; + std::wstring devid; + if(!name.empty()) { - auto iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + auto devlock = DeviceListLock{gDeviceList}; + auto devlist = al::span{devlock.getCaptureList()}; + auto iter = std::find_if(devlist.cbegin(), devlist.cend(), [name](const DevMap &entry) -> bool { return entry.name == name || entry.endpoint_guid == name; }); - if(iter == CaptureDevices.cend()) + if(iter == devlist.cend()) { const std::wstring wname{utf8_to_wstr(name)}; - iter = std::find_if(CaptureDevices.cbegin(), CaptureDevices.cend(), + iter = std::find_if(devlist.cbegin(), devlist.cend(), [&wname](const DevMap &entry) -> bool { return entry.devid == wname; }); } - if(iter == CaptureDevices.cend()) + if(iter == devlist.cend()) { - WARN("Failed to find device name matching \"%s\"\n", name); + WARN("Failed to find device name matching \"%.*s\"\n", al::sizei(name), name.data()); return E_FAIL; } - name = iter->name.c_str(); - devid = iter->devid.c_str(); + devname = iter->name; + devid = iter->devid; } - void *ptr; - HRESULT hr{CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr)}; - if(SUCCEEDED(hr)) - { - ComPtr enumerator{static_cast(ptr)}; - if(!devid) - hr = enumerator->GetDefaultAudioEndpoint(eCapture, eMultimedia, mMMDev.getPtr()); - else - hr = enumerator->GetDevice(devid, mMMDev.getPtr()); - } + HRESULT hr{sDeviceHelper->openDevice(devid, eCapture, mMMDev)}; if(FAILED(hr)) { - WARN("Failed to open device \"%s\"\n", name?name:"(default)"); + WARN("Failed to open device \"%s\"\n", devname.empty() ? "(default)" : devname.c_str()); return hr; } - mClient = nullptr; - if(name) mDevice->DeviceName = std::string{DevNameHead} + name; - else mDevice->DeviceName = DevNameHead + get_device_name_and_guid(mMMDev.get()).first; + if(!devname.empty()) + mDevice->DeviceName = std::string{GetDevicePrefix()}+std::move(devname); + else + mDevice->DeviceName = std::string{GetDevicePrefix()}+GetDeviceNameAndGuid(mMMDev).first; - return hr; + return S_OK; } void WasapiCapture::closeProxy() @@ -1550,14 +2370,13 @@ HRESULT WasapiCapture::resetProxy() { mClient = nullptr; - void *ptr; - HRESULT hr{mMMDev->Activate(IID_IAudioClient, CLSCTX_INPROC_SERVER, nullptr, &ptr)}; + HRESULT hr{sDeviceHelper->activateAudioClient(mMMDev, __uuidof(IAudioClient), + al::out_ptr(mClient))}; if(FAILED(hr)) { ERR("Failed to reactivate audio client: 0x%08lx\n", hr); return hr; } - mClient = ComPtr{static_cast(ptr)}; WAVEFORMATEX *wfx; hr = mClient->GetMixFormat(&wfx); @@ -1617,6 +2436,7 @@ HRESULT WasapiCapture::resetProxy() InputType.dwChannelMask = X7DOT1DOT4; break; + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: return E_FAIL; @@ -1644,6 +2464,7 @@ HRESULT WasapiCapture::resetProxy() InputType.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break; } + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) */ InputType.Samples.wValidBitsPerSample = InputType.Format.wBitsPerSample; InputType.Format.nSamplesPerSec = mDevice->Frequency; @@ -1706,6 +2527,8 @@ HRESULT WasapiCapture::resetProxy() return (chancount == 8 && (chanmask == 0 || (chanmask&X71Mask) == X7DOT1)); case DevFmtX714: return (chancount == 12 && (chanmask == 0 || (chanmask&X714Mask) == X7DOT1DOT4)); + case DevFmtX7144: + return (chancount == 16 && chanmask == 0); case DevFmtAmbi3D: return (chanmask == 0 && chancount == device->channelsFromFmt()); } @@ -1849,11 +2672,9 @@ HRESULT WasapiCapture::startProxy() return hr; } - void *ptr; - hr = mClient->GetService(IID_IAudioCaptureClient, &ptr); + hr = mClient->GetService(__uuidof(IAudioCaptureClient), al::out_ptr(mCapture)); if(SUCCEEDED(hr)) { - mCapture = ComPtr{static_cast(ptr)}; try { mKillNow.store(false, std::memory_order_release); mThread = std::thread{std::mem_fn(&WasapiCapture::recordProc), this}; @@ -1892,8 +2713,8 @@ void WasapiCapture::stopProxy() } -void WasapiCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void WasapiCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint WasapiCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -1904,34 +2725,13 @@ uint WasapiCapture::availableSamples() bool WasapiBackendFactory::init() { static HRESULT InitResult{E_FAIL}; - if(FAILED(InitResult)) try { - auto res = std::async(std::launch::async, []() -> HRESULT - { - HRESULT hr{CoInitializeEx(nullptr, COINIT_MULTITHREADED)}; - if(FAILED(hr)) - { - WARN("Failed to initialize COM: 0x%08lx\n", hr); - return hr; - } + std::promise promise; + auto future = promise.get_future(); - void *ptr{}; - hr = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_INPROC_SERVER, - IID_IMMDeviceEnumerator, &ptr); - if(FAILED(hr)) - { - WARN("Failed to create IMMDeviceEnumerator instance: 0x%08lx\n", hr); - CoUninitialize(); - return hr; - } - static_cast(ptr)->Release(); - CoUninitialize(); - - return S_OK; - }); - - InitResult = res.get(); + std::thread{&WasapiProxy::messageHandler, &promise}.detach(); + InitResult = future.get(); } catch(...) { } @@ -1942,36 +2742,42 @@ bool WasapiBackendFactory::init() bool WasapiBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string WasapiBackendFactory::probe(BackendType type) +auto WasapiBackendFactory::enumerate(BackendType type) -> std::vector { - struct ProxyControl { - HRESULT mResult{}; - ProxyControl() { mResult = WasapiProxy::InitThread(); } - ~ProxyControl() { if(SUCCEEDED(mResult)) WasapiProxy::DeinitThread(); } - }; - ProxyControl proxy; - - std::string outnames; - if(FAILED(proxy.mResult)) - return outnames; + std::vector outnames; + auto devlock = DeviceListLock{gDeviceList}; switch(type) { case BackendType::Playback: - WasapiProxy::pushMessageStatic(MsgType::EnumeratePlayback).wait(); - for(const DevMap &entry : PlaybackDevices) { - /* +1 to also append the null char (to ensure a null-separated list - * and double-null terminated list). - */ - outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + auto defaultId = devlock.getPlaybackDefaultId(); + for(const DevMap &entry : devlock.getPlaybackList()) + { + if(entry.devid != defaultId) + { + outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name); + continue; + } + /* Default device goes first. */ + outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name); + } } break; case BackendType::Capture: - WasapiProxy::pushMessageStatic(MsgType::EnumerateCapture).wait(); - for(const DevMap &entry : CaptureDevices) - outnames.append(DevNameHead).append(entry.name.c_str(), entry.name.length()+1); + { + auto defaultId = devlock.getCaptureDefaultId(); + for(const DevMap &entry : devlock.getCaptureList()) + { + if(entry.devid != defaultId) + { + outnames.emplace_back(std::string{GetDevicePrefix()}+entry.name); + continue; + } + outnames.emplace(outnames.cbegin(), std::string{GetDevicePrefix()}+entry.name); + } + } break; } @@ -1992,3 +2798,22 @@ BackendFactory &WasapiBackendFactory::getFactory() static WasapiBackendFactory factory{}; return factory; } + +alc::EventSupport WasapiBackendFactory::queryEventSupport(alc::EventType eventType, BackendType) +{ + switch(eventType) + { + case alc::EventType::DefaultDeviceChanged: + return alc::EventSupport::FullSupport; + + case alc::EventType::DeviceAdded: + case alc::EventType::DeviceRemoved: +#if !defined(ALSOFT_UWP) + return alc::EventSupport::FullSupport; +#endif + + case alc::EventType::Count: + break; + } + return alc::EventSupport::NoSupport; +} diff --git a/Engine/lib/openal-soft/alc/backends/wasapi.h b/Engine/lib/openal-soft/alc/backends/wasapi.h index bb2671ee8..f3cb85413 100644 --- a/Engine/lib/openal-soft/alc/backends/wasapi.h +++ b/Engine/lib/openal-soft/alc/backends/wasapi.h @@ -5,15 +5,17 @@ struct WasapiBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto queryEventSupport(alc::EventType eventType, BackendType type) -> alc::EventSupport final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - static BackendFactory &getFactory(); + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; + + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WASAPI_H */ diff --git a/Engine/lib/openal-soft/alc/backends/wave.cpp b/Engine/lib/openal-soft/alc/backends/wave.cpp index 1b40640c7..5e61ab52b 100644 --- a/Engine/lib/openal-soft/alc/backends/wave.cpp +++ b/Engine/lib/openal-soft/alc/backends/wave.cpp @@ -31,24 +31,26 @@ #include #include #include +#include #include +#include #include "albit.h" -#include "albyte.h" #include "alc/alconfig.h" #include "almalloc.h" #include "alnumeric.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "opthelpers.h" #include "strutils.h" -#include "threads.h" -#include "vector.h" namespace { +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; @@ -56,38 +58,43 @@ using std::chrono::nanoseconds; using ubyte = unsigned char; using ushort = unsigned short; -constexpr char waveDevice[] = "Wave File Writer"; +struct FileDeleter { + void operator()(gsl::owner f) { fclose(f); } +}; +using FilePtr = std::unique_ptr; -constexpr ubyte SUBTYPE_PCM[]{ +[[nodiscard]] constexpr auto GetDeviceName() noexcept { return "Wave File Writer"sv; } + +constexpr std::array SUBTYPE_PCM{{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 -}; -constexpr ubyte SUBTYPE_FLOAT[]{ +}}; +constexpr std::array SUBTYPE_FLOAT{{ 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 -}; +}}; -constexpr ubyte SUBTYPE_BFORMAT_PCM[]{ +constexpr std::array SUBTYPE_BFORMAT_PCM{{ 0x01, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 -}; +}}; -constexpr ubyte SUBTYPE_BFORMAT_FLOAT[]{ +constexpr std::array SUBTYPE_BFORMAT_FLOAT{{ 0x03, 0x00, 0x00, 0x00, 0x21, 0x07, 0xd3, 0x11, 0x86, 0x44, 0xc8, 0xc1, 0xca, 0x00, 0x00, 0x00 -}; +}}; void fwrite16le(ushort val, FILE *f) { - ubyte data[2]{ static_cast(val&0xff), static_cast((val>>8)&0xff) }; - fwrite(data, 1, 2, f); + std::array data{static_cast(val&0xff), static_cast((val>>8)&0xff)}; + fwrite(data.data(), 1, data.size(), f); } void fwrite32le(uint val, FILE *f) { - ubyte data[4]{ static_cast(val&0xff), static_cast((val>>8)&0xff), - static_cast((val>>16)&0xff), static_cast((val>>24)&0xff) }; - fwrite(data, 1, 4, f); + std::array data{static_cast(val&0xff), static_cast((val>>8)&0xff), + static_cast((val>>16)&0xff), static_cast((val>>24)&0xff)}; + fwrite(data.data(), 1, data.size(), f); } @@ -97,34 +104,27 @@ struct WaveBackend final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; - FILE *mFile{nullptr}; + FilePtr mFile{nullptr}; long mDataStart{-1}; - al::vector mBuffer; + std::vector mBuffer; std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WaveBackend) }; -WaveBackend::~WaveBackend() -{ - if(mFile) - fclose(mFile); - mFile = nullptr; -} +WaveBackend::~WaveBackend() = default; int WaveBackend::mixerProc() { const milliseconds restTime{mDevice->UpdateSize*1000/mDevice->Frequency / 2}; - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); const size_t frameStep{mDevice->channelsFromFmt()}; const size_t frameSize{mDevice->frameSizeFromFmt()}; @@ -155,13 +155,13 @@ int WaveBackend::mixerProc() if(bytesize == 2) { - const size_t len{mBuffer.size() & ~size_t{1}}; + const size_t len{mBuffer.size() & ~1_uz}; for(size_t i{0};i < len;i+=2) std::swap(mBuffer[i], mBuffer[i+1]); } else if(bytesize == 4) { - const size_t len{mBuffer.size() & ~size_t{3}}; + const size_t len{mBuffer.size() & ~3_uz}; for(size_t i{0};i < len;i+=4) { std::swap(mBuffer[i ], mBuffer[i+3]); @@ -170,8 +170,8 @@ int WaveBackend::mixerProc() } } - const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile)}; - if(fs < mDevice->UpdateSize || ferror(mFile)) + const size_t fs{fwrite(mBuffer.data(), frameSize, mDevice->UpdateSize, mFile.get())}; + if(fs < mDevice->UpdateSize || ferror(mFile.get())) { ERR("Error writing to file\n"); mDevice->handleDisconnect("Failed to write playback samples"); @@ -195,32 +195,32 @@ int WaveBackend::mixerProc() return 0; } -void WaveBackend::open(const char *name) +void WaveBackend::open(std::string_view name) { - auto fname = ConfigValueStr(nullptr, "wave", "file"); + auto fname = ConfigValueStr({}, "wave", "file"); if(!fname) throw al::backend_exception{al::backend_error::NoDevice, "No wave output filename"}; - if(!name) - name = waveDevice; - else if(strcmp(name, waveDevice) != 0) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + if(name.empty()) + name = GetDeviceName(); + else if(name != GetDeviceName()) + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; /* There's only one "device", so if it's already open, we're done. */ if(mFile) return; #ifdef _WIN32 { - std::wstring wname{utf8_to_wstr(fname->c_str())}; - mFile = _wfopen(wname.c_str(), L"wb"); + std::wstring wname{utf8_to_wstr(fname.value())}; + mFile = FilePtr{_wfopen(wname.c_str(), L"wb")}; } #else - mFile = fopen(fname->c_str(), "wb"); + mFile = FilePtr{fopen(fname->c_str(), "wb")}; #endif if(!mFile) throw al::backend_exception{al::backend_error::DeviceError, "Could not open file '%s': %s", - fname->c_str(), strerror(errno)}; + fname->c_str(), std::generic_category().message(errno).c_str()}; mDevice->DeviceName = name; } @@ -229,12 +229,11 @@ bool WaveBackend::reset() { uint channels{0}, bytes{0}, chanmask{0}; bool isbformat{false}; - size_t val; - fseek(mFile, 0, SEEK_SET); - clearerr(mFile); + fseek(mFile.get(), 0, SEEK_SET); + clearerr(mFile.get()); - if(GetConfigValueBool(nullptr, "wave", "bformat", false)) + if(GetConfigValueBool({}, "wave", "bformat", false)) { mDevice->FmtChans = DevFmtAmbi3D; mDevice->mAmbiOrder = 1; @@ -265,6 +264,9 @@ bool WaveBackend::reset() case DevFmtX51: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x200 | 0x400; break; case DevFmtX61: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x100 | 0x200 | 0x400; break; case DevFmtX71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; + case DevFmtX7144: + mDevice->FmtChans = DevFmtX714; + [[fallthrough]]; case DevFmtX714: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400 | 0x1000 | 0x4000 | 0x8000 | 0x20000; @@ -273,7 +275,7 @@ bool WaveBackend::reset() case DevFmtX3D71: chanmask = 0x01 | 0x02 | 0x04 | 0x08 | 0x010 | 0x020 | 0x200 | 0x400; break; case DevFmtAmbi3D: /* .amb output requires FuMa */ - mDevice->mAmbiOrder = minu(mDevice->mAmbiOrder, 3); + mDevice->mAmbiOrder = std::min(mDevice->mAmbiOrder, 3u); mDevice->mAmbiLayout = DevAmbiLayout::FuMa; mDevice->mAmbiScale = DevAmbiScaling::FuMa; isbformat = true; @@ -283,49 +285,48 @@ bool WaveBackend::reset() bytes = mDevice->bytesFromFmt(); channels = mDevice->channelsFromFmt(); - rewind(mFile); + rewind(mFile.get()); - fputs("RIFF", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'RIFF' header len; filled in at close + fputs("RIFF", mFile.get()); + fwrite32le(0xFFFFFFFF, mFile.get()); // 'RIFF' header len; filled in at close - fputs("WAVE", mFile); + fputs("WAVE", mFile.get()); - fputs("fmt ", mFile); - fwrite32le(40, mFile); // 'fmt ' header len; 40 bytes for EXTENSIBLE + fputs("fmt ", mFile.get()); + fwrite32le(40, mFile.get()); // 'fmt ' header len; 40 bytes for EXTENSIBLE // 16-bit val, format type id (extensible: 0xFFFE) - fwrite16le(0xFFFE, mFile); + fwrite16le(0xFFFE, mFile.get()); // 16-bit val, channel count - fwrite16le(static_cast(channels), mFile); + fwrite16le(static_cast(channels), mFile.get()); // 32-bit val, frequency - fwrite32le(mDevice->Frequency, mFile); + fwrite32le(mDevice->Frequency, mFile.get()); // 32-bit val, bytes per second - fwrite32le(mDevice->Frequency * channels * bytes, mFile); + fwrite32le(mDevice->Frequency * channels * bytes, mFile.get()); // 16-bit val, frame size - fwrite16le(static_cast(channels * bytes), mFile); + fwrite16le(static_cast(channels * bytes), mFile.get()); // 16-bit val, bits per sample - fwrite16le(static_cast(bytes * 8), mFile); + fwrite16le(static_cast(bytes * 8), mFile.get()); // 16-bit val, extra byte count - fwrite16le(22, mFile); + fwrite16le(22, mFile.get()); // 16-bit val, valid bits per sample - fwrite16le(static_cast(bytes * 8), mFile); + fwrite16le(static_cast(bytes * 8), mFile.get()); // 32-bit val, channel mask - fwrite32le(chanmask, mFile); + fwrite32le(chanmask, mFile.get()); // 16 byte GUID, sub-type format - val = fwrite((mDevice->FmtType == DevFmtFloat) ? - (isbformat ? SUBTYPE_BFORMAT_FLOAT : SUBTYPE_FLOAT) : - (isbformat ? SUBTYPE_BFORMAT_PCM : SUBTYPE_PCM), 1, 16, mFile); - (void)val; + std::ignore = fwrite((mDevice->FmtType == DevFmtFloat) ? + (isbformat ? SUBTYPE_BFORMAT_FLOAT.data() : SUBTYPE_FLOAT.data()) : + (isbformat ? SUBTYPE_BFORMAT_PCM.data() : SUBTYPE_PCM.data()), 1, 16, mFile.get()); - fputs("data", mFile); - fwrite32le(0xFFFFFFFF, mFile); // 'data' header len; filled in at close + fputs("data", mFile.get()); + fwrite32le(0xFFFFFFFF, mFile.get()); // 'data' header len; filled in at close - if(ferror(mFile)) + if(ferror(mFile.get())) { - ERR("Error writing header: %s\n", strerror(errno)); + ERR("Error writing header: %s\n", std::generic_category().message(errno).c_str()); return false; } - mDataStart = ftell(mFile); + mDataStart = ftell(mFile.get()); setDefaultWFXChannelOrder(); @@ -337,7 +338,7 @@ bool WaveBackend::reset() void WaveBackend::start() { - if(mDataStart > 0 && fseek(mFile, 0, SEEK_END) != 0) + if(mDataStart > 0 && fseek(mFile.get(), 0, SEEK_END) != 0) WARN("Failed to seek on output file\n"); try { mKillNow.store(false, std::memory_order_release); @@ -357,14 +358,14 @@ void WaveBackend::stop() if(mDataStart > 0) { - long size{ftell(mFile)}; + long size{ftell(mFile.get())}; if(size > 0) { long dataLen{size - mDataStart}; - if(fseek(mFile, 4, SEEK_SET) == 0) - fwrite32le(static_cast(size-8), mFile); // 'WAVE' header len - if(fseek(mFile, mDataStart-4, SEEK_SET) == 0) - fwrite32le(static_cast(dataLen), mFile); // 'data' header len + if(fseek(mFile.get(), 4, SEEK_SET) == 0) + fwrite32le(static_cast(size-8), mFile.get()); // 'WAVE' header len + if(fseek(mFile.get(), mDataStart-4, SEEK_SET) == 0) + fwrite32le(static_cast(dataLen), mFile.get()); // 'data' header len } } } @@ -378,19 +379,16 @@ bool WaveBackendFactory::init() bool WaveBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback; } -std::string WaveBackendFactory::probe(BackendType type) +auto WaveBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; switch(type) { case BackendType::Playback: - /* Includes null char. */ - outnames.append(waveDevice, sizeof(waveDevice)); - break; + return std::vector{std::string{GetDeviceName()}}; case BackendType::Capture: break; } - return outnames; + return {}; } BackendPtr WaveBackendFactory::createBackend(DeviceBase *device, BackendType type) diff --git a/Engine/lib/openal-soft/alc/backends/wave.h b/Engine/lib/openal-soft/alc/backends/wave.h index e768d3361..85f4c76ff 100644 --- a/Engine/lib/openal-soft/alc/backends/wave.h +++ b/Engine/lib/openal-soft/alc/backends/wave.h @@ -5,15 +5,15 @@ struct WaveBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WAVE_H */ diff --git a/Engine/lib/openal-soft/alc/backends/winmm.cpp b/Engine/lib/openal-soft/alc/backends/winmm.cpp index 38e1193f9..ea4fee1e8 100644 --- a/Engine/lib/openal-soft/alc/backends/winmm.cpp +++ b/Engine/lib/openal-soft/alc/backends/winmm.cpp @@ -22,8 +22,8 @@ #include "winmm.h" -#include -#include +#include +#include #include #include @@ -38,13 +38,15 @@ #include #include -#include "alnumeric.h" +#include "alsem.h" +#include "alstring.h" +#include "althrd_setname.h" #include "core/device.h" #include "core/helpers.h" #include "core/logging.h" #include "ringbuffer.h" #include "strutils.h" -#include "threads.h" +#include "vector.h" #ifndef WAVE_FORMAT_IEEE_FLOAT #define WAVE_FORMAT_IEEE_FLOAT 0x0003 @@ -55,13 +57,13 @@ namespace { #define DEVNAME_HEAD "OpenAL Soft on " -al::vector PlaybackDevices; -al::vector CaptureDevices; +std::vector PlaybackDevices; +std::vector CaptureDevices; -bool checkName(const al::vector &list, const std::string &name) +bool checkName(const std::vector &list, const std::string &name) { return std::find(list.cbegin(), list.cend(), name) != list.cend(); } -void ProbePlaybackDevices(void) +void ProbePlaybackDevices() { PlaybackDevices.clear(); @@ -74,7 +76,7 @@ void ProbePlaybackDevices(void) WAVEOUTCAPSW WaveCaps{}; if(waveOutGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; + const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; int count{1}; std::string newname{basename}; @@ -92,7 +94,7 @@ void ProbePlaybackDevices(void) } } -void ProbeCaptureDevices(void) +void ProbeCaptureDevices() { CaptureDevices.clear(); @@ -105,7 +107,7 @@ void ProbeCaptureDevices(void) WAVEINCAPSW WaveCaps{}; if(waveInGetDevCapsW(i, &WaveCaps, sizeof(WaveCaps)) == MMSYSERR_NOERROR) { - const std::string basename{DEVNAME_HEAD + wstr_to_utf8(WaveCaps.szPname)}; + const std::string basename{DEVNAME_HEAD + wstr_to_utf8(std::data(WaveCaps.szPname))}; int count{1}; std::string newname{basename}; @@ -134,7 +136,7 @@ struct WinMMPlayback final : public BackendBase { int mixerProc(); - void open(const char *name) override; + void open(std::string_view name) override; bool reset() override; void start() override; void stop() override; @@ -143,6 +145,7 @@ struct WinMMPlayback final : public BackendBase { al::semaphore mSem; uint mIdx{0u}; std::array mWaveBuffer{}; + al::vector mBuffer; HWAVEOUT mOutHdl{nullptr}; @@ -150,8 +153,6 @@ struct WinMMPlayback final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WinMMPlayback) }; WinMMPlayback::~WinMMPlayback() @@ -159,14 +160,11 @@ WinMMPlayback::~WinMMPlayback() if(mOutHdl) waveOutClose(mOutHdl); mOutHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } /* WinMMPlayback::waveOutProc * - * Posts a message to 'WinMMPlayback::mixerProc' everytime a WaveOut Buffer is + * Posts a message to 'WinMMPlayback::mixerProc' every time a WaveOut Buffer is * completed and returns to the application (for more data) */ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -179,7 +177,7 @@ void CALLBACK WinMMPlayback::waveOutProc(HWAVEOUT, UINT msg, DWORD_PTR, DWORD_PT FORCE_ALIGN int WinMMPlayback::mixerProc() { SetRTPriority(); - althrd_setname(MIXER_THREAD_NAME); + althrd_setname(GetMixerThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -207,59 +205,55 @@ FORCE_ALIGN int WinMMPlayback::mixerProc() } -void WinMMPlayback::open(const char *name) +void WinMMPlayback::open(std::string_view name) { if(PlaybackDevices.empty()) ProbePlaybackDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(PlaybackDevices.cbegin(), PlaybackDevices.cend(), name) : PlaybackDevices.cbegin(); if(iter == PlaybackDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; auto DeviceID = static_cast(std::distance(PlaybackDevices.cbegin(), iter)); DevFmtType fmttype{mDevice->FmtType}; -retry_open: WAVEFORMATEX format{}; - if(fmttype == DevFmtFloat) - { - format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; - format.wBitsPerSample = 32; - } - else - { - format.wFormatTag = WAVE_FORMAT_PCM; - if(fmttype == DevFmtUByte || fmttype == DevFmtByte) - format.wBitsPerSample = 8; - else - format.wBitsPerSample = 16; - } - format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); - format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); - format.nSamplesPerSec = mDevice->Frequency; - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - format.cbSize = 0; - - HWAVEOUT outHandle{}; - MMRESULT res{waveOutOpen(&outHandle, DeviceID, &format, - reinterpret_cast(&WinMMPlayback::waveOutProcC), - reinterpret_cast(this), CALLBACK_FUNCTION)}; - if(res != MMSYSERR_NOERROR) - { + do { + format = WAVEFORMATEX{}; if(fmttype == DevFmtFloat) { - fmttype = DevFmtShort; - goto retry_open; + format.wFormatTag = WAVE_FORMAT_IEEE_FLOAT; + format.wBitsPerSample = 32; } - throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", res}; - } + else + { + format.wFormatTag = WAVE_FORMAT_PCM; + if(fmttype == DevFmtUByte || fmttype == DevFmtByte) + format.wBitsPerSample = 8; + else + format.wBitsPerSample = 16; + } + format.nChannels = ((mDevice->FmtChans == DevFmtMono) ? 1 : 2); + format.nBlockAlign = static_cast(format.wBitsPerSample * format.nChannels / 8); + format.nSamplesPerSec = mDevice->Frequency; + format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; + format.cbSize = 0; + + MMRESULT res{waveOutOpen(&mOutHdl, DeviceID, &format, + reinterpret_cast(&WinMMPlayback::waveOutProcC), + reinterpret_cast(this), CALLBACK_FUNCTION)}; + if(res == MMSYSERR_NOERROR) break; + + if(fmttype != DevFmtFloat) + throw al::backend_exception{al::backend_error::DeviceError, "waveOutOpen failed: %u", + res}; + + fmttype = DevFmtShort; + } while(true); - if(mOutHdl) - waveOutClose(mOutHdl); - mOutHdl = outHandle; mFormat = format; mDevice->DeviceName = PlaybackDevices[DeviceID]; @@ -312,11 +306,11 @@ bool WinMMPlayback::reset() } setDefaultWFXChannelOrder(); - uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; + const uint BufferSize{mDevice->UpdateSize * mFormat.nChannels * mDevice->bytesFromFmt()}; - al_free(mWaveBuffer[0].lpData); + decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize * mWaveBuffer.size())); + mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();i++) { @@ -369,16 +363,17 @@ struct WinMMCapture final : public BackendBase { int captureProc(); - void open(const char *name) override; + void open(std::string_view name) override; void start() override; void stop() override; - void captureSamples(al::byte *buffer, uint samples) override; + void captureSamples(std::byte *buffer, uint samples) override; uint availableSamples() override; std::atomic mReadable{0u}; al::semaphore mSem; uint mIdx{0}; std::array mWaveBuffer{}; + al::vector mBuffer; HWAVEIN mInHdl{nullptr}; @@ -388,8 +383,6 @@ struct WinMMCapture final : public BackendBase { std::atomic mKillNow{true}; std::thread mThread; - - DEF_NEWDEL(WinMMCapture) }; WinMMCapture::~WinMMCapture() @@ -398,14 +391,11 @@ WinMMCapture::~WinMMCapture() if(mInHdl) waveInClose(mInHdl); mInHdl = nullptr; - - al_free(mWaveBuffer[0].lpData); - std::fill(mWaveBuffer.begin(), mWaveBuffer.end(), WAVEHDR{}); } /* WinMMCapture::waveInProc * - * Posts a message to 'WinMMCapture::captureProc' everytime a WaveIn Buffer is + * Posts a message to 'WinMMCapture::captureProc' every time a WaveIn Buffer is * completed and returns to the application (with more data). */ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) noexcept @@ -417,7 +407,7 @@ void CALLBACK WinMMCapture::waveInProc(HWAVEIN, UINT msg, DWORD_PTR, DWORD_PTR) int WinMMCapture::captureProc() { - althrd_setname(RECORD_THREAD_NAME); + althrd_setname(GetRecordThreadName()); while(!mKillNow.load(std::memory_order_acquire) && mDevice->Connected.load(std::memory_order_acquire)) @@ -434,7 +424,8 @@ int WinMMCapture::captureProc() WAVEHDR &waveHdr = mWaveBuffer[widx]; widx = (widx+1) % mWaveBuffer.size(); - mRing->write(waveHdr.lpData, waveHdr.dwBytesRecorded / mFormat.nBlockAlign); + std::ignore = mRing->write(waveHdr.lpData, + waveHdr.dwBytesRecorded / mFormat.nBlockAlign); mReadable.fetch_sub(1, std::memory_order_acq_rel); waveInAddBuffer(mInHdl, &waveHdr, sizeof(WAVEHDR)); } while(--todo); @@ -445,18 +436,18 @@ int WinMMCapture::captureProc() } -void WinMMCapture::open(const char *name) +void WinMMCapture::open(std::string_view name) { if(CaptureDevices.empty()) ProbeCaptureDevices(); // Find the Device ID matching the deviceName if valid - auto iter = name ? + auto iter = !name.empty() ? std::find(CaptureDevices.cbegin(), CaptureDevices.cend(), name) : CaptureDevices.cbegin(); if(iter == CaptureDevices.cend()) - throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found", - name}; + throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%.*s\" not found", + al::sizei(name), name.data()}; auto DeviceID = static_cast(std::distance(CaptureDevices.cbegin(), iter)); switch(mDevice->FmtChans) @@ -470,6 +461,7 @@ void WinMMCapture::open(const char *name) case DevFmtX61: case DevFmtX71: case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: throw al::backend_exception{al::backend_error::DeviceError, "%s capture not supported", @@ -513,14 +505,14 @@ void WinMMCapture::open(const char *name) // Allocate circular memory buffer for the captured audio // Make sure circular buffer is at least 100ms in size - uint CapturedDataSize{mDevice->BufferSize}; - CapturedDataSize = static_cast(maxz(CapturedDataSize, BufferSize*mWaveBuffer.size())); + const auto CapturedDataSize = std::max(mDevice->BufferSize, + BufferSize*mWaveBuffer.size()); mRing = RingBuffer::Create(CapturedDataSize, mFormat.nBlockAlign, false); - al_free(mWaveBuffer[0].lpData); + decltype(mBuffer)(BufferSize*mWaveBuffer.size()).swap(mBuffer); mWaveBuffer[0] = WAVEHDR{}; - mWaveBuffer[0].lpData = static_cast(al_calloc(16, BufferSize * mWaveBuffer.size())); + mWaveBuffer[0].lpData = mBuffer.data(); mWaveBuffer[0].dwBufferLength = BufferSize; for(size_t i{1};i < mWaveBuffer.size();++i) { @@ -571,8 +563,8 @@ void WinMMCapture::stop() mIdx = 0; } -void WinMMCapture::captureSamples(al::byte *buffer, uint samples) -{ mRing->read(buffer, samples); } +void WinMMCapture::captureSamples(std::byte *buffer, uint samples) +{ std::ignore = mRing->read(buffer, samples); } uint WinMMCapture::availableSamples() { return static_cast(mRing->readSpace()); } @@ -586,26 +578,23 @@ bool WinMMBackendFactory::init() bool WinMMBackendFactory::querySupport(BackendType type) { return type == BackendType::Playback || type == BackendType::Capture; } -std::string WinMMBackendFactory::probe(BackendType type) +auto WinMMBackendFactory::enumerate(BackendType type) -> std::vector { - std::string outnames; + std::vector outnames; auto add_device = [&outnames](const std::string &dname) -> void - { - /* +1 to also append the null char (to ensure a null-separated list and - * double-null terminated list). - */ - if(!dname.empty()) - outnames.append(dname.c_str(), dname.length()+1); - }; + { if(!dname.empty()) outnames.emplace_back(dname); }; + switch(type) { case BackendType::Playback: ProbePlaybackDevices(); + outnames.reserve(PlaybackDevices.size()); std::for_each(PlaybackDevices.cbegin(), PlaybackDevices.cend(), add_device); break; case BackendType::Capture: ProbeCaptureDevices(); + outnames.reserve(CaptureDevices.size()); std::for_each(CaptureDevices.cbegin(), CaptureDevices.cend(), add_device); break; } diff --git a/Engine/lib/openal-soft/alc/backends/winmm.h b/Engine/lib/openal-soft/alc/backends/winmm.h index 45a706aa3..51ce432ca 100644 --- a/Engine/lib/openal-soft/alc/backends/winmm.h +++ b/Engine/lib/openal-soft/alc/backends/winmm.h @@ -5,15 +5,15 @@ struct WinMMBackendFactory final : public BackendFactory { public: - bool init() override; + auto init() -> bool final; - bool querySupport(BackendType type) override; + auto querySupport(BackendType type) -> bool final; - std::string probe(BackendType type) override; + auto enumerate(BackendType type) -> std::vector final; - BackendPtr createBackend(DeviceBase *device, BackendType type) override; + auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final; - static BackendFactory &getFactory(); + static auto getFactory() -> BackendFactory&; }; #endif /* BACKENDS_WINMM_H */ diff --git a/Engine/lib/openal-soft/alc/context.cpp b/Engine/lib/openal-soft/alc/context.cpp index e02c549b3..2b7a9ea9d 100644 --- a/Engine/lib/openal-soft/alc/context.cpp +++ b/Engine/lib/openal-soft/alc/context.cpp @@ -4,21 +4,28 @@ #include "context.h" #include +#include +#include +#include #include #include #include -#include #include +#include +#include #include "AL/efx.h" #include "al/auxeffectslot.h" +#include "al/debug.h" #include "al/source.h" #include "al/effect.h" #include "al/event.h" #include "al/listener.h" #include "albit.h" #include "alc/alu.h" +#include "alc/backends/base.h" +#include "alspan.h" #include "core/async_event.h" #include "core/device.h" #include "core/effectslot.h" @@ -26,63 +33,68 @@ #include "core/voice.h" #include "core/voice_change.h" #include "device.h" +#include "flexarray.h" #include "ringbuffer.h" #include "vecmat.h" #ifdef ALSOFT_EAX -#include -#include "alstring.h" #include "al/eax/globals.h" #endif // ALSOFT_EAX namespace { -using namespace std::placeholders; - +using namespace std::string_view_literals; using voidp = void*; /* Default context extensions */ -constexpr ALchar alExtList[] = - "AL_EXT_ALAW " - "AL_EXT_BFORMAT " - "AL_EXT_DOUBLE " - "AL_EXT_EXPONENT_DISTANCE " - "AL_EXT_FLOAT32 " - "AL_EXT_IMA4 " - "AL_EXT_LINEAR_DISTANCE " - "AL_EXT_MCFORMATS " - "AL_EXT_MULAW " - "AL_EXT_MULAW_BFORMAT " - "AL_EXT_MULAW_MCFORMATS " - "AL_EXT_OFFSET " - "AL_EXT_source_distance_model " - "AL_EXT_SOURCE_RADIUS " - "AL_EXT_STATIC_BUFFER " - "AL_EXT_STEREO_ANGLES " - "AL_LOKI_quadriphonic " - "AL_SOFT_bformat_ex " - "AL_SOFTX_bformat_hoa " - "AL_SOFT_block_alignment " - "AL_SOFT_buffer_length_query " - "AL_SOFT_callback_buffer " - "AL_SOFTX_convolution_reverb " - "AL_SOFT_deferred_updates " - "AL_SOFT_direct_channels " - "AL_SOFT_direct_channels_remix " - "AL_SOFT_effect_target " - "AL_SOFT_events " - "AL_SOFT_gain_clamp_ex " - "AL_SOFTX_hold_on_disconnect " - "AL_SOFT_loop_points " - "AL_SOFTX_map_buffer " - "AL_SOFT_MSADPCM " - "AL_SOFT_source_latency " - "AL_SOFT_source_length " - "AL_SOFT_source_resampler " - "AL_SOFT_source_spatialize " - "AL_SOFT_source_start_delay " - "AL_SOFT_UHJ " - "AL_SOFT_UHJ_ex"; +std::vector getContextExtensions() noexcept +{ + return std::vector{ + "AL_EXT_ALAW"sv, + "AL_EXT_BFORMAT"sv, + "AL_EXT_debug"sv, + "AL_EXT_direct_context"sv, + "AL_EXT_DOUBLE"sv, + "AL_EXT_EXPONENT_DISTANCE"sv, + "AL_EXT_FLOAT32"sv, + "AL_EXT_IMA4"sv, + "AL_EXT_LINEAR_DISTANCE"sv, + "AL_EXT_MCFORMATS"sv, + "AL_EXT_MULAW"sv, + "AL_EXT_MULAW_BFORMAT"sv, + "AL_EXT_MULAW_MCFORMATS"sv, + "AL_EXT_OFFSET"sv, + "AL_EXT_source_distance_model"sv, + "AL_EXT_SOURCE_RADIUS"sv, + "AL_EXT_STATIC_BUFFER"sv, + "AL_EXT_STEREO_ANGLES"sv, + "AL_LOKI_quadriphonic"sv, + "AL_SOFT_bformat_ex"sv, + "AL_SOFTX_bformat_hoa"sv, + "AL_SOFT_block_alignment"sv, + "AL_SOFT_buffer_length_query"sv, + "AL_SOFT_callback_buffer"sv, + "AL_SOFTX_convolution_effect"sv, + "AL_SOFT_deferred_updates"sv, + "AL_SOFT_direct_channels"sv, + "AL_SOFT_direct_channels_remix"sv, + "AL_SOFT_effect_target"sv, + "AL_SOFT_events"sv, + "AL_SOFT_gain_clamp_ex"sv, + "AL_SOFTX_hold_on_disconnect"sv, + "AL_SOFT_loop_points"sv, + "AL_SOFTX_map_buffer"sv, + "AL_SOFT_MSADPCM"sv, + "AL_SOFT_source_latency"sv, + "AL_SOFT_source_length"sv, + "AL_SOFTX_source_panning"sv, + "AL_SOFT_source_resampler"sv, + "AL_SOFT_source_spatialize"sv, + "AL_SOFT_source_start_delay"sv, + "AL_SOFT_UHJ"sv, + "AL_SOFT_UHJ_ex"sv, + }; +} } // namespace @@ -90,10 +102,9 @@ constexpr ALchar alExtList[] = std::atomic ALCcontext::sGlobalContextLock{false}; std::atomic ALCcontext::sGlobalContext{nullptr}; -thread_local ALCcontext *ALCcontext::sLocalContext{nullptr}; ALCcontext::ThreadCtx::~ThreadCtx() { - if(ALCcontext *ctx{ALCcontext::sLocalContext}) + if(ALCcontext *ctx{std::exchange(ALCcontext::sLocalContext, nullptr)}) { const bool result{ctx->releaseIfNoDelete()}; ERR("Context %p current for thread being destroyed%s!\n", voidp{ctx}, @@ -105,23 +116,18 @@ thread_local ALCcontext::ThreadCtx ALCcontext::sThreadContext; ALeffect ALCcontext::sDefaultEffect; -#ifdef __MINGW32__ -ALCcontext *ALCcontext::getThreadContext() noexcept -{ return sLocalContext; } -void ALCcontext::setThreadContext(ALCcontext *context) noexcept -{ sThreadContext.set(context); } -#endif - -ALCcontext::ALCcontext(al::intrusive_ptr device) - : ContextBase{device.get()}, mALDevice{std::move(device)} +ALCcontext::ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags) + : ContextBase{device.get()}, mALDevice{std::move(device)}, mContextFlags{flags} { + mDebugGroups.emplace_back(DebugSource::Other, 0, std::string{}); + mDebugEnabled.store(mContextFlags.test(ContextFlags::DebugBit), std::memory_order_relaxed); } ALCcontext::~ALCcontext() { TRACE("Freeing context %p\n", voidp{this}); - size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), size_t{0u}, + size_t count{std::accumulate(mSourceList.cbegin(), mSourceList.cend(), 0_uz, [](size_t cur, const SourceSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) @@ -134,7 +140,7 @@ ALCcontext::~ALCcontext() #endif // ALSOFT_EAX mDefaultSlot = nullptr; - count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), size_t{0u}, + count = std::accumulate(mEffectSlotList.cbegin(), mEffectSlotList.cend(), 0_uz, [](size_t cur, const EffectSlotSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) @@ -151,16 +157,17 @@ void ALCcontext::init() aluInitEffectPanning(mDefaultSlot->mSlot, this); } - EffectSlotArray *auxslots; + std::unique_ptr auxslots; if(!mDefaultSlot) auxslots = EffectSlot::CreatePtrArray(0); else { - auxslots = EffectSlot::CreatePtrArray(1); + auxslots = EffectSlot::CreatePtrArray(2); (*auxslots)[0] = mDefaultSlot->mSlot; + (*auxslots)[1] = mDefaultSlot->mSlot; mDefaultSlot->mState = SlotState::Playing; } - mActiveAuxSlots.store(auxslots, std::memory_order_relaxed); + mActiveAuxSlots.store(std::move(auxslots), std::memory_order_relaxed); allocVoiceChanges(); { @@ -170,26 +177,41 @@ void ALCcontext::init() mCurrentVoiceChange.store(cur, std::memory_order_relaxed); } - mExtensionList = alExtList; + mExtensions = getContextExtensions(); if(sBufferSubDataCompat) { - std::string extlist{mExtensionList}; - - const auto pos = extlist.find("AL_EXT_SOURCE_RADIUS "); - if(pos != std::string::npos) - extlist.replace(pos, 20, "AL_SOFT_buffer_sub_data"); - else - extlist += " AL_SOFT_buffer_sub_data"; - - mExtensionListOverride = std::move(extlist); - mExtensionList = mExtensionListOverride.c_str(); + auto iter = std::find(mExtensions.begin(), mExtensions.end(), "AL_EXT_SOURCE_RADIUS"sv); + if(iter != mExtensions.end()) mExtensions.erase(iter); + /* TODO: Would be nice to sort this alphabetically. Needs case- + * insensitive searching. + */ + mExtensions.emplace_back("AL_SOFT_buffer_sub_data"sv); } #ifdef ALSOFT_EAX eax_initialize_extensions(); #endif // ALSOFT_EAX + if(!mExtensions.empty()) + { + const size_t len{std::accumulate(mExtensions.cbegin()+1, mExtensions.cend(), + mExtensions.front().length(), + [](size_t current, std::string_view ext) noexcept + { return current + ext.length() + 1; })}; + + std::string extensions; + extensions.reserve(len); + extensions += mExtensions.front(); + for(std::string_view ext : al::span{mExtensions}.subspan<1>()) + { + extensions += ' '; + extensions += ext; + } + + mExtensionsString = std::move(extensions); + } + mParams.Position = alu::Vector{0.0f, 0.0f, 0.0f, 1.0f}; mParams.Matrix = alu::Matrix::Identity(); mParams.Velocity = alu::Vector{}; @@ -202,7 +224,7 @@ void ALCcontext::init() mParams.mDistanceModel = mDistanceModel; - mAsyncEvents = RingBuffer::Create(511, sizeof(AsyncEvent), false); + mAsyncEvents = RingBuffer::Create(1024, sizeof(AsyncEvent), false); StartEventThrd(this); @@ -210,7 +232,7 @@ void ALCcontext::init() mActiveVoiceCount.store(64, std::memory_order_relaxed); } -bool ALCcontext::deinit() +void ALCcontext::deinit() { if(sLocalContext == this) { @@ -230,18 +252,14 @@ bool ALCcontext::deinit() dec_ref(); } - bool ret{}; + bool stopPlayback{}; /* First make sure this context exists in the device's list. */ auto *oldarray = mDevice->mContexts.load(std::memory_order_acquire); if(auto toremove = static_cast(std::count(oldarray->begin(), oldarray->end(), this))) { using ContextArray = al::FlexArray; - auto alloc_ctx_array = [](const size_t count) -> ContextArray* - { - if(count == 0) return &DeviceBase::sEmptyContextArray; - return ContextArray::Create(count).release(); - }; - auto *newarray = alloc_ctx_array(oldarray->size() - toremove); + const size_t newsize{oldarray->size() - toremove}; + auto newarray = ContextArray::Create(newsize); /* Copy the current/old context handles to the new array, excluding the * given context. @@ -252,21 +270,21 @@ bool ALCcontext::deinit() /* Store the new context array in the device. Wait for any current mix * to finish before deleting the old array. */ - mDevice->mContexts.store(newarray); - if(oldarray != &DeviceBase::sEmptyContextArray) - { - mDevice->waitForMix(); - delete oldarray; - } + auto prevarray = mDevice->mContexts.exchange(std::move(newarray)); + std::ignore = mDevice->waitForMix(); - ret = !newarray->empty(); + stopPlayback = (newsize == 0); } else - ret = !oldarray->empty(); + stopPlayback = oldarray->empty(); StopEventThrd(this); - return ret; + if(stopPlayback && mALDevice->mDeviceState == DeviceState::Playing) + { + mALDevice->Backend->stop(); + mALDevice->mDeviceState = DeviceState::Configured; + } } void ALCcontext::applyAllUpdates() @@ -295,6 +313,7 @@ void ALCcontext::applyAllUpdates() mHoldUpdates.store(false, std::memory_order_release); } + #ifdef ALSOFT_EAX namespace { @@ -306,10 +325,10 @@ void ForEachSource(ALCcontext *context, F func) uint64_t usemask{~sublist.FreeMask}; while(usemask) { - const int idx{al::countr_zero(usemask)}; + const auto idx = static_cast(al::countr_zero(usemask)); usemask &= ~(1_u64 << idx); - func(sublist.Sources[idx]); + func((*sublist.Sources)[idx]); } } } @@ -447,43 +466,15 @@ void ALCcontext::eax_initialize_extensions() if(!eax_g_is_enabled) return; - const auto string_max_capacity = - std::strlen(mExtensionList) + 1 + - std::strlen(eax1_ext_name) + 1 + - std::strlen(eax2_ext_name) + 1 + - std::strlen(eax3_ext_name) + 1 + - std::strlen(eax4_ext_name) + 1 + - std::strlen(eax5_ext_name) + 1 + - std::strlen(eax_x_ram_ext_name) + 1; - - std::string extlist; - extlist.reserve(string_max_capacity); - + mExtensions.emplace(mExtensions.begin(), "EAX-RAM"sv); if(eaxIsCapable()) { - extlist += eax1_ext_name; - extlist += ' '; - - extlist += eax2_ext_name; - extlist += ' '; - - extlist += eax3_ext_name; - extlist += ' '; - - extlist += eax4_ext_name; - extlist += ' '; - - extlist += eax5_ext_name; - extlist += ' '; + mExtensions.emplace(mExtensions.begin(), "EAX5.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX4.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX3.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX2.0"sv); + mExtensions.emplace(mExtensions.begin(), "EAX"sv); } - - extlist += eax_x_ram_ext_name; - extlist += ' '; - - extlist += mExtensionList; - - mExtensionListOverride = std::move(extlist); - mExtensionList = mExtensionListOverride.c_str(); } void ALCcontext::eax_initialize() @@ -555,10 +546,11 @@ unsigned long ALCcontext::eax_detect_speaker_configuration() const case DevFmtX51: return SPEAKERS_5; case DevFmtX61: return SPEAKERS_6; case DevFmtX71: return SPEAKERS_7; - /* 7.1.4 is compatible with 7.1. This could instead be HEADPHONES to + /* 7.1.4(.4) is compatible with 7.1. This could instead be HEADPHONES to * suggest with-height surround sound (like HRTF). */ case DevFmtX714: return SPEAKERS_7; + case DevFmtX7144: return SPEAKERS_7; /* 3D7.1 is only compatible with 5.1. This could instead be HEADPHONES to * suggest full-sphere surround sound (like HRTF). */ @@ -582,7 +574,7 @@ void ALCcontext::eax_update_speaker_configuration() void ALCcontext::eax_set_last_error_defaults() noexcept { - mEaxLastError = EAX_OK; + mEaxLastError = EAXCONTEXT_DEFAULTLASTERROR; } void ALCcontext::eax_session_set_defaults() noexcept @@ -671,6 +663,7 @@ void ALCcontext::eax_get_misc(const EaxCall& call) break; case EAXCONTEXT_LASTERROR: call.set_value(mEaxLastError); + mEaxLastError = EAX_OK; break; case EAXCONTEXT_SPEAKERCONFIG: call.set_value(mEaxSpeakerConfig); @@ -1018,88 +1011,49 @@ void ALCcontext::eaxCommit() eax_update_sources(); } -namespace { -class EaxSetException : public EaxException { -public: - explicit EaxSetException(const char* message) - : EaxException{"EAX_SET", message} - {} -}; - -[[noreturn]] void eax_fail_set(const char* message) -{ - throw EaxSetException{message}; -} - -class EaxGetException : public EaxException { -public: - explicit EaxGetException(const char* message) - : EaxException{"EAX_GET", message} - {} -}; - -[[noreturn]] void eax_fail_get(const char* message) -{ - throw EaxGetException{message}; -} - -} // namespace - - -FORCE_ALIGN ALenum AL_APIENTRY EAXSet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept -try +FORCE_ALIGN auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum { auto context = GetContextRef(); - - if(!context) - eax_fail_set("No current context."); - - std::lock_guard prop_lock{context->mPropLock}; - - return context->eax_eax_set( - property_set_id, - property_id, - property_source_id, - property_value, - property_value_size); + if(!context) UNLIKELY return AL_INVALID_OPERATION; + return EAXSetDirect(context.get(), property_set_id, property_id, source_id, value, value_size); } -catch (...) + +FORCE_ALIGN auto AL_APIENTRY EAXSetDirect(ALCcontext *context, const GUID *property_set_id, + ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum +try { - eax_log_exception(__func__); + std::lock_guard prop_lock{context->mPropLock}; + return context->eax_eax_set(property_set_id, property_id, source_id, value, value_size); +} +catch(...) +{ + context->eaxSetLastError(); + eax_log_exception(std::data(__func__)); return AL_INVALID_OPERATION; } -FORCE_ALIGN ALenum AL_APIENTRY EAXGet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept -try + +FORCE_ALIGN auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum { auto context = GetContextRef(); - - if(!context) - eax_fail_get("No current context."); - - std::lock_guard prop_lock{context->mPropLock}; - - return context->eax_eax_get( - property_set_id, - property_id, - property_source_id, - property_value, - property_value_size); + if(!context) UNLIKELY return AL_INVALID_OPERATION; + return EAXGetDirect(context.get(), property_set_id, property_id, source_id, value, value_size); } -catch (...) + +FORCE_ALIGN auto AL_APIENTRY EAXGetDirect(ALCcontext *context, const GUID *property_set_id, + ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum +try { - eax_log_exception(__func__); + std::lock_guard prop_lock{context->mPropLock}; + return context->eax_eax_get(property_set_id, property_id, source_id, value, value_size); +} +catch(...) +{ + context->eaxSetLastError(); + eax_log_exception(std::data(__func__)); return AL_INVALID_OPERATION; } #endif // ALSOFT_EAX diff --git a/Engine/lib/openal-soft/alc/context.h b/Engine/lib/openal-soft/alc/context.h index e8efdbf1d..e1437fb31 100644 --- a/Engine/lib/openal-soft/alc/context.h +++ b/Engine/lib/openal-soft/alc/context.h @@ -1,11 +1,17 @@ #ifndef ALC_CONTEXT_H #define ALC_CONTEXT_H +#include #include +#include +#include #include #include -#include +#include +#include +#include #include +#include #include "AL/al.h" #include "AL/alc.h" @@ -14,10 +20,11 @@ #include "al/listener.h" #include "almalloc.h" #include "alnumeric.h" +#include "althreads.h" #include "atomic.h" #include "core/context.h" +#include "inprogext.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX #include "al/eax/call.h" @@ -30,40 +37,40 @@ struct ALeffect; struct ALeffectslot; struct ALsource; +struct DebugGroup; +struct EffectSlotSubList; +struct SourceSubList; + +enum class DebugSource : std::uint8_t; +enum class DebugType : std::uint8_t; +enum class DebugSeverity : std::uint8_t; using uint = unsigned int; -struct SourceSubList { - uint64_t FreeMask{~0_u64}; - ALsource *Sources{nullptr}; /* 64 */ +enum ContextFlags { + DebugBit = 0, /* ALC_CONTEXT_DEBUG_BIT_EXT */ +}; +using ContextFlagBitset = std::bitset; - SourceSubList() noexcept = default; - SourceSubList(const SourceSubList&) = delete; - SourceSubList(SourceSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Sources{rhs.Sources} - { rhs.FreeMask = ~0_u64; rhs.Sources = nullptr; } - ~SourceSubList(); - SourceSubList& operator=(const SourceSubList&) = delete; - SourceSubList& operator=(SourceSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Sources, rhs.Sources); return *this; } +struct DebugLogEntry { + const DebugSource mSource; + const DebugType mType; + const DebugSeverity mSeverity; + const uint mId; + + std::string mMessage; + + template + DebugLogEntry(DebugSource source, DebugType type, uint id, DebugSeverity severity, T&& message) + : mSource{source}, mType{type}, mSeverity{severity}, mId{id} + , mMessage{std::forward(message)} + { } + DebugLogEntry(const DebugLogEntry&) = default; + DebugLogEntry(DebugLogEntry&&) = default; }; -struct EffectSlotSubList { - uint64_t FreeMask{~0_u64}; - ALeffectslot *EffectSlots{nullptr}; /* 64 */ - - EffectSlotSubList() noexcept = default; - EffectSlotSubList(const EffectSlotSubList&) = delete; - EffectSlotSubList(EffectSlotSubList&& rhs) noexcept - : FreeMask{rhs.FreeMask}, EffectSlots{rhs.EffectSlots} - { rhs.FreeMask = ~0_u64; rhs.EffectSlots = nullptr; } - ~EffectSlotSubList(); - - EffectSlotSubList& operator=(const EffectSlotSubList&) = delete; - EffectSlotSubList& operator=(EffectSlotSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(EffectSlots, rhs.EffectSlots); return *this; } -}; struct ALCcontext : public al::intrusive_ref, ContextBase { const al::intrusive_ptr mALDevice; @@ -74,7 +81,10 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { std::mutex mPropLock; - std::atomic mLastError{AL_NO_ERROR}; + al::tss mLastThreadError{AL_NO_ERROR}; + + const ContextFlagBitset mContextFlags; + std::atomic mDebugEnabled{false}; DistanceModel mDistanceModel{DistanceModel::Default}; bool mSourceDistanceModel{false}; @@ -88,25 +98,32 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { ALEVENTPROCSOFT mEventCb{}; void *mEventParam{nullptr}; + std::mutex mDebugCbLock; + ALDEBUGPROCEXT mDebugCb{}; + void *mDebugParam{nullptr}; + std::vector mDebugGroups; + std::deque mDebugLog; + ALlistener mListener{}; - al::vector mSourceList; + std::vector mSourceList; ALuint mNumSources{0}; std::mutex mSourceLock; - al::vector mEffectSlotList; + std::vector mEffectSlotList; ALuint mNumEffectSlots{0u}; std::mutex mEffectSlotLock; /* Default effect slot */ std::unique_ptr mDefaultSlot; - const char *mExtensionList{nullptr}; + std::vector mExtensions; + std::string mExtensionsString{}; - std::string mExtensionListOverride{}; + std::unordered_map mSourceNames; + std::unordered_map mEffectSlotNames; - - ALCcontext(al::intrusive_ptr device); + ALCcontext(al::intrusive_ptr device, ContextFlagBitset flags); ALCcontext(const ALCcontext&) = delete; ALCcontext& operator=(const ALCcontext&) = delete; ~ALCcontext(); @@ -114,10 +131,10 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { void init(); /** * Removes the context from its device and removes it from being current on - * the running thread or globally. Returns true if other contexts still - * exist on the device. + * the running thread or globally. Stops device playback if this was the + * last context on its device. */ - bool deinit(); + void deinit(); /** * Defers/suspends updates for the given context's listener and sources. @@ -142,20 +159,32 @@ struct ALCcontext : public al::intrusive_ref, ContextBase { */ void applyAllUpdates(); -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf, 3, 4)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT, 3, 4)]] #else [[gnu::format(printf, 3, 4)]] #endif void setError(ALenum errorCode, const char *msg, ...); + void sendDebugMessage(std::unique_lock &debuglock, DebugSource source, + DebugType type, ALuint id, DebugSeverity severity, std::string_view message); + + void debugMessage(DebugSource source, DebugType type, ALuint id, DebugSeverity severity, + std::string_view message) + { + if(!mDebugEnabled.load(std::memory_order_relaxed)) LIKELY + return; + std::unique_lock debuglock{mDebugCbLock}; + sendDebugMessage(debuglock, source, type, id, severity, message); + } + /* Process-wide current context */ static std::atomic sGlobalContextLock; static std::atomic sGlobalContext; private: /* Thread-local current context. */ - static thread_local ALCcontext *sLocalContext; + static inline thread_local ALCcontext *sLocalContext{}; /* Thread-local context handling. This handles attempting to release the * context which may have been left current when the thread is destroyed. @@ -163,30 +192,25 @@ private: class ThreadCtx { public: ~ThreadCtx(); + /* NOLINTBEGIN(readability-convert-member-functions-to-static) + * This should be non-static to invoke construction of the thread-local + * sThreadContext, so that it's destructor gets run at thread exit to + * clear sLocalContext (which isn't a member variable to make read + * access efficient). + */ void set(ALCcontext *ctx) const noexcept { sLocalContext = ctx; } + /* NOLINTEND(readability-convert-member-functions-to-static) */ }; static thread_local ThreadCtx sThreadContext; public: - /* HACK: MinGW generates bad code when accessing an extern thread_local - * object. Add a wrapper function for it that only accesses it where it's - * defined. - */ -#ifdef __MINGW32__ - static ALCcontext *getThreadContext() noexcept; - static void setThreadContext(ALCcontext *context) noexcept; -#else static ALCcontext *getThreadContext() noexcept { return sLocalContext; } static void setThreadContext(ALCcontext *context) noexcept { sThreadContext.set(context); } -#endif /* Default effect that applies to sources that don't have an effect on send 0. */ static ALeffect sDefaultEffect; - DEF_NEWDEL(ALCcontext) - #ifdef ALSOFT_EAX -public: bool hasEax() const noexcept { return mEaxIsInitialized; } bool eaxIsCapable() const noexcept; @@ -430,7 +454,7 @@ private: typename TMemberResult, typename TProps, typename TState> - void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) noexcept + void eax_defer(const EaxCall& call, TState& state, TMemberResult TProps::*member) { const auto& src = call.get_value(); TValidator{}(src); @@ -513,28 +537,20 @@ private: using ContextRef = al::intrusive_ptr; -ContextRef GetContextRef(void); +ContextRef GetContextRef() noexcept; void UpdateContextProps(ALCcontext *context); -extern bool TrapALError; +inline bool TrapALError{false}; #ifdef ALSOFT_EAX -ALenum AL_APIENTRY EAXSet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept; +auto AL_APIENTRY EAXSet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; -ALenum AL_APIENTRY EAXGet( - const GUID* property_set_id, - ALuint property_id, - ALuint property_source_id, - ALvoid* property_value, - ALuint property_value_size) noexcept; +auto AL_APIENTRY EAXGet(const GUID *property_set_id, ALuint property_id, + ALuint source_id, ALvoid *value, ALuint value_size) noexcept -> ALenum; #endif // ALSOFT_EAX #endif /* ALC_CONTEXT_H */ diff --git a/Engine/lib/openal-soft/alc/device.cpp b/Engine/lib/openal-soft/alc/device.cpp index 66b13c5e4..86fddcabd 100644 --- a/Engine/lib/openal-soft/alc/device.cpp +++ b/Engine/lib/openal-soft/alc/device.cpp @@ -3,19 +3,22 @@ #include "device.h" +#include +#include #include -#include +#include "al/buffer.h" +#include "al/effect.h" +#include "al/filter.h" #include "albit.h" -#include "alconfig.h" +#include "alnumeric.h" +#include "atomic.h" #include "backends/base.h" -#include "core/bformatdec.h" -#include "core/bs2b.h" -#include "core/front_stablizer.h" +#include "core/devformat.h" #include "core/hrtf.h" #include "core/logging.h" #include "core/mastering.h" -#include "core/uhjfilter.h" +#include "flexarray.h" namespace { @@ -34,19 +37,19 @@ ALCdevice::~ALCdevice() Backend = nullptr; - size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), size_t{0u}, + size_t count{std::accumulate(BufferList.cbegin(), BufferList.cend(), 0_uz, [](size_t cur, const BufferSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); })}; if(count > 0) WARN("%zu Buffer%s not deleted\n", count, (count==1)?"":"s"); - count = std::accumulate(EffectList.cbegin(), EffectList.cend(), size_t{0u}, + count = std::accumulate(EffectList.cbegin(), EffectList.cend(), 0_uz, [](size_t cur, const EffectSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) WARN("%zu Effect%s not deleted\n", count, (count==1)?"":"s"); - count = std::accumulate(FilterList.cbegin(), FilterList.cend(), size_t{0u}, + count = std::accumulate(FilterList.cbegin(), FilterList.cend(), 0_uz, [](size_t cur, const FilterSubList &sublist) noexcept -> size_t { return cur + static_cast(al::popcount(~sublist.FreeMask)); }); if(count > 0) @@ -55,8 +58,8 @@ ALCdevice::~ALCdevice() void ALCdevice::enumerateHrtfs() { - mHrtfList = EnumerateHrtf(configValue(nullptr, "hrtf-paths")); - if(auto defhrtfopt = configValue(nullptr, "default-hrtf")) + mHrtfList = EnumerateHrtf(configValue({}, "hrtf-paths")); + if(auto defhrtfopt = configValue({}, "default-hrtf")) { auto iter = std::find(mHrtfList.begin(), mHrtfList.end(), *defhrtfopt); if(iter == mHrtfList.end()) @@ -85,6 +88,7 @@ auto ALCdevice::getOutputMode1() const noexcept -> OutputMode1 case DevFmtX61: return OutputMode1::X61; case DevFmtX71: return OutputMode1::X71; case DevFmtX714: + case DevFmtX7144: case DevFmtX3D71: case DevFmtAmbi3D: break; diff --git a/Engine/lib/openal-soft/alc/device.h b/Engine/lib/openal-soft/alc/device.h index ef50f53e7..47a5513f8 100644 --- a/Engine/lib/openal-soft/alc/device.h +++ b/Engine/lib/openal-soft/alc/device.h @@ -4,79 +4,32 @@ #include #include #include -#include +#include #include -#include +#include +#include +#include +#include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" #include "alconfig.h" -#include "almalloc.h" -#include "alnumeric.h" #include "core/device.h" -#include "inprogext.h" #include "intrusive_ptr.h" -#include "vector.h" #ifdef ALSOFT_EAX #include "al/eax/x_ram.h" #endif // ALSOFT_EAX -struct ALbuffer; -struct ALeffect; -struct ALfilter; struct BackendBase; +struct BufferSubList; +struct EffectSubList; +struct FilterSubList; using uint = unsigned int; -struct BufferSubList { - uint64_t FreeMask{~0_u64}; - ALbuffer *Buffers{nullptr}; /* 64 */ - - BufferSubList() noexcept = default; - BufferSubList(const BufferSubList&) = delete; - BufferSubList(BufferSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Buffers{rhs.Buffers} - { rhs.FreeMask = ~0_u64; rhs.Buffers = nullptr; } - ~BufferSubList(); - - BufferSubList& operator=(const BufferSubList&) = delete; - BufferSubList& operator=(BufferSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Buffers, rhs.Buffers); return *this; } -}; - -struct EffectSubList { - uint64_t FreeMask{~0_u64}; - ALeffect *Effects{nullptr}; /* 64 */ - - EffectSubList() noexcept = default; - EffectSubList(const EffectSubList&) = delete; - EffectSubList(EffectSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Effects{rhs.Effects} - { rhs.FreeMask = ~0_u64; rhs.Effects = nullptr; } - ~EffectSubList(); - - EffectSubList& operator=(const EffectSubList&) = delete; - EffectSubList& operator=(EffectSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Effects, rhs.Effects); return *this; } -}; - -struct FilterSubList { - uint64_t FreeMask{~0_u64}; - ALfilter *Filters{nullptr}; /* 64 */ - - FilterSubList() noexcept = default; - FilterSubList(const FilterSubList&) = delete; - FilterSubList(FilterSubList&& rhs) noexcept : FreeMask{rhs.FreeMask}, Filters{rhs.Filters} - { rhs.FreeMask = ~0_u64; rhs.Filters = nullptr; } - ~FilterSubList(); - - FilterSubList& operator=(const FilterSubList&) = delete; - FilterSubList& operator=(FilterSubList&& rhs) noexcept - { std::swap(FreeMask, rhs.FreeMask); std::swap(Filters, rhs.Filters); return *this; } -}; - - struct ALCdevice : public al::intrusive_ref, DeviceBase { /* This lock protects the device state (format, update size, etc) from * being from being changed in multiple threads, or being accessed while @@ -94,7 +47,7 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { uint AuxiliaryEffectSlotMax{}; std::string mHrtfName; - al::vector mHrtfList; + std::vector mHrtfList; ALCenum mHrtfStatus{ALC_FALSE}; enum class OutputMode1 : ALCenum { @@ -117,49 +70,54 @@ struct ALCdevice : public al::intrusive_ref, DeviceBase { // Map of Buffers for this device std::mutex BufferLock; - al::vector BufferList; + std::vector BufferList; // Map of Effects for this device std::mutex EffectLock; - al::vector EffectList; + std::vector EffectList; // Map of Filters for this device std::mutex FilterLock; - al::vector FilterList; + std::vector FilterList; #ifdef ALSOFT_EAX ALuint eax_x_ram_free_size{eax_x_ram_max_size}; #endif // ALSOFT_EAX + std::unordered_map mBufferNames; + std::unordered_map mEffectNames; + std::unordered_map mFilterNames; + ALCdevice(DeviceType type); ~ALCdevice(); void enumerateHrtfs(); - bool getConfigValueBool(const char *block, const char *key, bool def) - { return GetConfigValueBool(DeviceName.c_str(), block, key, def); } + bool getConfigValueBool(const std::string_view block, const std::string_view key, bool def) + { return GetConfigValueBool(DeviceName, block, key, def); } template - inline al::optional configValue(const char *block, const char *key) = delete; - - DEF_NEWDEL(ALCdevice) + inline std::optional configValue(const std::string_view block, const std::string_view key) = delete; }; template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueStr(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueStr(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueInt(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueInt(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueUInt(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueUInt(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueFloat(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueFloat(DeviceName, block, key); } template<> -inline al::optional ALCdevice::configValue(const char *block, const char *key) -{ return ConfigValueBool(DeviceName.c_str(), block, key); } +inline std::optional ALCdevice::configValue(const std::string_view block, const std::string_view key) +{ return ConfigValueBool(DeviceName, block, key); } + +/** Stores the latest ALC device error. */ +void alcSetError(ALCdevice *device, ALCenum errorCode); #endif diff --git a/Engine/lib/openal-soft/alc/effects/autowah.cpp b/Engine/lib/openal-soft/alc/effects/autowah.cpp index 4f874ef29..b208996e7 100644 --- a/Engine/lib/openal-soft/alc/effects/autowah.cpp +++ b/Engine/lib/openal-soft/alc/effects/autowah.cpp @@ -22,24 +22,24 @@ #include #include +#include #include -#include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { @@ -50,35 +50,37 @@ constexpr float QFactor{5.0f}; struct AutowahState final : public EffectState { /* Effect parameters */ - float mAttackRate; - float mReleaseRate; - float mResonanceGain; - float mPeakGain; - float mFreqMinNorm; - float mBandwidthNorm; - float mEnvDelay; + float mAttackRate{}; + float mReleaseRate{}; + float mResonanceGain{}; + float mPeakGain{}; + float mFreqMinNorm{}; + float mBandwidthNorm{}; + float mEnvDelay{}; /* Filter components derived from the envelope. */ - struct { - float cos_w0; - float alpha; - } mEnv[BufferLineSize]; + struct FilterParam { + float cos_w0{}; + float alpha{}; + }; + std::array mEnv; - struct { + struct ChannelData { uint mTargetChannel{InvalidChannelIndex}; - /* Effect filters' history. */ - struct { - float z1, z2; - } mFilter; + struct FilterHistory { + float z1{}, z2{}; + }; + FilterHistory mFilter; /* Effect gains for each output channel */ - float mCurrentGain; - float mTargetGain; - } mChans[MaxAmbiChannels]; + float mCurrentGain{}; + float mTargetGain{}; + }; + std::array mChans; /* Effects buffers */ - alignas(16) float mBufferOut[BufferLineSize]; + alignas(16) FloatBufferLine mBufferOut{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -86,8 +88,6 @@ struct AutowahState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(AutowahState) }; void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -118,18 +118,19 @@ void AutowahState::deviceUpdate(const DeviceBase*, const BufferStorage*) } void AutowahState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); - const float ReleaseTime{clampf(props->Autowah.ReleaseTime, 0.001f, 1.0f)}; + const float ReleaseTime{std::clamp(props.ReleaseTime, 0.001f, 1.0f)}; - mAttackRate = std::exp(-1.0f / (props->Autowah.AttackTime*frequency)); + mAttackRate = std::exp(-1.0f / (props.AttackTime*frequency)); mReleaseRate = std::exp(-1.0f / (ReleaseTime*frequency)); /* 0-20dB Resonance Peak gain */ - mResonanceGain = std::sqrt(std::log10(props->Autowah.Resonance)*10.0f / 3.0f); - mPeakGain = 1.0f - std::log10(props->Autowah.PeakGain / GainScale); + mResonanceGain = std::sqrt(std::log10(props.Resonance)*10.0f / 3.0f); + mPeakGain = 1.0f - std::log10(props.PeakGain / GainScale); mFreqMinNorm = MinFreq / frequency; mBandwidthNorm = (MaxFreq-MinFreq) / frequency; @@ -155,23 +156,22 @@ void AutowahState::process(const size_t samplesToDo, float env_delay{mEnvDelay}; for(size_t i{0u};i < samplesToDo;i++) { - float w0, sample, a; - /* Envelope follower described on the book: Audio Effects, Theory, * Implementation and Application. */ - sample = peak_gain * std::fabs(samplesIn[0][i]); - a = (sample > env_delay) ? attack_rate : release_rate; + const float sample{peak_gain * std::fabs(samplesIn[0][i])}; + const float a{(sample > env_delay) ? attack_rate : release_rate}; env_delay = lerpf(sample, env_delay, a); /* Calculate the cos and alpha components for this sample's filter. */ - w0 = minf((bandwidth*env_delay + freq_min), 0.46f) * (al::numbers::pi_v*2.0f); + const float w0{std::min(bandwidth*env_delay + freq_min, 0.46f) * + (al::numbers::pi_v*2.0f)}; mEnv[i].cos_w0 = std::cos(w0); mEnv[i].alpha = std::sin(w0)/(2.0f * QFactor); } mEnvDelay = env_delay; - auto chandata = std::begin(mChans); + auto chandata = mChans.begin(); for(const auto &insamples : samplesIn) { const size_t outidx{chandata->mTargetChannel}; @@ -194,18 +194,18 @@ void AutowahState::process(const size_t samplesToDo, { const float alpha{mEnv[i].alpha}; const float cos_w0{mEnv[i].cos_w0}; - float input, output; - float a[3], b[3]; - b[0] = 1.0f + alpha*res_gain; - b[1] = -2.0f * cos_w0; - b[2] = 1.0f - alpha*res_gain; - a[0] = 1.0f + alpha/res_gain; - a[1] = -2.0f * cos_w0; - a[2] = 1.0f - alpha/res_gain; + const std::array b{ + 1.0f + alpha*res_gain, + -2.0f * cos_w0, + 1.0f - alpha*res_gain}; + const std::array a{ + 1.0f + alpha/res_gain, + -2.0f * cos_w0, + 1.0f - alpha/res_gain}; - input = insamples[i]; - output = input*(b[0]/a[0]) + z1; + const float input{insamples[i]}; + const float output{input*(b[0]/a[0]) + z1}; z1 = input*(b[1]/a[0]) - output*(a[1]/a[0]) + z2; z2 = input*(b[2]/a[0]) - output*(a[2]/a[0]); mBufferOut[i] = output; @@ -214,8 +214,8 @@ void AutowahState::process(const size_t samplesToDo, chandata->mFilter.z2 = z2; /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut, samplesToDo}, samplesOut[outidx].data(), chandata->mCurrentGain, - chandata->mTargetGain, samplesToDo); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut[outidx], + chandata->mCurrentGain, chandata->mTargetGain, samplesToDo); ++chandata; } } diff --git a/Engine/lib/openal-soft/alc/effects/base.h b/Engine/lib/openal-soft/alc/effects/base.h index 95695857a..a64880d26 100644 --- a/Engine/lib/openal-soft/alc/effects/base.h +++ b/Engine/lib/openal-soft/alc/effects/base.h @@ -4,23 +4,27 @@ #include "core/effects/base.h" -EffectStateFactory *NullStateFactory_getFactory(void); -EffectStateFactory *ReverbStateFactory_getFactory(void); -EffectStateFactory *StdReverbStateFactory_getFactory(void); -EffectStateFactory *AutowahStateFactory_getFactory(void); -EffectStateFactory *ChorusStateFactory_getFactory(void); -EffectStateFactory *CompressorStateFactory_getFactory(void); -EffectStateFactory *DistortionStateFactory_getFactory(void); -EffectStateFactory *EchoStateFactory_getFactory(void); -EffectStateFactory *EqualizerStateFactory_getFactory(void); -EffectStateFactory *FlangerStateFactory_getFactory(void); -EffectStateFactory *FshifterStateFactory_getFactory(void); -EffectStateFactory *ModulatorStateFactory_getFactory(void); -EffectStateFactory *PshifterStateFactory_getFactory(void); -EffectStateFactory* VmorpherStateFactory_getFactory(void); +/* This is a user config option for modifying the overall output of the reverb + * effect. + */ +inline float ReverbBoost{1.0f}; -EffectStateFactory *DedicatedStateFactory_getFactory(void); -EffectStateFactory *ConvolutionStateFactory_getFactory(void); +EffectStateFactory *NullStateFactory_getFactory(); +EffectStateFactory *ReverbStateFactory_getFactory(); +EffectStateFactory *ChorusStateFactory_getFactory(); +EffectStateFactory *AutowahStateFactory_getFactory(); +EffectStateFactory *CompressorStateFactory_getFactory(); +EffectStateFactory *DistortionStateFactory_getFactory(); +EffectStateFactory *EchoStateFactory_getFactory(); +EffectStateFactory *EqualizerStateFactory_getFactory(); +EffectStateFactory *FshifterStateFactory_getFactory(); +EffectStateFactory *ModulatorStateFactory_getFactory(); +EffectStateFactory *PshifterStateFactory_getFactory(); +EffectStateFactory* VmorpherStateFactory_getFactory(); + +EffectStateFactory *DedicatedStateFactory_getFactory(); + +EffectStateFactory *ConvolutionStateFactory_getFactory(); #endif /* EFFECTS_BASE_H */ diff --git a/Engine/lib/openal-soft/alc/effects/chorus.cpp b/Engine/lib/openal-soft/alc/effects/chorus.cpp index 10ccf9f6b..4e6d36c55 100644 --- a/Engine/lib/openal-soft/alc/effects/chorus.cpp +++ b/Engine/lib/openal-soft/alc/effects/chorus.cpp @@ -22,34 +22,44 @@ #include #include -#include +#include #include -#include +#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" +#include "core/cubic_tables.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "core/resampler_limits.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" +struct BufferStorage; namespace { using uint = unsigned int; +constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); +constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f}); +constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f}); +constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2}); +constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); + + struct ChorusState final : public EffectState { - al::vector mDelayBuffer; + std::vector mDelayBuffer; uint mOffset{0}; uint mLfoOffset{0}; @@ -58,16 +68,17 @@ struct ChorusState final : public EffectState { uint mLfoDisp{0}; /* Calculated delays to apply to the left and right outputs. */ - uint mModDelays[2][BufferLineSize]; + std::array,2> mModDelays{}; /* Temp storage for the modulated left and right outputs. */ - alignas(16) float mBuffer[2][BufferLineSize]; + alignas(16) std::array mBuffer{}; /* Gains for left and right outputs. */ - struct { - float Current[MaxAmbiChannels]{}; - float Target[MaxAmbiChannels]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; /* effect parameters */ ChorusWaveform mWaveform{}; @@ -78,66 +89,70 @@ struct ChorusState final : public EffectState { void calcTriangleDelays(const size_t todo); void calcSinusoidDelays(const size_t todo); - void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) override; - void process(const size_t samplesToDo, const al::span samplesIn, - const al::span samplesOut) override; + void deviceUpdate(const DeviceBase *device, const float MaxDelay); + void update(const ContextBase *context, const EffectSlot *slot, const ChorusWaveform waveform, + const float delay, const float depth, const float feedback, const float rate, + int phase, const EffectTarget target); - DEF_NEWDEL(ChorusState) + void deviceUpdate(const DeviceBase *device, const BufferStorage*) final; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) final; + void process(const size_t samplesToDo, const al::span samplesIn, + const al::span samplesOut) final; }; + void ChorusState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) { - constexpr float max_delay{maxf(ChorusMaxDelay, FlangerMaxDelay)}; - + constexpr auto MaxDelay = std::max(ChorusMaxDelay, FlangerMaxDelay); const auto frequency = static_cast(Device->Frequency); - const size_t maxlen{NextPowerOf2(float2uint(max_delay*2.0f*frequency) + 1u)}; + const size_t maxlen{NextPowerOf2(float2uint(MaxDelay*2.0f*frequency) + 1u)}; if(maxlen != mDelayBuffer.size()) decltype(mDelayBuffer)(maxlen).swap(mDelayBuffer); std::fill(mDelayBuffer.begin(), mDelayBuffer.end(), 0.0f); for(auto &e : mGains) { - std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); - std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); + e.Current.fill(0.0f); + e.Target.fill(0.0f); } } -void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, - const EffectProps *props, const EffectTarget target) +void ChorusState::update(const ContextBase *context, const EffectSlot *slot, + const EffectProps *props_, const EffectTarget target) { - constexpr int mindelay{(MaxResamplerPadding>>1) << MixerFracBits}; + static constexpr int mindelay{MaxResamplerEdge << gCubicTable.sTableBits}; + auto &props = std::get(*props_); /* The LFO depth is scaled to be relative to the sample delay. Clamp the * delay and depth to allow enough padding for resampling. */ - const DeviceBase *device{Context->mDevice}; + const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); - mWaveform = props->Chorus.Waveform; + mWaveform = props.Waveform; - mDelay = maxi(float2int(props->Chorus.Delay*frequency*MixerFracOne + 0.5f), mindelay); - mDepth = minf(props->Chorus.Depth * static_cast(mDelay), + const auto stepscale = float{frequency * gCubicTable.sTableSteps}; + mDelay = std::max(float2int(std::round(props.Delay * stepscale)), mindelay); + mDepth = std::min(static_cast(mDelay) * props.Depth, static_cast(mDelay - mindelay)); - mFeedback = props->Chorus.Feedback; + mFeedback = props.Feedback; /* Gains for left and right sides */ - static constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); - static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); - static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); - static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2}); - static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2}); - auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; - auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; + const bool ispairwise{device->mRenderMode == RenderMode::Pairwise}; + const auto lcoeffs = (!ispairwise) ? al::span{lcoeffs_nrml} : al::span{lcoeffs_pw}; + const auto rcoeffs = (!ispairwise) ? al::span{rcoeffs_nrml} : al::span{rcoeffs_pw}; + /* Attenuate the outputs by -3dB, since we duplicate a single mono input to + * separate left/right outputs. + */ + const auto gain = slot->Gain * (1.0f/al::numbers::sqrt2_v); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, lcoeffs.data(), Slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, rcoeffs.data(), Slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs, gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs, gain, mGains[1].Target); - float rate{props->Chorus.Rate}; - if(!(rate > 0.0f)) + if(!(props.Rate > 0.0f)) { mLfoOffset = 0; mLfoRange = 1; @@ -149,7 +164,9 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, /* Calculate LFO coefficient (number of samples per cycle). Limit the * max range to avoid overflow when calculating the displacement. */ - uint lfo_range{float2uint(minf(frequency/rate + 0.5f, float{INT_MAX/360 - 180}))}; + static constexpr int range_limit{std::numeric_limits::max()/360 - 180}; + const auto range = std::round(frequency / props.Rate); + const uint lfo_range{float2uint(std::min(range, float{range_limit}))}; mLfoOffset = mLfoOffset * lfo_range / mLfoRange; mLfoRange = lfo_range; @@ -164,8 +181,8 @@ void ChorusState::update(const ContextBase *Context, const EffectSlot *Slot, } /* Calculate lfo phase displacement */ - int phase{props->Chorus.Phase}; - if(phase < 0) phase = 360 + phase; + auto phase = props.Phase; + if(phase < 0) phase += 360; mLfoDisp = (mLfoRange*static_cast(phase) + 180) / 360; } } @@ -178,9 +195,6 @@ void ChorusState::calcTriangleDelays(const size_t todo) const float depth{mDepth}; const int delay{mDelay}; - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; @@ -188,25 +202,24 @@ void ChorusState::calcTriangleDelays(const size_t todo) }; uint offset{mLfoOffset}; + ASSUME(lfo_range > offset); + auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[0][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; + auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[1][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; @@ -219,9 +232,6 @@ void ChorusState::calcSinusoidDelays(const size_t todo) const float depth{mDepth}; const int delay{mDelay}; - ASSUME(lfo_range > 0); - ASSUME(todo > 0); - auto gen_lfo = [lfo_scale,depth,delay](const uint offset) -> uint { const float offset_norm{static_cast(offset) * lfo_scale}; @@ -229,25 +239,24 @@ void ChorusState::calcSinusoidDelays(const size_t todo) }; uint offset{mLfoOffset}; + ASSUME(lfo_range > offset); + auto ldelays = mModDelays[0].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[0][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + ldelays = std::generate_n(ldelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } offset = (mLfoOffset+mLfoDisp) % lfo_range; + auto rdelays = mModDelays[1].begin(); for(size_t i{0};i < todo;) { - size_t rem{minz(todo-i, lfo_range-offset)}; - do { - mModDelays[1][i++] = gen_lfo(offset++); - } while(--rem); - if(offset == lfo_range) - offset = 0; + const size_t rem{std::min(todo-i, size_t{lfo_range-offset})}; + rdelays = std::generate_n(rdelays, rem, [&offset,gen_lfo] { return gen_lfo(offset++); }); + if(offset == lfo_range) offset = 0; + i += rem; } mLfoOffset = static_cast(mLfoOffset+todo) % lfo_range; @@ -255,10 +264,10 @@ void ChorusState::calcSinusoidDelays(const size_t todo) void ChorusState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const size_t bufmask{mDelayBuffer.size()-1}; + const auto delaybuf = al::span{mDelayBuffer}; + const size_t bufmask{delaybuf.size()-1}; const float feedback{mFeedback}; const uint avgdelay{(static_cast(mDelay) + MixerFracHalf) >> MixerFracBits}; - float *RESTRICT delaybuf{mDelayBuffer.data()}; uint offset{mOffset}; if(mWaveform == ChorusWaveform::Sinusoid) @@ -266,35 +275,39 @@ void ChorusState::process(const size_t samplesToDo, const al::span(mBuffer[0])}; - float *RESTRICT rbuffer{al::assume_aligned<16>(mBuffer[1])}; + const auto ldelays = al::span{mModDelays[0]}; + const auto rdelays = al::span{mModDelays[1]}; + const auto lbuffer = al::span{mBuffer[0]}; + const auto rbuffer = al::span{mBuffer[1]}; for(size_t i{0u};i < samplesToDo;++i) { // Feed the buffer's input first (necessary for delays < 1). delaybuf[offset&bufmask] = samplesIn[0][i]; // Tap for the left output. - uint delay{offset - (ldelays[i]>>MixerFracBits)}; - float mu{static_cast(ldelays[i]&MixerFracMask) * (1.0f/MixerFracOne)}; - lbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); + size_t delay{offset - (ldelays[i] >> gCubicTable.sTableBits)}; + size_t phase{ldelays[i] & gCubicTable.sTableMask}; + lbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) + + delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) + + delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) + + delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase); // Tap for the right output. - delay = offset - (rdelays[i]>>MixerFracBits); - mu = static_cast(rdelays[i]&MixerFracMask) * (1.0f/MixerFracOne); - rbuffer[i] = cubic(delaybuf[(delay+1) & bufmask], delaybuf[(delay ) & bufmask], - delaybuf[(delay-1) & bufmask], delaybuf[(delay-2) & bufmask], mu); + delay = offset - (rdelays[i] >> gCubicTable.sTableBits); + phase = rdelays[i] & gCubicTable.sTableMask; + rbuffer[i] = delaybuf[(delay+1) & bufmask]*gCubicTable.getCoeff0(phase) + + delaybuf[(delay ) & bufmask]*gCubicTable.getCoeff1(phase) + + delaybuf[(delay-1) & bufmask]*gCubicTable.getCoeff2(phase) + + delaybuf[(delay-2) & bufmask]*gCubicTable.getCoeff3(phase); // Accumulate feedback from the average delay of the taps. delaybuf[offset&bufmask] += delaybuf[(offset-avgdelay) & bufmask] * feedback; ++offset; } - MixSamples({lbuffer, samplesToDo}, samplesOut, mGains[0].Current, mGains[0].Target, + MixSamples(lbuffer.first(samplesToDo), samplesOut, mGains[0].Current, mGains[0].Target, samplesToDo, 0); - MixSamples({rbuffer, samplesToDo}, samplesOut, mGains[1].Current, mGains[1].Target, + MixSamples(rbuffer.first(samplesToDo), samplesOut, mGains[1].Current, mGains[1].Target, samplesToDo, 0); mOffset = offset; @@ -306,15 +319,6 @@ struct ChorusStateFactory final : public EffectStateFactory { { return al::intrusive_ptr{new ChorusState{}}; } }; - -/* Flanger is basically a chorus with a really short delay. They can both use - * the same processing functions, so piggyback flanger on the chorus functions. - */ -struct FlangerStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new ChorusState{}}; } -}; - } // namespace EffectStateFactory *ChorusStateFactory_getFactory() @@ -322,9 +326,3 @@ EffectStateFactory *ChorusStateFactory_getFactory() static ChorusStateFactory ChorusFactory{}; return &ChorusFactory; } - -EffectStateFactory *FlangerStateFactory_getFactory() -{ - static FlangerStateFactory FlangerFactory{}; - return &FlangerFactory; -} diff --git a/Engine/lib/openal-soft/alc/effects/compressor.cpp b/Engine/lib/openal-soft/alc/effects/compressor.cpp index 0a7ed67a7..3197119c7 100644 --- a/Engine/lib/openal-soft/alc/effects/compressor.cpp +++ b/Engine/lib/openal-soft/alc/effects/compressor.cpp @@ -32,48 +32,49 @@ #include "config.h" +#include #include +#include #include -#include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" -#include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" -#include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +struct BufferStorage; struct ContextBase; namespace { -#define AMP_ENVELOPE_MIN 0.5f -#define AMP_ENVELOPE_MAX 2.0f +constexpr float AmpEnvelopeMin{0.5f}; +constexpr float AmpEnvelopeMax{2.0f}; -#define ATTACK_TIME 0.1f /* 100ms to rise from min to max */ -#define RELEASE_TIME 0.2f /* 200ms to drop from max to min */ +constexpr float AttackTime{0.1f}; /* 100ms to rise from min to max */ +constexpr float ReleaseTime{0.2f}; /* 200ms to drop from max to min */ struct CompressorState final : public EffectState { /* Effect gains for each channel */ - struct { + struct TargetGain { uint mTarget{InvalidChannelIndex}; float mGain{1.0f}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; /* Effect parameters */ bool mEnabled{true}; float mAttackMult{1.0f}; float mReleaseMult{1.0f}; float mEnvFollower{1.0f}; + alignas(16) FloatBufferLine mGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -81,8 +82,6 @@ struct CompressorState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(CompressorState) }; void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage*) @@ -90,20 +89,20 @@ void CompressorState::deviceUpdate(const DeviceBase *device, const BufferStorage /* Number of samples to do a full attack and release (non-integer sample * counts are okay). */ - const float attackCount{static_cast(device->Frequency) * ATTACK_TIME}; - const float releaseCount{static_cast(device->Frequency) * RELEASE_TIME}; + const float attackCount{static_cast(device->Frequency) * AttackTime}; + const float releaseCount{static_cast(device->Frequency) * ReleaseTime}; /* Calculate per-sample multipliers to attack and release at the desired * rates. */ - mAttackMult = std::pow(AMP_ENVELOPE_MAX/AMP_ENVELOPE_MIN, 1.0f/attackCount); - mReleaseMult = std::pow(AMP_ENVELOPE_MIN/AMP_ENVELOPE_MAX, 1.0f/releaseCount); + mAttackMult = std::pow(AmpEnvelopeMax/AmpEnvelopeMin, 1.0f/attackCount); + mReleaseMult = std::pow(AmpEnvelopeMin/AmpEnvelopeMax, 1.0f/releaseCount); } void CompressorState::update(const ContextBase*, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) { - mEnabled = props->Compressor.OnOff; + mEnabled = std::get(*props).OnOff; mOutTarget = target.Main->Buffer; auto set_channel = [this](size_t idx, uint outchan, float outgain) @@ -117,72 +116,62 @@ void CompressorState::update(const ContextBase*, const EffectSlot *slot, void CompressorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - for(size_t base{0u};base < samplesToDo;) + /* Generate the per-sample gains from the signal envelope. */ + float env{mEnvFollower}; + if(mEnabled) { - float gains[256]; - const size_t td{minz(256, samplesToDo-base)}; - - /* Generate the per-sample gains from the signal envelope. */ - float env{mEnvFollower}; - if(mEnabled) + for(size_t i{0u};i < samplesToDo;++i) { - for(size_t i{0u};i < td;++i) - { - /* Clamp the absolute amplitude to the defined envelope limits, - * then attack or release the envelope to reach it. - */ - const float amplitude{clampf(std::fabs(samplesIn[0][base+i]), AMP_ENVELOPE_MIN, - AMP_ENVELOPE_MAX)}; - if(amplitude > env) - env = minf(env*mAttackMult, amplitude); - else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); - - /* Apply the reciprocal of the envelope to normalize the volume - * (compress the dynamic range). - */ - gains[i] = 1.0f / env; - } - } - else - { - /* Same as above, except the amplitude is forced to 1. This helps - * ensure smooth gain changes when the compressor is turned on and - * off. + /* Clamp the absolute amplitude to the defined envelope limits, + * then attack or release the envelope to reach it. */ - for(size_t i{0u};i < td;++i) - { - const float amplitude{1.0f}; - if(amplitude > env) - env = minf(env*mAttackMult, amplitude); - else if(amplitude < env) - env = maxf(env*mReleaseMult, amplitude); + const float amplitude{std::clamp(std::fabs(samplesIn[0][i]), AmpEnvelopeMin, + AmpEnvelopeMax)}; + if(amplitude > env) + env = std::min(env*mAttackMult, amplitude); + else if(amplitude < env) + env = std::max(env*mReleaseMult, amplitude); - gains[i] = 1.0f / env; - } + /* Apply the reciprocal of the envelope to normalize the volume + * (compress the dynamic range). + */ + mGains[i] = 1.0f / env; } - mEnvFollower = env; - - /* Now compress the signal amplitude to output. */ - auto chan = std::cbegin(mChans); - for(const auto &input : samplesIn) + } + else + { + /* Same as above, except the amplitude is forced to 1. This helps + * ensure smooth gain changes when the compressor is turned on and off. + */ + for(size_t i{0u};i < samplesToDo;++i) { - const size_t outidx{chan->mTarget}; - if(outidx != InvalidChannelIndex) - { - const float *RESTRICT src{input.data() + base}; - float *RESTRICT dst{samplesOut[outidx].data() + base}; - const float gain{chan->mGain}; - if(!(std::fabs(gain) > GainSilenceThreshold)) - { - for(size_t i{0u};i < td;i++) - dst[i] += src[i] * gains[i] * gain; - } - } - ++chan; - } + const float amplitude{1.0f}; + if(amplitude > env) + env = std::min(env*mAttackMult, amplitude); + else if(amplitude < env) + env = std::max(env*mReleaseMult, amplitude); - base += td; + mGains[i] = 1.0f / env; + } + } + mEnvFollower = env; + + /* Now compress the signal amplitude to output. */ + auto chan = mChans.cbegin(); + for(const auto &input : samplesIn) + { + const size_t outidx{chan->mTarget}; + if(outidx != InvalidChannelIndex) + { + const auto dst = al::span{samplesOut[outidx]}; + const float gain{chan->mGain}; + if(!(std::fabs(gain) > GainSilenceThreshold)) + { + for(size_t i{0u};i < samplesToDo;++i) + dst[i] += input[i] * mGains[i] * gain; + } + } + ++chan; } } diff --git a/Engine/lib/openal-soft/alc/effects/convolution.cpp b/Engine/lib/openal-soft/alc/effects/convolution.cpp index 7f36c4157..7eb9cd6fd 100644 --- a/Engine/lib/openal-soft/alc/effects/convolution.cpp +++ b/Engine/lib/openal-soft/alc/effects/convolution.cpp @@ -3,13 +3,15 @@ #include #include +#include +#include #include #include +#include #include -#include #include -#include -#include +#include +#include #ifdef HAVE_SSE_INTRINSICS #include @@ -17,7 +19,6 @@ #include #endif -#include "albyte.h" #include "alcomplex.h" #include "almalloc.h" #include "alnumbers.h" @@ -30,56 +31,85 @@ #include "core/context.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/splitter.h" #include "core/fmt_traits.h" #include "core/mixer.h" +#include "core/uhjfilter.h" #include "intrusive_ptr.h" +#include "opthelpers.h" +#include "pffft.h" #include "polyphase_resampler.h" +#include "vecmat.h" #include "vector.h" namespace { -/* Convolution reverb is implemented using a segmented overlap-add method. The - * impulse response is broken up into multiple segments of 128 samples, and - * each segment has an FFT applied with a 256-sample buffer (the latter half - * left silent) to get its frequency-domain response. The resulting response - * has its positive/non-mirrored frequencies saved (129 bins) in each segment. +/* Convolution is implemented using a segmented overlap-add method. The impulse + * response is split into multiple segments of 128 samples, and each segment + * has an FFT applied with a 256-sample buffer (the latter half left silent) to + * get its frequency-domain response. The resulting response has its positive/ + * non-mirrored frequencies saved (129 bins) in each segment. Note that since + * the 0- and half-frequency bins are real for a real signal, their imaginary + * components are always 0 and can be dropped, allowing their real components + * to be combined so only 128 complex values are stored for the 129 bins. * - * Input samples are similarly broken up into 128-sample segments, with an FFT - * applied to each new incoming segment to get its 129 bins. A history of FFT'd - * input segments is maintained, equal to the length of the impulse response. + * Input samples are similarly broken up into 128-sample segments, with a 256- + * sample FFT applied to each new incoming segment to get its 129 bins. A + * history of FFT'd input segments is maintained, equal to the number of + * impulse response segments. * - * To apply the reverberation, each impulse response segment is convolved with + * To apply the convolution, each impulse response segment is convolved with * its paired input segment (using complex multiplies, far cheaper than FIRs), - * accumulating into a 256-bin FFT buffer. The input history is then shifted to - * align with later impulse response segments for next time. + * accumulating into a 129-bin FFT buffer. The input history is then shifted to + * align with later impulse response segments for the next input segment. * * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- * sample time-domain response for output, which is split in two halves. The * first half is the 128-sample output, and the second half is a 128-sample * (really, 127) delayed extension, which gets added to the output next time. - * Convolving two time-domain responses of lengths N and M results in a time- - * domain signal of length N+M-1, and this holds true regardless of the - * convolution being applied in the frequency domain, so these "overflow" - * samples need to be accounted for. + * Convolving two time-domain responses of length N results in a time-domain + * signal of length N*2 - 1, and this holds true regardless of the convolution + * being applied in the frequency domain, so these "overflow" samples need to + * be accounted for. * - * To avoid a delay with gathering enough input samples to apply an FFT with, - * the first segment is applied directly in the time-domain as the samples come - * in. Once enough have been retrieved, the FFT is applied on the input and - * it's paired with the remaining (FFT'd) filter segments for processing. + * To avoid a delay with gathering enough input samples for the FFT, the first + * segment is applied directly in the time-domain as the samples come in. Once + * enough have been retrieved, the FFT is applied on the input and it's paired + * with the remaining (FFT'd) filter segments for processing. */ -void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, FmtType srctype, - const size_t samples) noexcept +template +inline void LoadSampleArray(const al::span dst, const std::byte *src, + const std::size_t channel, const std::size_t srcstep) noexcept { -#define HANDLE_FMT(T) case T: al::LoadSampleArray(dst, src, srcstep, samples); break + using TypeTraits = al::FmtTypeTraits; + using SampleType = typename TypeTraits::Type; + const auto converter = TypeTraits{}; + assert(channel < srcstep); + + const auto srcspan = al::span{reinterpret_cast(src), dst.size()*srcstep}; + auto ssrc = srcspan.cbegin(); + std::generate(dst.begin(), dst.end(), [converter,channel,srcstep,&ssrc] + { + const auto ret = converter(ssrc[channel]); + ssrc += ptrdiff_t(srcstep); + return ret; + }); +} + +void LoadSamples(const al::span dst, const std::byte *src, const size_t channel, + const size_t srcstep, const FmtType srctype) noexcept +{ +#define HANDLE_FMT(T) case T: LoadSampleArray(dst, src, channel, srcstep); break switch(srctype) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); @@ -87,47 +117,50 @@ void LoadSamples(float *RESTRICT dst, const al::byte *src, const size_t srcstep, /* FIXME: Handle ADPCM decoding here. */ case FmtIMA4: case FmtMSADPCM: - std::fill_n(dst, samples, 0.0f); + std::fill(dst.begin(), dst.end(), 0.0f); break; } #undef HANDLE_FMT } -inline auto& GetAmbiScales(AmbiScaling scaletype) noexcept +constexpr auto GetAmbiScales(AmbiScaling scaletype) noexcept { switch(scaletype) { - case AmbiScaling::FuMa: return AmbiScale::FromFuMa(); - case AmbiScaling::SN3D: return AmbiScale::FromSN3D(); - case AmbiScaling::UHJ: return AmbiScale::FromUHJ(); + case AmbiScaling::FuMa: return al::span{AmbiScale::FromFuMa}; + case AmbiScaling::SN3D: return al::span{AmbiScale::FromSN3D}; + case AmbiScaling::UHJ: return al::span{AmbiScale::FromUHJ}; case AmbiScaling::N3D: break; } - return AmbiScale::FromN3D(); + return al::span{AmbiScale::FromN3D}; } -inline auto& GetAmbiLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbiLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa(); - return AmbiIndex::FromACN(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; + return al::span{AmbiIndex::FromACN}; } -inline auto& GetAmbi2DLayout(AmbiLayout layouttype) noexcept +constexpr auto GetAmbi2DLayout(AmbiLayout layouttype) noexcept { - if(layouttype == AmbiLayout::FuMa) return AmbiIndex::FromFuMa2D(); - return AmbiIndex::FromACN2D(); + if(layouttype == AmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa2D}; + return al::span{AmbiIndex::FromACN2D}; } -struct ChanMap { +constexpr float sin30{0.5f}; +constexpr float cos30{0.866025403785f}; +constexpr float sin45{al::numbers::sqrt2_v*0.5f}; +constexpr float cos45{al::numbers::sqrt2_v*0.5f}; +constexpr float sin110{ 0.939692620786f}; +constexpr float cos110{-0.342020143326f}; + +struct ChanPosMap { Channel channel; - float angle; - float elevation; + std::array pos; }; -constexpr float Deg2Rad(float x) noexcept -{ return static_cast(al::numbers::pi / 180.0 * x); } - using complex_f = std::complex; @@ -135,10 +168,11 @@ constexpr size_t ConvolveUpdateSize{256}; constexpr size_t ConvolveUpdateSamples{ConvolveUpdateSize / 2}; -void apply_fir(al::span dst, const float *RESTRICT src, const float *RESTRICT filter) +void apply_fir(al::span dst, const al::span input, const al::span filter) { + auto src = input.begin(); #ifdef HAVE_SSE_INTRINSICS - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&src,filter] { __m128 r4{_mm_setzero_ps()}; for(size_t j{0};j < ConvolveUpdateSamples;j+=4) @@ -148,39 +182,40 @@ void apply_fir(al::span dst, const float *RESTRICT src, const float *REST r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); } + ++src; + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - output = _mm_cvtss_f32(r4); - - ++src; - } + return _mm_cvtss_f32(r4); + }); #elif defined(HAVE_NEON) - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&src,filter] { float32x4_t r4{vdupq_n_f32(0.0f)}; for(size_t j{0};j < ConvolveUpdateSamples;j+=4) r4 = vmlaq_f32(r4, vld1q_f32(&src[j]), vld1q_f32(&filter[j])); - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - output = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); - ++src; - } + + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + }); #else - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&src,filter] { float ret{0.0f}; for(size_t j{0};j < ConvolveUpdateSamples;++j) ret += src[j] * filter[j]; - output = ret; ++src; - } + return ret; + }); #endif } + struct ConvolutionState final : public EffectState { FmtChannels mChannels{}; AmbiLayout mAmbiLayout{}; @@ -188,11 +223,13 @@ struct ConvolutionState final : public EffectState { uint mAmbiOrder{}; size_t mFifoPos{0}; - std::array mInput{}; + alignas(16) std::array mInput{}; al::vector,16> mFilter; al::vector,16> mOutput; - alignas(16) std::array mFftBuffer{}; + PFFFTSetup mFft{}; + alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mFftWorkBuffer{}; size_t mCurrentSegment{0}; size_t mNumConvolveSegs{0}; @@ -201,12 +238,11 @@ struct ConvolutionState final : public EffectState { alignas(16) FloatBufferLine mBuffer{}; float mHfScale{}, mLfScale{}; BandSplitter mFilter{}; - float Current[MAX_OUTPUT_CHANNELS]{}; - float Target[MAX_OUTPUT_CHANNELS]{}; + std::array Current{}; + std::array Target{}; }; - using ChannelDataArray = al::FlexArray; - std::unique_ptr mChans; - std::unique_ptr mComplexData; + std::vector mChans; + al::vector mComplexData; ConvolutionState() = default; @@ -222,24 +258,22 @@ struct ConvolutionState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(ConvolutionState) }; void ConvolutionState::NormalMix(const al::span samplesOut, const size_t samplesToDo) { - for(auto &chan : *mChans) - MixSamples({chan.mBuffer.data(), samplesToDo}, samplesOut, chan.Current, chan.Target, - samplesToDo, 0); + for(auto &chan : mChans) + MixSamples(al::span{chan.mBuffer}.first(samplesToDo), samplesOut, chan.Current, + chan.Target, samplesToDo, 0); } void ConvolutionState::UpsampleMix(const al::span samplesOut, const size_t samplesToDo) { - for(auto &chan : *mChans) + for(auto &chan : mChans) { - const al::span src{chan.mBuffer.data(), samplesToDo}; + const auto src = al::span{chan.mBuffer}.first(samplesToDo); chan.mFilter.processScale(src, chan.mHfScale, chan.mLfScale); MixSamples(src, samplesOut, chan.Current, chan.Target, samplesToDo, 0); } @@ -251,19 +285,23 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag using UhjDecoderType = UhjDecoder<512>; static constexpr auto DecoderPadding = UhjDecoderType::sInputPadding; - constexpr uint MaxConvolveAmbiOrder{1u}; + static constexpr uint MaxConvolveAmbiOrder{1u}; + + if(!mFft) + mFft = PFFFTSetup{ConvolveUpdateSize, PFFFT_REAL}; mFifoPos = 0; mInput.fill(0.0f); decltype(mFilter){}.swap(mFilter); decltype(mOutput){}.swap(mOutput); - mFftBuffer.fill(complex_f{}); + mFftBuffer.fill(0.0f); + mFftWorkBuffer.fill(0.0f); mCurrentSegment = 0; mNumConvolveSegs = 0; - mChans = nullptr; - mComplexData = nullptr; + decltype(mChans){}.swap(mChans); + decltype(mComplexData){}.swap(mComplexData); /* An empty buffer doesn't need a convolution filter. */ if(!buffer || buffer->mSampleLen < 1) return; @@ -271,14 +309,12 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag mChannels = buffer->mChannels; mAmbiLayout = IsUHJ(mChannels) ? AmbiLayout::FuMa : buffer->mAmbiLayout; mAmbiScaling = IsUHJ(mChannels) ? AmbiScaling::UHJ : buffer->mAmbiScaling; - mAmbiOrder = minu(buffer->mAmbiOrder, MaxConvolveAmbiOrder); + mAmbiOrder = std::min(buffer->mAmbiOrder, MaxConvolveAmbiOrder); - constexpr size_t m{ConvolveUpdateSize/2 + 1}; - const auto bytesPerSample = BytesFromFmt(buffer->mType); const auto realChannels = buffer->channelsFromFmt(); const auto numChannels = (mChannels == FmtUHJ2) ? 3u : ChannelsFromFmt(mChannels, mAmbiOrder); - mChans = ChannelDataArray::Create(numChannels); + mChans.resize(numChannels); /* The impulse response needs to have the same sample rate as the input and * output. The bsinc24 resampler is decent, but there is high-frequency @@ -293,7 +329,7 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag buffer->mSampleRate); const BandSplitter splitter{device->mXOverFreq / static_cast(device->Frequency)}; - for(auto &e : *mChans) + for(auto &e : mChans) e.mFilter = splitter; mFilter.resize(numChannels, {}); @@ -305,126 +341,150 @@ void ConvolutionState::deviceUpdate(const DeviceBase *device, const BufferStorag * segment is allocated to simplify handling. */ mNumConvolveSegs = (resampledCount+(ConvolveUpdateSamples-1)) / ConvolveUpdateSamples; - mNumConvolveSegs = maxz(mNumConvolveSegs, 2) - 1; + mNumConvolveSegs = std::max(mNumConvolveSegs, 2_uz) - 1_uz; - const size_t complex_length{mNumConvolveSegs * m * (numChannels+1)}; - mComplexData = std::make_unique(complex_length); - std::fill_n(mComplexData.get(), complex_length, complex_f{}); + const size_t complex_length{mNumConvolveSegs * ConvolveUpdateSize * (numChannels+1)}; + mComplexData.resize(complex_length, 0.0f); /* Load the samples from the buffer. */ const size_t srclinelength{RoundUp(buffer->mSampleLen+DecoderPadding, 16)}; - auto srcsamples = std::make_unique(srclinelength * numChannels); - std::fill_n(srcsamples.get(), srclinelength * numChannels, 0.0f); + auto srcsamples = std::vector(srclinelength * numChannels); + std::fill(srcsamples.begin(), srcsamples.end(), 0.0f); for(size_t c{0};c < numChannels && c < realChannels;++c) - LoadSamples(srcsamples.get() + srclinelength*c, buffer->mData.data() + bytesPerSample*c, - realChannels, buffer->mType, buffer->mSampleLen); + LoadSamples(al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen), + buffer->mData.data(), c, realChannels, buffer->mType); if(IsUHJ(mChannels)) { auto decoder = std::make_unique(); std::array samples{}; for(size_t c{0};c < numChannels;++c) - samples[c] = srcsamples.get() + srclinelength*c; + samples[c] = al::to_address(srcsamples.begin() + ptrdiff_t(srclinelength*c)); decoder->decode({samples.data(), numChannels}, buffer->mSampleLen, buffer->mSampleLen); } - auto ressamples = std::make_unique(buffer->mSampleLen + - (resampler ? resampledCount : 0)); - complex_f *filteriter = mComplexData.get() + mNumConvolveSegs*m; + auto ressamples = std::vector(buffer->mSampleLen + (resampler ? resampledCount : 0)); + auto ffttmp = al::vector(ConvolveUpdateSize); + auto fftbuffer = std::vector>(ConvolveUpdateSize); + + auto filteriter = mComplexData.begin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); for(size_t c{0};c < numChannels;++c) { + auto bufsamples = al::span{srcsamples}.subspan(srclinelength*c, buffer->mSampleLen); /* Resample to match the device. */ if(resampler) { - std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, - ressamples.get() + resampledCount); - resampler.process(buffer->mSampleLen, ressamples.get()+resampledCount, - resampledCount, ressamples.get()); + auto restmp = al::span{ressamples}.subspan(resampledCount, buffer->mSampleLen); + std::copy(bufsamples.cbegin(), bufsamples.cend(), restmp.begin()); + resampler.process(restmp, al::span{ressamples}.first(resampledCount)); } else - std::copy_n(srcsamples.get() + srclinelength*c, buffer->mSampleLen, ressamples.get()); + std::copy(bufsamples.cbegin(), bufsamples.cend(), ressamples.begin()); /* Store the first segment's samples in reverse in the time-domain, to * apply as a FIR filter. */ - const size_t first_size{minz(resampledCount, ConvolveUpdateSamples)}; - std::transform(ressamples.get(), ressamples.get()+first_size, mFilter[c].rbegin(), + const size_t first_size{std::min(size_t{resampledCount}, ConvolveUpdateSamples)}; + auto sampleseg = al::span{ressamples.cbegin(), first_size}; + std::transform(sampleseg.cbegin(), sampleseg.cend(), mFilter[c].rbegin(), [](const double d) noexcept -> float { return static_cast(d); }); - auto fftbuffer = std::vector>(ConvolveUpdateSize); size_t done{first_size}; for(size_t s{0};s < mNumConvolveSegs;++s) { - const size_t todo{minz(resampledCount-done, ConvolveUpdateSamples)}; + const size_t todo{std::min(resampledCount-done, ConvolveUpdateSamples)}; + sampleseg = al::span{ressamples}.subspan(done, todo); - auto iter = std::copy_n(&ressamples[done], todo, fftbuffer.begin()); + /* Apply a double-precision forward FFT for more precise frequency + * measurements. + */ + auto iter = std::copy(sampleseg.cbegin(), sampleseg.cend(), fftbuffer.begin()); done += todo; std::fill(iter, fftbuffer.end(), std::complex{}); + forward_fft(al::span{fftbuffer}); - forward_fft(al::as_span(fftbuffer)); - filteriter = std::copy_n(fftbuffer.cbegin(), m, filteriter); + /* Convert to, and pack in, a float buffer for PFFFT. Note that the + * first bin stores the real component of the half-frequency bin in + * the imaginary component. Also scale the FFT by its length so the + * iFFT'd output will be normalized. + */ + static constexpr float fftscale{1.0f / float{ConvolveUpdateSize}}; + for(size_t i{0};i < ConvolveUpdateSamples;++i) + { + ffttmp[i*2 ] = static_cast(fftbuffer[i].real()) * fftscale; + ffttmp[i*2 + 1] = static_cast((i == 0) ? + fftbuffer[ConvolveUpdateSamples].real() : fftbuffer[i].imag()) * fftscale; + } + /* Reorder backward to make it suitable for pffft_zconvolve and the + * subsequent pffft_transform(..., PFFFT_BACKWARD). + */ + mFft.zreorder(ffttmp.data(), al::to_address(filteriter), PFFFT_BACKWARD); + filteriter += ConvolveUpdateSize; } } } void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps* /*props*/, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { - /* NOTE: Stereo and Rear are slightly different from normal mixing (as - * defined in alu.cpp). These are 45 degrees from center, rather than the - * 30 degrees used there. - * - * TODO: LFE is not mixed to output. This will require each buffer channel + /* TODO: LFE is not mixed to output. This will require each buffer channel * to have its own output target since the main mixing buffer won't have an * LFE channel (due to being B-Format). */ - static constexpr ChanMap MonoMap[1]{ - { FrontCenter, 0.0f, 0.0f } - }, StereoMap[2]{ - { FrontLeft, Deg2Rad(-45.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) } - }, RearMap[2]{ - { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } - }, QuadMap[4]{ - { FrontLeft, Deg2Rad( -45.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 45.0f), Deg2Rad(0.0f) }, - { BackLeft, Deg2Rad(-135.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 135.0f), Deg2Rad(0.0f) } - }, X51Map[6]{ - { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { SideLeft, Deg2Rad(-110.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 110.0f), Deg2Rad(0.0f) } - }, X61Map[7]{ - { FrontLeft, Deg2Rad(-30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { BackCenter, Deg2Rad(180.0f), Deg2Rad(0.0f) }, - { SideLeft, Deg2Rad(-90.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } - }, X71Map[8]{ - { FrontLeft, Deg2Rad( -30.0f), Deg2Rad(0.0f) }, - { FrontRight, Deg2Rad( 30.0f), Deg2Rad(0.0f) }, - { FrontCenter, Deg2Rad( 0.0f), Deg2Rad(0.0f) }, - { LFE, 0.0f, 0.0f }, - { BackLeft, Deg2Rad(-150.0f), Deg2Rad(0.0f) }, - { BackRight, Deg2Rad( 150.0f), Deg2Rad(0.0f) }, - { SideLeft, Deg2Rad( -90.0f), Deg2Rad(0.0f) }, - { SideRight, Deg2Rad( 90.0f), Deg2Rad(0.0f) } + static constexpr std::array MonoMap{ + ChanPosMap{FrontCenter, std::array{0.0f, 0.0f, -1.0f}} + }; + static constexpr std::array StereoMap{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + }; + static constexpr std::array RearMap{ + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + }; + static constexpr std::array QuadMap{ + ChanPosMap{FrontLeft, std::array{-sin45, 0.0f, -cos45}}, + ChanPosMap{FrontRight, std::array{ sin45, 0.0f, -cos45}}, + ChanPosMap{BackLeft, std::array{-sin45, 0.0f, cos45}}, + ChanPosMap{BackRight, std::array{ sin45, 0.0f, cos45}}, + }; + static constexpr std::array X51Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{SideLeft, std::array{-sin110, 0.0f, -cos110}}, + ChanPosMap{SideRight, std::array{ sin110, 0.0f, -cos110}}, + }; + static constexpr std::array X61Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackCenter, std::array{ 0.0f, 0.0f, 1.0f} }, + ChanPosMap{SideLeft, std::array{-1.0f, 0.0f, 0.0f} }, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f} }, + }; + static constexpr std::array X71Map{ + ChanPosMap{FrontLeft, std::array{-sin30, 0.0f, -cos30}}, + ChanPosMap{FrontRight, std::array{ sin30, 0.0f, -cos30}}, + ChanPosMap{FrontCenter, std::array{ 0.0f, 0.0f, -1.0f}}, + ChanPosMap{LFE, {}}, + ChanPosMap{BackLeft, std::array{-sin30, 0.0f, cos30}}, + ChanPosMap{BackRight, std::array{ sin30, 0.0f, cos30}}, + ChanPosMap{SideLeft, std::array{ -1.0f, 0.0f, 0.0f}}, + ChanPosMap{SideRight, std::array{ 1.0f, 0.0f, 0.0f}}, }; if(mNumConvolveSegs < 1) UNLIKELY return; + auto &props = std::get(*props_); mMix = &ConvolutionState::NormalMix; - for(auto &chan : *mChans) - std::fill(std::begin(chan.Target), std::end(chan.Target), 0.0f); + for(auto &chan : mChans) + std::fill(chan.Target.begin(), chan.Target.end(), 0.0f); const float gain{slot->Gain}; if(IsAmbisonic(mChannels)) { @@ -432,49 +492,68 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot if(mChannels == FmtUHJ2 && !device->mUhjEncoder) { mMix = &ConvolutionState::UpsampleMix; - (*mChans)[0].mHfScale = 1.0f; - (*mChans)[0].mLfScale = DecoderBase::sWLFScale; - (*mChans)[1].mHfScale = 1.0f; - (*mChans)[1].mLfScale = DecoderBase::sXYLFScale; - (*mChans)[2].mHfScale = 1.0f; - (*mChans)[2].mLfScale = DecoderBase::sXYLFScale; + mChans[0].mHfScale = 1.0f; + mChans[0].mLfScale = DecoderBase::sWLFScale; + mChans[1].mHfScale = 1.0f; + mChans[1].mLfScale = DecoderBase::sXYLFScale; + mChans[2].mHfScale = 1.0f; + mChans[2].mLfScale = DecoderBase::sXYLFScale; } else if(device->mAmbiOrder > mAmbiOrder) { mMix = &ConvolutionState::UpsampleMix; const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); - (*mChans)[0].mHfScale = scales[0]; - (*mChans)[0].mLfScale = 1.0f; - for(size_t i{1};i < mChans->size();++i) + mChans[0].mHfScale = scales[0]; + mChans[0].mLfScale = 1.0f; + for(size_t i{1};i < mChans.size();++i) { - (*mChans)[i].mHfScale = scales[1]; - (*mChans)[i].mLfScale = 1.0f; + mChans[i].mHfScale = scales[1]; + mChans[i].mLfScale = 1.0f; } } mOutTarget = target.Main->Buffer; - auto&& scales = GetAmbiScales(mAmbiScaling); - const uint8_t *index_map{Is2DAmbisonic(mChannels) ? - GetAmbi2DLayout(mAmbiLayout).data() : - GetAmbiLayout(mAmbiLayout).data()}; + alu::Vector N{props.OrientAt[0], props.OrientAt[1], props.OrientAt[2], 0.0f}; + N.normalize(); + alu::Vector V{props.OrientUp[0], props.OrientUp[1], props.OrientUp[2], 0.0f}; + V.normalize(); + /* Build and normalize right-vector */ + alu::Vector U{N.cross_product(V)}; + U.normalize(); + + const std::array mixmatrix{ + std::array{1.0f, 0.0f, 0.0f, 0.0f}, + std::array{0.0f, U[0], -U[1], U[2]}, + std::array{0.0f, -V[0], V[1], -V[2]}, + std::array{0.0f, -N[0], N[1], -N[2]}, + }; + + const auto scales = GetAmbiScales(mAmbiScaling); + const auto index_map = Is2DAmbisonic(mChannels) ? + al::span{GetAmbi2DLayout(mAmbiLayout)}.subspan(0) : + al::span{GetAmbiLayout(mAmbiLayout)}.subspan(0); std::array coeffs{}; - for(size_t c{0u};c < mChans->size();++c) + for(size_t c{0u};c < mChans.size();++c) { const size_t acn{index_map[c]}; - coeffs[acn] = scales[acn]; - ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[c].Target); - coeffs[acn] = 0.0f; + const float scale{scales[acn]}; + + std::transform(mixmatrix[acn].cbegin(), mixmatrix[acn].cend(), coeffs.begin(), + [scale](const float in) noexcept -> float { return in * scale; }); + + ComputePanGains(target.Main, coeffs, gain, mChans[c].Target); } } else { DeviceBase *device{context->mDevice}; - al::span chanmap{}; + al::span chanmap{}; switch(mChannels) { case FmtMono: chanmap = MonoMap; break; + case FmtMonoDup: chanmap = MonoMap; break; case FmtSuperStereo: case FmtStereo: chanmap = StereoMap; break; case FmtRear: chanmap = RearMap; break; @@ -493,28 +572,55 @@ void ConvolutionState::update(const ContextBase *context, const EffectSlot *slot mOutTarget = target.Main->Buffer; if(device->mRenderMode == RenderMode::Pairwise) { - auto ScaleAzimuthFront = [](float azimuth, float scale) -> float + /* Scales the azimuth of the given vector by 3 if it's in front. + * Effectively scales +/-30 degrees to +/-90 degrees, leaving > +90 + * and < -90 alone. + */ + auto ScaleAzimuthFront = [](std::array pos) -> std::array { - constexpr float half_pi{al::numbers::pi_v*0.5f}; - const float abs_azi{std::fabs(azimuth)}; - if(!(abs_azi >= half_pi)) - return std::copysign(minf(abs_azi*scale, half_pi), azimuth); - return azimuth; + if(pos[2] < 0.0f) + { + /* Normalize the length of the x,z components for a 2D + * vector of the azimuth angle. Negate Z since {0,0,-1} is + * angle 0. + */ + const float len2d{std::sqrt(pos[0]*pos[0] + pos[2]*pos[2])}; + float x{pos[0] / len2d}; + float z{-pos[2] / len2d}; + + /* Z > cos(pi/6) = -30 < azimuth < 30 degrees. */ + if(z > cos30) + { + /* Triple the angle represented by x,z. */ + x = x*3.0f - x*x*x*4.0f; + z = z*z*z*4.0f - z*3.0f; + + /* Scale the vector back to fit in 3D. */ + pos[0] = x * len2d; + pos[2] = -z * len2d; + } + else + { + /* If azimuth >= 30 degrees, clamp to 90 degrees. */ + pos[0] = std::copysign(len2d, pos[0]); + pos[2] = 0.0f; + } + } + return pos; }; for(size_t i{0};i < chanmap.size();++i) { if(chanmap[i].channel == LFE) continue; - const auto coeffs = CalcAngleCoeffs(ScaleAzimuthFront(chanmap[i].angle, 2.0f), - chanmap[i].elevation, 0.0f); - ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + const auto coeffs = CalcDirectionCoeffs(ScaleAzimuthFront(chanmap[i].pos), 0.0f); + ComputePanGains(target.Main, coeffs, gain, mChans[i].Target); } } else for(size_t i{0};i < chanmap.size();++i) { if(chanmap[i].channel == LFE) continue; - const auto coeffs = CalcAngleCoeffs(chanmap[i].angle, chanmap[i].elevation, 0.0f); - ComputePanGains(target.Main, coeffs.data(), gain, (*mChans)[i].Target); + const auto coeffs = CalcDirectionCoeffs(chanmap[i].pos, 0.0f); + ComputePanGains(target.Main, coeffs, gain, mChans[i].Target); } } } @@ -525,27 +631,26 @@ void ConvolutionState::process(const size_t samplesToDo, if(mNumConvolveSegs < 1) UNLIKELY return; - constexpr size_t m{ConvolveUpdateSize/2 + 1}; size_t curseg{mCurrentSegment}; - auto &chans = *mChans; for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; + const size_t todo{std::min(ConvolveUpdateSamples-mFifoPos, samplesToDo-base)}; - std::copy_n(samplesIn[0].begin() + base, todo, - mInput.begin()+ConvolveUpdateSamples+mFifoPos); + std::copy_n(samplesIn[0].begin() + ptrdiff_t(base), todo, + mInput.begin()+ptrdiff_t(ConvolveUpdateSamples+mFifoPos)); /* Apply the FIR for the newly retrieved input samples, and combine it * with the inverse FFT'd output samples. */ - for(size_t c{0};c < chans.size();++c) + for(size_t c{0};c < mChans.size();++c) { - auto buf_iter = chans[c].mBuffer.begin() + base; - apply_fir({buf_iter, todo}, mInput.data()+1 + mFifoPos, mFilter[c].data()); + auto outspan = al::span{mChans[c].mBuffer}.subspan(base, todo); + apply_fir(outspan, al::span{mInput}.subspan(1+mFifoPos), mFilter[c]); - auto fifo_iter = mOutput[c].begin() + mFifoPos; - std::transform(fifo_iter, fifo_iter+todo, buf_iter, buf_iter, std::plus<>{}); + auto fifospan = al::span{mOutput[c]}.subspan(mFifoPos, todo); + std::transform(fifospan.cbegin(), fifospan.cend(), outspan.cbegin(), outspan.begin(), + std::plus{}); } mFifoPos += todo; @@ -557,59 +662,51 @@ void ConvolutionState::process(const size_t samplesToDo, /* Move the newest input to the front for the next iteration's history. */ std::copy(mInput.cbegin()+ConvolveUpdateSamples, mInput.cend(), mInput.begin()); + std::fill(mInput.begin()+ConvolveUpdateSamples, mInput.end(), 0.0f); - /* Calculate the frequency domain response and add the relevant + /* Calculate the frequency-domain response and add the relevant * frequency bins to the FFT history. */ - auto fftiter = std::copy_n(mInput.cbegin(), ConvolveUpdateSamples, mFftBuffer.begin()); - std::fill(fftiter, mFftBuffer.end(), complex_f{}); - forward_fft(al::as_span(mFftBuffer)); + mFft.transform(mInput.data(), &mComplexData[curseg*ConvolveUpdateSize], + mFftWorkBuffer.data(), PFFFT_FORWARD); - std::copy_n(mFftBuffer.cbegin(), m, &mComplexData[curseg*m]); - - const complex_f *RESTRICT filter{mComplexData.get() + mNumConvolveSegs*m}; - for(size_t c{0};c < chans.size();++c) + auto filter = mComplexData.cbegin() + ptrdiff_t(mNumConvolveSegs*ConvolveUpdateSize); + for(size_t c{0};c < mChans.size();++c) { - std::fill_n(mFftBuffer.begin(), m, complex_f{}); - /* Convolve each input segment with its IR filter counterpart * (aligned in time). */ - const complex_f *RESTRICT input{&mComplexData[curseg*m]}; + mFftBuffer.fill(0.0f); + auto input = mComplexData.cbegin() + ptrdiff_t(curseg*ConvolveUpdateSize); for(size_t s{curseg};s < mNumConvolveSegs;++s) { - for(size_t i{0};i < m;++i,++input,++filter) - mFftBuffer[i] += *input * *filter; + mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += ConvolveUpdateSize; + filter += ConvolveUpdateSize; } - input = mComplexData.get(); + input = mComplexData.cbegin(); for(size_t s{0};s < curseg;++s) { - for(size_t i{0};i < m;++i,++input,++filter) - mFftBuffer[i] += *input * *filter; + mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += ConvolveUpdateSize; + filter += ConvolveUpdateSize; } - /* Reconstruct the mirrored/negative frequencies to do a proper - * inverse FFT. - */ - for(size_t i{m};i < ConvolveUpdateSize;++i) - mFftBuffer[i] = std::conj(mFftBuffer[ConvolveUpdateSize-i]); - /* Apply iFFT to get the 256 (really 255) samples for output. The * 128 output samples are combined with the last output's 127 * second-half samples (and this output's second half is * subsequently saved for next time). */ - inverse_fft(al::as_span(mFftBuffer)); + mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_BACKWARD); - /* The iFFT'd response is scaled up by the number of bins, so apply - * the inverse to normalize the output. - */ - for(size_t i{0};i < ConvolveUpdateSamples;++i) - mOutput[c][i] = - (mFftBuffer[i].real()+mOutput[c][ConvolveUpdateSamples+i]) * - (1.0f/float{ConvolveUpdateSize}); - for(size_t i{0};i < ConvolveUpdateSamples;++i) - mOutput[c][ConvolveUpdateSamples+i] = mFftBuffer[ConvolveUpdateSamples+i].real(); + /* The filter was attenuated, so the response is already scaled. */ + std::transform(mFftBuffer.cbegin(), mFftBuffer.cbegin()+ConvolveUpdateSamples, + mOutput[c].cbegin()+ConvolveUpdateSamples, mOutput[c].begin(), std::plus{}); + std::copy(mFftBuffer.cbegin()+ConvolveUpdateSamples, mFftBuffer.cend(), + mOutput[c].begin()+ConvolveUpdateSamples); } /* Shift the input history. */ diff --git a/Engine/lib/openal-soft/alc/effects/dedicated.cpp b/Engine/lib/openal-soft/alc/effects/dedicated.cpp index 047e6761a..df1bd5ed0 100644 --- a/Engine/lib/openal-soft/alc/effects/dedicated.cpp +++ b/Engine/lib/openal-soft/alc/effects/dedicated.cpp @@ -23,18 +23,19 @@ #include #include #include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alspan.h" #include "core/bufferline.h" #include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; struct ContextBase; @@ -47,45 +48,36 @@ struct DedicatedState final : public EffectState { * gains for all possible output channels and not just the main ambisonic * buffer. */ - float mCurrentGains[MAX_OUTPUT_CHANNELS]; - float mTargetGains[MAX_OUTPUT_CHANNELS]; + std::array mCurrentGains{}; + std::array mTargetGains{}; - void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; - void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, - const EffectTarget target) override; + void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) final; + void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props_, + const EffectTarget target) final; void process(const size_t samplesToDo, const al::span samplesIn, - const al::span samplesOut) override; - - DEF_NEWDEL(DedicatedState) + const al::span samplesOut) final; }; void DedicatedState::deviceUpdate(const DeviceBase*, const BufferStorage*) { - std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); + std::fill(mCurrentGains.begin(), mCurrentGains.end(), 0.0f); } void DedicatedState::update(const ContextBase*, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); + std::fill(mTargetGains.begin(), mTargetGains.end(), 0.0f); - const float Gain{slot->Gain * props->Dedicated.Gain}; + auto &props = std::get(*props_); + const float Gain{slot->Gain * props.Gain}; - if(slot->EffectType == EffectSlotType::DedicatedLFE) - { - const uint idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; - if(idx != InvalidChannelIndex) - { - mOutTarget = target.RealOut->Buffer; - mTargetGains[idx] = Gain; - } - } - else if(slot->EffectType == EffectSlotType::DedicatedDialog) + if(props.Target == DedicatedProps::Dialog) { /* Dialog goes to the front-center speaker if it exists, otherwise it - * plays from the front-center location. */ - const uint idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] + * plays from the front-center location. + */ + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[FrontCenter] : InvalidChannelIndex}; if(idx != InvalidChannelIndex) { @@ -94,17 +86,26 @@ void DedicatedState::update(const ContextBase*, const EffectSlot *slot, } else { - static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), Gain, mTargetGains); + ComputePanGains(target.Main, coeffs, Gain, mTargetGains); + } + } + else if(props.Target == DedicatedProps::Lfe) + { + const size_t idx{target.RealOut ? target.RealOut->ChannelIndex[LFE] : InvalidChannelIndex}; + if(idx != InvalidChannelIndex) + { + mOutTarget = target.RealOut->Buffer; + mTargetGains[idx] = Gain; } } } void DedicatedState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - MixSamples({samplesIn[0].data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, + MixSamples(al::span{samplesIn[0]}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, samplesToDo, 0); } diff --git a/Engine/lib/openal-soft/alc/effects/distortion.cpp b/Engine/lib/openal-soft/alc/effects/distortion.cpp index b4e2167e4..202e4fd79 100644 --- a/Engine/lib/openal-soft/alc/effects/distortion.cpp +++ b/Engine/lib/openal-soft/alc/effects/distortion.cpp @@ -22,30 +22,32 @@ #include #include +#include #include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { struct DistortionState final : public EffectState { /* Effect gains for each channel */ - float mGain[MaxAmbiChannels]{}; + std::array mGain{}; /* Effect parameters */ BiquadFilter mLowpass; @@ -53,7 +55,7 @@ struct DistortionState final : public EffectState { float mAttenuation{}; float mEdgeCoeff{}; - alignas(16) float mBuffer[2][BufferLineSize]{}; + alignas(16) std::array mBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -61,8 +63,6 @@ struct DistortionState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(DistortionState) }; void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -72,16 +72,16 @@ void DistortionState::deviceUpdate(const DeviceBase*, const BufferStorage*) } void DistortionState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; /* Store waveshaper edge settings. */ - const float edge{minf(std::sin(al::numbers::pi_v*0.5f * props->Distortion.Edge), - 0.99f)}; + const float edge{std::min(std::sin(al::numbers::pi_v*0.5f * props.Edge), 0.99f)}; mEdgeCoeff = 2.0f * edge / (1.0f-edge); - float cutoff{props->Distortion.LowpassCutoff}; + float cutoff{props.LowpassCutoff}; /* Bandwidth value is constant in octaves. */ float bandwidth{(cutoff / 2.0f) / (cutoff * 0.67f)}; /* Divide normalized frequency by the amount of oversampling done during @@ -90,15 +90,15 @@ void DistortionState::update(const ContextBase *context, const EffectSlot *slot, auto frequency = static_cast(device->Frequency); mLowpass.setParamsFromBandwidth(BiquadType::LowPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - cutoff = props->Distortion.EQCenter; + cutoff = props.EQCenter; /* Convert bandwidth in Hz to octaves. */ - bandwidth = props->Distortion.EQBandwidth / (cutoff * 0.67f); + bandwidth = props.EQBandwidth / (cutoff * 0.67f); mBandpass.setParamsFromBandwidth(BiquadType::BandPass, cutoff/frequency/4.0f, 1.0f, bandwidth); - static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), slot->Gain*props->Distortion.Gain, mGain); + ComputePanGains(target.Main, coeffs, slot->Gain*props.Gain, mGain); } void DistortionState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) @@ -111,7 +111,7 @@ void DistortionState::process(const size_t samplesToDo, const al::span float { - smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); - smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)) * -1.0f; - smp = (1.0f + fc) * smp/(1.0f + fc*std::abs(smp)); + smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)); + smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)) * -1.0f; + smp = (1.0f + fc) * smp/(1.0f + fc*std::fabs(smp)); return smp; }; - std::transform(std::begin(mBuffer[1]), std::begin(mBuffer[1])+todo, std::begin(mBuffer[0]), + std::transform(mBuffer[1].begin(), mBuffer[1].begin()+todo, mBuffer[0].begin(), proc_sample); /* Third step, do bandpass filtering of distorted signal. */ - mBandpass.process({mBuffer[0], todo}, mBuffer[1]); + mBandpass.process(al::span{mBuffer[0]}.first(todo), mBuffer[1]); todo >>= 2; - const float *outgains{mGain}; - for(FloatBufferLine &output : samplesOut) + auto outgains = mGain.cbegin(); + auto proc_bufline = [this,base,todo,&outgains](FloatBufferSpan output) { /* Fourth step, final, do attenuation and perform decimation, * storing only one sample out of four. */ const float gain{*(outgains++)}; if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; + return; - for(size_t i{0u};i < todo;i++) - output[base+i] += gain * mBuffer[1][i*4]; - } + auto src = mBuffer[1].cbegin(); + const auto dst = al::span{output}.subspan(base, todo); + auto dec_sample = [gain,&src](float sample) noexcept -> float + { + sample += *src * gain; + src += 4; + return sample; + }; + std::transform(dst.begin(), dst.end(), dst.begin(), dec_sample); + }; + std::for_each(samplesOut.begin(), samplesOut.end(), proc_bufline); base += todo; } diff --git a/Engine/lib/openal-soft/alc/effects/echo.cpp b/Engine/lib/openal-soft/alc/effects/echo.cpp index a69529dc1..a303117d9 100644 --- a/Engine/lib/openal-soft/alc/effects/echo.cpp +++ b/Engine/lib/openal-soft/alc/effects/echo.cpp @@ -22,25 +22,26 @@ #include #include +#include #include -#include -#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "vector.h" +struct BufferStorage; namespace { @@ -49,33 +50,30 @@ using uint = unsigned int; constexpr float LowpassFreqRef{5000.0f}; struct EchoState final : public EffectState { - al::vector mSampleBuffer; + std::vector mSampleBuffer; // The echo is two tap. The delay is the number of samples from before the // current offset - struct { - size_t delay{0u}; - } mTap[2]; + std::array mDelayTap{}; size_t mOffset{0u}; /* The panning gains for the two taps */ - struct { - float Current[MaxAmbiChannels]{}; - float Target[MaxAmbiChannels]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; BiquadFilter mFilter; float mFeedGain{0.0f}; - alignas(16) float mTempBuffer[2][BufferLineSize]; + alignas(16) std::array mTempBuffer{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(EchoState) }; void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) @@ -87,61 +85,62 @@ void EchoState::deviceUpdate(const DeviceBase *Device, const BufferStorage*) const uint maxlen{NextPowerOf2(float2uint(EchoMaxDelay*frequency + 0.5f) + float2uint(EchoMaxLRDelay*frequency + 0.5f))}; if(maxlen != mSampleBuffer.size()) - al::vector(maxlen).swap(mSampleBuffer); + decltype(mSampleBuffer)(maxlen).swap(mSampleBuffer); std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); for(auto &e : mGains) { - std::fill(std::begin(e.Current), std::end(e.Current), 0.0f); - std::fill(std::begin(e.Target), std::end(e.Target), 0.0f); + std::fill(e.Current.begin(), e.Current.end(), 0.0f); + std::fill(e.Target.begin(), e.Target.end(), 0.0f); } } void EchoState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const auto frequency = static_cast(device->Frequency); - mTap[0].delay = maxu(float2uint(props->Echo.Delay*frequency + 0.5f), 1); - mTap[1].delay = float2uint(props->Echo.LRDelay*frequency + 0.5f) + mTap[0].delay; + mDelayTap[0] = std::max(float2uint(std::round(props.Delay*frequency)), 1u); + mDelayTap[1] = float2uint(std::round(props.LRDelay*frequency)) + mDelayTap[0]; - const float gainhf{maxf(1.0f - props->Echo.Damping, 0.0625f)}; /* Limit -24dB */ + const float gainhf{std::max(1.0f - props.Damping, 0.0625f)}; /* Limit -24dB */ mFilter.setParamsFromSlope(BiquadType::HighShelf, LowpassFreqRef/frequency, gainhf, 1.0f); - mFeedGain = props->Echo.Feedback; + mFeedGain = props.Feedback; - /* Convert echo spread (where 0 = center, +/-1 = sides) to angle. */ - const float angle{std::asin(props->Echo.Spread)}; + /* Convert echo spread (where 0 = center, +/-1 = sides) to a 2D vector. */ + const float x{props.Spread}; /* +x = left */ + const float z{std::sqrt(1.0f - x*x)}; - const auto coeffs0 = CalcAngleCoeffs(-angle, 0.0f, 0.0f); - const auto coeffs1 = CalcAngleCoeffs( angle, 0.0f, 0.0f); + const auto coeffs0 = CalcAmbiCoeffs( x, 0.0f, z, 0.0f); + const auto coeffs1 = CalcAmbiCoeffs(-x, 0.0f, z, 0.0f); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs0.data(), slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, coeffs1.data(), slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, coeffs0, slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, coeffs1, slot->Gain, mGains[1].Target); } void EchoState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const size_t mask{mSampleBuffer.size()-1}; - float *RESTRICT delaybuf{mSampleBuffer.data()}; + const auto delaybuf = al::span{mSampleBuffer}; + const size_t mask{delaybuf.size()-1}; size_t offset{mOffset}; - size_t tap1{offset - mTap[0].delay}; - size_t tap2{offset - mTap[1].delay}; - float z1, z2; + size_t tap1{offset - mDelayTap[0]}; + size_t tap2{offset - mDelayTap[1]}; ASSUME(samplesToDo > 0); const BiquadFilter filter{mFilter}; - std::tie(z1, z2) = mFilter.getComponents(); + auto [z1, z2] = mFilter.getComponents(); for(size_t i{0u};i < samplesToDo;) { offset &= mask; tap1 &= mask; tap2 &= mask; - size_t td{minz(mask+1 - maxz(offset, maxz(tap1, tap2)), samplesToDo-i)}; + size_t td{std::min(mask+1 - std::max(offset, std::max(tap1, tap2)), samplesToDo-i)}; do { /* Feed the delay buffer's input first. */ delaybuf[offset] = samplesIn[0][i]; @@ -161,8 +160,8 @@ void EchoState::process(const size_t samplesToDo, const al::span #include +#include #include #include -#include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { @@ -86,16 +86,17 @@ namespace { struct EqualizerState final : public EffectState { - struct { + struct OutParams { uint mTargetChannel{InvalidChannelIndex}; /* Effect parameters */ - BiquadFilter mFilter[4]; + std::array mFilter; /* Effect gains for each channel */ float mCurrentGain{}; float mTargetGain{}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; alignas(16) FloatBufferLine mSampleBuffer{}; @@ -105,8 +106,6 @@ struct EqualizerState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(EqualizerState) }; void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -114,18 +113,17 @@ void EqualizerState::deviceUpdate(const DeviceBase*, const BufferStorage*) for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; - std::for_each(std::begin(e.mFilter), std::end(e.mFilter), - std::mem_fn(&BiquadFilter::clear)); + std::for_each(e.mFilter.begin(), e.mFilter.end(), std::mem_fn(&BiquadFilter::clear)); e.mCurrentGain = 0.0f; } } void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; auto frequency = static_cast(device->Frequency); - float gain, f0norm; /* Calculate coefficients for the each type of filter. Note that the shelf * and peaking filters' gain is for the centerpoint of the transition band, @@ -133,22 +131,22 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, * property gains need their dB halved (sqrt of linear gain) for the * shelf/peak to reach the provided gain. */ - gain = std::sqrt(props->Equalizer.LowGain); - f0norm = props->Equalizer.LowCutoff / frequency; + float gain{std::sqrt(props.LowGain)}; + float f0norm{props.LowCutoff / frequency}; mChans[0].mFilter[0].setParamsFromSlope(BiquadType::LowShelf, f0norm, gain, 0.75f); - gain = std::sqrt(props->Equalizer.Mid1Gain); - f0norm = props->Equalizer.Mid1Center / frequency; + gain = std::sqrt(props.Mid1Gain); + f0norm = props.Mid1Center / frequency; mChans[0].mFilter[1].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, - props->Equalizer.Mid1Width); + props.Mid1Width); - gain = std::sqrt(props->Equalizer.Mid2Gain); - f0norm = props->Equalizer.Mid2Center / frequency; + gain = std::sqrt(props.Mid2Gain); + f0norm = props.Mid2Center / frequency; mChans[0].mFilter[2].setParamsFromBandwidth(BiquadType::Peaking, f0norm, gain, - props->Equalizer.Mid2Width); + props.Mid2Width); - gain = std::sqrt(props->Equalizer.HighGain); - f0norm = props->Equalizer.HighCutoff / frequency; + gain = std::sqrt(props.HighGain); + f0norm = props.HighCutoff / frequency; mChans[0].mFilter[3].setParamsFromSlope(BiquadType::HighShelf, f0norm, gain, 0.75f); /* Copy the filter coefficients for the other input channels. */ @@ -171,18 +169,17 @@ void EqualizerState::update(const ContextBase *context, const EffectSlot *slot, void EqualizerState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - const al::span buffer{mSampleBuffer.data(), samplesToDo}; - auto chan = std::begin(mChans); + const auto buffer = al::span{mSampleBuffer}.first(samplesToDo); + auto chan = mChans.begin(); for(const auto &input : samplesIn) { - const size_t outidx{chan->mTargetChannel}; - if(outidx != InvalidChannelIndex) + if(const size_t outidx{chan->mTargetChannel}; outidx != InvalidChannelIndex) { - const al::span inbuf{input.data(), samplesToDo}; - DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer.begin()); - DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer.begin()); + const auto inbuf = al::span{input}.first(samplesToDo); + DualBiquad{chan->mFilter[0], chan->mFilter[1]}.process(inbuf, buffer); + DualBiquad{chan->mFilter[2], chan->mFilter[3]}.process(buffer, buffer); - MixSamples(buffer, samplesOut[outidx].data(), chan->mCurrentGain, chan->mTargetGain, + MixSamples(buffer, samplesOut[outidx], chan->mCurrentGain, chan->mTargetGain, samplesToDo); } ++chan; diff --git a/Engine/lib/openal-soft/alc/effects/fshifter.cpp b/Engine/lib/openal-soft/alc/effects/fshifter.cpp index 3e6a7385e..7790a2435 100644 --- a/Engine/lib/openal-soft/alc/effects/fshifter.cpp +++ b/Engine/lib/openal-soft/alc/effects/fshifter.cpp @@ -25,23 +25,25 @@ #include #include #include -#include +#include #include "alc/effects/base.h" #include "alcomplex.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +#include "opthelpers.h" +struct BufferStorage; namespace { @@ -57,7 +59,7 @@ constexpr size_t HilStep{HilSize / OversampleFactor}; /* Define a Hann window, used to filter the HIL input and output. */ struct Windower { - alignas(16) std::array mData; + alignas(16) std::array mData{}; Windower() { @@ -91,10 +93,11 @@ struct FshifterState final : public EffectState { alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ - struct { - float Current[MaxAmbiChannels]{}; - float Target[MaxAmbiChannels]{}; - } mGains[2]; + struct OutGains { + std::array Current{}; + std::array Target{}; + }; + std::array mGains; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -102,8 +105,6 @@ struct FshifterState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(FshifterState) }; void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -122,20 +123,21 @@ void FshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) for(auto &gain : mGains) { - std::fill(std::begin(gain.Current), std::end(gain.Current), 0.0f); - std::fill(std::begin(gain.Target), std::end(gain.Target), 0.0f); + gain.Current.fill(0.0f); + gain.Target.fill(0.0f); } } void FshifterState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float step{props->Fshifter.Frequency / static_cast(device->Frequency)}; - mPhaseStep[0] = mPhaseStep[1] = fastf2u(minf(step, 1.0f) * MixerFracOne); + const float step{props.Frequency / static_cast(device->Frequency)}; + mPhaseStep[0] = mPhaseStep[1] = fastf2u(std::min(step, 1.0f) * MixerFracOne); - switch(props->Fshifter.LeftDirection) + switch(props.LeftDirection) { case FShifterDirection::Down: mSign[0] = -1.0; @@ -149,7 +151,7 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, break; } - switch(props->Fshifter.RightDirection) + switch(props.RightDirection) { case FShifterDirection::Down: mSign[1] = -1.0; @@ -164,23 +166,23 @@ void FshifterState::update(const ContextBase *context, const EffectSlot *slot, } static constexpr auto inv_sqrt2 = static_cast(1.0 / al::numbers::sqrt2); - static constexpr auto lcoeffs_pw = CalcDirectionCoeffs({-1.0f, 0.0f, 0.0f}); - static constexpr auto rcoeffs_pw = CalcDirectionCoeffs({ 1.0f, 0.0f, 0.0f}); - static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs({-inv_sqrt2, 0.0f, inv_sqrt2}); - static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs({ inv_sqrt2, 0.0f, inv_sqrt2}); + static constexpr auto lcoeffs_pw = CalcDirectionCoeffs(std::array{-1.0f, 0.0f, 0.0f}); + static constexpr auto rcoeffs_pw = CalcDirectionCoeffs(std::array{ 1.0f, 0.0f, 0.0f}); + static constexpr auto lcoeffs_nrml = CalcDirectionCoeffs(std::array{-inv_sqrt2, 0.0f, inv_sqrt2}); + static constexpr auto rcoeffs_nrml = CalcDirectionCoeffs(std::array{ inv_sqrt2, 0.0f, inv_sqrt2}); auto &lcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? lcoeffs_nrml : lcoeffs_pw; auto &rcoeffs = (device->mRenderMode != RenderMode::Pairwise) ? rcoeffs_nrml : rcoeffs_pw; mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, lcoeffs.data(), slot->Gain, mGains[0].Target); - ComputePanGains(target.Main, rcoeffs.data(), slot->Gain, mGains[1].Target); + ComputePanGains(target.Main, lcoeffs, slot->Gain, mGains[0].Target); + ComputePanGains(target.Main, rcoeffs, slot->Gain, mGains[1].Target); } void FshifterState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { for(size_t base{0u};base < samplesToDo;) { - size_t todo{minz(HilStep-mCount, samplesToDo-base)}; + size_t todo{std::min(HilStep-mCount, samplesToDo-base)}; /* Fill FIFO buffer with samples data */ const size_t pos{mPos}; @@ -218,25 +220,27 @@ void FshifterState::process(const size_t samplesToDo, const al::span(mBufferOut.data())}; for(size_t c{0};c < 2;++c) { + const double sign{mSign[c]}; const uint phase_step{mPhaseStep[c]}; uint phase_idx{mPhase[c]}; - for(size_t k{0};k < samplesToDo;++k) - { - const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; - BufferOut[k] = static_cast(mOutdata[k].real()*std::cos(phase) + - mOutdata[k].imag()*std::sin(phase)*mSign[c]); + std::transform(mOutdata.cbegin(), mOutdata.cbegin()+samplesToDo, mBufferOut.begin(), + [&phase_idx,phase_step,sign](const complex_d &in) -> float + { + const double phase{phase_idx * (al::numbers::pi*2.0 / MixerFracOne)}; + const auto out = static_cast(in.real()*std::cos(phase) + + in.imag()*std::sin(phase)*sign); - phase_idx += phase_step; - phase_idx &= MixerFracMask; - } + phase_idx += phase_step; + phase_idx &= MixerFracMask; + return out; + }); mPhase[c] = phase_idx; /* Now, mix the processed sound data to the output. */ - MixSamples({BufferOut, samplesToDo}, samplesOut, mGains[c].Current, mGains[c].Target, - maxz(samplesToDo, 512), 0); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mGains[c].Current, + mGains[c].Target, std::max(samplesToDo, 512_uz), 0); } } diff --git a/Engine/lib/openal-soft/alc/effects/modulator.cpp b/Engine/lib/openal-soft/alc/effects/modulator.cpp index 14ee5004d..e86a3c5d8 100644 --- a/Engine/lib/openal-soft/alc/effects/modulator.cpp +++ b/Engine/lib/openal-soft/alc/effects/modulator.cpp @@ -22,75 +22,73 @@ #include #include +#include +#include #include -#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/mixer.h" #include "intrusive_ptr.h" +#include "opthelpers.h" +struct BufferStorage; namespace { using uint = unsigned int; -#define MAX_UPDATE_SAMPLES 128 +struct SinFunc { + static auto Get(uint index, float scale) noexcept(noexcept(std::sin(0.0f))) -> float + { return std::sin(static_cast(index) * scale); } +}; -#define WAVEFORM_FRACBITS 24 -#define WAVEFORM_FRACONE (1< float + { return static_cast(index)*scale - 1.0f; } +}; -inline float Sin(uint index) -{ - constexpr float scale{al::numbers::pi_v*2.0f / WAVEFORM_FRACONE}; - return std::sin(static_cast(index) * scale); -} +struct SquareFunc { + static constexpr auto Get(uint index, float scale) noexcept -> float + { return float(static_cast(index)*scale < 0.5f)*2.0f - 1.0f; } +}; -inline float Saw(uint index) -{ return static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f; } - -inline float Square(uint index) -{ return static_cast(static_cast((index>>(WAVEFORM_FRACBITS-2))&2) - 1); } - -inline float One(uint) { return 1.0f; } - -template -void Modulate(float *RESTRICT dst, uint index, const uint step, size_t todo) -{ - for(size_t i{0u};i < todo;i++) - { - index += step; - index &= WAVEFORM_FRACMASK; - dst[i] = func(index); - } -} +struct OneFunc { + static constexpr auto Get(uint, float) noexcept -> float + { return 1.0f; } +}; struct ModulatorState final : public EffectState { - void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; + std::variant mSampleGen; uint mIndex{0}; - uint mStep{1}; + uint mRange{1}; + float mIndexScale{0.0f}; - struct { + alignas(16) FloatBufferLine mModSamples{}; + alignas(16) FloatBufferLine mBuffer{}; + + struct OutParams { uint mTargetChannel{InvalidChannelIndex}; BiquadFilter mFilter; float mCurrentGain{}; float mTargetGain{}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -98,8 +96,6 @@ struct ModulatorState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(ModulatorState) }; void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -113,24 +109,54 @@ void ModulatorState::deviceUpdate(const DeviceBase*, const BufferStorage*) } void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; - const float step{props->Modulator.Frequency / static_cast(device->Frequency)}; - mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); + /* The effective frequency will be adjusted to have a whole number of + * samples per cycle (at 48khz, that allows 8000, 6857.14, 6000, 5333.33, + * 4800, etc). We could do better by using fixed-point stepping over a sin + * function, with additive synthesis for the square and sawtooth waveforms, + * but that may need a more efficient sin function since it needs to do + * many iterations per sample. + */ + const float samplesPerCycle{props.Frequency > 0.0f + ? static_cast(device->Frequency)/props.Frequency + 0.5f + : 1.0f}; + const uint range{static_cast(std::clamp(samplesPerCycle, 1.0f, + static_cast(device->Frequency)))}; + mIndex = static_cast(uint64_t{mIndex} * range / mRange); + mRange = range; - if(mStep == 0) - mGetSamples = Modulate; - else if(props->Modulator.Waveform == ModulatorWaveform::Sinusoid) - mGetSamples = Modulate; - else if(props->Modulator.Waveform == ModulatorWaveform::Sawtooth) - mGetSamples = Modulate; - else /*if(props->Modulator.Waveform == ModulatorWaveform::Square)*/ - mGetSamples = Modulate; + if(mRange == 1) + { + mIndexScale = 0.0f; + mSampleGen.emplace(); + } + else if(props.Waveform == ModulatorWaveform::Sinusoid) + { + mIndexScale = al::numbers::pi_v*2.0f / static_cast(mRange); + mSampleGen.emplace(); + } + else if(props.Waveform == ModulatorWaveform::Sawtooth) + { + mIndexScale = 2.0f / static_cast(mRange-1); + mSampleGen.emplace(); + } + else if(props.Waveform == ModulatorWaveform::Square) + { + /* For square wave, the range should be even (there should be an equal + * number of high and low samples). An odd number of samples per cycle + * would need a more complex value generator. + */ + mRange = (mRange+1) & ~1u; + mIndexScale = 1.0f / static_cast(mRange-1); + mSampleGen.emplace(); + } - float f0norm{props->Modulator.HighPassCutoff / static_cast(device->Frequency)}; - f0norm = clampf(f0norm, 1.0f/512.0f, 0.49f); + float f0norm{props.HighPassCutoff / static_cast(device->Frequency)}; + f0norm = std::clamp(f0norm, 1.0f/512.0f, 0.49f); /* Bandwidth value is constant in octaves. */ mChans[0].mFilter.setParamsFromBandwidth(BiquadType::HighPass, f0norm, 1.0f, 0.75f); for(size_t i{1u};i < slot->Wet.Buffer.size();++i) @@ -147,34 +173,41 @@ void ModulatorState::update(const ContextBase *context, const EffectSlot *slot, void ModulatorState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { - for(size_t base{0u};base < samplesToDo;) + ASSUME(samplesToDo > 0); + + std::visit([this,samplesToDo](auto&& type) { - alignas(16) float modsamples[MAX_UPDATE_SAMPLES]; - const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + const uint range{mRange}; + const float scale{mIndexScale}; + uint index{mIndex}; - mGetSamples(modsamples, mIndex, mStep, td); - mIndex += static_cast(mStep * td); - mIndex &= WAVEFORM_FRACMASK; + ASSUME(range > 1); - auto chandata = std::begin(mChans); - for(const auto &input : samplesIn) + for(size_t i{0};i < samplesToDo;) { - const size_t outidx{chandata->mTargetChannel}; - if(outidx != InvalidChannelIndex) - { - alignas(16) float temps[MAX_UPDATE_SAMPLES]; - - chandata->mFilter.process({&input[base], td}, temps); - for(size_t i{0u};i < td;i++) - temps[i] *= modsamples[i]; - - MixSamples({temps, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, - chandata->mTargetGain, samplesToDo-base); - } - ++chandata; + size_t rem{std::min(samplesToDo-i, size_t{range-index})}; + do { + mModSamples[i++] = type.Get(index++, scale); + } while(--rem); + if(index == range) + index = 0; } + mIndex = index; + }, mSampleGen); - base += td; + auto chandata = mChans.begin(); + for(const auto &input : samplesIn) + { + if(const size_t outidx{chandata->mTargetChannel}; outidx != InvalidChannelIndex) + { + chandata->mFilter.process(al::span{input}.first(samplesToDo), mBuffer); + std::transform(mBuffer.cbegin(), mBuffer.cbegin()+samplesToDo, mModSamples.cbegin(), + mBuffer.begin(), std::multiplies<>{}); + + MixSamples(al::span{mBuffer}.first(samplesToDo), samplesOut[outidx], + chandata->mCurrentGain, chandata->mTargetGain, std::min(samplesToDo, 64_uz)); + } + ++chandata; } } diff --git a/Engine/lib/openal-soft/alc/effects/null.cpp b/Engine/lib/openal-soft/alc/effects/null.cpp index 1f9ae67bc..217181a8e 100644 --- a/Engine/lib/openal-soft/alc/effects/null.cpp +++ b/Engine/lib/openal-soft/alc/effects/null.cpp @@ -1,14 +1,15 @@ #include "config.h" -#include +#include -#include "almalloc.h" #include "alspan.h" #include "base.h" #include "core/bufferline.h" +#include "core/effects/base.h" #include "intrusive_ptr.h" +struct BufferStorage; struct ContextBase; struct DeviceBase; struct EffectSlot; @@ -25,8 +26,6 @@ struct NullState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(NullState) }; /* This constructs the effect state. It's called when the object is first diff --git a/Engine/lib/openal-soft/alc/effects/pshifter.cpp b/Engine/lib/openal-soft/alc/effects/pshifter.cpp index 426a2264b..8b3c6d291 100644 --- a/Engine/lib/openal-soft/alc/effects/pshifter.cpp +++ b/Engine/lib/openal-soft/alc/effects/pshifter.cpp @@ -25,22 +25,23 @@ #include #include #include -#include +#include #include "alc/effects/base.h" -#include "alcomplex.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" +#include "core/ambidefs.h" #include "core/bufferline.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "core/mixer/defs.h" #include "intrusive_ptr.h" +#include "pffft.h" +struct BufferStorage; struct ContextBase; @@ -58,7 +59,7 @@ constexpr size_t StftStep{StftSize / OversampleFactor}; /* Define a Hann window, used to filter the STFT input and output. */ struct Windower { - alignas(16) std::array mData; + alignas(16) std::array mData{}; Windower() { @@ -82,27 +83,29 @@ struct FrequencyBin { struct PshifterState final : public EffectState { /* Effect parameters */ - size_t mCount; - size_t mPos; - uint mPitchShiftI; - float mPitchShift; + size_t mCount{}; + size_t mPos{}; + uint mPitchShiftI{}; + float mPitchShift{}; /* Effects buffers */ - std::array mFIFO; - std::array mLastPhase; - std::array mSumPhase; - std::array mOutputAccum; + std::array mFIFO{}; + std::array mLastPhase{}; + std::array mSumPhase{}; + std::array mOutputAccum{}; - std::array mFftBuffer; + PFFFTSetup mFft; + alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mFftWorkBuffer{}; - std::array mAnalysisBuffer; - std::array mSynthesisBuffer; + std::array mAnalysisBuffer{}; + std::array mSynthesisBuffer{}; - alignas(16) FloatBufferLine mBufferOut; + alignas(16) FloatBufferLine mBufferOut{}; /* Effect gains for each output channel */ - float mCurrentGains[MaxAmbiChannels]; - float mTargetGains[MaxAmbiChannels]; + std::array mCurrentGains{}; + std::array mTargetGains{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; @@ -110,8 +113,6 @@ struct PshifterState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(PshifterState) }; void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) @@ -126,26 +127,31 @@ void PshifterState::deviceUpdate(const DeviceBase*, const BufferStorage*) mLastPhase.fill(0.0f); mSumPhase.fill(0.0f); mOutputAccum.fill(0.0f); - mFftBuffer.fill(complex_f{}); + mFftBuffer.fill(0.0f); mAnalysisBuffer.fill(FrequencyBin{}); mSynthesisBuffer.fill(FrequencyBin{}); - std::fill(std::begin(mCurrentGains), std::end(mCurrentGains), 0.0f); - std::fill(std::begin(mTargetGains), std::end(mTargetGains), 0.0f); + mCurrentGains.fill(0.0f); + mTargetGains.fill(0.0f); + + if(!mFft) + mFft = PFFFTSetup{StftSize, PFFFT_REAL}; } void PshifterState::update(const ContextBase*, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { - const int tune{props->Pshifter.CoarseTune*100 + props->Pshifter.FineTune}; + auto &props = std::get(*props_); + const int tune{props.CoarseTune*100 + props.FineTune}; const float pitch{std::pow(2.0f, static_cast(tune) / 1200.0f)}; - mPitchShiftI = clampu(fastf2u(pitch*MixerFracOne), MixerFracHalf, MixerFracOne*2); + mPitchShiftI = std::clamp(fastf2u(pitch*MixerFracOne), uint{MixerFracHalf}, + uint{MixerFracOne}*2u); mPitchShift = static_cast(mPitchShiftI) * float{1.0f/MixerFracOne}; - static constexpr auto coeffs = CalcDirectionCoeffs({0.0f, 0.0f, -1.0f}); + static constexpr auto coeffs = CalcDirectionCoeffs(std::array{0.0f, 0.0f, -1.0f}); mOutTarget = target.Main->Buffer; - ComputePanGains(target.Main, coeffs.data(), slot->Gain, mTargetGains); + ComputePanGains(target.Main, coeffs, slot->Gain, mTargetGains); } void PshifterState::process(const size_t samplesToDo, @@ -162,7 +168,7 @@ void PshifterState::process(const size_t samplesToDo, for(size_t base{0u};base < samplesToDo;) { - const size_t todo{minz(StftStep-mCount, samplesToDo-base)}; + const size_t todo{std::min(StftStep-mCount, samplesToDo-base)}; /* Retrieve the output samples from the FIFO and fill in the new input * samples. @@ -186,15 +192,19 @@ void PshifterState::process(const size_t samplesToDo, mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; for(size_t src{0u}, k{StftSize-mPos};src < mPos;++src,++k) mFftBuffer[k] = mFIFO[src] * gWindow.mData[k]; - forward_fft(al::as_span(mFftBuffer)); + mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_FORWARD); /* Analyze the obtained data. Since the real FFT is symmetric, only * StftHalfSize+1 samples are needed. */ - for(size_t k{0u};k < StftHalfSize+1;k++) + for(size_t k{0u};k < StftHalfSize+1;++k) { - const float magnitude{std::abs(mFftBuffer[k])}; - const float phase{std::arg(mFftBuffer[k])}; + const auto cplx = (k == 0) ? complex_f{mFftBuffer[0]} : + (k == StftHalfSize) ? complex_f{mFftBuffer[1]} : + complex_f{mFftBuffer[k*2], mFftBuffer[k*2 + 1]}; + const float magnitude{std::abs(cplx)}; + const float phase{std::arg(cplx)}; /* Compute the phase difference from the last update and subtract * the expected phase difference for this bin. @@ -232,8 +242,8 @@ void PshifterState::process(const size_t samplesToDo, */ std::fill(mSynthesisBuffer.begin(), mSynthesisBuffer.end(), FrequencyBin{}); - constexpr size_t bin_limit{((StftHalfSize+1)<> MixerFracBits}; @@ -266,21 +276,29 @@ void PshifterState::process(const size_t samplesToDo, tmp -= static_cast(qpd + (qpd%2)); mSumPhase[k] = tmp * al::numbers::pi_v; - mFftBuffer[k] = std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k]); + const complex_f cplx{std::polar(mSynthesisBuffer[k].Magnitude, mSumPhase[k])}; + if(k == 0) + mFftBuffer[0] = cplx.real(); + else if(k == StftHalfSize) + mFftBuffer[1] = cplx.real(); + else + { + mFftBuffer[k*2 + 0] = cplx.real(); + mFftBuffer[k*2 + 1] = cplx.imag(); + } } - for(size_t k{StftHalfSize+1};k < StftSize;++k) - mFftBuffer[k] = std::conj(mFftBuffer[StftSize-k]); /* Apply an inverse FFT to get the time-domain signal, and accumulate * for the output with windowing. */ - inverse_fft(al::as_span(mFftBuffer)); + mFft.transform_ordered(mFftBuffer.data(), mFftBuffer.data(), mFftWorkBuffer.data(), + PFFFT_BACKWARD); static constexpr float scale{3.0f / OversampleFactor / StftSize}; for(size_t dst{mPos}, k{0u};dst < StftSize;++dst,++k) - mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale; for(size_t dst{0u}, k{StftSize-mPos};dst < mPos;++dst,++k) - mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k].real() * scale; + mOutputAccum[dst] += gWindow.mData[k]*mFftBuffer[k] * scale; /* Copy out the accumulated result, then clear for the next iteration. */ std::copy_n(mOutputAccum.begin() + mPos, StftStep, mFIFO.begin() + mPos); @@ -288,8 +306,8 @@ void PshifterState::process(const size_t samplesToDo, } /* Now, mix the processed sound data to the output. */ - MixSamples({mBufferOut.data(), samplesToDo}, samplesOut, mCurrentGains, mTargetGains, - maxz(samplesToDo, 512), 0); + MixSamples(al::span{mBufferOut}.first(samplesToDo), samplesOut, mCurrentGains, mTargetGains, + std::max(samplesToDo, 512_uz), 0); } diff --git a/Engine/lib/openal-soft/alc/effects/reverb.cpp b/Engine/lib/openal-soft/alc/effects/reverb.cpp index 3875bedb1..63cb629b2 100644 --- a/Engine/lib/openal-soft/alc/effects/reverb.cpp +++ b/Engine/lib/openal-soft/alc/effects/reverb.cpp @@ -22,22 +22,25 @@ #include #include +#include +#include +#include #include #include -#include #include -#include +#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" +#include "core/cubic_tables.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/filters/biquad.h" #include "core/filters/splitter.h" @@ -45,13 +48,9 @@ #include "core/mixer/defs.h" #include "intrusive_ptr.h" #include "opthelpers.h" -#include "vecmat.h" #include "vector.h" -/* This is a user config option for modifying the overall output of the reverb - * effect. - */ -float ReverbBoost = 1.0f; +struct BufferStorage; namespace { @@ -65,41 +64,6 @@ constexpr float DefaultModulationTime{0.25f}; #define MOD_FRACMASK (MOD_FRACONE-1) -struct CubicFilter { - static constexpr size_t sTableBits{8}; - static constexpr size_t sTableSteps{1 << sTableBits}; - static constexpr size_t sTableMask{sTableSteps - 1}; - - float mFilter[sTableSteps*2 + 1]{}; - - constexpr CubicFilter() - { - /* This creates a lookup table for a cubic spline filter, with 256 - * steps between samples. Only half the coefficients are needed, since - * Coeff2 is just Coeff1 in reverse and Coeff3 is just Coeff0 in - * reverse. - */ - for(size_t i{0};i < sTableSteps;++i) - { - const double mu{static_cast(i) / double{sTableSteps}}; - const double mu2{mu*mu}, mu3{mu2*mu}; - const double a0{-0.5*mu3 + mu2 + -0.5*mu}; - const double a1{ 1.5*mu3 + -2.5*mu2 + 1.0f}; - mFilter[i] = static_cast(a1); - mFilter[sTableSteps+i] = static_cast(a0); - } - } - - constexpr float getCoeff0(size_t i) const noexcept { return mFilter[sTableSteps+i]; } - constexpr float getCoeff1(size_t i) const noexcept { return mFilter[i]; } - constexpr float getCoeff2(size_t i) const noexcept { return mFilter[sTableSteps-i]; } - constexpr float getCoeff3(size_t i) const noexcept { return mFilter[sTableSteps*2-i]; } -}; -constexpr CubicFilter gCubicTable; - - -using namespace std::placeholders; - /* Max samples per process iteration. Used to limit the size needed for * temporary buffers. Must be a multiple of 4 for SIMD alignment. */ @@ -122,35 +86,41 @@ constexpr size_t NUM_LINES{4u}; constexpr float MODULATION_DEPTH_COEFF{0.05f}; -/* The B-Format to A-Format conversion matrix. The arrangement of rows is - * deliberately chosen to align the resulting lines to their spatial opposites - * (0:above front left <-> 3:above back right, 1:below front right <-> 2:below - * back left). It's not quite opposite, since the A-Format results in a - * tetrahedron, but it's close enough. Should the model be extended to 8-lines - * in the future, true opposites can be used. +/* The B-Format to (W-normalized) A-Format conversion matrix. This produces a + * tetrahedral array of discrete signals (boosted by a factor of sqrt(3), to + * reduce the error introduced in the conversion). */ -alignas(16) constexpr float B2A[NUM_LINES][NUM_LINES]{ - { 0.5f, 0.5f, 0.5f, 0.5f }, - { 0.5f, -0.5f, -0.5f, 0.5f }, - { 0.5f, 0.5f, -0.5f, -0.5f }, - { 0.5f, -0.5f, 0.5f, -0.5f } -}; - -/* Converts A-Format to B-Format for early reflections. */ -alignas(16) constexpr std::array,NUM_LINES> EarlyA2B{{ - {{ 0.5f, 0.5f, 0.5f, 0.5f }}, - {{ 0.5f, -0.5f, 0.5f, -0.5f }}, - {{ 0.5f, -0.5f, -0.5f, 0.5f }}, - {{ 0.5f, 0.5f, -0.5f, -0.5f }} +alignas(16) constexpr std::array,NUM_LINES> B2A{{ + /* W Y Z X */ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* A0 */ + {{ 0.5f, -0.5f, -0.5f, 0.5f }}, /* A1 */ + {{ 0.5f, 0.5f, -0.5f, -0.5f }}, /* A2 */ + {{ 0.5f, -0.5f, 0.5f, -0.5f }} /* A3 */ }}; -/* Converts A-Format to B-Format for late reverb. */ +/* Converts (W-normalized) A-Format to B-Format for early reflections (scaled + * by 1/sqrt(3) to compensate for the boost in the B2A matrix). + */ +alignas(16) constexpr std::array,NUM_LINES> EarlyA2B{{ + /* A0 A1 A2 A3 */ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* W */ + {{ 0.5f, -0.5f, 0.5f, -0.5f }}, /* Y */ + {{ 0.5f, -0.5f, -0.5f, 0.5f }}, /* Z */ + {{ 0.5f, 0.5f, -0.5f, -0.5f }} /* X */ +}}; + +/* Converts (W-normalized) A-Format to B-Format for late reverb (scaled + * by 1/sqrt(3) to compensate for the boost in the B2A matrix). The response + * is rotated around Z (ambisonic X) so that the front lines are placed + * horizontally in front, and the rear lines are placed vertically in back. + */ constexpr auto InvSqrt2 = static_cast(1.0/al::numbers::sqrt2); alignas(16) constexpr std::array,NUM_LINES> LateA2B{{ - {{ 0.5f, 0.5f, 0.5f, 0.5f }}, - {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, - {{ 0.0f, 0.0f, InvSqrt2, -InvSqrt2 }}, - {{ 0.5f, 0.5f, -0.5f, -0.5f }} + /* A0 A1 A2 A3 */ + {{ 0.5f, 0.5f, 0.5f, 0.5f }}, /* W */ + {{ InvSqrt2, -InvSqrt2, 0.0f, 0.0f }}, /* Y */ + {{ 0.0f, 0.0f, -InvSqrt2, InvSqrt2 }}, /* Z */ + {{ 0.5f, 0.5f, -0.5f, -0.5f }} /* X */ }}; /* The all-pass and delay lines have a variable length dependent on the @@ -251,7 +221,7 @@ constexpr std::array EARLY_ALLPASS_LENGTHS{{ * Using an average dimension of 1m, we get: */ constexpr std::array EARLY_LINE_LENGTHS{{ - 5.9850400e-4f, 1.0913150e-3f, 1.5376658e-3f, 1.9419362e-3f + 0.0000000e+0f, 4.9281100e-4f, 9.3916180e-4f, 1.3434322e-3f }}; /* The late all-pass filter lengths are based on the late line lengths: @@ -288,21 +258,17 @@ struct DelayLineI { /* The delay lines use interleaved samples, with the lengths being powers * of 2 to allow the use of bit-masking instead of a modulus for wrapping. */ - size_t Mask{0u}; - union { - uintptr_t LineOffset{0u}; - std::array *Line; - }; + al::span mLine; /* Given the allocated sample buffer, this function updates each delay line * offset. */ - void realizeLineOffset(std::array *sampleBuffer) noexcept - { Line = sampleBuffer + LineOffset; } + void realizeLineOffset(al::span sampleBuffer) noexcept + { mLine = sampleBuffer; } /* Calculate the length of a delay line and store its mask and offset. */ - uint calcLineLength(const float length, const uintptr_t offset, const float frequency, - const uint extra) + static + auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { /* All line lengths are powers of 2, calculated from their lengths in * seconds, rounded up. @@ -310,23 +276,85 @@ struct DelayLineI { uint samples{float2uint(std::ceil(length*frequency))}; samples = NextPowerOf2(samples + extra); - /* All lines share a single sample buffer. */ - Mask = samples - 1; - LineOffset = offset; - /* Return the sample count for accumulation. */ - return samples; + return samples*NUM_LINES; + } +}; + +struct DelayLineU { + al::span mLine; + + void realizeLineOffset(al::span sampleBuffer) noexcept + { + assert(sampleBuffer.size() > 4 && !(sampleBuffer.size() & (sampleBuffer.size()-1))); + mLine = sampleBuffer; } - void write(size_t offset, const size_t c, const float *RESTRICT in, const size_t count) const noexcept + static + auto calcLineLength(const float length, const float frequency, const uint extra) -> size_t { - ASSUME(count > 0); + uint samples{float2uint(std::ceil(length*frequency))}; + samples = NextPowerOf2(samples + extra); + + return samples*NUM_LINES; + } + + [[nodiscard]] + auto get(size_t chan) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; + return mLine.subspan(chan*stride, stride); + } + + void write(size_t offset, const size_t c, al::span in) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; + const auto output = mLine.subspan(c*stride); + while(!in.empty()) + { + offset &= stride-1; + const size_t td{std::min(stride - offset, in.size())}; + std::copy_n(in.begin(), td, output.begin() + ptrdiff_t(offset)); + offset += td; + in = in.subspan(td); + } + } + + /* Writes the given input lines to the delay buffer, applying a geometric + * reflection. This effectively applies the matrix + * + * [ +1/2 -1/2 -1/2 -1/2 ] + * [ -1/2 +1/2 -1/2 -1/2 ] + * [ -1/2 -1/2 +1/2 -1/2 ] + * [ -1/2 -1/2 -1/2 +1/2 ] + * + * to the four input lines when writing to the delay buffer. The effect on + * the B-Format signal is negating W, applying a 180-degree phase shift and + * moving each response to its spatially opposite location. + */ + void writeReflected(size_t offset, const al::span in, + const size_t count) const noexcept + { + const size_t stride{mLine.size() / NUM_LINES}; for(size_t i{0u};i < count;) { - offset &= Mask; - size_t td{minz(Mask+1 - offset, count - i)}; + offset &= stride-1; + size_t td{std::min(stride - offset, count - i)}; do { - Line[offset++][c] = in[i++]; + const std::array src{in[0][i], in[1][i], in[2][i], in[3][i]}; + ++i; + + const std::array f{ + (src[0] - src[1] - src[2] - src[3]) * 0.5f, + (src[1] - src[0] - src[2] - src[3]) * 0.5f, + (src[2] - src[0] - src[1] - src[3]) * 0.5f, + (src[3] - src[0] - src[1] - src[2] ) * 0.5f + }; + mLine[0*stride + offset] = f[0]; + mLine[1*stride + offset] = f[1]; + mLine[2*stride + offset] = f[2]; + mLine[3*stride + offset] = f[3]; + ++offset; } while(--td); } } @@ -335,10 +363,19 @@ struct DelayLineI { struct VecAllpass { DelayLineI Delay; float Coeff{0.0f}; - size_t Offset[NUM_LINES]{}; + std::array Offset{}; void process(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo); + const float xCoeff, const float yCoeff, const size_t todo) const noexcept; +}; + +struct Allpass4 { + DelayLineU Delay; + float Coeff{0.0f}; + std::array Offset{}; + + void process(const al::span samples, const size_t offset, + const size_t todo) const noexcept; }; struct T60Filter { @@ -353,30 +390,37 @@ struct T60Filter { /* Applies the two T60 damping filter sections. */ void process(const al::span samples) - { DualBiquad{HFFilter, LFFilter}.process(samples, samples.data()); } + { DualBiquad{HFFilter, LFFilter}.process(samples, samples); } void clear() noexcept { HFFilter.clear(); LFFilter.clear(); } }; struct EarlyReflections { - /* A Gerzon vector all-pass filter is used to simulate initial diffusion. - * The spread from this filter also helps smooth out the reverb tail. - */ - VecAllpass VecAp; + Allpass4 VecAp; /* An echo line is used to complete the second half of the early * reflections. */ - DelayLineI Delay; - size_t Offset[NUM_LINES]{}; - float Coeff[NUM_LINES]{}; + DelayLineU Delay; + std::array Offset{}; + std::array Coeff{}; /* The gain for each output channel based on 3D panning. */ - float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; - float TargetGains[NUM_LINES][MaxAmbiChannels]{}; + struct OutGains { + std::array Current{}; + std::array Target{}; + + void clear() { Current.fill(0.0f); Target.fill(0.0); } + }; + std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float decayTime, const float frequency); + + void clear() + { + std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); + } }; @@ -384,22 +428,29 @@ struct Modulation { /* The vibrato time is tracked with an index over a (MOD_FRACONE) * normalized range. */ - uint Index, Step; + uint Index{0u}, Step{1u}; /* The depth of frequency change, in samples. */ - float Depth; + float Depth{0.0f}; - float ModDelays[MAX_UPDATE_SAMPLES]; + std::array ModDelays{}; void updateModulator(float modTime, float modDepth, float frequency); - void calcDelays(size_t todo); + auto calcDelays(size_t todo) -> al::span; + + void clear() noexcept + { + Index = 0u; + Step = 1u; + Depth = 0.0f; + } }; struct LateReverb { /* A recursive delay line is used fill in the reverb tail. */ - DelayLineI Delay; - size_t Offset[NUM_LINES]{}; + DelayLineU Delay; + std::array Offset{}; /* Attenuation to compensate for the modal density and decay rate of the * late lines. @@ -407,7 +458,7 @@ struct LateReverb { float DensityGain{0.0f}; /* T60 decay filters are used to simulate absorption. */ - T60Filter T60[NUM_LINES]; + std::array T60; Modulation Mod; @@ -415,40 +466,49 @@ struct LateReverb { VecAllpass VecAp; /* The gain for each output channel based on 3D panning. */ - float CurrentGains[NUM_LINES][MaxAmbiChannels]{}; - float TargetGains[NUM_LINES][MaxAmbiChannels]{}; + struct OutGains { + std::array Current{}; + std::array Target{}; + + void clear() { Current.fill(0.0f); Target.fill(0.0); } + }; + std::array Gains{}; void updateLines(const float density_mult, const float diffusion, const float lfDecayTime, const float mfDecayTime, const float hfDecayTime, const float lf0norm, const float hf0norm, const float frequency); - void clear() noexcept + void clear() { - for(auto &filter : T60) - filter.clear(); + std::for_each(T60.begin(), T60.end(), std::mem_fn(&T60Filter::clear)); + Mod.clear(); + std::for_each(Gains.begin(), Gains.end(), std::mem_fn(&OutGains::clear)); } }; struct ReverbPipeline { /* Master effect filters */ - struct { + struct FilterPair { BiquadFilter Lp; BiquadFilter Hp; - } mFilter[NUM_LINES]; + void clear() noexcept { Lp.clear(); Hp.clear(); } + }; + std::array mFilter; - /* Core delay line (early reflections and late reverb tap from this). */ - DelayLineI mEarlyDelayIn; - DelayLineI mLateDelayIn; + /* Late reverb input delay line (early reflections feed this, and late + * reverb taps from it). + */ + DelayLineU mLateDelayIn; - /* Tap points for early reflection delay. */ - size_t mEarlyDelayTap[NUM_LINES][2]{}; - float mEarlyDelayCoeff[NUM_LINES]{}; + /* Tap points for early reflection input delay. */ + std::array,NUM_LINES> mEarlyDelayTap{}; + std::array,NUM_LINES> mEarlyDelayCoeff{}; /* Tap points for late reverb feed and delay. */ - size_t mLateDelayTap[NUM_LINES][2]{}; + std::array,NUM_LINES> mLateDelayTap{}; /* Coefficients for the all-pass and line scattering matrices. */ - float mMixX{0.0f}; + float mMixX{1.0f}; float mMixY{0.0f}; EarlyReflections mEarly; @@ -459,12 +519,13 @@ struct ReverbPipeline { size_t mFadeSampleCount{1}; - void updateDelayLine(const float earlyDelay, const float lateDelay, const float density_mult, - const float decayTime, const float frequency); - void update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, - const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix); + void updateDelayLine(const float gain, const float earlyDelay, const float lateDelay, + const float density_mult, const float decayTime, const float frequency); + void update3DPanning(const al::span ReflectionsPan, + const al::span LateReverbPan, const float earlyGain, const float lateGain, + const bool doUpmix, const MixParams *mainMix); - void processEarly(size_t offset, const size_t samplesToDo, + void processEarly(const DelayLineU &main_delay, size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples); void processLate(size_t offset, const size_t samplesToDo, @@ -473,17 +534,15 @@ struct ReverbPipeline { void clear() noexcept { - for(auto &filter : mFilter) - { - filter.Lp.clear(); - filter.Hp.clear(); - } + std::for_each(mFilter.begin(), mFilter.end(), std::mem_fn(&FilterPair::clear)); + mEarlyDelayTap = {}; + mEarlyDelayCoeff = {}; + mLateDelayTap = {}; + mEarly.clear(); mLate.clear(); - for(auto &filters : mAmbiSplitter) - { - for(auto &filter : filters) - filter.clear(); - } + auto clear_filters = [](const al::span filters) + { std::for_each(filters.begin(), filters.end(), std::mem_fn(&BandSplitter::clear)); }; + std::for_each(mAmbiSplitter.begin(), mAmbiSplitter.end(), clear_filters); } }; @@ -491,9 +550,9 @@ struct ReverbState final : public EffectState { /* All delay lines are allocated as a single buffer to reduce memory * fragmentation and management code. */ - al::vector,16> mSampleBuffer; + al::vector mSampleBuffer; - struct { + struct Params { /* Calculated parameters which indicate if cross-fading is needed after * an update. */ @@ -506,7 +565,8 @@ struct ReverbState final : public EffectState { float ModulationDepth{0.0f}; float HFReference{5000.0f}; float LFReference{250.0f}; - } mParams; + }; + Params mParams; enum PipelineState : uint8_t { DeviceClear, @@ -516,18 +576,20 @@ struct ReverbState final : public EffectState { Normal, }; PipelineState mPipelineState{DeviceClear}; - uint8_t mCurrentPipeline{0}; + bool mCurrentPipeline{false}; - ReverbPipeline mPipelines[2]; + /* Core delay line (early reflections tap from this). */ + DelayLineU mMainDelay; + + std::array mPipelines; /* The current write offset for all delay lines. */ size_t mOffset{}; /* Temporary storage used when processing. */ - union { - alignas(16) FloatBufferLine mTempLine{}; - alignas(16) std::array mTempSamples; - }; + alignas(16) FloatBufferLine mTempLine{}; + alignas(16) std::array mTempSamples{}; + alignas(16) std::array mEarlySamples{}; alignas(16) std::array mLateSamples{}; @@ -537,48 +599,43 @@ struct ReverbState final : public EffectState { void MixOutPlain(ReverbPipeline &pipeline, const al::span samplesOut, - const size_t todo) + const size_t todo) const { - ASSUME(todo > 0); - /* When not upsampling, the panning gains convert to B-Format and pan * at the same time. */ - for(size_t c{0u};c < NUM_LINES;c++) + auto inBuffer = mEarlySamples.cbegin(); + for(auto &gains : pipeline.mEarly.Gains) { - const al::span tmpspan{mEarlySamples[c].data(), todo}; - MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], - pipeline.mEarly.TargetGains[c], todo, 0); + MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, + todo, 0); } - for(size_t c{0u};c < NUM_LINES;c++) + inBuffer = mLateSamples.cbegin(); + for(auto &gains : pipeline.mLate.Gains) { - const al::span tmpspan{mLateSamples[c].data(), todo}; - MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], - pipeline.mLate.TargetGains[c], todo, 0); + MixSamples(al::span{*inBuffer++}.first(todo), samplesOut, gains.Current, gains.Target, + todo, 0); } } void MixOutAmbiUp(ReverbPipeline &pipeline, const al::span samplesOut, const size_t todo) { - ASSUME(todo > 0); - auto DoMixRow = [](const al::span OutBuffer, const al::span Gains, - const float *InSamples, const size_t InStride) + const al::span InSamples) { + auto inBuffer = InSamples.cbegin(); std::fill(OutBuffer.begin(), OutBuffer.end(), 0.0f); for(const float gain : Gains) { - const float *RESTRICT input{al::assume_aligned<16>(InSamples)}; - InSamples += InStride; - - if(!(std::fabs(gain) > GainSilenceThreshold)) - continue; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(OutBuffer.begin(), OutBuffer.end(), input, OutBuffer.begin(), - mix_sample); + if(std::fabs(gain) > GainSilenceThreshold) + { + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(OutBuffer.begin(), OutBuffer.end(), inBuffer->cbegin(), + OutBuffer.begin(), mix_sample); + } + ++inBuffer; } }; @@ -586,29 +643,33 @@ struct ReverbState final : public EffectState { * so the proper HF scaling can be applied to each B-Format channel. * The panning gains then pan and upsample the B-Format channels. */ - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), todo}; - for(size_t c{0u};c < NUM_LINES;c++) + const auto tmpspan = al::span{mTempLine}.first(todo); + auto hfscale = float{mOrderScales[0]}; + auto splitter = pipeline.mAmbiSplitter[0].begin(); + auto a2bcoeffs = EarlyA2B.cbegin(); + for(auto &gains : pipeline.mEarly.Gains) { - DoMixRow(tmpspan, EarlyA2B[c], mEarlySamples[0].data(), mEarlySamples[0].size()); + DoMixRow(tmpspan, *(a2bcoeffs++), mEarlySamples); /* Apply scaling to the B-Format's HF response to "upsample" it to * higher-order output. */ - const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - pipeline.mAmbiSplitter[0][c].processHfScale(tmpspan, hfscale); + (splitter++)->processHfScale(tmpspan, hfscale); + hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, pipeline.mEarly.CurrentGains[c], - pipeline.mEarly.TargetGains[c], todo, 0); + MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } - for(size_t c{0u};c < NUM_LINES;c++) + hfscale = mOrderScales[0]; + splitter = pipeline.mAmbiSplitter[1].begin(); + a2bcoeffs = LateA2B.cbegin(); + for(auto &gains : pipeline.mLate.Gains) { - DoMixRow(tmpspan, LateA2B[c], mLateSamples[0].data(), mLateSamples[0].size()); + DoMixRow(tmpspan, *(a2bcoeffs++), mLateSamples); - const float hfscale{(c==0) ? mOrderScales[0] : mOrderScales[1]}; - pipeline.mAmbiSplitter[1][c].processHfScale(tmpspan, hfscale); + (splitter++)->processHfScale(tmpspan, hfscale); + hfscale = mOrderScales[1]; - MixSamples(tmpspan, samplesOut, pipeline.mLate.CurrentGains[c], - pipeline.mLate.TargetGains[c], todo, 0); + MixSamples(tmpspan, samplesOut, gains.Current, gains.Target, todo, 0); } } @@ -627,8 +688,6 @@ struct ReverbState final : public EffectState { const EffectTarget target) override; void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - - DEF_NEWDEL(ReverbState) }; /************************************** @@ -636,18 +695,13 @@ struct ReverbState final : public EffectState { **************************************/ inline float CalcDelayLengthMult(float density) -{ return maxf(5.0f, std::cbrt(density*DENSITY_SCALE)); } +{ return std::max(5.0f, std::cbrt(density*DENSITY_SCALE)); } /* Calculates the delay line metrics and allocates the shared sample buffer * for all lines given the sample rate (frequency). */ void ReverbState::allocLines(const float frequency) { - /* All delay line lengths are calculated to accomodate the full range of - * lengths given their respective paramters. - */ - size_t totalSamples{0u}; - /* Multiplier for the maximum density value, i.e. density=1, which is * actually the least density... */ @@ -657,64 +711,83 @@ void ReverbState::allocLines(const float frequency) * time and depth coefficient, and halfed for the low-to-high frequency * swing. */ - constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; + static constexpr float max_mod_delay{MaxModulationTime*MODULATION_DEPTH_COEFF / 2.0f}; + std::array linelengths{}; + size_t oidx{0}; + + size_t totalSamples{0u}; + /* The main delay length includes the maximum early reflection delay and + * the largest early tap width. It must also be extended by the update size + * (BufferLineSize) for block processing. + */ + float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; + size_t count{mMainDelay.calcLineLength(length, frequency, BufferLineSize)}; + linelengths[oidx++] = count; + totalSamples += count; for(auto &pipeline : mPipelines) { - /* The main delay length includes the maximum early reflection delay, - * the largest early tap width, the maximum late reverb delay, and the - * largest late tap width. Finally, it must also be extended by the - * update size (BufferLineSize) for block processing. - */ - float length{ReverbMaxReflectionsDelay + EARLY_TAP_LENGTHS.back()*multiplier}; - totalSamples += pipeline.mEarlyDelayIn.calcLineLength(length, totalSamples, frequency, - BufferLineSize); - - constexpr float LateLineDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / + static constexpr float LateDiffAvg{(LATE_LINE_LENGTHS.back()-LATE_LINE_LENGTHS.front()) / float{NUM_LINES}}; - length = ReverbMaxLateReverbDelay + LateLineDiffAvg*multiplier; - totalSamples += pipeline.mLateDelayIn.calcLineLength(length, totalSamples, frequency, - BufferLineSize); + length = ReverbMaxLateReverbDelay + LateDiffAvg*multiplier; + count = pipeline.mLateDelayIn.calcLineLength(length, frequency, BufferLineSize); + linelengths[oidx++] = count; + totalSamples += count; /* The early vector all-pass line. */ length = EARLY_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += pipeline.mEarly.VecAp.Delay.calcLineLength(length, totalSamples, frequency, - 0); + count = pipeline.mEarly.VecAp.Delay.calcLineLength(length, frequency, 0); + linelengths[oidx++] = count; + totalSamples += count; /* The early reflection line. */ length = EARLY_LINE_LENGTHS.back() * multiplier; - totalSamples += pipeline.mEarly.Delay.calcLineLength(length, totalSamples, frequency, - MAX_UPDATE_SAMPLES); + count = pipeline.mEarly.Delay.calcLineLength(length, frequency, MAX_UPDATE_SAMPLES); + linelengths[oidx++] = count; + totalSamples += count; /* The late vector all-pass line. */ length = LATE_ALLPASS_LENGTHS.back() * multiplier; - totalSamples += pipeline.mLate.VecAp.Delay.calcLineLength(length, totalSamples, frequency, - 0); + count = pipeline.mLate.VecAp.Delay.calcLineLength(length, frequency, 0); + linelengths[oidx++] = count; + totalSamples += count; /* The late delay lines are calculated from the largest maximum density * line length, and the maximum modulation delay. Four additional * samples are needed for resampling the modulator delay. */ length = LATE_LINE_LENGTHS.back()*multiplier + max_mod_delay; - totalSamples += pipeline.mLate.Delay.calcLineLength(length, totalSamples, frequency, 4); + count = pipeline.mLate.Delay.calcLineLength(length, frequency, 4); + linelengths[oidx++] = count; + totalSamples += count; } + assert(oidx == linelengths.size()); if(totalSamples != mSampleBuffer.size()) decltype(mSampleBuffer)(totalSamples).swap(mSampleBuffer); /* Clear the sample buffer. */ - std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), decltype(mSampleBuffer)::value_type{}); + std::fill(mSampleBuffer.begin(), mSampleBuffer.end(), 0.0f); /* Update all delays to reflect the new sample buffer. */ + auto bufferspan = al::span{mSampleBuffer}; + oidx = 0; + mMainDelay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); for(auto &pipeline : mPipelines) { - pipeline.mEarlyDelayIn.realizeLineOffset(mSampleBuffer.data()); - pipeline.mLateDelayIn.realizeLineOffset(mSampleBuffer.data()); - pipeline.mEarly.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - pipeline.mEarly.Delay.realizeLineOffset(mSampleBuffer.data()); - pipeline.mLate.VecAp.Delay.realizeLineOffset(mSampleBuffer.data()); - pipeline.mLate.Delay.realizeLineOffset(mSampleBuffer.data()); + pipeline.mLateDelayIn.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mEarly.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mEarly.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mLate.VecAp.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); + pipeline.mLate.Delay.realizeLineOffset(bufferspan.first(linelengths[oidx])); + bufferspan = bufferspan.subspan(linelengths[oidx++]); } + assert(oidx == linelengths.size()); } void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) @@ -724,41 +797,7 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) /* Allocate the delay lines. */ allocLines(frequency); - for(auto &pipeline : mPipelines) - { - /* Clear filters and gain coefficients since the delay lines were all just - * cleared (if not reallocated). - */ - for(auto &filter : pipeline.mFilter) - { - filter.Lp.clear(); - filter.Hp.clear(); - } - - std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); - std::fill(std::begin(pipeline.mEarlyDelayCoeff),std::end(pipeline.mEarlyDelayCoeff), 0.0f); - - pipeline.mLate.DensityGain = 0.0f; - for(auto &t60 : pipeline.mLate.T60) - { - t60.MidGain = 0.0f; - t60.HFFilter.clear(); - t60.LFFilter.clear(); - } - - pipeline.mLate.Mod.Index = 0; - pipeline.mLate.Mod.Step = 1; - pipeline.mLate.Mod.Depth = 0.0f; - - for(auto &gains : pipeline.mEarly.CurrentGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : pipeline.mEarly.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : pipeline.mLate.CurrentGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : pipeline.mLate.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - } + std::for_each(mPipelines.begin(), mPipelines.end(), std::mem_fn(&ReverbPipeline::clear)); mPipelineState = DeviceClear; /* Reset offset base. */ @@ -774,14 +813,14 @@ void ReverbState::deviceUpdate(const DeviceBase *device, const BufferStorage*) mUpmixOutput = false; mOrderScales.fill(1.0f); } - mPipelines[0].mAmbiSplitter[0][0].init(device->mXOverFreq / frequency); - for(auto &pipeline : mPipelines) + + auto splitter = BandSplitter{device->mXOverFreq / frequency}; + auto set_splitters = [&splitter](ReverbPipeline &pipeline) { - std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), - pipeline.mAmbiSplitter[0][0]); - std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), - pipeline.mAmbiSplitter[0][0]); - } + std::fill(pipeline.mAmbiSplitter[0].begin(), pipeline.mAmbiSplitter[0].end(), splitter); + std::fill(pipeline.mAmbiSplitter[1].begin(), pipeline.mAmbiSplitter[1].end(), splitter); + }; + std::for_each(mPipelines.begin(), mPipelines.end(), set_splitters); } /************************************** @@ -852,7 +891,7 @@ float CalcLimitedHfRatio(const float hfRatio, const float airAbsorptionGainHF, CalcDecayLength(airAbsorptionGainHF, decayTime)}; /* Using the limit calculated above, apply the upper bound to the HF ratio. */ - return minf(limitRatio, hfRatio); + return std::min(limitRatio, hfRatio); } @@ -908,7 +947,7 @@ void Modulation::updateModulator(float modTime, float modDepth, float frequency) * appropriate step size to generate an LFO, which will vary the feedback * delay over time. */ - Step = maxu(fastf2u(MOD_FRACONE / (frequency * modTime)), 1); + Step = std::max(fastf2u(MOD_FRACONE / (frequency * modTime)), 1u); /* The modulation depth effects the amount of frequency change over the * range of the sinus. It needs to be scaled by the modulation time so that @@ -981,7 +1020,7 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, * includes one sample of delay. Reduce by one to compensate. */ length = LATE_LINE_LENGTHS[i] * density_mult; - Offset[i] = maxu(float2uint(length*frequency + 0.5f), 1u) - 1u; + Offset[i] = std::max(float2uint(length*frequency + 0.5f), 1u) - 1u; /* Approximate the absorption that the vector all-pass would exhibit * given the current diffusion so we don't have to process a full T60 @@ -998,8 +1037,8 @@ void LateReverb::updateLines(const float density_mult, const float diffusion, /* Update the offsets for the main effect delay line. */ -void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDelay, - const float density_mult, const float decayTime, const float frequency) +void ReverbPipeline::updateDelayLine(const float gain, const float earlyDelay, + const float lateDelay, const float density_mult, const float decayTime, const float frequency) { /* Early reflection taps are decorrelated by means of an average room * reflection approximation described above the definition of the taps. @@ -1015,8 +1054,12 @@ void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDel { float length{EARLY_TAP_LENGTHS[i]*density_mult}; mEarlyDelayTap[i][1] = float2uint((earlyDelay+length) * frequency); - mEarlyDelayCoeff[i] = CalcDecayCoeff(length, decayTime); + mEarlyDelayCoeff[i][1] = CalcDecayCoeff(length, decayTime) * gain; + /* Reduce the late delay tap by the shortest early delay line length to + * compensate for the late line input being fed by the delayed early + * output. + */ length = (LATE_LINE_LENGTHS[i] - LATE_LINE_LENGTHS.front())/float{NUM_LINES}*density_mult + lateDelay; mLateDelayTap[i][1] = float2uint(length * frequency); @@ -1028,7 +1071,7 @@ void ReverbPipeline::updateDelayLine(const float earlyDelay, const float lateDel * focal strength. This function results in a B-Format transformation matrix * that spatially focuses the signal in the desired direction. */ -std::array,4> GetTransformFromVector(const float *vec) +std::array,4> GetTransformFromVector(const al::span vec) { /* Normalize the panning vector according to the N3D scale, which has an * extra sqrt(3) term on the directional components. Converting from OpenAL @@ -1037,13 +1080,14 @@ std::array,4> GetTransformFromVector(const float *vec) * rest of OpenAL which use right-handed. This is fixed by negating Z, * which cancels out with the B-Format Z negation. */ - float norm[3]; + std::array norm{{vec[0], vec[1], vec[2]}}; float mag{std::sqrt(vec[0]*vec[0] + vec[1]*vec[1] + vec[2]*vec[2])}; if(mag > 1.0f) { - norm[0] = vec[0] / mag * -al::numbers::sqrt3_v; - norm[1] = vec[1] / mag * al::numbers::sqrt3_v; - norm[2] = vec[2] / mag * al::numbers::sqrt3_v; + const float scale{al::numbers::sqrt3_v / mag}; + norm[0] *= -scale; + norm[1] *= scale; + norm[2] *= scale; mag = 1.0f; } else @@ -1052,9 +1096,9 @@ std::array,4> GetTransformFromVector(const float *vec) * term. There's no need to renormalize the magnitude since it would * just be reapplied in the matrix. */ - norm[0] = vec[0] * -al::numbers::sqrt3_v; - norm[1] = vec[1] * al::numbers::sqrt3_v; - norm[2] = vec[2] * al::numbers::sqrt3_v; + norm[0] *= -al::numbers::sqrt3_v; + norm[1] *= al::numbers::sqrt3_v; + norm[2] *= al::numbers::sqrt3_v; } return std::array,4>{{ @@ -1066,51 +1110,47 @@ std::array,4> GetTransformFromVector(const float *vec) } /* Update the early and late 3D panning gains. */ -void ReverbPipeline::update3DPanning(const float *ReflectionsPan, const float *LateReverbPan, - const float earlyGain, const float lateGain, const bool doUpmix, const MixParams *mainMix) +void ReverbPipeline::update3DPanning(const al::span ReflectionsPan, + const al::span LateReverbPan, const float earlyGain, const float lateGain, + const bool doUpmix, const MixParams *mainMix) { /* Create matrices that transform a B-Format signal according to the * panning vectors. */ - const std::array,4> earlymat{GetTransformFromVector(ReflectionsPan)}; - const std::array,4> latemat{GetTransformFromVector(LateReverbPan)}; + const auto earlymat = GetTransformFromVector(ReflectionsPan); + const auto latemat = GetTransformFromVector(LateReverbPan); - if(doUpmix) - { - /* When upsampling, combine the early and late transforms with the - * first-order upsample matrix. This results in panning gains that - * apply the panning transform to first-order B-Format, which is then - * upsampled. - */ - auto mult_matrix = [](const al::span,4> mtx1) + const auto [earlycoeffs, latecoeffs] = [&]{ + if(doUpmix) { - auto&& mtx2 = AmbiScale::FirstOrderUp; - std::array,NUM_LINES> res{}; - - for(size_t i{0};i < mtx1[0].size();++i) + /* When upsampling, combine the early and late transforms with the + * first-order upsample matrix. This results in panning gains that + * apply the panning transform to first-order B-Format, which is + * then upsampled. + */ + auto mult_matrix = [](const al::span,4> mtx1) { - float *RESTRICT dst{res[i].data()}; - for(size_t k{0};k < mtx1.size();++k) + std::array,NUM_LINES> res{}; + const auto mtx2 = al::span{AmbiScale::FirstOrderUp}; + + for(size_t i{0};i < mtx1[0].size();++i) { - const float *RESTRICT src{mtx2[k].data()}; - const float a{mtx1[k][i]}; - for(size_t j{0};j < mtx2[0].size();++j) - dst[j] += a * src[j]; + const al::span dst{res[i]}; + static_assert(dst.size() >= std::tuple_size_v); + for(size_t k{0};k < mtx1.size();++k) + { + const float a{mtx1[k][i]}; + std::transform(mtx2[k].begin(), mtx2[k].end(), dst.begin(), dst.begin(), + [a](const float in, const float out) noexcept -> float + { return a*in + out; }); + } } - } - return res; - }; - auto earlycoeffs = mult_matrix(earlymat); - auto latecoeffs = mult_matrix(latemat); + return res; + }; + return std::make_pair(mult_matrix(earlymat), mult_matrix(latemat)); + } - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); - } - else - { /* When not upsampling, combine the early and late A-to-B-Format * conversions with their respective transform. This results panning * gains that convert A-Format to B-Format, which is then panned. @@ -1122,131 +1162,154 @@ void ReverbPipeline::update3DPanning(const float *ReflectionsPan, const float *L for(size_t i{0};i < mtx1[0].size();++i) { - float *RESTRICT dst{res[i].data()}; + const al::span dst{res[i]}; + static_assert(dst.size() >= std::tuple_size_v); for(size_t k{0};k < mtx1.size();++k) { const float a{mtx1[k][i]}; - for(size_t j{0};j < mtx2.size();++j) - dst[j] += a * mtx2[j][k]; + std::transform(mtx2[k].begin(), mtx2[k].end(), dst.begin(), dst.begin(), + [a](const float in, const float out) noexcept -> float + { return a*in + out; }); } } return res; }; - auto earlycoeffs = mult_matrix(EarlyA2B, earlymat); - auto latecoeffs = mult_matrix(LateA2B, latemat); + return std::make_pair(mult_matrix(EarlyA2B, earlymat), mult_matrix(LateA2B, latemat)); + }(); - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, earlycoeffs[i].data(), earlyGain, mEarly.TargetGains[i]); - for(size_t i{0u};i < NUM_LINES;i++) - ComputePanGains(mainMix, latecoeffs[i].data(), lateGain, mLate.TargetGains[i]); - } + auto earlygains = mEarly.Gains.begin(); + for(auto &coeffs : earlycoeffs) + ComputePanGains(mainMix, coeffs, earlyGain, (earlygains++)->Target); + auto lategains = mLate.Gains.begin(); + for(auto &coeffs : latecoeffs) + ComputePanGains(mainMix, coeffs, lateGain, (lategains++)->Target); } void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *Device{Context->mDevice}; const auto frequency = static_cast(Device->Frequency); /* If the HF limit parameter is flagged, calculate an appropriate limit * based on the air absorption parameter. */ - float hfRatio{props->Reverb.DecayHFRatio}; - if(props->Reverb.DecayHFLimit && props->Reverb.AirAbsorptionGainHF < 1.0f) - hfRatio = CalcLimitedHfRatio(hfRatio, props->Reverb.AirAbsorptionGainHF, - props->Reverb.DecayTime); + float hfRatio{props.DecayHFRatio}; + if(props.DecayHFLimit && props.AirAbsorptionGainHF < 1.0f) + hfRatio = CalcLimitedHfRatio(hfRatio, props.AirAbsorptionGainHF, props.DecayTime); /* Calculate the LF/HF decay times. */ constexpr float MinDecayTime{0.1f}, MaxDecayTime{20.0f}; - const float lfDecayTime{clampf(props->Reverb.DecayTime*props->Reverb.DecayLFRatio, - MinDecayTime, MaxDecayTime)}; - const float hfDecayTime{clampf(props->Reverb.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; + const float lfDecayTime{std::clamp(props.DecayTime*props.DecayLFRatio, MinDecayTime, + MaxDecayTime)}; + const float hfDecayTime{std::clamp(props.DecayTime*hfRatio, MinDecayTime, MaxDecayTime)}; /* Determine if a full update is required. */ const bool fullUpdate{mPipelineState == DeviceClear || /* Density is essentially a master control for the feedback delays, so * changes the offsets of many delay lines. */ - mParams.Density != props->Reverb.Density || + mParams.Density != props.Density || /* Diffusion and decay times influences the decay rate (gain) of the * late reverb T60 filter. */ - mParams.Diffusion != props->Reverb.Diffusion || - mParams.DecayTime != props->Reverb.DecayTime || + mParams.Diffusion != props.Diffusion || + mParams.DecayTime != props.DecayTime || mParams.HFDecayTime != hfDecayTime || mParams.LFDecayTime != lfDecayTime || /* Modulation time and depth both require fading the modulation delay. */ - mParams.ModulationTime != props->Reverb.ModulationTime || - mParams.ModulationDepth != props->Reverb.ModulationDepth || + mParams.ModulationTime != props.ModulationTime || + mParams.ModulationDepth != props.ModulationDepth || /* HF/LF References control the weighting used to calculate the density * gain. */ - mParams.HFReference != props->Reverb.HFReference || - mParams.LFReference != props->Reverb.LFReference}; + mParams.HFReference != props.HFReference || + mParams.LFReference != props.LFReference}; if(fullUpdate) { - mParams.Density = props->Reverb.Density; - mParams.Diffusion = props->Reverb.Diffusion; - mParams.DecayTime = props->Reverb.DecayTime; + mParams.Density = props.Density; + mParams.Diffusion = props.Diffusion; + mParams.DecayTime = props.DecayTime; mParams.HFDecayTime = hfDecayTime; mParams.LFDecayTime = lfDecayTime; - mParams.ModulationTime = props->Reverb.ModulationTime; - mParams.ModulationDepth = props->Reverb.ModulationDepth; - mParams.HFReference = props->Reverb.HFReference; - mParams.LFReference = props->Reverb.LFReference; + mParams.ModulationTime = props.ModulationTime; + mParams.ModulationDepth = props.ModulationDepth; + mParams.HFReference = props.HFReference; + mParams.LFReference = props.LFReference; mPipelineState = (mPipelineState != DeviceClear) ? StartFade : Normal; - mCurrentPipeline ^= 1; + mCurrentPipeline = !mCurrentPipeline; + + auto &oldpipeline = mPipelines[!mCurrentPipeline]; + for(size_t j{0};j < NUM_LINES;++j) + oldpipeline.mEarlyDelayCoeff[j][1] = 0.0f; } auto &pipeline = mPipelines[mCurrentPipeline]; + /* The density-based room size (delay length) multiplier. */ + const float density_mult{CalcDelayLengthMult(props.Density)}; + + /* Update the main effect delay and associated taps. */ + pipeline.updateDelayLine(props.Gain, props.ReflectionsDelay, props.LateReverbDelay, + density_mult, props.DecayTime, frequency); + /* Update early and late 3D panning. */ mOutTarget = target.Main->Buffer; - const float gain{props->Reverb.Gain * Slot->Gain * ReverbBoost}; - pipeline.update3DPanning(props->Reverb.ReflectionsPan, props->Reverb.LateReverbPan, - props->Reverb.ReflectionsGain*gain, props->Reverb.LateReverbGain*gain, mUpmixOutput, - target.Main); + const float gain{Slot->Gain * ReverbBoost}; + pipeline.update3DPanning(props.ReflectionsPan, props.LateReverbPan, props.ReflectionsGain*gain, + props.LateReverbGain*gain, mUpmixOutput, target.Main); /* Calculate the master filters */ - float hf0norm{minf(props->Reverb.HFReference/frequency, 0.49f)}; - pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props->Reverb.GainHF, 1.0f); - float lf0norm{minf(props->Reverb.LFReference/frequency, 0.49f)}; - pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props->Reverb.GainLF, 1.0f); + float hf0norm{std::min(props.HFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Lp.setParamsFromSlope(BiquadType::HighShelf, hf0norm, props.GainHF, 1.0f); + float lf0norm{std::min(props.LFReference/frequency, 0.49f)}; + pipeline.mFilter[0].Hp.setParamsFromSlope(BiquadType::LowShelf, lf0norm, props.GainLF, 1.0f); for(size_t i{1u};i < NUM_LINES;i++) { pipeline.mFilter[i].Lp.copyParamsFrom(pipeline.mFilter[0].Lp); pipeline.mFilter[i].Hp.copyParamsFrom(pipeline.mFilter[0].Hp); } - /* The density-based room size (delay length) multiplier. */ - const float density_mult{CalcDelayLengthMult(props->Reverb.Density)}; - - /* Update the main effect delay and associated taps. */ - pipeline.updateDelayLine(props->Reverb.ReflectionsDelay, props->Reverb.LateReverbDelay, - density_mult, props->Reverb.DecayTime, frequency); - if(fullUpdate) { /* Update the early lines. */ - pipeline.mEarly.updateLines(density_mult, props->Reverb.Diffusion, props->Reverb.DecayTime, - frequency); + pipeline.mEarly.updateLines(density_mult, props.Diffusion, props.DecayTime, frequency); /* Get the mixing matrix coefficients. */ - CalcMatrixCoeffs(props->Reverb.Diffusion, &pipeline.mMixX, &pipeline.mMixY); + CalcMatrixCoeffs(props.Diffusion, &pipeline.mMixX, &pipeline.mMixY); /* Update the modulator rate and depth. */ - pipeline.mLate.Mod.updateModulator(props->Reverb.ModulationTime, - props->Reverb.ModulationDepth, frequency); + pipeline.mLate.Mod.updateModulator(props.ModulationTime, props.ModulationDepth, frequency); /* Update the late lines. */ - pipeline.mLate.updateLines(density_mult, props->Reverb.Diffusion, lfDecayTime, - props->Reverb.DecayTime, hfDecayTime, lf0norm, hf0norm, frequency); + pipeline.mLate.updateLines(density_mult, props.Diffusion, lfDecayTime, props.DecayTime, + hfDecayTime, lf0norm, hf0norm, frequency); } - const float decaySamples{(props->Reverb.ReflectionsDelay + props->Reverb.LateReverbDelay - + props->Reverb.DecayTime) * frequency}; - pipeline.mFadeSampleCount = static_cast(minf(decaySamples, 1'000'000.0f)); + /* Calculate the gain at the start of the late reverb stage, and the gain + * difference from the decay target (0.001, or -60dB). + */ + const float decayBase{props.ReflectionsGain * props.LateReverbGain}; + const float decayDiff{ReverbDecayGain / decayBase}; + + /* Given the DecayTime (the amount of time for the late reverb to decay by + * -60dB), calculate the time to decay to -60dB from the start of the late + * reverb. + * + * Otherwise, if the late reverb already starts at -60dB or less, only + * include the time to get to the late reverb. + */ + const float diffTime{!(decayDiff < 1.0f) ? 0.0f + : (std::log10(decayDiff)*(20.0f / -60.0f) * props.DecayTime)}; + + const float decaySamples{(props.ReflectionsDelay+props.LateReverbDelay+diffTime) + * frequency}; + /* Limit to 100,000 samples (a touch over 2 seconds at 48khz) to avoid + * excessive double-processing. + */ + pipeline.mFadeSampleCount = static_cast(std::min(decaySamples, 100'000.0f)); } @@ -1292,35 +1355,34 @@ void ReverbState::update(const ContextBase *Context, const EffectSlot *Slot, * Where D is a diagonal matrix (of x), and S is a triangular matrix (of y) * whose combination of signs are being iterated. */ -inline auto VectorPartialScatter(const std::array &RESTRICT in, - const float xCoeff, const float yCoeff) -> std::array +inline auto VectorPartialScatter(const std::array &in, const float xCoeff, + const float yCoeff) noexcept -> std::array { - return std::array{{ + return std::array{ xCoeff*in[0] + yCoeff*( in[1] + -in[2] + in[3]), xCoeff*in[1] + yCoeff*(-in[0] + in[2] + in[3]), xCoeff*in[2] + yCoeff*( in[0] + -in[1] + in[3]), xCoeff*in[3] + yCoeff*(-in[0] + -in[1] + -in[2] ) - }}; + }; } -/* Utilizes the above, but reverses the input channels. */ -void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float xCoeff, - const float yCoeff, const al::span in, const size_t count) +/* Utilizes the above, but also applies a line-based reflection on the input + * channels (swapping 0<->3 and 1<->2). + */ +void VectorScatterRev(const float xCoeff, const float yCoeff, + const al::span samples, const size_t count) noexcept { ASSUME(count > 0); - for(size_t i{0u};i < count;) + for(size_t i{0u};i < count;++i) { - offset &= delay.Mask; - size_t td{minz(delay.Mask+1 - offset, count-i)}; - do { - std::array f; - for(size_t j{0u};j < NUM_LINES;j++) - f[NUM_LINES-1-j] = in[j][i]; - ++i; + std::array src{samples[0][i], samples[1][i], samples[2][i], samples[3][i]}; - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); - } while(--td); + src = VectorPartialScatter(std::array{src[3], src[2], src[1], src[0]}, xCoeff, yCoeff); + samples[0][i] = src[0]; + samples[1][i] = src[1]; + samples[2][i] = src[2]; + samples[3][i] = src[3]; } } @@ -1330,162 +1392,229 @@ void VectorScatterRevDelayIn(const DelayLineI delay, size_t offset, const float * It works by vectorizing a regular all-pass filter and replacing the delay * element with a scattering matrix (like the one above) and a diagonal * matrix of delay elements. - * - * Two static specializations are used for transitional (cross-faded) delay - * line processing and non-transitional processing. */ -void VecAllpass::process(const al::span samples, size_t offset, - const float xCoeff, const float yCoeff, const size_t todo) +void VecAllpass::process(const al::span samples, size_t main_offset, + const float xCoeff, const float yCoeff, const size_t todo) const noexcept { - const DelayLineI delay{Delay}; + const auto linelen = size_t{Delay.mLine.size()/NUM_LINES}; const float feedCoeff{Coeff}; ASSUME(todo > 0); - size_t vap_offset[NUM_LINES]; - for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] = offset - Offset[j]; for(size_t i{0u};i < todo;) { - for(size_t j{0u};j < NUM_LINES;j++) - vap_offset[j] &= delay.Mask; - offset &= delay.Mask; + std::array vap_offset{}; + std::transform(Offset.cbegin(), Offset.cend(), vap_offset.begin(), + [main_offset,mask=linelen-1](const size_t delay) noexcept -> size_t + { return (main_offset-delay) & mask; }); + main_offset &= linelen-1; - size_t maxoff{offset}; - for(size_t j{0u};j < NUM_LINES;j++) - maxoff = maxz(maxoff, vap_offset[j]); - size_t td{minz(delay.Mask+1 - maxoff, todo - i)}; + const auto maxoff = std::accumulate(vap_offset.cbegin(), vap_offset.cend(), main_offset, + [](const size_t offset, const size_t apoffset) { return std::max(offset, apoffset); }); + size_t td{std::min(linelen - maxoff, todo - i)}; + + auto delayIn = Delay.mLine.begin(); + auto delayOut = Delay.mLine.begin() + ptrdiff_t(main_offset*NUM_LINES); + main_offset += td; do { - std::array f; + std::array f{}; for(size_t j{0u};j < NUM_LINES;j++) { const float input{samples[j][i]}; - const float out{delay.Line[vap_offset[j]++][j] - feedCoeff*input}; + const float out{delayIn[vap_offset[j]*NUM_LINES + j] - feedCoeff*input}; f[j] = input + feedCoeff*out; samples[j][i] = out; } + delayIn += NUM_LINES; ++i; - delay.Line[offset++] = VectorPartialScatter(f, xCoeff, yCoeff); + f = VectorPartialScatter(f, xCoeff, yCoeff); + delayOut = std::copy_n(f.cbegin(), f.size(), delayOut); } while(--td); } } +/* This applies a more typical all-pass to each line, without the scattering + * matrix. + */ +void Allpass4::process(const al::span samples, const size_t offset, + const size_t todo) const noexcept +{ + const DelayLineU delay{Delay}; + const float feedCoeff{Coeff}; + + ASSUME(todo > 0); + + for(size_t j{0u};j < NUM_LINES;j++) + { + auto smpiter = samples[j].begin(); + const auto buffer = delay.get(j); + size_t dstoffset{offset}; + size_t vap_offset{offset - Offset[j]}; + for(size_t i{0u};i < todo;) + { + vap_offset &= buffer.size()-1; + dstoffset &= buffer.size()-1; + + const size_t maxoff{std::max(dstoffset, vap_offset)}; + const size_t td{std::min(buffer.size() - maxoff, todo - i)}; + + auto proc_sample = [buffer,feedCoeff,&vap_offset,&dstoffset](const float x) -> float + { + const float y{buffer[vap_offset++] - feedCoeff*x}; + buffer[dstoffset++] = x + feedCoeff*y; + return y; + }; + smpiter = std::transform(smpiter, smpiter+td, smpiter, proc_sample); + i += td; + } + } +} + + /* This generates early reflections. * * This is done by obtaining the primary reflections (those arriving from the * same direction as the source) from the main delay line. These are * attenuated and all-pass filtered (based on the diffusion parameter). * - * The early lines are then fed in reverse (according to the approximately - * opposite spatial location of the A-Format lines) to create the secondary + * The early lines are then reflected about the origin to create the secondary * reflections (those arriving from the opposite direction as the source). * * The early response is then completed by combining the primary reflections * with the delayed and attenuated output from the early lines. * - * Finally, the early response is reversed, scattered (based on diffusion), + * Finally, the early response is reflected, scattered (based on diffusion), * and fed into the late reverb section of the main delay line. */ -void ReverbPipeline::processEarly(size_t offset, const size_t samplesToDo, - const al::span tempSamples, +void ReverbPipeline::processEarly(const DelayLineU &main_delay, size_t offset, + const size_t samplesToDo, const al::span tempSamples, const al::span outSamples) { - const DelayLineI early_delay{mEarly.Delay}; - const DelayLineI in_delay{mEarlyDelayIn}; + const DelayLineU early_delay{mEarly.Delay}; + const DelayLineU in_delay{main_delay}; const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { - const size_t todo{minz(samplesToDo-base, MAX_UPDATE_SAMPLES)}; + const size_t todo{std::min(samplesToDo-base, MAX_UPDATE_SAMPLES)}; /* First, load decorrelated samples from the main delay line as the * primary reflections. */ - const float fadeStep{1.0f / static_cast(todo)}; - for(size_t j{0u};j < NUM_LINES;j++) + const auto fadeStep = float{1.0f / static_cast(todo)}; + for(size_t j{0_uz};j < NUM_LINES;j++) { - size_t early_delay_tap0{offset - mEarlyDelayTap[j][0]}; - size_t early_delay_tap1{offset - mEarlyDelayTap[j][1]}; - const float coeff{mEarlyDelayCoeff[j]}; - const float coeffStep{early_delay_tap0 != early_delay_tap1 ? coeff*fadeStep : 0.0f}; - float fadeCount{0.0f}; - - for(size_t i{0u};i < todo;) - { - early_delay_tap0 &= in_delay.Mask; - early_delay_tap1 &= in_delay.Mask; - const size_t max_tap{maxz(early_delay_tap0, early_delay_tap1)}; - size_t td{minz(in_delay.Mask+1 - max_tap, todo-i)}; - do { - const float fade0{coeff - coeffStep*fadeCount}; - const float fade1{coeffStep*fadeCount}; - fadeCount += 1.0f; - tempSamples[j][i++] = in_delay.Line[early_delay_tap0++][j]*fade0 + - in_delay.Line[early_delay_tap1++][j]*fade1; - } while(--td); - } - + const auto input = in_delay.get(j); + auto early_delay_tap0 = size_t{offset - mEarlyDelayTap[j][0]}; + auto early_delay_tap1 = size_t{offset - mEarlyDelayTap[j][1]}; mEarlyDelayTap[j][0] = mEarlyDelayTap[j][1]; + const auto coeff0 = float{mEarlyDelayCoeff[j][0]}; + const auto coeff1 = float{mEarlyDelayCoeff[j][1]}; + mEarlyDelayCoeff[j][0] = mEarlyDelayCoeff[j][1]; + auto fadeCount = float{0.0f}; + + auto tmp = tempSamples[j].begin(); + for(size_t i{0_uz};i < todo;) + { + early_delay_tap0 &= input.size()-1; + early_delay_tap1 &= input.size()-1; + const auto max_tap = size_t{std::max(early_delay_tap0, early_delay_tap1)}; + const auto td = size_t{std::min(input.size()-max_tap, todo-i)}; + const auto intap0 = input.subspan(early_delay_tap0, td); + const auto intap1 = input.subspan(early_delay_tap1, td); + + auto do_blend = [coeff0,coeff1,fadeStep,&fadeCount](const float in0, + const float in1) noexcept -> float + { + const auto ret = lerpf(in0*coeff0, in1*coeff1, fadeStep*fadeCount); + fadeCount += 1.0f; + return ret; + }; + tmp = std::transform(intap0.begin(), intap0.end(), intap1.begin(), tmp, do_blend); + early_delay_tap0 += td; + early_delay_tap1 += td; + i += td; + } + + /* Band-pass the incoming samples. */ + auto&& filter = DualBiquad{mFilter[j].Lp, mFilter[j].Hp}; + filter.process(al::span{tempSamples[j]}.first(todo), tempSamples[j]); } - /* Apply a vector all-pass, to help color the initial reflections based - * on the diffusion strength. - */ - mEarly.VecAp.process(tempSamples, offset, mixX, mixY, todo); + /* Apply an all-pass, to help color the initial reflections. */ + mEarly.VecAp.process(tempSamples, offset, todo); - /* Apply a delay and bounce to generate secondary reflections, combine - * with the primary reflections and write out the result for mixing. - */ - for(size_t j{0u};j < NUM_LINES;j++) - early_delay.write(offset, NUM_LINES-1-j, tempSamples[j].data(), todo); - for(size_t j{0u};j < NUM_LINES;j++) + /* Apply a delay and bounce to generate secondary reflections. */ + early_delay.writeReflected(offset, tempSamples, todo); + for(size_t j{0_uz};j < NUM_LINES;j++) { - size_t feedb_tap{offset - mEarly.Offset[j]}; - const float feedb_coeff{mEarly.Coeff[j]}; - float *RESTRICT out{al::assume_aligned<16>(outSamples[j].data() + base)}; + const auto input = early_delay.get(j); + auto feedb_tap = size_t{offset - mEarly.Offset[j]}; + const auto feedb_coeff = float{mEarly.Coeff[j]}; + auto out = outSamples[j].begin() + base; + auto tmp = tempSamples[j].begin(); - for(size_t i{0u};i < todo;) + for(size_t i{0_uz};i < todo;) { - feedb_tap &= early_delay.Mask; - size_t td{minz(early_delay.Mask+1 - feedb_tap, todo - i)}; - do { - tempSamples[j][i] += early_delay.Line[feedb_tap++][j]*feedb_coeff; - out[i] = tempSamples[j][i]; - ++i; - } while(--td); + feedb_tap &= input.size()-1; + + const auto td = size_t{std::min(input.size() - feedb_tap, todo - i)}; + const auto delaySrc = input.subspan(feedb_tap, td); + + /* Combine the main input with the attenuated delayed echo for + * the early output. + */ + out = std::transform(delaySrc.begin(), delaySrc.end(), tmp, out, + [feedb_coeff](const float delayspl, const float mainspl) noexcept -> float + { return delayspl*feedb_coeff + mainspl; }); + + /* Move the (non-attenuated) delayed echo to the temp buffer + * for feeding the late reverb. + */ + tmp = std::copy_n(delaySrc.begin(), delaySrc.size(), tmp); + feedb_tap += td; + i += td; } } - /* Finally, write the result to the late delay line input for the late - * reverb stage to pick up at the appropriate time, applying a scatter - * and bounce to improve the initial diffusion in the late reverb. + /* Finally, apply a scatter and bounce to improve the initial diffusion + * in the late reverb, writing the result to the late delay line input. */ - VectorScatterRevDelayIn(mLateDelayIn, offset, mixX, mixY, tempSamples, todo); + VectorScatterRev(mixX, mixY, tempSamples, todo); + for(size_t j{0_uz};j < NUM_LINES;j++) + mLateDelayIn.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; } } -void Modulation::calcDelays(size_t todo) +auto Modulation::calcDelays(size_t todo) -> al::span { - constexpr float mod_scale{al::numbers::pi_v * 2.0f / MOD_FRACONE}; - uint idx{Index}; - const uint step{Step}; - const float depth{Depth}; - for(size_t i{0};i < todo;++i) + auto idx = uint{Index}; + const auto step = uint{Step}; + const auto depth = float{Depth * float{gCubicTable.sTableSteps}}; + const auto delays = al::span{ModDelays}.first(todo); + std::generate(delays.begin(), delays.end(), [step,depth,&idx] { idx += step; - const float lfo{std::sin(static_cast(idx&MOD_FRACMASK) * mod_scale)}; - ModDelays[i] = (lfo+1.0f) * depth; - } + const auto x = float{static_cast(idx&MOD_FRACMASK) * (1.0f/MOD_FRACONE)}; + /* Approximate sin(x*2pi). As long as it roughly fits a sinusoid shape + * and stays within [-1...+1], it needn't be perfect. + */ + const auto lfo = float{!(idx&(MOD_FRACONE>>1)) + ? ((-16.0f * x * x) + (8.0f * x)) + : ((16.0f * x * x) + (-8.0f * x) + (-16.0f * x) + 8.0f)}; + return float2uint((lfo+1.0f) * depth); + }); Index = idx; + return delays; } @@ -1504,88 +1633,105 @@ void ReverbPipeline::processLate(size_t offset, const size_t samplesToDo, const al::span tempSamples, const al::span outSamples) { - const DelayLineI late_delay{mLate.Delay}; - const DelayLineI in_delay{mLateDelayIn}; + const DelayLineU late_delay{mLate.Delay}; + const DelayLineU in_delay{mLateDelayIn}; const float mixX{mMixX}; const float mixY{mMixY}; - ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); for(size_t base{0};base < samplesToDo;) { - const size_t todo{minz(samplesToDo-base, minz(mLate.Offset[0], MAX_UPDATE_SAMPLES))}; + const size_t todo{std::min(std::min(mLate.Offset[0], MAX_UPDATE_SAMPLES), + samplesToDo-base)}; ASSUME(todo > 0); /* First, calculate the modulated delays for the late feedback. */ - mLate.Mod.calcDelays(todo); + const auto delays = mLate.Mod.calcDelays(todo); - /* Next, load decorrelated samples from the main and feedback delay - * lines. Filter the signal to apply its frequency-dependent decay. + /* Now load samples from the feedback delay lines. Filter the signal to + * apply its frequency-dependent decay. */ - const float fadeStep{1.0f / static_cast(todo)}; - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t j{0_uz};j < NUM_LINES;++j) { - size_t late_delay_tap0{offset - mLateDelayTap[j][0]}; - size_t late_delay_tap1{offset - mLateDelayTap[j][1]}; - size_t late_feedb_tap{offset - mLate.Offset[j]}; - const float midGain{mLate.T60[j].MidGain}; - const float densityGain{mLate.DensityGain * midGain}; - const float densityStep{late_delay_tap0 != late_delay_tap1 ? - densityGain*fadeStep : 0.0f}; - float fadeCount{0.0f}; + const auto input = late_delay.get(j); + const auto midGain = float{mLate.T60[j].MidGain}; + auto late_feedb_tap = size_t{offset - mLate.Offset[j]}; + auto proc_sample = [input,midGain,&late_feedb_tap](const size_t idelay) -> float + { + /* Calculate the read sample offset and sub-sample offset + * between it and the next sample. + */ + const auto delay = size_t{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; + const auto delayoffset = size_t{idelay & gCubicTable.sTableMask}; + ++late_feedb_tap; + + /* Get the samples around the delayed offset, interpolated for + * output. + */ + const auto out0 = float{input[(delay ) & (input.size()-1)]}; + const auto out1 = float{input[(delay-1) & (input.size()-1)]}; + const auto out2 = float{input[(delay-2) & (input.size()-1)]}; + const auto out3 = float{input[(delay-3) & (input.size()-1)]}; + + const auto out = float{out0*gCubicTable.getCoeff0(delayoffset) + + out1*gCubicTable.getCoeff1(delayoffset) + + out2*gCubicTable.getCoeff2(delayoffset) + + out3*gCubicTable.getCoeff3(delayoffset)}; + return out * midGain; + }; + std::transform(delays.begin(), delays.end(), tempSamples[j].begin(), proc_sample); + + mLate.T60[j].process(al::span{tempSamples[j]}.first(todo)); + } + + /* Next load decorrelated samples from the main delay lines. */ + const float fadeStep{1.0f / static_cast(todo)}; + for(size_t j{0_uz};j < NUM_LINES;++j) + { + const auto input = in_delay.get(j); + auto late_delay_tap0 = size_t{offset - mLateDelayTap[j][0]}; + auto late_delay_tap1 = size_t{offset - mLateDelayTap[j][1]}; + mLateDelayTap[j][0] = mLateDelayTap[j][1]; + const auto densityGain = float{mLate.DensityGain}; + const auto densityStep = float{late_delay_tap0 != late_delay_tap1 + ? densityGain*fadeStep : 0.0f}; + auto fadeCount = float{0.0f}; + + auto samples = tempSamples[j].begin(); for(size_t i{0u};i < todo;) { - late_delay_tap0 &= in_delay.Mask; - late_delay_tap1 &= in_delay.Mask; - size_t td{minz(todo-i, in_delay.Mask+1 - maxz(late_delay_tap0, late_delay_tap1))}; - do { - /* Calculate the read offset and offset between it and the - * next sample. - */ - const float fdelay{mLate.Mod.ModDelays[i]}; - const size_t idelay{float2uint(fdelay * float{gCubicTable.sTableSteps})}; - const size_t delay{late_feedb_tap - (idelay>>gCubicTable.sTableBits)}; - const size_t delayoffset{idelay & gCubicTable.sTableMask}; - ++late_feedb_tap; + late_delay_tap0 &= input.size()-1; + late_delay_tap1 &= input.size()-1; + const auto td = size_t{std::min(todo - i, + input.size() - std::max(late_delay_tap0, late_delay_tap1))}; - /* Get the samples around by the delayed offset. */ - const float out0{late_delay.Line[(delay ) & late_delay.Mask][j]}; - const float out1{late_delay.Line[(delay-1) & late_delay.Mask][j]}; - const float out2{late_delay.Line[(delay-2) & late_delay.Mask][j]}; - const float out3{late_delay.Line[(delay-3) & late_delay.Mask][j]}; - - /* The output is obtained by interpolating the four samples - * that were acquired above, and combined with the main - * delay tap. - */ - const float out{out0*gCubicTable.getCoeff0(delayoffset) - + out1*gCubicTable.getCoeff1(delayoffset) - + out2*gCubicTable.getCoeff2(delayoffset) - + out3*gCubicTable.getCoeff3(delayoffset)}; - const float fade0{densityGain - densityStep*fadeCount}; - const float fade1{densityStep*fadeCount}; + auto proc_sample = [input,densityGain,densityStep,&late_delay_tap0, + &late_delay_tap1,&fadeCount](const float sample) noexcept -> float + { + const auto fade0 = float{densityGain - densityStep*fadeCount}; + const auto fade1 = float{densityStep*fadeCount}; fadeCount += 1.0f; - tempSamples[j][i] = out*midGain + - in_delay.Line[late_delay_tap0++][j]*fade0 + - in_delay.Line[late_delay_tap1++][j]*fade1; - ++i; - } while(--td); + return input[late_delay_tap0++]*fade0 + input[late_delay_tap1++]*fade1 + + sample; + }; + samples = std::transform(samples, samples+ptrdiff_t(td), samples, proc_sample); + i += td; } - mLateDelayTap[j][0] = mLateDelayTap[j][1]; - - mLate.T60[j].process({tempSamples[j].data(), todo}); } /* Apply a vector all-pass to improve micro-surface diffusion, and * write out the results for mixing. */ mLate.VecAp.process(tempSamples, offset, mixX, mixY, todo); - for(size_t j{0u};j < NUM_LINES;j++) + for(size_t j{0_uz};j < NUM_LINES;++j) std::copy_n(tempSamples[j].begin(), todo, outSamples[j].begin()+base); /* Finally, scatter and bounce the results to refeed the feedback buffer. */ - VectorScatterRevDelayIn(late_delay, offset, mixX, mixY, tempSamples, todo); + VectorScatterRev(mixX, mixY, tempSamples, todo); + for(size_t j{0_uz};j < NUM_LINES;++j) + late_delay.write(offset, j, al::span{tempSamples[j]}.first(todo)); base += todo; offset += todo; @@ -1596,110 +1742,35 @@ void ReverbState::process(const size_t samplesToDo, const al::span 0); + ASSUME(samplesToDo <= BufferLineSize); - auto &oldpipeline = mPipelines[mCurrentPipeline^1]; + auto &oldpipeline = mPipelines[!mCurrentPipeline]; auto &pipeline = mPipelines[mCurrentPipeline]; - if(mPipelineState >= Fading) + /* Convert B-Format to A-Format for processing. */ + const size_t numInput{std::min(samplesIn.size(), NUM_LINES)}; + const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; + for(size_t c{0u};c < NUM_LINES;++c) { - /* Convert B-Format to A-Format for processing. */ - const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; - for(size_t c{0u};c < NUM_LINES;c++) + std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); + for(size_t i{0};i < numInput;++i) { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; + const float gain{B2A[c][i]}; - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), - mix_sample); - } - - /* Band-pass the incoming samples and feed the initial delay line. */ - auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); + auto mix_sample = [gain](const float sample, const float in) noexcept -> float + { return sample + in*gain; }; + std::transform(tmpspan.begin(), tmpspan.end(), samplesIn[i].begin(), tmpspan.begin(), + mix_sample); } - if(mPipelineState == Fading) - { - /* Give the old pipeline silence if it's still fading out. */ - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); - } - } + mMainDelay.write(offset, c, tmpspan); } - else - { - /* At the start of a fade, fade in input for the current pipeline, and - * fade out input for the old pipeline. - */ - const size_t numInput{minz(samplesIn.size(), NUM_LINES)}; - const al::span tmpspan{al::assume_aligned<16>(mTempLine.data()), samplesToDo}; - const float fadeStep{1.0f / static_cast(samplesToDo)}; - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), - mix_sample); - } - float stepCount{0.0f}; - for(float &sample : tmpspan) - { - stepCount += 1.0f; - sample *= stepCount*fadeStep; - } - - auto&& filter = DualBiquad{pipeline.mFilter[c].Lp, pipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - pipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); - } - for(size_t c{0u};c < NUM_LINES;c++) - { - std::fill(tmpspan.begin(), tmpspan.end(), 0.0f); - for(size_t i{0};i < numInput;++i) - { - const float gain{B2A[c][i]}; - const float *RESTRICT input{al::assume_aligned<16>(samplesIn[i].data())}; - - auto mix_sample = [gain](const float sample, const float in) noexcept -> float - { return sample + in*gain; }; - std::transform(tmpspan.begin(), tmpspan.end(), input, tmpspan.begin(), - mix_sample); - } - float stepCount{0.0f}; - for(float &sample : tmpspan) - { - stepCount += 1.0f; - sample *= 1.0f - stepCount*fadeStep; - } - - auto&& filter = DualBiquad{oldpipeline.mFilter[c].Lp, oldpipeline.mFilter[c].Hp}; - filter.process(tmpspan, tmpspan.data()); - oldpipeline.mEarlyDelayIn.write(offset, c, tmpspan.cbegin(), samplesToDo); - } + if(mPipelineState < Fading) mPipelineState = Fading; - } /* Process reverb for these samples. and mix them to the output. */ - pipeline.processEarly(offset, samplesToDo, mTempSamples, mEarlySamples); + pipeline.processEarly(mMainDelay, offset, samplesToDo, mTempSamples, mEarlySamples); pipeline.processLate(offset, samplesToDo, mTempSamples, mLateSamples); mixOut(pipeline, samplesOut, samplesToDo); @@ -1708,9 +1779,9 @@ void ReverbState::process(const size_t samplesToDo, const al::span= oldpipeline.mFadeSampleCount) { - for(auto &gains : oldpipeline.mEarly.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); - for(auto &gains : oldpipeline.mLate.TargetGains) - std::fill(std::begin(gains), std::end(gains), 0.0f); + for(auto &gains : oldpipeline.mEarly.Gains) + std::fill(gains.Target.begin(), gains.Target.end(), 0.0f); + for(auto &gains : oldpipeline.mLate.Gains) + std::fill(gains.Target.begin(), gains.Target.end(), 0.0f); oldpipeline.mFadeSampleCount = 0; mPipelineState = Cleanup; } @@ -1735,7 +1806,7 @@ void ReverbState::process(const size_t samplesToDo, const al::span{new ReverbState{}}; } }; -struct StdReverbStateFactory final : public EffectStateFactory { - al::intrusive_ptr create() override - { return al::intrusive_ptr{new ReverbState{}}; } -}; - } // namespace EffectStateFactory *ReverbStateFactory_getFactory() @@ -1762,9 +1828,3 @@ EffectStateFactory *ReverbStateFactory_getFactory() static ReverbStateFactory ReverbFactory{}; return &ReverbFactory; } - -EffectStateFactory *StdReverbStateFactory_getFactory() -{ - static StdReverbStateFactory ReverbFactory{}; - return &ReverbFactory; -} diff --git a/Engine/lib/openal-soft/alc/effects/vmorpher.cpp b/Engine/lib/openal-soft/alc/effects/vmorpher.cpp index 872c7add1..07b2257e5 100644 --- a/Engine/lib/openal-soft/alc/effects/vmorpher.cpp +++ b/Engine/lib/openal-soft/alc/effects/vmorpher.cpp @@ -34,68 +34,69 @@ #include #include +#include #include #include -#include +#include #include "alc/effects/base.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" #include "alspan.h" #include "core/ambidefs.h" #include "core/bufferline.h" #include "core/context.h" -#include "core/devformat.h" #include "core/device.h" +#include "core/effects/base.h" #include "core/effectslot.h" #include "core/mixer.h" #include "intrusive_ptr.h" +struct BufferStorage; namespace { using uint = unsigned int; -#define MAX_UPDATE_SAMPLES 256 -#define NUM_FORMANTS 4 -#define NUM_FILTERS 2 -#define Q_FACTOR 5.0f +constexpr size_t MaxUpdateSamples{256}; +constexpr size_t NumFormants{4}; +constexpr float RcpQFactor{1.0f / 5.0f}; +enum : size_t { + VowelAIndex, + VowelBIndex, + NumFilters +}; -#define VOWEL_A_INDEX 0 -#define VOWEL_B_INDEX 1 - -#define WAVEFORM_FRACBITS 24 -#define WAVEFORM_FRACONE (1<*2.0f / WAVEFORM_FRACONE}; + constexpr float scale{al::numbers::pi_v*2.0f / float{WaveformFracOne}}; return std::sin(static_cast(index) * scale)*0.5f + 0.5f; } inline float Saw(uint index) -{ return static_cast(index) / float{WAVEFORM_FRACONE}; } +{ return static_cast(index) / float{WaveformFracOne}; } inline float Triangle(uint index) -{ return std::fabs(static_cast(index)*(2.0f/WAVEFORM_FRACONE) - 1.0f); } +{ return std::fabs(static_cast(index)*(2.0f/WaveformFracOne) - 1.0f); } inline float Half(uint) { return 0.5f; } template -void Oscillate(float *RESTRICT dst, uint index, const uint step, size_t todo) +void Oscillate(const al::span dst, uint index, const uint step) { - for(size_t i{0u};i < todo;i++) + std::generate(dst.begin(), dst.end(), [&index,step] { index += step; - index &= WAVEFORM_FRACMASK; - dst[i] = func(index); - } + index &= WaveformFracMask; + return func(index); + }); } -struct FormantFilter -{ +struct FormantFilter { float mCoeff{0.0f}; float mGain{1.0f}; float mS1{0.0f}; @@ -106,34 +107,38 @@ struct FormantFilter : mCoeff{std::tan(al::numbers::pi_v * f0norm)}, mGain{gain} { } - inline void process(const float *samplesIn, float *samplesOut, const size_t numInput) + void process(const float *samplesIn, float *samplesOut, const size_t numInput) noexcept { /* A state variable filter from a topology-preserving transform. * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg */ const float g{mCoeff}; const float gain{mGain}; - const float h{1.0f / (1.0f + (g/Q_FACTOR) + (g*g))}; + const float h{1.0f / (1.0f + (g*RcpQFactor) + (g*g))}; + const float coeff{RcpQFactor + g}; float s1{mS1}; float s2{mS2}; - for(size_t i{0u};i < numInput;i++) - { - const float H{(samplesIn[i] - (1.0f/Q_FACTOR + g)*s1 - s2)*h}; - const float B{g*H + s1}; - const float L{g*B + s2}; + const auto input = al::span{samplesIn, numInput}; + const auto output = al::span{samplesOut, numInput}; + std::transform(input.cbegin(), input.cend(), output.cbegin(), output.begin(), + [g,gain,h,coeff,&s1,&s2](const float in, const float out) noexcept -> float + { + const float H{(in - coeff*s1 - s2)*h}; + const float B{g*H + s1}; + const float L{g*B + s2}; - s1 = g*H + B; - s2 = g*B + L; + s1 = g*H + B; + s2 = g*B + L; - // Apply peak and accumulate samples. - samplesOut[i] += B * gain; - } + // Apply peak and accumulate samples. + return out + B*gain; + }); mS1 = s1; mS2 = s2; } - inline void clear() + void clear() noexcept { mS1 = 0.0f; mS2 = 0.0f; @@ -142,26 +147,27 @@ struct FormantFilter struct VmorpherState final : public EffectState { - struct { + struct OutParams { uint mTargetChannel{InvalidChannelIndex}; /* Effect parameters */ - FormantFilter mFormants[NUM_FILTERS][NUM_FORMANTS]; + std::array,NumFilters> mFormants; /* Effect gains for each channel */ float mCurrentGain{}; float mTargetGain{}; - } mChans[MaxAmbiChannels]; + }; + std::array mChans; - void (*mGetSamples)(float*RESTRICT, uint, const uint, size_t){}; + void (*mGetSamples)(const al::span dst, uint index, const uint step){}; uint mIndex{0}; uint mStep{1}; /* Effects buffers */ - alignas(16) float mSampleBufferA[MAX_UPDATE_SAMPLES]{}; - alignas(16) float mSampleBufferB[MAX_UPDATE_SAMPLES]{}; - alignas(16) float mLfo[MAX_UPDATE_SAMPLES]{}; + alignas(16) std::array mSampleBufferA{}; + alignas(16) std::array mSampleBufferB{}; + alignas(16) std::array mLfo{}; void deviceUpdate(const DeviceBase *device, const BufferStorage *buffer) override; void update(const ContextBase *context, const EffectSlot *slot, const EffectProps *props, @@ -169,14 +175,12 @@ struct VmorpherState final : public EffectState { void process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) override; - static std::array getFiltersByPhoneme(VMorpherPhenome phoneme, - float frequency, float pitch); - - DEF_NEWDEL(VmorpherState) + static std::array getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch) noexcept; }; -std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, - float frequency, float pitch) +std::array VmorpherState::getFiltersByPhoneme(VMorpherPhenome phoneme, + float frequency, float pitch) noexcept { /* Using soprano formant set of values to * better match mid-range frequency space. @@ -232,44 +236,43 @@ void VmorpherState::deviceUpdate(const DeviceBase*, const BufferStorage*) for(auto &e : mChans) { e.mTargetChannel = InvalidChannelIndex; - std::for_each(std::begin(e.mFormants[VOWEL_A_INDEX]), std::end(e.mFormants[VOWEL_A_INDEX]), + std::for_each(e.mFormants[VowelAIndex].begin(), e.mFormants[VowelAIndex].end(), std::mem_fn(&FormantFilter::clear)); - std::for_each(std::begin(e.mFormants[VOWEL_B_INDEX]), std::end(e.mFormants[VOWEL_B_INDEX]), + std::for_each(e.mFormants[VowelBIndex].begin(), e.mFormants[VowelBIndex].end(), std::mem_fn(&FormantFilter::clear)); e.mCurrentGain = 0.0f; } } void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, - const EffectProps *props, const EffectTarget target) + const EffectProps *props_, const EffectTarget target) { + auto &props = std::get(*props_); const DeviceBase *device{context->mDevice}; const float frequency{static_cast(device->Frequency)}; - const float step{props->Vmorpher.Rate / frequency}; - mStep = fastf2u(clampf(step*WAVEFORM_FRACONE, 0.0f, float{WAVEFORM_FRACONE-1})); + const float step{props.Rate / frequency}; + mStep = fastf2u(std::clamp(step*WaveformFracOne, 0.0f, WaveformFracOne-1.0f)); if(mStep == 0) mGetSamples = Oscillate; - else if(props->Vmorpher.Waveform == VMorpherWaveform::Sinusoid) + else if(props.Waveform == VMorpherWaveform::Sinusoid) mGetSamples = Oscillate; - else if(props->Vmorpher.Waveform == VMorpherWaveform::Triangle) + else if(props.Waveform == VMorpherWaveform::Triangle) mGetSamples = Oscillate; - else /*if(props->Vmorpher.Waveform == VMorpherWaveform::Sawtooth)*/ + else /*if(props.Waveform == VMorpherWaveform::Sawtooth)*/ mGetSamples = Oscillate; - const float pitchA{std::pow(2.0f, - static_cast(props->Vmorpher.PhonemeACoarseTuning) / 12.0f)}; - const float pitchB{std::pow(2.0f, - static_cast(props->Vmorpher.PhonemeBCoarseTuning) / 12.0f)}; + const float pitchA{std::pow(2.0f, static_cast(props.PhonemeACoarseTuning) / 12.0f)}; + const float pitchB{std::pow(2.0f, static_cast(props.PhonemeBCoarseTuning) / 12.0f)}; - auto vowelA = getFiltersByPhoneme(props->Vmorpher.PhonemeA, frequency, pitchA); - auto vowelB = getFiltersByPhoneme(props->Vmorpher.PhonemeB, frequency, pitchB); + auto vowelA = getFiltersByPhoneme(props.PhonemeA, frequency, pitchA); + auto vowelB = getFiltersByPhoneme(props.PhonemeB, frequency, pitchB); /* Copy the filter coefficients to the input channels. */ for(size_t i{0u};i < slot->Wet.Buffer.size();++i) { - std::copy(vowelA.begin(), vowelA.end(), std::begin(mChans[i].mFormants[VOWEL_A_INDEX])); - std::copy(vowelB.begin(), vowelB.end(), std::begin(mChans[i].mFormants[VOWEL_B_INDEX])); + std::copy(vowelA.begin(), vowelA.end(), mChans[i].mFormants[VowelAIndex].begin()); + std::copy(vowelB.begin(), vowelB.end(), mChans[i].mFormants[VowelBIndex].begin()); } mOutTarget = target.Main->Buffer; @@ -283,18 +286,20 @@ void VmorpherState::update(const ContextBase *context, const EffectSlot *slot, void VmorpherState::process(const size_t samplesToDo, const al::span samplesIn, const al::span samplesOut) { + alignas(16) std::array blended{}; + /* Following the EFX specification for a conformant implementation which describes * the effect as a pair of 4-band formant filters blended together using an LFO. */ for(size_t base{0u};base < samplesToDo;) { - const size_t td{minz(MAX_UPDATE_SAMPLES, samplesToDo-base)}; + const size_t td{std::min(MaxUpdateSamples, samplesToDo-base)}; - mGetSamples(mLfo, mIndex, mStep, td); + mGetSamples(al::span{mLfo}.first(td), mIndex, mStep); mIndex += static_cast(mStep * td); - mIndex &= WAVEFORM_FRACMASK; + mIndex &= WaveformFracMask; - auto chandata = std::begin(mChans); + auto chandata = mChans.begin(); for(const auto &input : samplesIn) { const size_t outidx{chandata->mTargetChannel}; @@ -304,30 +309,29 @@ void VmorpherState::process(const size_t samplesToDo, const al::spanmFormants[VOWEL_A_INDEX]; - auto& vowelB = chandata->mFormants[VOWEL_B_INDEX]; + const auto vowelA = al::span{chandata->mFormants[VowelAIndex]}; + const auto vowelB = al::span{chandata->mFormants[VowelBIndex]}; /* Process first vowel. */ - std::fill_n(std::begin(mSampleBufferA), td, 0.0f); - vowelA[0].process(&input[base], mSampleBufferA, td); - vowelA[1].process(&input[base], mSampleBufferA, td); - vowelA[2].process(&input[base], mSampleBufferA, td); - vowelA[3].process(&input[base], mSampleBufferA, td); + std::fill_n(mSampleBufferA.begin(), td, 0.0f); + vowelA[0].process(&input[base], mSampleBufferA.data(), td); + vowelA[1].process(&input[base], mSampleBufferA.data(), td); + vowelA[2].process(&input[base], mSampleBufferA.data(), td); + vowelA[3].process(&input[base], mSampleBufferA.data(), td); /* Process second vowel. */ - std::fill_n(std::begin(mSampleBufferB), td, 0.0f); - vowelB[0].process(&input[base], mSampleBufferB, td); - vowelB[1].process(&input[base], mSampleBufferB, td); - vowelB[2].process(&input[base], mSampleBufferB, td); - vowelB[3].process(&input[base], mSampleBufferB, td); + std::fill_n(mSampleBufferB.begin(), td, 0.0f); + vowelB[0].process(&input[base], mSampleBufferB.data(), td); + vowelB[1].process(&input[base], mSampleBufferB.data(), td); + vowelB[2].process(&input[base], mSampleBufferB.data(), td); + vowelB[3].process(&input[base], mSampleBufferB.data(), td); - alignas(16) float blended[MAX_UPDATE_SAMPLES]; for(size_t i{0u};i < td;i++) blended[i] = lerpf(mSampleBufferA[i], mSampleBufferB[i], mLfo[i]); /* Now, mix the processed sound data to the output. */ - MixSamples({blended, td}, samplesOut[outidx].data()+base, chandata->mCurrentGain, - chandata->mTargetGain, samplesToDo-base); + MixSamples(al::span{blended}.first(td), al::span{samplesOut[outidx]}.subspan(base), + chandata->mCurrentGain, chandata->mTargetGain, samplesToDo-base); ++chandata; } diff --git a/Engine/lib/openal-soft/alc/events.cpp b/Engine/lib/openal-soft/alc/events.cpp new file mode 100644 index 000000000..1010a3384 --- /dev/null +++ b/Engine/lib/openal-soft/alc/events.cpp @@ -0,0 +1,95 @@ + +#include "config.h" + +#include "events.h" + +#include "alspan.h" +#include "core/logging.h" +#include "device.h" + + +namespace { + +ALCenum EnumFromEventType(const alc::EventType type) +{ + switch(type) + { + case alc::EventType::DefaultDeviceChanged: return ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT; + case alc::EventType::DeviceAdded: return ALC_EVENT_TYPE_DEVICE_ADDED_SOFT; + case alc::EventType::DeviceRemoved: return ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT; + case alc::EventType::Count: break; + } + throw std::runtime_error{"Invalid EventType: "+std::to_string(al::to_underlying(type))}; +} + +} // namespace + +namespace alc { + +std::optional GetEventType(ALCenum type) +{ + switch(type) + { + case ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT: return alc::EventType::DefaultDeviceChanged; + case ALC_EVENT_TYPE_DEVICE_ADDED_SOFT: return alc::EventType::DeviceAdded; + case ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT: return alc::EventType::DeviceRemoved; + } + return std::nullopt; +} + +void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept +{ + auto eventlock = std::unique_lock{EventMutex}; + if(EventCallback && EventsEnabled.test(al::to_underlying(eventType))) + EventCallback(EnumFromEventType(eventType), al::to_underlying(deviceType), device, + static_cast(message.length()), message.data(), EventUserPtr); +} + +} // namespace alc + +FORCE_ALIGN ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, + ALCboolean enable) noexcept +{ + if(enable != ALC_FALSE && enable != ALC_TRUE) + { + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_FALSE; + } + if(count < 0) + { + alcSetError(nullptr, ALC_INVALID_VALUE); + return ALC_FALSE; + } + if(count == 0) + return ALC_TRUE; + if(!events) + { + alcSetError(nullptr, ALC_INVALID_VALUE); + return ALC_FALSE; + } + + alc::EventBitSet eventSet{0}; + for(ALCenum type : al::span{events, static_cast(count)}) + { + auto etype = alc::GetEventType(type); + if(!etype) + { + WARN("Invalid event type: 0x%04x\n", type); + alcSetError(nullptr, ALC_INVALID_ENUM); + return ALC_FALSE; + } + eventSet.set(al::to_underlying(*etype)); + } + + auto eventlock = std::unique_lock{alc::EventMutex}; + if(enable) alc::EventsEnabled |= eventSet; + else alc::EventsEnabled &= ~eventSet; + return ALC_TRUE; +} + +FORCE_ALIGN void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) noexcept +{ + auto eventlock = std::unique_lock{alc::EventMutex}; + alc::EventCallback = callback; + alc::EventUserPtr = userParam; +} diff --git a/Engine/lib/openal-soft/alc/events.h b/Engine/lib/openal-soft/alc/events.h new file mode 100644 index 000000000..3f53ec762 --- /dev/null +++ b/Engine/lib/openal-soft/alc/events.h @@ -0,0 +1,50 @@ +#ifndef ALC_EVENTS_H +#define ALC_EVENTS_H + +#include "inprogext.h" +#include "opthelpers.h" + +#include +#include +#include +#include + + +namespace alc { + +enum class EventType : uint8_t { + DefaultDeviceChanged, + DeviceAdded, + DeviceRemoved, + + Count +}; + +std::optional GetEventType(ALCenum type); + +enum class EventSupport : ALCenum { + FullSupport = ALC_EVENT_SUPPORTED_SOFT, + NoSupport = ALC_EVENT_NOT_SUPPORTED_SOFT, +}; + +enum class DeviceType : ALCenum { + Playback = ALC_PLAYBACK_DEVICE_SOFT, + Capture = ALC_CAPTURE_DEVICE_SOFT, +}; + +using EventBitSet = std::bitset; +inline EventBitSet EventsEnabled{0}; + +inline std::mutex EventMutex; + +inline ALCEVENTPROCTYPESOFT EventCallback{}; +inline void *EventUserPtr{}; + +void Event(EventType eventType, DeviceType deviceType, ALCdevice *device, std::string_view message) noexcept; + +inline void Event(EventType eventType, DeviceType deviceType, std::string_view message) noexcept +{ Event(eventType, deviceType, nullptr, message); } + +} // namespace alc + +#endif /* ALC_EVENTS_H */ diff --git a/Engine/lib/openal-soft/alc/export_list.h b/Engine/lib/openal-soft/alc/export_list.h new file mode 100644 index 000000000..2c291ac0d --- /dev/null +++ b/Engine/lib/openal-soft/alc/export_list.h @@ -0,0 +1,917 @@ +#ifndef ALC_EXPORT_LIST_H +#define ALC_EXPORT_LIST_H + +#include "AL/alc.h" +#include "AL/al.h" +#include "AL/alext.h" + +#include "inprogext.h" +#ifdef ALSOFT_EAX +#include "context.h" +#include "al/eax/x_ram.h" +#endif + + +struct FuncExport { + const char *funcName; + void *address; +}; +#define DECL(x) FuncExport{#x, reinterpret_cast(x)} +/* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */ +inline const FuncExport alcFunctions[]{ + DECL(alcCreateContext), + DECL(alcMakeContextCurrent), + DECL(alcProcessContext), + DECL(alcSuspendContext), + DECL(alcDestroyContext), + DECL(alcGetCurrentContext), + DECL(alcGetContextsDevice), + DECL(alcOpenDevice), + DECL(alcCloseDevice), + DECL(alcGetError), + DECL(alcIsExtensionPresent), + DECL(alcGetProcAddress), + DECL(alcGetEnumValue), + DECL(alcGetString), + DECL(alcGetIntegerv), + DECL(alcCaptureOpenDevice), + DECL(alcCaptureCloseDevice), + DECL(alcCaptureStart), + DECL(alcCaptureStop), + DECL(alcCaptureSamples), + + DECL(alcSetThreadContext), + DECL(alcGetThreadContext), + + DECL(alcLoopbackOpenDeviceSOFT), + DECL(alcIsRenderFormatSupportedSOFT), + DECL(alcRenderSamplesSOFT), + + DECL(alcDevicePauseSOFT), + DECL(alcDeviceResumeSOFT), + + DECL(alcGetStringiSOFT), + DECL(alcResetDeviceSOFT), + + DECL(alcGetInteger64vSOFT), + + DECL(alcReopenDeviceSOFT), + + DECL(alcEventIsSupportedSOFT), + DECL(alcEventControlSOFT), + DECL(alcEventCallbackSOFT), + + DECL(alEnable), + DECL(alDisable), + DECL(alIsEnabled), + DECL(alGetString), + DECL(alGetBooleanv), + DECL(alGetIntegerv), + DECL(alGetFloatv), + DECL(alGetDoublev), + DECL(alGetBoolean), + DECL(alGetInteger), + DECL(alGetFloat), + DECL(alGetDouble), + DECL(alGetError), + DECL(alIsExtensionPresent), + DECL(alGetProcAddress), + DECL(alGetEnumValue), + DECL(alListenerf), + DECL(alListener3f), + DECL(alListenerfv), + DECL(alListeneri), + DECL(alListener3i), + DECL(alListeneriv), + DECL(alGetListenerf), + DECL(alGetListener3f), + DECL(alGetListenerfv), + DECL(alGetListeneri), + DECL(alGetListener3i), + DECL(alGetListeneriv), + DECL(alGenSources), + DECL(alDeleteSources), + DECL(alIsSource), + DECL(alSourcef), + DECL(alSource3f), + DECL(alSourcefv), + DECL(alSourcei), + DECL(alSource3i), + DECL(alSourceiv), + DECL(alGetSourcef), + DECL(alGetSource3f), + DECL(alGetSourcefv), + DECL(alGetSourcei), + DECL(alGetSource3i), + DECL(alGetSourceiv), + DECL(alSourcePlayv), + DECL(alSourceStopv), + DECL(alSourceRewindv), + DECL(alSourcePausev), + DECL(alSourcePlay), + DECL(alSourceStop), + DECL(alSourceRewind), + DECL(alSourcePause), + DECL(alSourceQueueBuffers), + DECL(alSourceUnqueueBuffers), + DECL(alGenBuffers), + DECL(alDeleteBuffers), + DECL(alIsBuffer), + DECL(alBufferData), + DECL(alBufferf), + DECL(alBuffer3f), + DECL(alBufferfv), + DECL(alBufferi), + DECL(alBuffer3i), + DECL(alBufferiv), + DECL(alGetBufferf), + DECL(alGetBuffer3f), + DECL(alGetBufferfv), + DECL(alGetBufferi), + DECL(alGetBuffer3i), + DECL(alGetBufferiv), + DECL(alDopplerFactor), + DECL(alDopplerVelocity), + DECL(alSpeedOfSound), + DECL(alDistanceModel), + + DECL(alGenFilters), + DECL(alDeleteFilters), + DECL(alIsFilter), + DECL(alFilteri), + DECL(alFilteriv), + DECL(alFilterf), + DECL(alFilterfv), + DECL(alGetFilteri), + DECL(alGetFilteriv), + DECL(alGetFilterf), + DECL(alGetFilterfv), + DECL(alGenEffects), + DECL(alDeleteEffects), + DECL(alIsEffect), + DECL(alEffecti), + DECL(alEffectiv), + DECL(alEffectf), + DECL(alEffectfv), + DECL(alGetEffecti), + DECL(alGetEffectiv), + DECL(alGetEffectf), + DECL(alGetEffectfv), + DECL(alGenAuxiliaryEffectSlots), + DECL(alDeleteAuxiliaryEffectSlots), + DECL(alIsAuxiliaryEffectSlot), + DECL(alAuxiliaryEffectSloti), + DECL(alAuxiliaryEffectSlotiv), + DECL(alAuxiliaryEffectSlotf), + DECL(alAuxiliaryEffectSlotfv), + DECL(alGetAuxiliaryEffectSloti), + DECL(alGetAuxiliaryEffectSlotiv), + DECL(alGetAuxiliaryEffectSlotf), + DECL(alGetAuxiliaryEffectSlotfv), + + DECL(alDeferUpdatesSOFT), + DECL(alProcessUpdatesSOFT), + + DECL(alSourcedSOFT), + DECL(alSource3dSOFT), + DECL(alSourcedvSOFT), + DECL(alGetSourcedSOFT), + DECL(alGetSource3dSOFT), + DECL(alGetSourcedvSOFT), + DECL(alSourcei64SOFT), + DECL(alSource3i64SOFT), + DECL(alSourcei64vSOFT), + DECL(alGetSourcei64SOFT), + DECL(alGetSource3i64SOFT), + DECL(alGetSourcei64vSOFT), + + DECL(alGetStringiSOFT), + + DECL(alBufferStorageSOFT), + DECL(alMapBufferSOFT), + DECL(alUnmapBufferSOFT), + DECL(alFlushMappedBufferSOFT), + + DECL(alEventControlSOFT), + DECL(alEventCallbackSOFT), + DECL(alGetPointerSOFT), + DECL(alGetPointervSOFT), + + DECL(alBufferCallbackSOFT), + DECL(alGetBufferPtrSOFT), + DECL(alGetBuffer3PtrSOFT), + DECL(alGetBufferPtrvSOFT), + + DECL(alSourcePlayAtTimeSOFT), + DECL(alSourcePlayAtTimevSOFT), + + DECL(alBufferSubDataSOFT), + + DECL(alBufferDataStatic), + + DECL(alDebugMessageCallbackEXT), + DECL(alDebugMessageInsertEXT), + DECL(alDebugMessageControlEXT), + DECL(alPushDebugGroupEXT), + DECL(alPopDebugGroupEXT), + DECL(alGetDebugMessageLogEXT), + + /* Direct Context functions */ + DECL(alcGetProcAddress2), + DECL(alEnableDirect), + DECL(alDisableDirect), + DECL(alIsEnabledDirect), + DECL(alDopplerFactorDirect), + DECL(alSpeedOfSoundDirect), + DECL(alDistanceModelDirect), + DECL(alGetStringDirect), + DECL(alGetBooleanvDirect), + DECL(alGetIntegervDirect), + DECL(alGetFloatvDirect), + DECL(alGetDoublevDirect), + DECL(alGetBooleanDirect), + DECL(alGetIntegerDirect), + DECL(alGetFloatDirect), + DECL(alGetDoubleDirect), + + DECL(alGetErrorDirect), + DECL(alIsExtensionPresentDirect), + DECL(alGetProcAddress), + DECL(alGetEnumValueDirect), + + DECL(alListeneriDirect), + DECL(alListener3iDirect), + DECL(alListenerivDirect), + DECL(alListenerfDirect), + DECL(alListener3fDirect), + DECL(alListenerfvDirect), + DECL(alGetListeneriDirect), + DECL(alGetListener3iDirect), + DECL(alGetListenerivDirect), + DECL(alGetListenerfDirect), + DECL(alGetListener3fDirect), + DECL(alGetListenerfvDirect), + + DECL(alGenBuffersDirect), + DECL(alDeleteBuffersDirect), + DECL(alIsBufferDirect), + DECL(alBufferDataDirect), + DECL(alBufferiDirect), + DECL(alBuffer3iDirect), + DECL(alBufferivDirect), + DECL(alBufferfDirect), + DECL(alBuffer3fDirect), + DECL(alBufferfvDirect), + DECL(alGetBufferiDirect), + DECL(alGetBuffer3iDirect), + DECL(alGetBufferivDirect), + DECL(alGetBufferfDirect), + DECL(alGetBuffer3fDirect), + DECL(alGetBufferfvDirect), + + DECL(alGenSourcesDirect), + DECL(alDeleteSourcesDirect), + DECL(alIsSourceDirect), + DECL(alSourcePlayDirect), + DECL(alSourceStopDirect), + DECL(alSourcePauseDirect), + DECL(alSourceRewindDirect), + DECL(alSourcePlayvDirect), + DECL(alSourceStopvDirect), + DECL(alSourcePausevDirect), + DECL(alSourceRewindvDirect), + DECL(alSourceiDirect), + DECL(alSource3iDirect), + DECL(alSourceivDirect), + DECL(alSourcefDirect), + DECL(alSource3fDirect), + DECL(alSourcefvDirect), + DECL(alGetSourceiDirect), + DECL(alGetSource3iDirect), + DECL(alGetSourceivDirect), + DECL(alGetSourcefDirect), + DECL(alGetSource3fDirect), + DECL(alGetSourcefvDirect), + DECL(alSourceQueueBuffersDirect), + DECL(alSourceUnqueueBuffersDirect), + + DECL(alGenFiltersDirect), + DECL(alDeleteFiltersDirect), + DECL(alIsFilterDirect), + DECL(alFilteriDirect), + DECL(alFilterivDirect), + DECL(alFilterfDirect), + DECL(alFilterfvDirect), + DECL(alGetFilteriDirect), + DECL(alGetFilterivDirect), + DECL(alGetFilterfDirect), + DECL(alGetFilterfvDirect), + DECL(alGenEffectsDirect), + DECL(alDeleteEffectsDirect), + DECL(alIsEffectDirect), + DECL(alEffectiDirect), + DECL(alEffectivDirect), + DECL(alEffectfDirect), + DECL(alEffectfvDirect), + DECL(alGetEffectiDirect), + DECL(alGetEffectivDirect), + DECL(alGetEffectfDirect), + DECL(alGetEffectfvDirect), + DECL(alGenAuxiliaryEffectSlotsDirect), + DECL(alDeleteAuxiliaryEffectSlotsDirect), + DECL(alIsAuxiliaryEffectSlotDirect), + DECL(alAuxiliaryEffectSlotiDirect), + DECL(alAuxiliaryEffectSlotivDirect), + DECL(alAuxiliaryEffectSlotfDirect), + DECL(alAuxiliaryEffectSlotfvDirect), + DECL(alGetAuxiliaryEffectSlotiDirect), + DECL(alGetAuxiliaryEffectSlotivDirect), + DECL(alGetAuxiliaryEffectSlotfDirect), + DECL(alGetAuxiliaryEffectSlotfvDirect), + + DECL(alDeferUpdatesDirectSOFT), + DECL(alProcessUpdatesDirectSOFT), + DECL(alGetStringiDirectSOFT), + + DECL(alBufferDataStaticDirect), + DECL(alBufferCallbackDirectSOFT), + DECL(alBufferSubDataDirectSOFT), + DECL(alBufferStorageDirectSOFT), + DECL(alMapBufferDirectSOFT), + DECL(alUnmapBufferDirectSOFT), + DECL(alFlushMappedBufferDirectSOFT), + + DECL(alSourcei64DirectSOFT), + DECL(alSource3i64DirectSOFT), + DECL(alSourcei64vDirectSOFT), + DECL(alSourcedDirectSOFT), + DECL(alSource3dDirectSOFT), + DECL(alSourcedvDirectSOFT), + DECL(alGetSourcei64DirectSOFT), + DECL(alGetSource3i64DirectSOFT), + DECL(alGetSourcei64vDirectSOFT), + DECL(alGetSourcedDirectSOFT), + DECL(alGetSource3dDirectSOFT), + DECL(alGetSourcedvDirectSOFT), + DECL(alSourcePlayAtTimeDirectSOFT), + DECL(alSourcePlayAtTimevDirectSOFT), + + DECL(alEventControlDirectSOFT), + DECL(alEventCallbackDirectSOFT), + + DECL(alDebugMessageCallbackDirectEXT), + DECL(alDebugMessageInsertDirectEXT), + DECL(alDebugMessageControlDirectEXT), + DECL(alPushDebugGroupDirectEXT), + DECL(alPopDebugGroupDirectEXT), + DECL(alGetDebugMessageLogDirectEXT), + DECL(alObjectLabelEXT), + DECL(alObjectLabelDirectEXT), + DECL(alGetObjectLabelEXT), + DECL(alGetObjectLabelDirectEXT), + + /* Extra functions */ + DECL(alsoft_set_log_callback), +}; +#ifdef ALSOFT_EAX +inline const std::array eaxFunctions{ + DECL(EAXGet), + DECL(EAXSet), + DECL(EAXGetBufferMode), + DECL(EAXSetBufferMode), + + DECL(EAXGetDirect), + DECL(EAXSetDirect), + DECL(EAXGetBufferModeDirect), + DECL(EAXSetBufferModeDirect), +}; +#endif +#undef DECL + +struct EnumExport { + const char *enumName; + int value; +}; +#define DECL(x) EnumExport{#x, (x)} +/* NOLINTNEXTLINE(*-avoid-c-arrays) Too large for std::array auto-deduction :( */ +inline const EnumExport alcEnumerations[]{ + DECL(ALC_INVALID), + DECL(ALC_FALSE), + DECL(ALC_TRUE), + + DECL(ALC_MAJOR_VERSION), + DECL(ALC_MINOR_VERSION), + DECL(ALC_ATTRIBUTES_SIZE), + DECL(ALC_ALL_ATTRIBUTES), + DECL(ALC_DEFAULT_DEVICE_SPECIFIER), + DECL(ALC_DEVICE_SPECIFIER), + DECL(ALC_ALL_DEVICES_SPECIFIER), + DECL(ALC_DEFAULT_ALL_DEVICES_SPECIFIER), + DECL(ALC_EXTENSIONS), + DECL(ALC_FREQUENCY), + DECL(ALC_REFRESH), + DECL(ALC_SYNC), + DECL(ALC_MONO_SOURCES), + DECL(ALC_STEREO_SOURCES), + DECL(ALC_CAPTURE_DEVICE_SPECIFIER), + DECL(ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER), + DECL(ALC_CAPTURE_SAMPLES), + DECL(ALC_CONNECTED), + + DECL(ALC_EFX_MAJOR_VERSION), + DECL(ALC_EFX_MINOR_VERSION), + DECL(ALC_MAX_AUXILIARY_SENDS), + + DECL(ALC_FORMAT_CHANNELS_SOFT), + DECL(ALC_FORMAT_TYPE_SOFT), + + DECL(ALC_MONO_SOFT), + DECL(ALC_STEREO_SOFT), + DECL(ALC_QUAD_SOFT), + DECL(ALC_5POINT1_SOFT), + DECL(ALC_6POINT1_SOFT), + DECL(ALC_7POINT1_SOFT), + DECL(ALC_BFORMAT3D_SOFT), + + DECL(ALC_BYTE_SOFT), + DECL(ALC_UNSIGNED_BYTE_SOFT), + DECL(ALC_SHORT_SOFT), + DECL(ALC_UNSIGNED_SHORT_SOFT), + DECL(ALC_INT_SOFT), + DECL(ALC_UNSIGNED_INT_SOFT), + DECL(ALC_FLOAT_SOFT), + + DECL(ALC_HRTF_SOFT), + DECL(ALC_DONT_CARE_SOFT), + DECL(ALC_HRTF_STATUS_SOFT), + DECL(ALC_HRTF_DISABLED_SOFT), + DECL(ALC_HRTF_ENABLED_SOFT), + DECL(ALC_HRTF_DENIED_SOFT), + DECL(ALC_HRTF_REQUIRED_SOFT), + DECL(ALC_HRTF_HEADPHONES_DETECTED_SOFT), + DECL(ALC_HRTF_UNSUPPORTED_FORMAT_SOFT), + DECL(ALC_NUM_HRTF_SPECIFIERS_SOFT), + DECL(ALC_HRTF_SPECIFIER_SOFT), + DECL(ALC_HRTF_ID_SOFT), + + DECL(ALC_AMBISONIC_LAYOUT_SOFT), + DECL(ALC_AMBISONIC_SCALING_SOFT), + DECL(ALC_AMBISONIC_ORDER_SOFT), + DECL(ALC_ACN_SOFT), + DECL(ALC_FUMA_SOFT), + DECL(ALC_N3D_SOFT), + DECL(ALC_SN3D_SOFT), + + DECL(ALC_OUTPUT_LIMITER_SOFT), + + DECL(ALC_DEVICE_CLOCK_SOFT), + DECL(ALC_DEVICE_LATENCY_SOFT), + DECL(ALC_DEVICE_CLOCK_LATENCY_SOFT), + DECL(AL_SAMPLE_OFFSET_CLOCK_SOFT), + DECL(AL_SEC_OFFSET_CLOCK_SOFT), + + DECL(ALC_OUTPUT_MODE_SOFT), + DECL(ALC_ANY_SOFT), + DECL(ALC_STEREO_BASIC_SOFT), + DECL(ALC_STEREO_UHJ_SOFT), + DECL(ALC_STEREO_HRTF_SOFT), + DECL(ALC_SURROUND_5_1_SOFT), + DECL(ALC_SURROUND_6_1_SOFT), + DECL(ALC_SURROUND_7_1_SOFT), + + DECL(ALC_NO_ERROR), + DECL(ALC_INVALID_DEVICE), + DECL(ALC_INVALID_CONTEXT), + DECL(ALC_INVALID_ENUM), + DECL(ALC_INVALID_VALUE), + DECL(ALC_OUT_OF_MEMORY), + + DECL(ALC_CONTEXT_FLAGS_EXT), + DECL(ALC_CONTEXT_DEBUG_BIT_EXT), + + DECL(ALC_PLAYBACK_DEVICE_SOFT), + DECL(ALC_CAPTURE_DEVICE_SOFT), + DECL(ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT), + DECL(ALC_EVENT_TYPE_DEVICE_ADDED_SOFT), + DECL(ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT), + + + DECL(AL_INVALID), + DECL(AL_NONE), + DECL(AL_FALSE), + DECL(AL_TRUE), + + DECL(AL_SOURCE_RELATIVE), + DECL(AL_CONE_INNER_ANGLE), + DECL(AL_CONE_OUTER_ANGLE), + DECL(AL_PITCH), + DECL(AL_POSITION), + DECL(AL_DIRECTION), + DECL(AL_VELOCITY), + DECL(AL_LOOPING), + DECL(AL_BUFFER), + DECL(AL_GAIN), + DECL(AL_MIN_GAIN), + DECL(AL_MAX_GAIN), + DECL(AL_ORIENTATION), + DECL(AL_REFERENCE_DISTANCE), + DECL(AL_ROLLOFF_FACTOR), + DECL(AL_CONE_OUTER_GAIN), + DECL(AL_MAX_DISTANCE), + DECL(AL_SEC_OFFSET), + DECL(AL_SAMPLE_OFFSET), + DECL(AL_BYTE_OFFSET), + DECL(AL_SOURCE_TYPE), + DECL(AL_STATIC), + DECL(AL_STREAMING), + DECL(AL_UNDETERMINED), + DECL(AL_METERS_PER_UNIT), + DECL(AL_LOOP_POINTS_SOFT), + DECL(AL_DIRECT_CHANNELS_SOFT), + + DECL(AL_DIRECT_FILTER), + DECL(AL_AUXILIARY_SEND_FILTER), + DECL(AL_AIR_ABSORPTION_FACTOR), + DECL(AL_ROOM_ROLLOFF_FACTOR), + DECL(AL_CONE_OUTER_GAINHF), + DECL(AL_DIRECT_FILTER_GAINHF_AUTO), + DECL(AL_AUXILIARY_SEND_FILTER_GAIN_AUTO), + DECL(AL_AUXILIARY_SEND_FILTER_GAINHF_AUTO), + + DECL(AL_SOURCE_STATE), + DECL(AL_INITIAL), + DECL(AL_PLAYING), + DECL(AL_PAUSED), + DECL(AL_STOPPED), + + DECL(AL_BUFFERS_QUEUED), + DECL(AL_BUFFERS_PROCESSED), + + DECL(AL_FORMAT_MONO8), + DECL(AL_FORMAT_MONO16), + DECL(AL_FORMAT_MONO_FLOAT32), + DECL(AL_FORMAT_MONO_DOUBLE_EXT), + DECL(AL_FORMAT_STEREO8), + DECL(AL_FORMAT_STEREO16), + DECL(AL_FORMAT_STEREO_FLOAT32), + DECL(AL_FORMAT_STEREO_DOUBLE_EXT), + DECL(AL_FORMAT_MONO_IMA4), + DECL(AL_FORMAT_STEREO_IMA4), + DECL(AL_FORMAT_MONO_MSADPCM_SOFT), + DECL(AL_FORMAT_STEREO_MSADPCM_SOFT), + DECL(AL_FORMAT_QUAD8_LOKI), + DECL(AL_FORMAT_QUAD16_LOKI), + DECL(AL_FORMAT_QUAD8), + DECL(AL_FORMAT_QUAD16), + DECL(AL_FORMAT_QUAD32), + DECL(AL_FORMAT_51CHN8), + DECL(AL_FORMAT_51CHN16), + DECL(AL_FORMAT_51CHN32), + DECL(AL_FORMAT_61CHN8), + DECL(AL_FORMAT_61CHN16), + DECL(AL_FORMAT_61CHN32), + DECL(AL_FORMAT_71CHN8), + DECL(AL_FORMAT_71CHN16), + DECL(AL_FORMAT_71CHN32), + DECL(AL_FORMAT_REAR8), + DECL(AL_FORMAT_REAR16), + DECL(AL_FORMAT_REAR32), + DECL(AL_FORMAT_MONO_MULAW), + DECL(AL_FORMAT_MONO_MULAW_EXT), + DECL(AL_FORMAT_STEREO_MULAW), + DECL(AL_FORMAT_STEREO_MULAW_EXT), + DECL(AL_FORMAT_QUAD_MULAW), + DECL(AL_FORMAT_51CHN_MULAW), + DECL(AL_FORMAT_61CHN_MULAW), + DECL(AL_FORMAT_71CHN_MULAW), + DECL(AL_FORMAT_REAR_MULAW), + DECL(AL_FORMAT_MONO_ALAW_EXT), + DECL(AL_FORMAT_STEREO_ALAW_EXT), + + DECL(AL_FORMAT_BFORMAT2D_8), + DECL(AL_FORMAT_BFORMAT2D_16), + DECL(AL_FORMAT_BFORMAT2D_FLOAT32), + DECL(AL_FORMAT_BFORMAT2D_MULAW), + DECL(AL_FORMAT_BFORMAT3D_8), + DECL(AL_FORMAT_BFORMAT3D_16), + DECL(AL_FORMAT_BFORMAT3D_FLOAT32), + DECL(AL_FORMAT_BFORMAT3D_MULAW), + + DECL(AL_FORMAT_UHJ2CHN8_SOFT), + DECL(AL_FORMAT_UHJ2CHN16_SOFT), + DECL(AL_FORMAT_UHJ2CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ3CHN8_SOFT), + DECL(AL_FORMAT_UHJ3CHN16_SOFT), + DECL(AL_FORMAT_UHJ3CHN_FLOAT32_SOFT), + DECL(AL_FORMAT_UHJ4CHN8_SOFT), + DECL(AL_FORMAT_UHJ4CHN16_SOFT), + DECL(AL_FORMAT_UHJ4CHN_FLOAT32_SOFT), + DECL(AL_STEREO_MODE_SOFT), + DECL(AL_NORMAL_SOFT), + DECL(AL_SUPER_STEREO_SOFT), + DECL(AL_SUPER_STEREO_WIDTH_SOFT), + + DECL(AL_FORMAT_UHJ2CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ2CHN_ALAW_SOFT), + DECL(AL_FORMAT_UHJ2CHN_IMA4_SOFT), + DECL(AL_FORMAT_UHJ2CHN_MSADPCM_SOFT), + DECL(AL_FORMAT_UHJ3CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ3CHN_ALAW_SOFT), + DECL(AL_FORMAT_UHJ4CHN_MULAW_SOFT), + DECL(AL_FORMAT_UHJ4CHN_ALAW_SOFT), + + DECL(AL_FORMAT_MONO_I32), + DECL(AL_FORMAT_STEREO_I32), + DECL(AL_FORMAT_REAR_I32), + DECL(AL_FORMAT_QUAD_I32), + DECL(AL_FORMAT_51CHN_I32), + DECL(AL_FORMAT_61CHN_I32), + DECL(AL_FORMAT_71CHN_I32), + DECL(AL_FORMAT_UHJ2CHN_I32_SOFT), + DECL(AL_FORMAT_UHJ3CHN_I32_SOFT), + DECL(AL_FORMAT_UHJ4CHN_I32_SOFT), + + DECL(AL_FORMAT_REAR_FLOAT32), + DECL(AL_FORMAT_QUAD_FLOAT32), + DECL(AL_FORMAT_51CHN_FLOAT32), + DECL(AL_FORMAT_61CHN_FLOAT32), + DECL(AL_FORMAT_71CHN_FLOAT32), + + DECL(AL_FREQUENCY), + DECL(AL_BITS), + DECL(AL_CHANNELS), + DECL(AL_SIZE), + DECL(AL_UNPACK_BLOCK_ALIGNMENT_SOFT), + DECL(AL_PACK_BLOCK_ALIGNMENT_SOFT), + + DECL(AL_SOURCE_RADIUS), + + DECL(AL_SAMPLE_OFFSET_LATENCY_SOFT), + DECL(AL_SEC_OFFSET_LATENCY_SOFT), + + DECL(AL_STEREO_ANGLES), + + DECL(AL_UNUSED), + DECL(AL_PENDING), + DECL(AL_PROCESSED), + + DECL(AL_NO_ERROR), + DECL(AL_INVALID_NAME), + DECL(AL_INVALID_ENUM), + DECL(AL_INVALID_VALUE), + DECL(AL_INVALID_OPERATION), + DECL(AL_OUT_OF_MEMORY), + + DECL(AL_VENDOR), + DECL(AL_VERSION), + DECL(AL_RENDERER), + DECL(AL_EXTENSIONS), + + DECL(AL_DOPPLER_FACTOR), + DECL(AL_DOPPLER_VELOCITY), + DECL(AL_DISTANCE_MODEL), + DECL(AL_SPEED_OF_SOUND), + DECL(AL_SOURCE_DISTANCE_MODEL), + DECL(AL_DEFERRED_UPDATES_SOFT), + DECL(AL_GAIN_LIMIT_SOFT), + + DECL(AL_INVERSE_DISTANCE), + DECL(AL_INVERSE_DISTANCE_CLAMPED), + DECL(AL_LINEAR_DISTANCE), + DECL(AL_LINEAR_DISTANCE_CLAMPED), + DECL(AL_EXPONENT_DISTANCE), + DECL(AL_EXPONENT_DISTANCE_CLAMPED), + + DECL(AL_FILTER_TYPE), + DECL(AL_FILTER_NULL), + DECL(AL_FILTER_LOWPASS), + DECL(AL_FILTER_HIGHPASS), + DECL(AL_FILTER_BANDPASS), + + DECL(AL_LOWPASS_GAIN), + DECL(AL_LOWPASS_GAINHF), + + DECL(AL_HIGHPASS_GAIN), + DECL(AL_HIGHPASS_GAINLF), + + DECL(AL_BANDPASS_GAIN), + DECL(AL_BANDPASS_GAINHF), + DECL(AL_BANDPASS_GAINLF), + + DECL(AL_EFFECT_TYPE), + DECL(AL_EFFECT_NULL), + DECL(AL_EFFECT_REVERB), + DECL(AL_EFFECT_EAXREVERB), + DECL(AL_EFFECT_CHORUS), + DECL(AL_EFFECT_DISTORTION), + DECL(AL_EFFECT_ECHO), + DECL(AL_EFFECT_FLANGER), + DECL(AL_EFFECT_PITCH_SHIFTER), + DECL(AL_EFFECT_FREQUENCY_SHIFTER), + DECL(AL_EFFECT_VOCAL_MORPHER), + DECL(AL_EFFECT_RING_MODULATOR), + DECL(AL_EFFECT_AUTOWAH), + DECL(AL_EFFECT_COMPRESSOR), + DECL(AL_EFFECT_EQUALIZER), + DECL(AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT), + DECL(AL_EFFECT_DEDICATED_DIALOGUE), + + DECL(AL_EFFECTSLOT_EFFECT), + DECL(AL_EFFECTSLOT_GAIN), + DECL(AL_EFFECTSLOT_AUXILIARY_SEND_AUTO), + DECL(AL_EFFECTSLOT_NULL), + + DECL(AL_EAXREVERB_DENSITY), + DECL(AL_EAXREVERB_DIFFUSION), + DECL(AL_EAXREVERB_GAIN), + DECL(AL_EAXREVERB_GAINHF), + DECL(AL_EAXREVERB_GAINLF), + DECL(AL_EAXREVERB_DECAY_TIME), + DECL(AL_EAXREVERB_DECAY_HFRATIO), + DECL(AL_EAXREVERB_DECAY_LFRATIO), + DECL(AL_EAXREVERB_REFLECTIONS_GAIN), + DECL(AL_EAXREVERB_REFLECTIONS_DELAY), + DECL(AL_EAXREVERB_REFLECTIONS_PAN), + DECL(AL_EAXREVERB_LATE_REVERB_GAIN), + DECL(AL_EAXREVERB_LATE_REVERB_DELAY), + DECL(AL_EAXREVERB_LATE_REVERB_PAN), + DECL(AL_EAXREVERB_ECHO_TIME), + DECL(AL_EAXREVERB_ECHO_DEPTH), + DECL(AL_EAXREVERB_MODULATION_TIME), + DECL(AL_EAXREVERB_MODULATION_DEPTH), + DECL(AL_EAXREVERB_AIR_ABSORPTION_GAINHF), + DECL(AL_EAXREVERB_HFREFERENCE), + DECL(AL_EAXREVERB_LFREFERENCE), + DECL(AL_EAXREVERB_ROOM_ROLLOFF_FACTOR), + DECL(AL_EAXREVERB_DECAY_HFLIMIT), + + DECL(AL_REVERB_DENSITY), + DECL(AL_REVERB_DIFFUSION), + DECL(AL_REVERB_GAIN), + DECL(AL_REVERB_GAINHF), + DECL(AL_REVERB_DECAY_TIME), + DECL(AL_REVERB_DECAY_HFRATIO), + DECL(AL_REVERB_REFLECTIONS_GAIN), + DECL(AL_REVERB_REFLECTIONS_DELAY), + DECL(AL_REVERB_LATE_REVERB_GAIN), + DECL(AL_REVERB_LATE_REVERB_DELAY), + DECL(AL_REVERB_AIR_ABSORPTION_GAINHF), + DECL(AL_REVERB_ROOM_ROLLOFF_FACTOR), + DECL(AL_REVERB_DECAY_HFLIMIT), + + DECL(AL_CHORUS_WAVEFORM), + DECL(AL_CHORUS_PHASE), + DECL(AL_CHORUS_RATE), + DECL(AL_CHORUS_DEPTH), + DECL(AL_CHORUS_FEEDBACK), + DECL(AL_CHORUS_DELAY), + + DECL(AL_DISTORTION_EDGE), + DECL(AL_DISTORTION_GAIN), + DECL(AL_DISTORTION_LOWPASS_CUTOFF), + DECL(AL_DISTORTION_EQCENTER), + DECL(AL_DISTORTION_EQBANDWIDTH), + + DECL(AL_ECHO_DELAY), + DECL(AL_ECHO_LRDELAY), + DECL(AL_ECHO_DAMPING), + DECL(AL_ECHO_FEEDBACK), + DECL(AL_ECHO_SPREAD), + + DECL(AL_FLANGER_WAVEFORM), + DECL(AL_FLANGER_PHASE), + DECL(AL_FLANGER_RATE), + DECL(AL_FLANGER_DEPTH), + DECL(AL_FLANGER_FEEDBACK), + DECL(AL_FLANGER_DELAY), + + DECL(AL_FREQUENCY_SHIFTER_FREQUENCY), + DECL(AL_FREQUENCY_SHIFTER_LEFT_DIRECTION), + DECL(AL_FREQUENCY_SHIFTER_RIGHT_DIRECTION), + + DECL(AL_RING_MODULATOR_FREQUENCY), + DECL(AL_RING_MODULATOR_HIGHPASS_CUTOFF), + DECL(AL_RING_MODULATOR_WAVEFORM), + + DECL(AL_PITCH_SHIFTER_COARSE_TUNE), + DECL(AL_PITCH_SHIFTER_FINE_TUNE), + + DECL(AL_COMPRESSOR_ONOFF), + + DECL(AL_EQUALIZER_LOW_GAIN), + DECL(AL_EQUALIZER_LOW_CUTOFF), + DECL(AL_EQUALIZER_MID1_GAIN), + DECL(AL_EQUALIZER_MID1_CENTER), + DECL(AL_EQUALIZER_MID1_WIDTH), + DECL(AL_EQUALIZER_MID2_GAIN), + DECL(AL_EQUALIZER_MID2_CENTER), + DECL(AL_EQUALIZER_MID2_WIDTH), + DECL(AL_EQUALIZER_HIGH_GAIN), + DECL(AL_EQUALIZER_HIGH_CUTOFF), + + DECL(AL_DEDICATED_GAIN), + + DECL(AL_AUTOWAH_ATTACK_TIME), + DECL(AL_AUTOWAH_RELEASE_TIME), + DECL(AL_AUTOWAH_RESONANCE), + DECL(AL_AUTOWAH_PEAK_GAIN), + + DECL(AL_VOCAL_MORPHER_PHONEMEA), + DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), + DECL(AL_VOCAL_MORPHER_PHONEMEB), + DECL(AL_VOCAL_MORPHER_PHONEMEB_COARSE_TUNING), + DECL(AL_VOCAL_MORPHER_WAVEFORM), + DECL(AL_VOCAL_MORPHER_RATE), + + DECL(AL_EFFECTSLOT_TARGET_SOFT), + + DECL(AL_NUM_RESAMPLERS_SOFT), + DECL(AL_DEFAULT_RESAMPLER_SOFT), + DECL(AL_SOURCE_RESAMPLER_SOFT), + DECL(AL_RESAMPLER_NAME_SOFT), + + DECL(AL_SOURCE_SPATIALIZE_SOFT), + DECL(AL_AUTO_SOFT), + + DECL(AL_MAP_READ_BIT_SOFT), + DECL(AL_MAP_WRITE_BIT_SOFT), + DECL(AL_MAP_PERSISTENT_BIT_SOFT), + DECL(AL_PRESERVE_DATA_BIT_SOFT), + + DECL(AL_EVENT_CALLBACK_FUNCTION_SOFT), + DECL(AL_EVENT_CALLBACK_USER_PARAM_SOFT), + DECL(AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT), + DECL(AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT), + DECL(AL_EVENT_TYPE_DISCONNECTED_SOFT), + + DECL(AL_DROP_UNMATCHED_SOFT), + DECL(AL_REMIX_UNMATCHED_SOFT), + + DECL(AL_AMBISONIC_LAYOUT_SOFT), + DECL(AL_AMBISONIC_SCALING_SOFT), + DECL(AL_FUMA_SOFT), + DECL(AL_ACN_SOFT), + DECL(AL_SN3D_SOFT), + DECL(AL_N3D_SOFT), + + DECL(AL_BUFFER_CALLBACK_FUNCTION_SOFT), + DECL(AL_BUFFER_CALLBACK_USER_PARAM_SOFT), + + DECL(AL_UNPACK_AMBISONIC_ORDER_SOFT), + + DECL(AL_EFFECT_CONVOLUTION_SOFT), + DECL(AL_EFFECTSLOT_STATE_SOFT), + + DECL(AL_DONT_CARE_EXT), + DECL(AL_DEBUG_OUTPUT_EXT), + DECL(AL_DEBUG_CALLBACK_FUNCTION_EXT), + DECL(AL_DEBUG_CALLBACK_USER_PARAM_EXT), + DECL(AL_DEBUG_SOURCE_API_EXT), + DECL(AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT), + DECL(AL_DEBUG_SOURCE_THIRD_PARTY_EXT), + DECL(AL_DEBUG_SOURCE_APPLICATION_EXT), + DECL(AL_DEBUG_SOURCE_OTHER_EXT), + DECL(AL_DEBUG_TYPE_ERROR_EXT), + DECL(AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT), + DECL(AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT), + DECL(AL_DEBUG_TYPE_PORTABILITY_EXT), + DECL(AL_DEBUG_TYPE_PERFORMANCE_EXT), + DECL(AL_DEBUG_TYPE_MARKER_EXT), + DECL(AL_DEBUG_TYPE_PUSH_GROUP_EXT), + DECL(AL_DEBUG_TYPE_POP_GROUP_EXT), + DECL(AL_DEBUG_TYPE_OTHER_EXT), + DECL(AL_DEBUG_SEVERITY_HIGH_EXT), + DECL(AL_DEBUG_SEVERITY_MEDIUM_EXT), + DECL(AL_DEBUG_SEVERITY_LOW_EXT), + DECL(AL_DEBUG_SEVERITY_NOTIFICATION_EXT), + DECL(AL_DEBUG_LOGGED_MESSAGES_EXT), + DECL(AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT), + DECL(AL_MAX_DEBUG_MESSAGE_LENGTH_EXT), + DECL(AL_MAX_DEBUG_LOGGED_MESSAGES_EXT), + DECL(AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT), + DECL(AL_MAX_LABEL_LENGTH_EXT), + DECL(AL_STACK_OVERFLOW_EXT), + DECL(AL_STACK_UNDERFLOW_EXT), + DECL(AL_BUFFER_EXT), + DECL(AL_SOURCE_EXT), + DECL(AL_FILTER_EXT), + DECL(AL_EFFECT_EXT), + DECL(AL_AUXILIARY_EFFECT_SLOT_EXT), + + DECL(AL_PANNING_ENABLED_SOFT), + DECL(AL_PAN_SOFT), + + DECL(AL_STOP_SOURCES_ON_DISCONNECT_SOFT), +}; +#ifdef ALSOFT_EAX +inline const std::array eaxEnumerations{ + DECL(AL_EAX_RAM_SIZE), + DECL(AL_EAX_RAM_FREE), + DECL(AL_STORAGE_AUTOMATIC), + DECL(AL_STORAGE_HARDWARE), + DECL(AL_STORAGE_ACCESSIBLE), +}; +#endif // ALSOFT_EAX +#undef DECL + +#endif /* ALC_EXPORT_LIST_H */ diff --git a/Engine/lib/openal-soft/alc/inprogext.h b/Engine/lib/openal-soft/alc/inprogext.h index ccb9a4bed..ba5d4351f 100644 --- a/Engine/lib/openal-soft/alc/inprogext.h +++ b/Engine/lib/openal-soft/alc/inprogext.h @@ -1,6 +1,7 @@ #ifndef INPROGEXT_H #define INPROGEXT_H +/* NOLINTBEGIN */ #include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" @@ -16,15 +17,23 @@ typedef unsigned int ALbitfieldSOFT; #define AL_MAP_WRITE_BIT_SOFT 0x00000002 #define AL_MAP_PERSISTENT_BIT_SOFT 0x00000004 #define AL_PRESERVE_DATA_BIT_SOFT 0x00000008 -typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer); -typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length); +typedef void (AL_APIENTRY*LPALBUFFERSTORAGESOFT)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALMAPBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALUNMAPBUFFERSOFT)(ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERSOFT)(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALBUFFERSTORAGEDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALUNMAPBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALFLUSHMAPPEDBUFFERDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags); -AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access); -AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer); -AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length); +AL_API void AL_APIENTRY alBufferStorageSOFT(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT; +AL_API void* AL_APIENTRY alMapBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alUnmapBufferSOFT(ALuint buffer) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferStorageDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei freq, ALbitfieldSOFT flags) AL_API_NOEXCEPT; +void* AL_APIENTRY alMapBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length, ALbitfieldSOFT access) AL_API_NOEXCEPT; +void AL_APIENTRY alUnmapBufferDirectSOFT(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT; +void AL_APIENTRY alFlushMappedBufferDirectSOFT(ALCcontext *context, ALuint buffer, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; #endif #endif @@ -33,20 +42,11 @@ AL_API void AL_APIENTRY alFlushMappedBufferSOFT(ALuint buffer, ALsizei offset, A #define AL_UNPACK_AMBISONIC_ORDER_SOFT 0x199D #endif -#ifndef AL_SOFT_convolution_reverb -#define AL_SOFT_convolution_reverb -#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 -#define AL_EFFECTSLOT_STATE_SOFT 0x199D -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYSOFT)(ALuint slotid); -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTPLAYVSOFT)(ALsizei n, const ALuint *slotids); -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPSOFT)(ALuint slotid); -typedef void (AL_APIENTRY*LPALAUXILIARYEFFECTSLOTSTOPVSOFT)(ALsizei n, const ALuint *slotids); -#ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlaySOFT(ALuint slotid); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotPlayvSOFT(ALsizei n, const ALuint *slotids); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopSOFT(ALuint slotid); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint *slotids); -#endif +#ifndef AL_SOFT_convolution_effect +#define AL_SOFT_convolution_effect +#define AL_EFFECT_CONVOLUTION_SOFT 0xA000 +#define AL_CONVOLUTION_ORIENTATION_SOFT 0x100F /* same as AL_ORIENTATION */ +#define AL_EFFECTSLOT_STATE_SOFT 0x199E #endif #ifndef AL_SOFT_hold_on_disconnect @@ -55,19 +55,56 @@ AL_API void AL_APIENTRY alAuxiliaryEffectSlotStopvSOFT(ALsizei n, const ALuint * #endif -/* Non-standard export. Not part of any extension. */ -AL_API const ALchar* AL_APIENTRY alsoft_get_version(void); +#ifndef AL_EXT_32bit_formats +#define AL_EXT_32bit_formats +#define AL_FORMAT_MONO_I32 0x19DB +#define AL_FORMAT_STEREO_I32 0x19DC +#define AL_FORMAT_REAR_I32 0x19DD +#define AL_FORMAT_REAR_FLOAT32 0x19DE +#define AL_FORMAT_QUAD_I32 0x19DF +#define AL_FORMAT_QUAD_FLOAT32 0x19E0 +#define AL_FORMAT_51CHN_I32 0x19E1 +#define AL_FORMAT_51CHN_FLOAT32 0x19E2 +#define AL_FORMAT_61CHN_I32 0x19E3 +#define AL_FORMAT_61CHN_FLOAT32 0x19E4 +#define AL_FORMAT_71CHN_I32 0x19E5 +#define AL_FORMAT_71CHN_FLOAT32 0x19E6 +#define AL_FORMAT_UHJ2CHN_I32_SOFT 0x19E7 +#define AL_FORMAT_UHJ3CHN_I32_SOFT 0x19E8 +#define AL_FORMAT_UHJ4CHN_I32_SOFT 0x19E9 +#endif + +#ifndef AL_SOFT_source_panning +#define AL_SOFT_source_panning +#define AL_PANNING_ENABLED_SOFT 0x19EA +#define AL_PAN_SOFT 0x19EB +#endif + +/* Non-standard exports. Not part of any extension. */ +AL_API const ALchar* AL_APIENTRY alsoft_get_version(void) noexcept; + +typedef void (ALC_APIENTRY*LPALSOFTLOGCALLBACK)(void *userptr, char level, const char *message, int length) noexcept; +void ALC_APIENTRY alsoft_set_log_callback(LPALSOFTLOGCALLBACK callback, void *userptr) noexcept; /* Functions from abandoned extensions. Only here for binary compatibility. */ AL_API void AL_APIENTRY alSourceQueueBufferLayersSOFT(ALuint src, ALsizei nb, - const ALuint *buffers); + const ALuint *buffers) noexcept; -AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname); -AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values); +AL_API ALint64SOFT AL_APIENTRY alGetInteger64SOFT(ALenum pname) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetInteger64vSOFT(ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; +ALint64SOFT AL_APIENTRY alGetInteger64DirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; +void AL_APIENTRY alGetInteger64vDirectSOFT(ALCcontext *context, ALenum pname, ALint64SOFT *values) AL_API_NOEXCEPT; + +/* Not included in the public headers or export list, as a precaution for apps + * that check these to determine the behavior of the multi-channel *32 formats. + */ +#define AL_FORMAT_MONO32 0x1202 +#define AL_FORMAT_STEREO32 0x1203 #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* INPROGEXT_H */ diff --git a/Engine/lib/openal-soft/alc/panning.cpp b/Engine/lib/openal-soft/alc/panning.cpp index d118f99cf..83a410ba2 100644 --- a/Engine/lib/openal-soft/alc/panning.cpp +++ b/Engine/lib/openal-soft/alc/panning.cpp @@ -22,53 +22,61 @@ #include #include +#include #include #include #include +#include #include -#include +#include #include -#include #include -#include #include +#include #include +#include +#include +#include -#include "AL/al.h" #include "AL/alc.h" #include "AL/alext.h" -#include "al/auxeffectslot.h" -#include "albit.h" -#include "alconfig.h" #include "alc/context.h" -#include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "alu.h" #include "core/ambdec.h" #include "core/ambidefs.h" #include "core/bformatdec.h" +#include "core/bufferline.h" #include "core/bs2b.h" +#include "core/context.h" #include "core/devformat.h" +#include "core/device.h" +#include "core/effectslot.h" +#include "core/filters/nfc.h" +#include "core/filters/splitter.h" #include "core/front_stablizer.h" #include "core/hrtf.h" #include "core/logging.h" +#include "core/mixer/hrtfdefs.h" #include "core/uhjfilter.h" #include "device.h" +#include "flexarray.h" +#include "intrusive_ptr.h" #include "opthelpers.h" +#include "vector.h" namespace { -using namespace std::placeholders; +using namespace std::string_view_literals; using std::chrono::seconds; using std::chrono::nanoseconds; -inline const char *GetLabelFromChannel(Channel channel) +const char *GetLabelFromChannel(Channel channel) { switch(channel) { @@ -90,6 +98,11 @@ inline const char *GetLabelFromChannel(Channel channel) case TopBackCenter: return "top-back-center"; case TopBackRight: return "top-back-right"; + case BottomFrontLeft: return "bottom-front-left"; + case BottomFrontRight: return "bottom-front-right"; + case BottomBackLeft: return "bottom-back-left"; + case BottomBackRight: return "bottom-back-right"; + case Aux0: return "Aux0"; case Aux1: return "Aux1"; case Aux2: return "Aux2"; @@ -226,43 +239,47 @@ struct DecoderConfig { using DecoderView = DecoderConfig; -void InitNearFieldCtrl(ALCdevice *device, float ctrl_dist, uint order, bool is3d) +void InitNearFieldCtrl(ALCdevice *device, const float ctrl_dist, const uint order, const bool is3d) { - static const uint chans_per_order2d[MaxAmbiOrder+1]{ 1, 2, 2, 2 }; - static const uint chans_per_order3d[MaxAmbiOrder+1]{ 1, 3, 5, 7 }; + static const std::array chans_per_order2d{{1, 2, 2, 2}}; + static const std::array chans_per_order3d{{1, 3, 5, 7}}; /* NFC is only used when AvgSpeakerDist is greater than 0. */ if(!device->getConfigValueBool("decoder", "nfc", false) || !(ctrl_dist > 0.0f)) return; - device->AvgSpeakerDist = clampf(ctrl_dist, 0.1f, 10.0f); + device->AvgSpeakerDist = std::clamp(ctrl_dist, 0.1f, 10.0f); TRACE("Using near-field reference distance: %.2f meters\n", device->AvgSpeakerDist); const float w1{SpeedOfSoundMetersPerSec / (device->AvgSpeakerDist * static_cast(device->Frequency))}; device->mNFCtrlFilter.init(w1); - auto iter = std::copy_n(is3d ? chans_per_order3d : chans_per_order2d, order+1u, - std::begin(device->NumChannelsPerOrder)); - std::fill(iter, std::end(device->NumChannelsPerOrder), 0u); + auto iter = std::copy_n(is3d ? chans_per_order3d.begin() : chans_per_order2d.begin(), order+1u, + device->NumChannelsPerOrder.begin()); + std::fill(iter, device->NumChannelsPerOrder.end(), 0u); } void InitDistanceComp(ALCdevice *device, const al::span channels, - const al::span dists) + const al::span dists) { - const float maxdist{std::accumulate(std::begin(dists), std::end(dists), 0.0f, maxf)}; + const float maxdist{std::accumulate(dists.begin(), dists.end(), 0.0f, + [](const float a, const float b) noexcept -> float { return std::max(a, b); })}; if(!device->getConfigValueBool("decoder", "distance-comp", true) || !(maxdist > 0.0f)) return; const auto distSampleScale = static_cast(device->Frequency) / SpeedOfSoundMetersPerSec; - std::vector ChanDelay; + + struct DistCoeffs { uint Length{}; float Gain{}; }; + std::vector ChanDelay; ChanDelay.reserve(device->RealOut.Buffer.size()); + size_t total{0u}; for(size_t chidx{0};chidx < channels.size();++chidx) { const Channel ch{channels[chidx]}; - const uint idx{device->RealOut.ChannelIndex[ch]}; + const size_t idx{device->RealOut.ChannelIndex[ch]}; if(idx == InvalidChannelIndex) continue; @@ -277,12 +294,12 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, float delay{std::floor((maxdist - distance)*distSampleScale + 0.5f)}; if(delay > float{DistanceComp::MaxDelay-1}) { - ERR("Delay for channel %u (%s) exceeds buffer length (%f > %d)\n", idx, + ERR("Delay for channel %zu (%s) exceeds buffer length (%f > %d)\n", idx, GetLabelFromChannel(ch), delay, DistanceComp::MaxDelay-1); delay = float{DistanceComp::MaxDelay-1}; } - ChanDelay.resize(maxz(ChanDelay.size(), idx+1)); + ChanDelay.resize(std::max(ChanDelay.size(), idx+1_uz)); ChanDelay[idx].Length = static_cast(delay); ChanDelay[idx].Gain = distance / maxdist; TRACE("Channel %s distance comp: %u samples, %f gain\n", GetLabelFromChannel(ch), @@ -297,38 +314,39 @@ void InitDistanceComp(ALCdevice *device, const al::span channels, if(total > 0) { auto chandelays = DistanceComp::Create(total); + auto chanbuffer = chandelays->mSamples.begin(); - ChanDelay[0].Buffer = chandelays->mSamples.data(); - auto set_bufptr = [](const DistanceComp::ChanData &last, const DistanceComp::ChanData &cur) - -> DistanceComp::ChanData + auto set_bufptr = [&chanbuffer](const DistCoeffs &data) { - DistanceComp::ChanData ret{cur}; - ret.Buffer = last.Buffer + RoundUp(last.Length, 4); + DistanceComp::ChanData ret{}; + ret.Buffer = al::span{chanbuffer, data.Length}; + ret.Gain = data.Gain; + chanbuffer += ptrdiff_t(RoundUp(data.Length, 4)); return ret; }; - std::partial_sum(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), + std::transform(ChanDelay.begin(), ChanDelay.end(), chandelays->mChannels.begin(), set_bufptr); device->ChannelDelays = std::move(chandelays); } } -inline auto& GetAmbiScales(DevAmbiScaling scaletype) noexcept +constexpr auto GetAmbiScales(DevAmbiScaling scaletype) noexcept { - if(scaletype == DevAmbiScaling::FuMa) return AmbiScale::FromFuMa(); - if(scaletype == DevAmbiScaling::SN3D) return AmbiScale::FromSN3D(); - return AmbiScale::FromN3D(); + if(scaletype == DevAmbiScaling::FuMa) return al::span{AmbiScale::FromFuMa}; + if(scaletype == DevAmbiScaling::SN3D) return al::span{AmbiScale::FromSN3D}; + return al::span{AmbiScale::FromN3D}; } -inline auto& GetAmbiLayout(DevAmbiLayout layouttype) noexcept +constexpr auto GetAmbiLayout(DevAmbiLayout layouttype) noexcept { - if(layouttype == DevAmbiLayout::FuMa) return AmbiIndex::FromFuMa(); - return AmbiIndex::FromACN(); + if(layouttype == DevAmbiLayout::FuMa) return al::span{AmbiIndex::FromFuMa}; + return al::span{AmbiIndex::FromACN}; } DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, - DecoderConfig &decoder) + DecoderConfig &decoder) { DecoderView ret{}; @@ -345,23 +363,20 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, case AmbDecScale::FuMa: decoder.mScaling = DevAmbiScaling::FuMa; break; } - std::copy_n(std::begin(conf->HFOrderGain), - std::min(al::size(conf->HFOrderGain), al::size(decoder.mOrderGain)), - std::begin(decoder.mOrderGain)); - std::copy_n(std::begin(conf->LFOrderGain), - std::min(al::size(conf->LFOrderGain), al::size(decoder.mOrderGainLF)), - std::begin(decoder.mOrderGainLF)); + const auto hfordermin = std::min(conf->HFOrderGain.size(), decoder.mOrderGain.size()); + std::copy_n(conf->HFOrderGain.begin(), hfordermin, decoder.mOrderGain.begin()); + const auto lfordermin = std::min(conf->LFOrderGain.size(), decoder.mOrderGainLF.size()); + std::copy_n(conf->LFOrderGain.begin(), lfordermin, decoder.mOrderGainLF.begin()); const auto num_coeffs = decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder); - const auto idx_map = decoder.mIs3D ? AmbiIndex::FromACN().data() - : AmbiIndex::FromACN2D().data(); + const auto idx_map = decoder.mIs3D ? al::span{AmbiIndex::FromACN} + : al::span{AmbiIndex::FromACN2D}; const auto hfmatrix = conf->HFMatrix; const auto lfmatrix = conf->LFMatrix; uint chan_count{0}; - using const_speaker_span = al::span; - for(auto &speaker : const_speaker_span{conf->Speakers.get(), conf->NumSpeakers}) + for(auto &speaker : al::span{std::as_const(conf->Speakers)}) { /* NOTE: AmbDec does not define any standard speaker names, however * for this to work we have to by able to find the output channel @@ -380,36 +395,48 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, * RFT = Top front right * LBT = Top back left * RBT = Top back right + * LFB = Bottom front left + * RFB = Bottom front right + * LBB = Bottom back left + * RBB = Bottom back right * * Additionally, surround51 will acknowledge back speakers for side * channels, to avoid issues with an ambdec expecting 5.1 to use the * back channels. */ Channel ch{}; - if(speaker.Name == "LF") + if(speaker.Name == "LF"sv) ch = FrontLeft; - else if(speaker.Name == "RF") + else if(speaker.Name == "RF"sv) ch = FrontRight; - else if(speaker.Name == "CE") + else if(speaker.Name == "CE"sv) ch = FrontCenter; - else if(speaker.Name == "LS") + else if(speaker.Name == "LS"sv) ch = SideLeft; - else if(speaker.Name == "RS") + else if(speaker.Name == "RS"sv) ch = SideRight; - else if(speaker.Name == "LB") + else if(speaker.Name == "LB"sv) ch = (device->FmtChans == DevFmtX51) ? SideLeft : BackLeft; - else if(speaker.Name == "RB") + else if(speaker.Name == "RB"sv) ch = (device->FmtChans == DevFmtX51) ? SideRight : BackRight; - else if(speaker.Name == "CB") + else if(speaker.Name == "CB"sv) ch = BackCenter; - else if(speaker.Name == "LFT") + else if(speaker.Name == "LFT"sv) ch = TopFrontLeft; - else if(speaker.Name == "RFT") + else if(speaker.Name == "RFT"sv) ch = TopFrontRight; - else if(speaker.Name == "LBT") + else if(speaker.Name == "LBT"sv) ch = TopBackLeft; - else if(speaker.Name == "RBT") + else if(speaker.Name == "RBT"sv) ch = TopBackRight; + else if(speaker.Name == "LFB"sv) + ch = BottomFrontLeft; + else if(speaker.Name == "RFB"sv) + ch = BottomFrontRight; + else if(speaker.Name == "LBB"sv) + ch = BottomBackLeft; + else if(speaker.Name == "RBB"sv) + ch = BottomBackRight; else { int idx{}; @@ -445,13 +472,13 @@ DecoderView MakeDecoderView(ALCdevice *device, const AmbDecConf *conf, ret.mOrder = decoder.mOrder; ret.mIs3D = decoder.mIs3D; ret.mScaling = decoder.mScaling; - ret.mChannels = {decoder.mChannels.data(), chan_count}; + ret.mChannels = al::span{decoder.mChannels}.first(chan_count); ret.mOrderGain = decoder.mOrderGain; - ret.mCoeffs = {decoder.mCoeffs.data(), chan_count}; + ret.mCoeffs = al::span{decoder.mCoeffs}.first(chan_count); if(conf->FreqBands > 1) { ret.mOrderGainLF = decoder.mOrderGainLF; - ret.mCoeffsLF = {decoder.mCoeffsLF.data(), chan_count}; + ret.mCoeffsLF = al::span{decoder.mCoeffsLF}.first(chan_count); } } return ret; @@ -583,6 +610,44 @@ constexpr DecoderConfig X714Config{ {{8.80892603e-02f, -7.48948724e-02f, 9.08779842e-02f, -6.22480443e-02f}}, }} }; +constexpr DecoderConfig X7144Config{ + 1, true, {{BackLeft, SideLeft, FrontLeft, FrontRight, SideRight, BackRight, TopBackLeft, TopFrontLeft, TopFrontRight, TopBackRight, BottomBackLeft, BottomFrontLeft, BottomFrontRight, BottomBackRight}}, + DevAmbiScaling::N3D, + /*HF*/{{2.64575131e+0f, 1.52752523e+0f}}, + {{ + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + }}, + /*LF*/{{1.00000000e+0f, 1.00000000e+0f}}, + {{ + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, 5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, 8.82352941e-02f}}, + {{7.14285714e-02f, -1.01885342e-01f, 0.00000000e+00f, 0.00000000e+00f}}, + {{7.14285714e-02f, -5.09426708e-02f, 0.00000000e+00f, -8.82352941e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, 1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + {{7.14285714e-02f, 5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, 5.88235294e-02f}}, + {{7.14285714e-02f, -5.88235294e-02f, -1.25000000e-01f, -5.88235294e-02f}}, + }} +}; void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize=false, DecoderView decoder={}) @@ -598,15 +663,16 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= case DevFmtX61: decoder = X61Config; break; case DevFmtX71: decoder = X71Config; break; case DevFmtX714: decoder = X714Config; break; + case DevFmtX7144: decoder = X7144Config; break; case DevFmtX3D71: decoder = X3D71Config; break; case DevFmtAmbi3D: - auto&& acnmap = GetAmbiLayout(device->mAmbiLayout); - auto&& n3dscale = GetAmbiScales(device->mAmbiScale); - /* For DevFmtAmbi3D, the ambisonic order is already set. */ const size_t count{AmbiChannelsFromOrder(device->mAmbiOrder)}; - std::transform(acnmap.begin(), acnmap.begin()+count, std::begin(device->Dry.AmbiMap), - [&n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig + const auto acnmap = GetAmbiLayout(device->mAmbiLayout).first(count); + const auto n3dscale = GetAmbiScales(device->mAmbiScale); + + std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(), + [n3dscale](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f/n3dscale[acn], acn}; }); AllocChannels(device, count, 0); device->m2DMixing = false; @@ -628,10 +694,10 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= const size_t ambicount{decoder.mIs3D ? AmbiChannelsFromOrder(decoder.mOrder) : Ambi2DChannelsFromOrder(decoder.mOrder)}; const bool dual_band{hqdec && !decoder.mCoeffsLF.empty()}; - al::vector chancoeffs, chancoeffslf; + std::vector chancoeffs, chancoeffslf; for(size_t i{0u};i < decoder.mChannels.size();++i) { - const uint idx{device->channelIdxByName(decoder.mChannels[i])}; + const size_t idx{device->channelIdxByName(decoder.mChannels[i])}; if(idx == InvalidChannelIndex) { ERR("Failed to find %s channel in device\n", @@ -639,10 +705,10 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= continue; } - auto ordermap = decoder.mIs3D ? AmbiIndex::OrderFromChannel().data() - : AmbiIndex::OrderFrom2DChannel().data(); + auto ordermap = decoder.mIs3D ? al::span{AmbiIndex::OrderFromChannel} + : al::span{AmbiIndex::OrderFrom2DChannel}; - chancoeffs.resize(maxz(chancoeffs.size(), idx+1u), ChannelDec{}); + chancoeffs.resize(std::max(chancoeffs.size(), idx+1_zu), ChannelDec{}); al::span src{decoder.mCoeffs[i]}; al::span dst{chancoeffs[idx]}; for(size_t ambichan{0};ambichan < ambicount;++ambichan) @@ -651,7 +717,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= if(!dual_band) continue; - chancoeffslf.resize(maxz(chancoeffslf.size(), idx+1u), ChannelDec{}); + chancoeffslf.resize(std::max(chancoeffslf.size(), idx+1_zu), ChannelDec{}); src = decoder.mCoeffsLF[i]; dst = chancoeffslf[idx]; for(size_t ambichan{0};ambichan < ambicount;++ambichan) @@ -662,11 +728,11 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= device->mAmbiOrder = decoder.mOrder; device->m2DMixing = !decoder.mIs3D; - const al::span acnmap{decoder.mIs3D ? AmbiIndex::FromACN().data() : - AmbiIndex::FromACN2D().data(), ambicount}; - auto&& coeffscale = GetAmbiScales(decoder.mScaling); - std::transform(acnmap.begin(), acnmap.end(), std::begin(device->Dry.AmbiMap), - [&coeffscale](const uint8_t &acn) noexcept + const auto acnmap = decoder.mIs3D ? al::span{AmbiIndex::FromACN}.first(ambicount) + : al::span{AmbiIndex::FromACN2D}.first(ambicount); + const auto coeffscale = GetAmbiScales(decoder.mScaling); + std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), + [coeffscale](const uint8_t &acn) noexcept { return BFChannelConfig{1.0f/coeffscale[acn], acn}; }); AllocChannels(device, ambicount, device->channelsFromFmt()); @@ -676,7 +742,7 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= /* Only enable the stablizer if the decoder does not output to the * front-center channel. */ - const auto cidx = device->RealOut.ChannelIndex[FrontCenter]; + const size_t cidx{device->RealOut.ChannelIndex[FrontCenter]}; bool hasfc{false}; if(cidx < chancoeffs.size()) { @@ -707,120 +773,126 @@ void InitPanning(ALCdevice *device, const bool hqdec=false, const bool stablize= void InitHrtfPanning(ALCdevice *device) { - constexpr float Deg180{al::numbers::pi_v}; - constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; - constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; - constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; - constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; - constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; - constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; - constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; - constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; - constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; - constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; - static const AngularPoint AmbiPoints1O[]{ - { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{-Deg135} }, - { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{ Deg135} }, - { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{-Deg135} }, - { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{ Deg135} }, - }, AmbiPoints2O[]{ - { EvRadians{-Deg_32}, AzRadians{ 0.0f} }, - { EvRadians{ 0.0f}, AzRadians{ Deg_58} }, - { EvRadians{ Deg_58}, AzRadians{ Deg_90} }, - { EvRadians{ Deg_32}, AzRadians{ 0.0f} }, - { EvRadians{ 0.0f}, AzRadians{ Deg122} }, - { EvRadians{-Deg_58}, AzRadians{-Deg_90} }, - { EvRadians{-Deg_32}, AzRadians{ Deg180} }, - { EvRadians{ 0.0f}, AzRadians{-Deg122} }, - { EvRadians{ Deg_58}, AzRadians{-Deg_90} }, - { EvRadians{ Deg_32}, AzRadians{ Deg180} }, - { EvRadians{ 0.0f}, AzRadians{-Deg_58} }, - { EvRadians{-Deg_58}, AzRadians{ Deg_90} }, - }, AmbiPoints3O[]{ - { EvRadians{ Deg_69}, AzRadians{-Deg_90} }, - { EvRadians{ Deg_69}, AzRadians{ Deg_90} }, - { EvRadians{-Deg_69}, AzRadians{-Deg_90} }, - { EvRadians{-Deg_69}, AzRadians{ Deg_90} }, - { EvRadians{ 0.0f}, AzRadians{-Deg_69} }, - { EvRadians{ 0.0f}, AzRadians{-Deg111} }, - { EvRadians{ 0.0f}, AzRadians{ Deg_69} }, - { EvRadians{ 0.0f}, AzRadians{ Deg111} }, - { EvRadians{ Deg_21}, AzRadians{ 0.0f} }, - { EvRadians{ Deg_21}, AzRadians{ Deg180} }, - { EvRadians{-Deg_21}, AzRadians{ 0.0f} }, - { EvRadians{-Deg_21}, AzRadians{ Deg180} }, - { EvRadians{ Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{-Deg135} }, - { EvRadians{ Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{ Deg_35}, AzRadians{ Deg135} }, - { EvRadians{-Deg_35}, AzRadians{-Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{-Deg135} }, - { EvRadians{-Deg_35}, AzRadians{ Deg_45} }, - { EvRadians{-Deg_35}, AzRadians{ Deg135} }, + static constexpr float Deg180{al::numbers::pi_v}; + static constexpr float Deg_90{Deg180 / 2.0f /* 90 degrees*/}; + static constexpr float Deg_45{Deg_90 / 2.0f /* 45 degrees*/}; + static constexpr float Deg135{Deg_45 * 3.0f /*135 degrees*/}; + static constexpr float Deg_21{3.648638281e-01f /* 20~ 21 degrees*/}; + static constexpr float Deg_32{5.535743589e-01f /* 31~ 32 degrees*/}; + static constexpr float Deg_35{6.154797087e-01f /* 35~ 36 degrees*/}; + static constexpr float Deg_58{1.017221968e+00f /* 58~ 59 degrees*/}; + static constexpr float Deg_69{1.205932499e+00f /* 69~ 70 degrees*/}; + static constexpr float Deg111{1.935660155e+00f /*110~111 degrees*/}; + static constexpr float Deg122{2.124370686e+00f /*121~122 degrees*/}; + static constexpr std::array AmbiPoints1O{ + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}}, }; - static const float AmbiMatrix1O[][MaxAmbiChannels]{ - { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, - { 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f }, - { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f }, - { 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f }, - { 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f }, - { 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f }, - { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f }, - { 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f }, - }, AmbiMatrix2O[][MaxAmbiChannels]{ - { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - { 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - { 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }, - { 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }, - { 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }, - }, AmbiMatrix3O[][MaxAmbiChannels]{ - { 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }, - { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, - { 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, - { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }, - { 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }, - { 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }, - { 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }, + static constexpr std::array AmbiPoints2O{ + AngularPoint{EvRadians{-Deg_32}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_58}}, + AngularPoint{EvRadians{ Deg_58}, AzRadians{ Deg_90}}, + AngularPoint{EvRadians{ Deg_32}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg122}}, + AngularPoint{EvRadians{-Deg_58}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{-Deg_32}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg122}}, + AngularPoint{EvRadians{ Deg_58}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{ Deg_32}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_58}}, + AngularPoint{EvRadians{-Deg_58}, AzRadians{ Deg_90}}, }; - static const float AmbiOrderHFGain1O[MaxAmbiOrder+1]{ + static constexpr std::array AmbiPoints3O{ + AngularPoint{EvRadians{ Deg_69}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{ Deg_69}, AzRadians{ Deg_90}}, + AngularPoint{EvRadians{-Deg_69}, AzRadians{-Deg_90}}, + AngularPoint{EvRadians{-Deg_69}, AzRadians{ Deg_90}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg_69}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{-Deg111}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg_69}}, + AngularPoint{EvRadians{ 0.0f}, AzRadians{ Deg111}}, + AngularPoint{EvRadians{ Deg_21}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{ Deg_21}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{-Deg_21}, AzRadians{ 0.0f}}, + AngularPoint{EvRadians{-Deg_21}, AzRadians{ Deg180}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{ Deg_35}, AzRadians{ Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{-Deg135}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg_45}}, + AngularPoint{EvRadians{-Deg_35}, AzRadians{ Deg135}}, + }; + static constexpr std::array AmbiMatrix1O{ + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + ChannelCoeffs{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, + }; + static constexpr std::array AmbiMatrix2O{ + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + ChannelCoeffs{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + ChannelCoeffs{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + ChannelCoeffs{8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + }; + static constexpr std::array AmbiMatrix3O{ + ChannelCoeffs{5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + ChannelCoeffs{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, + }; + static constexpr std::array AmbiOrderHFGain1O{ /*ENRGY*/ 2.000000000e+00f, 1.154700538e+00f - }, AmbiOrderHFGain2O[MaxAmbiOrder+1]{ + }; + static constexpr std::array AmbiOrderHFGain2O{ /*ENRGY*/ 1.825741858e+00f, 1.414213562e+00f, 7.302967433e-01f /*AMP 1.000000000e+00f, 7.745966692e-01f, 4.000000000e-01f*/ /*RMS 9.128709292e-01f, 7.071067812e-01f, 3.651483717e-01f*/ - }, AmbiOrderHFGain3O[MaxAmbiOrder+1]{ + }; + static constexpr std::array AmbiOrderHFGain3O{ /*ENRGY 1.865086714e+00f, 1.606093894e+00f, 1.142055301e+00f, 5.683795528e-01f*/ /*AMP*/ 1.000000000e+00f, 8.611363116e-01f, 6.123336207e-01f, 3.047469850e-01f /*RMS 8.340921354e-01f, 7.182670250e-01f, 5.107426573e-01f, 2.541870634e-01f*/ }; - static_assert(al::size(AmbiPoints1O) == al::size(AmbiMatrix1O), "First-Order Ambisonic HRTF mismatch"); - static_assert(al::size(AmbiPoints2O) == al::size(AmbiMatrix2O), "Second-Order Ambisonic HRTF mismatch"); - static_assert(al::size(AmbiPoints3O) == al::size(AmbiMatrix3O), "Third-Order Ambisonic HRTF mismatch"); + static_assert(AmbiPoints1O.size() == AmbiMatrix1O.size(), "First-Order Ambisonic HRTF mismatch"); + static_assert(AmbiPoints2O.size() == AmbiMatrix2O.size(), "Second-Order Ambisonic HRTF mismatch"); + static_assert(AmbiPoints3O.size() == AmbiMatrix3O.size(), "Third-Order Ambisonic HRTF mismatch"); /* A 700hz crossover frequency provides tighter sound imaging at the sweet * spot with ambisonic decoding, as the distance between the ears is closer @@ -840,32 +912,32 @@ void InitHrtfPanning(ALCdevice *device) */ device->mRenderMode = RenderMode::Hrtf; uint ambi_order{1}; - if(auto modeopt = device->configValue(nullptr, "hrtf-mode")) + if(auto modeopt = device->configValue({}, "hrtf-mode")) { struct HrtfModeEntry { - char name[8]; + std::string_view name; RenderMode mode; uint order; }; - static const HrtfModeEntry hrtf_modes[]{ - { "full", RenderMode::Hrtf, 1 }, - { "ambi1", RenderMode::Normal, 1 }, - { "ambi2", RenderMode::Normal, 2 }, - { "ambi3", RenderMode::Normal, 3 }, + constexpr std::array hrtf_modes{ + HrtfModeEntry{"full"sv, RenderMode::Hrtf, 1}, + HrtfModeEntry{"ambi1"sv, RenderMode::Normal, 1}, + HrtfModeEntry{"ambi2"sv, RenderMode::Normal, 2}, + HrtfModeEntry{"ambi3"sv, RenderMode::Normal, 3}, }; - const char *mode{modeopt->c_str()}; - if(al::strcasecmp(mode, "basic") == 0) + std::string_view mode{*modeopt}; + if(al::case_compare(mode, "basic"sv) == 0) { - ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", mode, "ambi2"); + ERR("HRTF mode \"%s\" deprecated, substituting \"%s\"\n", modeopt->c_str(), "ambi2"); mode = "ambi2"; } auto match_entry = [mode](const HrtfModeEntry &entry) -> bool - { return al::strcasecmp(mode, entry.name) == 0; }; - auto iter = std::find_if(std::begin(hrtf_modes), std::end(hrtf_modes), match_entry); - if(iter == std::end(hrtf_modes)) - ERR("Unexpected hrtf-mode: %s\n", mode); + { return al::case_compare(mode, entry.name) == 0; }; + auto iter = std::find_if(hrtf_modes.begin(), hrtf_modes.end(), match_entry); + if(iter == hrtf_modes.end()) + ERR("Unexpected hrtf-mode: %s\n", modeopt->c_str()); else { device->mRenderMode = iter->mode; @@ -873,17 +945,13 @@ void InitHrtfPanning(ALCdevice *device) } } TRACE("%u%s order %sHRTF rendering enabled, using \"%s\"\n", ambi_order, - (((ambi_order%100)/10) == 1) ? "th" : - ((ambi_order%10) == 1) ? "st" : - ((ambi_order%10) == 2) ? "nd" : - ((ambi_order%10) == 3) ? "rd" : "th", - (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", + GetCounterSuffix(ambi_order), (device->mRenderMode == RenderMode::Hrtf) ? "+ Full " : "", device->mHrtfName.c_str()); bool perHrirMin{false}; - al::span AmbiPoints{AmbiPoints1O}; - const float (*AmbiMatrix)[MaxAmbiChannels]{AmbiMatrix1O}; - al::span AmbiOrderHFGain{AmbiOrderHFGain1O}; + auto AmbiPoints = al::span{AmbiPoints1O}.subspan(0); + auto AmbiMatrix = al::span{AmbiMatrix1O}.subspan(0); + auto AmbiOrderHFGain = al::span{AmbiOrderHFGain1O}; if(ambi_order >= 3) { perHrirMin = true; @@ -901,10 +969,9 @@ void InitHrtfPanning(ALCdevice *device) device->m2DMixing = false; const size_t count{AmbiChannelsFromOrder(ambi_order)}; - std::transform(AmbiIndex::FromACN().begin(), AmbiIndex::FromACN().begin()+count, - std::begin(device->Dry.AmbiMap), - [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; } - ); + const auto acnmap = al::span{AmbiIndex::FromACN}.first(count); + std::transform(acnmap.begin(), acnmap.end(), device->Dry.AmbiMap.begin(), + [](const uint8_t &index) noexcept { return BFChannelConfig{1.0f, index}; }); AllocChannels(device, count, device->channelsFromFmt()); HrtfStore *Hrtf{device->mHrtf.get()}; @@ -919,21 +986,21 @@ void InitHrtfPanning(ALCdevice *device) void InitUhjPanning(ALCdevice *device) { /* UHJ is always 2D first-order. */ - constexpr size_t count{Ambi2DChannelsFromOrder(1)}; + static constexpr size_t count{Ambi2DChannelsFromOrder(1)}; device->mAmbiOrder = 1; device->m2DMixing = true; - auto acnmap_begin = AmbiIndex::FromFuMa2D().begin(); - std::transform(acnmap_begin, acnmap_begin + count, std::begin(device->Dry.AmbiMap), + const auto acnmap = al::span{AmbiIndex::FromFuMa2D}.first(); + std::transform(acnmap.cbegin(), acnmap.cend(), device->Dry.AmbiMap.begin(), [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f/AmbiScale::FromUHJ()[acn], acn}; }); + { return BFChannelConfig{1.0f/AmbiScale::FromUHJ[acn], acn}; }); AllocChannels(device, count, device->channelsFromFmt()); } } // namespace -void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional stereomode) +void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional stereomode) { /* Hold the HRTF the device last used, in case it's used again. */ HrtfStorePtr old_hrtf{std::move(device->mHrtf)}; @@ -960,6 +1027,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional> decoder_store; + std::unique_ptr> decoder_store; DecoderView decoder{}; - float speakerdists[MAX_OUTPUT_CHANNELS]{}; + std::array speakerdists{}; auto load_config = [device,&decoder_store,&decoder,&speakerdists](const char *config) { AmbDecConf conf{}; @@ -978,28 +1046,42 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalc_str()); + return false; } - else if(conf.NumSpeakers > MAX_OUTPUT_CHANNELS) - ERR("Unsupported decoder speaker count %zu (max %d)\n", conf.NumSpeakers, - MAX_OUTPUT_CHANNELS); - else if(conf.ChanMask > Ambi3OrderMask) + if(conf.Speakers.size() > MaxOutputChannels) + { + ERR("Unsupported decoder speaker count %zu (max %zu)\n", conf.Speakers.size(), + MaxOutputChannels); + return false; + } + if(conf.ChanMask > Ambi3OrderMask) + { ERR("Unsupported decoder channel mask 0x%04x (max 0x%x)\n", conf.ChanMask, Ambi3OrderMask); - else - { - device->mXOverFreq = clampf(conf.XOverFreq, 100.0f, 1000.0f); - - decoder_store = std::make_unique>(); - decoder = MakeDecoderView(device, &conf, *decoder_store); - for(size_t i{0};i < decoder.mChannels.size();++i) - speakerdists[i] = conf.Speakers[i].Distance; + return false; } + + TRACE("Using %s decoder: \"%s\"\n", DevFmtChannelsString(device->FmtChans), + conf.Description.c_str()); + device->mXOverFreq = std::clamp(conf.XOverFreq, 100.0f, 1000.0f); + + decoder_store = std::make_unique>(); + decoder = MakeDecoderView(device, &conf, *decoder_store); + + const auto confspeakers = al::span{std::as_const(conf.Speakers)} + .first(decoder.mChannels.size()); + std::transform(confspeakers.cbegin(), confspeakers.cend(), speakerdists.begin(), + std::mem_fn(&AmbDecConf::SpeakerConf::Distance)); + return true; }; + bool usingCustom{false}; if(layout) { if(auto decopt = device->configValue("decoder", layout)) - load_config(decopt->c_str()); + usingCustom = load_config(decopt->c_str()); } + if(!usingCustom && device->FmtChans != DevFmtAmbi3D) + TRACE("Using built-in %s decoder\n", DevFmtChannelsString(device->FmtChans)); /* Enable the stablizer only for formats that have front-left, front- * right, and front-center outputs. @@ -1007,8 +1089,8 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalRealOut.ChannelIndex[FrontCenter] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontLeft] != InvalidChannelIndex && device->RealOut.ChannelIndex[FrontRight] != InvalidChannelIndex - && device->getConfigValueBool(nullptr, "front-stablizer", false) != 0}; - const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true) != 0}; + && device->getConfigValueBool({}, "front-stablizer", false)}; + const bool hqdec{device->getConfigValueBool("decoder", "hq-mode", true)}; InitPanning(device, hqdec, stablize, decoder); if(decoder) { @@ -1049,7 +1131,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optional= 0 && static_cast(hrtf_id) < device->mHrtfList.size()) { - const std::string &hrtfname = device->mHrtfList[static_cast(hrtf_id)]; + const std::string_view hrtfname{device->mHrtfList[static_cast(hrtf_id)]}; if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) { device->mHrtf = std::move(hrtf); @@ -1059,7 +1141,7 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmHrtf) { - for(const auto &hrtfname : device->mHrtfList) + for(const std::string_view hrtfname : device->mHrtfList) { if(HrtfStorePtr hrtf{GetLoadedHrtf(hrtfname, device->Frequency)}) { @@ -1076,10 +1158,10 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmHrtf.get()}; device->mIrSize = hrtf->mIrSize; - if(auto hrtfsizeopt = device->configValue(nullptr, "hrtf-size")) + if(auto hrtfsizeopt = device->configValue({}, "hrtf-size")) { if(*hrtfsizeopt > 0 && *hrtfsizeopt < device->mIrSize) - device->mIrSize = maxu(*hrtfsizeopt, MinIrLength); + device->mIrSize = std::max(*hrtfsizeopt, MinIrLength); } InitHrtfPanning(device); @@ -1115,13 +1197,13 @@ void aluInitRenderer(ALCdevice *device, int hrtf_id, al::optionalmRenderMode = RenderMode::Pairwise; if(device->Type != DeviceType::Loopback) { - if(auto cflevopt = device->configValue(nullptr, "cf_level")) + if(auto cflevopt = device->configValue({}, "cf_level")) { if(*cflevopt > 0 && *cflevopt <= 6) { - device->Bs2b = std::make_unique(); - bs2b_set_params(device->Bs2b.get(), *cflevopt, - static_cast(device->Frequency)); + auto bs2b = std::make_unique(); + bs2b->set_params(*cflevopt, static_cast(device->Frequency)); + device->Bs2b = std::move(bs2b); TRACE("BS2B enabled\n"); InitPanning(device); device->PostProcess = &ALCdevice::ProcessBs2b; @@ -1143,10 +1225,9 @@ void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context) slot->mWetBuffer.resize(count); - auto acnmap_begin = AmbiIndex::FromACN().begin(); - auto iter = std::transform(acnmap_begin, acnmap_begin + count, slot->Wet.AmbiMap.begin(), - [](const uint8_t &acn) noexcept -> BFChannelConfig - { return BFChannelConfig{1.0f, acn}; }); + const auto acnmap = al::span{AmbiIndex::FromACN}.first(count); + const auto iter = std::transform(acnmap.cbegin(), acnmap.cend(), slot->Wet.AmbiMap.begin(), + [](const uint8_t &acn) noexcept -> BFChannelConfig { return BFChannelConfig{1.0f, acn}; }); std::fill(iter, slot->Wet.AmbiMap.end(), BFChannelConfig{}); slot->Wet.Buffer = slot->mWetBuffer; } diff --git a/Engine/lib/openal-soft/alsoftrc.sample b/Engine/lib/openal-soft/alsoftrc.sample index 2906cca46..dd3ff8660 100644 --- a/Engine/lib/openal-soft/alsoftrc.sample +++ b/Engine/lib/openal-soft/alsoftrc.sample @@ -56,10 +56,10 @@ # Sets the default output channel configuration. If left unspecified, one will # try to be detected from the system, with a fallback to stereo. The available # values are: mono, stereo, quad, surround51, surround61, surround71, -# surround3d71, ambi1, ambi2, ambi3. Note that the ambi* configurations output -# ambisonic channels of the given order (using ACN ordering and SN3D -# normalization by default), which need to be decoded to play correctly on -# speakers. +# surround714, surround3d71, ambi1, ambi2, ambi3. Note that the ambi* +# configurations output ambisonic channels of the given order (using ACN +# ordering and SN3D normalization by default), which need to be decoded to +# play correctly on speakers. #channels = ## sample-type: @@ -179,7 +179,8 @@ # Selects the default resampler used when mixing sources. Valid values are: # point - nearest sample, no interpolation # linear - extrapolates samples using a linear slope between samples -# cubic - extrapolates samples using a Catmull-Rom spline +# spline - extrapolates samples using a Catmull-Rom spline +# gaussian - extrapolates samples using a 4-point Gaussian filter # bsinc12 - extrapolates samples using a band-limited Sinc filter (varying # between 12 and 24 points, with anti-aliasing) # fast_bsinc12 - same as bsinc12, except without interpolation between down- @@ -188,7 +189,7 @@ # between 24 and 48 points, with anti-aliasing) # fast_bsinc24 - same as bsinc24, except without interpolation between down- # sampling scales -#resampler = cubic +#resampler = gaussian ## rt-prio: (global) # Sets the real-time priority value for the mixing thread. Not all drivers may @@ -232,14 +233,15 @@ ## output-limiter: # Applies a gain limiter on the final mixed output. This reduces the volume # when the output samples would otherwise clamp, avoiding excessive clipping -# noise. -#output-limiter = true +# noise. On by default for integer sample types, and off by default for +# floating-point. +#output-limiter = ## dither: -# Applies dithering on the final mix, for 8- and 16-bit output by default. -# This replaces the distortion created by nearest-value quantization with low- -# level whitenoise. -#dither = true +# Applies dithering on the final mix, enabled by default for 8- and 16-bit +# output. This replaces the distortion created by nearest-value quantization +# with low-level whitenoise. +#dither = ## dither-depth: # Quantization bit-depth for dithered output. A value of 0 (or less) will @@ -343,6 +345,11 @@ # docs/ambdec.txt for a description of the file format. #surround71 = +## surround714: +# Decoder configuration file for 7.1.4 Surround channel output. See +# docs/ambdec.txt for a description of the file format. +#surround714 = + ## surround3d71: # Decoder configuration file for 3D7.1 Surround channel output. See # docs/ambdec.txt for a description of the file format. See also @@ -401,7 +408,7 @@ # Renders samples directly in the real-time processing callback. This allows # for lower latency and less overall CPU utilization, but can increase the # risk of underruns when increasing the amount of work the mixer needs to do. -#rt-mix = true +#rt-mix = false ## ## PulseAudio backend stuff @@ -574,6 +581,12 @@ ## [wasapi] +## spatial-api: +# Specifies whether to use a Spatial Audio stream for playback. This may +# provide expanded capabilities for surround sound and with-height speaker +# configurations. Very experimental. +#spatial-api = false + ## allow-resampler: # Specifies whether to allow an extra resampler pass on the output. Enabling # this will allow the playback device to be set to a different sample rate @@ -638,6 +651,15 @@ ## [game_compat] +## default-error: (global) +# An error value returned by alGetError when there's no current context. The +# default value is AL_INVALID_OPERATION, which lets the caller know the +# operation could not be executed. Some applications may erroneously call +# alGetError without a current context and expect 0 (AL_NO_ERROR), however +# that may cause other applications to think earlier AL calls succeeded when +# they actually failed. +#default-error = 0xA004 + ## nfc-scale: (global) # A meters-per-unit distance scale applied to NFC filters. If a game doesn't # use real-world meters for in-game units, the filters may create a too-near diff --git a/Engine/lib/openal-soft/appveyor.yml b/Engine/lib/openal-soft/appveyor.yml index aa155af43..8ad0f6bf1 100644 --- a/Engine/lib/openal-soft/appveyor.yml +++ b/Engine/lib/openal-soft/appveyor.yml @@ -1,8 +1,8 @@ version: 1.23.1.{build} environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - GEN: "Visual Studio 15 2017" + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 + GEN: "Visual Studio 17 2022" matrix: - ARCH: Win32 CFG: Release diff --git a/Engine/lib/openal-soft/cmake/FindOpenSL.cmake b/Engine/lib/openal-soft/cmake/FindOpenSL.cmake index 004287494..3df54d447 100644 --- a/Engine/lib/openal-soft/cmake/FindOpenSL.cmake +++ b/Engine/lib/openal-soft/cmake/FindOpenSL.cmake @@ -2,8 +2,9 @@ # Find the OpenSL libraries # # This module defines the following variables and targets: -# OPENSL_FOUND - True if OPENSL was found -# OpenSL::OpenSLES - The OpenSLES target +# OPENSL_FOUND - True if OPENSL was found +# OPENSL_INCLUDE_DIRS - The OpenSL include paths +# OPENSL_LIBRARIES - The OpenSL libraries to link # #============================================================================= @@ -53,11 +54,8 @@ find_package_handle_standard_args(OpenSL REQUIRED_VARS OPENSL_LIBRARY OPENSL_INC OPENSL_ANDROID_INCLUDE_DIR) if(OPENSL_FOUND) - add_library(OpenSL::OpenSLES UNKNOWN IMPORTED) - set_target_properties(OpenSL::OpenSLES PROPERTIES - IMPORTED_LOCATION ${OPENSL_LIBRARY} - INTERFACE_INCLUDE_DIRECTORIES ${OPENSL_INCLUDE_DIR} - INTERFACE_INCLUDE_DIRECTORIES ${OPENSL_ANDROID_INCLUDE_DIR}) + set(OPENSL_LIBRARIES ${OPENSL_LIBRARY}) + set(OPENSL_INCLUDE_DIRS ${OPENSL_INCLUDE_DIR} ${OPENSL_ANDROID_INCLUDE_DIR}) endif() mark_as_advanced(OPENSL_INCLUDE_DIR OPENSL_ANDROID_INCLUDE_DIR OPENSL_LIBRARY) diff --git a/Engine/lib/openal-soft/cmake/FindSndIO.cmake b/Engine/lib/openal-soft/cmake/FindSndIO.cmake new file mode 100644 index 000000000..eb4a2587b --- /dev/null +++ b/Engine/lib/openal-soft/cmake/FindSndIO.cmake @@ -0,0 +1,31 @@ +# - Find SndIO includes and libraries +# +# SNDIO_FOUND - True if SNDIO_INCLUDE_DIR & SNDIO_LIBRARY are found +# SNDIO_LIBRARIES - Set when SNDIO_LIBRARY is found +# SNDIO_INCLUDE_DIRS - Set when SNDIO_INCLUDE_DIR is found +# +# SNDIO_INCLUDE_DIR - where to find sndio.h, etc. +# SNDIO_LIBRARY - the sndio library +# + +find_path(SNDIO_INCLUDE_DIR + NAMES sndio.h + DOC "The SndIO include directory" +) + +find_library(SNDIO_LIBRARY + NAMES sndio + DOC "The SndIO library" +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SndIO + REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIR +) + +if(SNDIO_FOUND) + set(SNDIO_LIBRARIES ${SNDIO_LIBRARY}) + set(SNDIO_INCLUDE_DIRS ${SNDIO_INCLUDE_DIR}) +endif() + +mark_as_advanced(SNDIO_INCLUDE_DIR SNDIO_LIBRARY) diff --git a/Engine/lib/openal-soft/cmake/FindSoundIO.cmake b/Engine/lib/openal-soft/cmake/FindSoundIO.cmake deleted file mode 100644 index 10450254d..000000000 --- a/Engine/lib/openal-soft/cmake/FindSoundIO.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# - Find SoundIO (sndio) includes and libraries -# -# SOUNDIO_FOUND - True if SOUNDIO_INCLUDE_DIR & SOUNDIO_LIBRARY are -# found -# SOUNDIO_LIBRARIES - Set when SOUNDIO_LIBRARY is found -# SOUNDIO_INCLUDE_DIRS - Set when SOUNDIO_INCLUDE_DIR is found -# -# SOUNDIO_INCLUDE_DIR - where to find sndio.h, etc. -# SOUNDIO_LIBRARY - the sndio library -# - -find_path(SOUNDIO_INCLUDE_DIR - NAMES sndio.h - DOC "The SoundIO include directory" -) - -find_library(SOUNDIO_LIBRARY - NAMES sndio - DOC "The SoundIO library" -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(SoundIO - REQUIRED_VARS SOUNDIO_LIBRARY SOUNDIO_INCLUDE_DIR -) - -if(SOUNDIO_FOUND) - set(SOUNDIO_LIBRARIES ${SOUNDIO_LIBRARY}) - set(SOUNDIO_INCLUDE_DIRS ${SOUNDIO_INCLUDE_DIR}) -endif() - -mark_as_advanced(SOUNDIO_INCLUDE_DIR SOUNDIO_LIBRARY) diff --git a/Engine/lib/openal-soft/common/alassert.cpp b/Engine/lib/openal-soft/common/alassert.cpp new file mode 100644 index 000000000..f50642308 --- /dev/null +++ b/Engine/lib/openal-soft/common/alassert.cpp @@ -0,0 +1,38 @@ + +#include "alassert.h" + +#include +#include +#include + +namespace al { + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept +{ + std::string errstr{filename}; + errstr += ':'; + errstr += std::to_string(linenum); + errstr += ": "; + errstr += funcname; + errstr += ": "; + errstr += message; + /* Calling std::terminate in a catch block hopefully causes the system to + * provide info about the caught exception in the error dialog. At least on + * Linux, this results in the process printing + * + * terminate called after throwing an instance of 'std::runtime_error' + * what(): + * + * before terminating from a SIGABRT. Hopefully Windows and Mac will do the + * appropriate things with the message for an abnormal termination. + */ + try { + throw std::runtime_error{errstr}; + } + catch(...) { + std::terminate(); + } +} + +} /* namespace al */ diff --git a/Engine/lib/openal-soft/common/alassert.h b/Engine/lib/openal-soft/common/alassert.h new file mode 100644 index 000000000..3797e96d5 --- /dev/null +++ b/Engine/lib/openal-soft/common/alassert.h @@ -0,0 +1,24 @@ +#ifndef AL_ASSERT_H +#define AL_ASSERT_H + +#include + +#include "opthelpers.h" + +namespace al { + +[[noreturn]] +void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept; + +} /* namespace al */ + +/* A custom assert macro that is not compiled out for Release/NDEBUG builds, + * making it an appropriate replacement for assert() checks that must not be + * ignored. + */ +#define alassert(cond) do { \ + if(!(cond)) UNLIKELY \ + al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \ +} while(0) + +#endif /* AL_ASSERT_H */ diff --git a/Engine/lib/openal-soft/common/albit.h b/Engine/lib/openal-soft/common/albit.h index ad5962081..98544fa52 100644 --- a/Engine/lib/openal-soft/common/albit.h +++ b/Engine/lib/openal-soft/common/albit.h @@ -1,8 +1,13 @@ #ifndef AL_BIT_H #define AL_BIT_H +#include +#ifndef __GNUC__ #include +#endif +#include #include +#include #include #if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64)) #include @@ -10,6 +15,16 @@ namespace al { +template +std::enable_if_t + && std::is_trivially_copyable_v, +To> bit_cast(const From &src) noexcept +{ + alignas(To) std::array dst; + std::memcpy(dst.data(), &src, sizeof(To)); + return *std::launder(reinterpret_cast(dst.data())); +} + #ifdef __BYTE_ORDER__ enum class endian { little = __ORDER_LITTLE_ENDIAN__, diff --git a/Engine/lib/openal-soft/common/albyte.h b/Engine/lib/openal-soft/common/albyte.h deleted file mode 100644 index be586869d..000000000 --- a/Engine/lib/openal-soft/common/albyte.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef AL_BYTE_H -#define AL_BYTE_H - -#include -#include -#include -#include - -using uint = unsigned int; - -namespace al { - -using byte = unsigned char; - -} // namespace al - -#endif /* AL_BYTE_H */ diff --git a/Engine/lib/openal-soft/common/alcomplex.cpp b/Engine/lib/openal-soft/common/alcomplex.cpp index 4420a1bbb..954c5fadb 100644 --- a/Engine/lib/openal-soft/common/alcomplex.cpp +++ b/Engine/lib/openal-soft/common/alcomplex.cpp @@ -4,10 +4,11 @@ #include "alcomplex.h" #include +#include #include -#include #include #include +#include #include #include "albit.h" @@ -20,36 +21,38 @@ namespace { using ushort = unsigned short; using ushort2 = std::pair; +using complex_d = std::complex; -constexpr size_t BitReverseCounter(size_t log2_size) noexcept +constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept { /* Some magic math that calculates the number of swaps needed for a * sequence of bit-reversed indices when index < reversed_index. */ - return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u)); + return (1_zu<<(log2_size-1)) - (1_zu<<((log2_size-1_zu)/2_zu)); } -template +template struct BitReverser { static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table."); - ushort2 mData[BitReverseCounter(N)]{}; + std::array mData{}; constexpr BitReverser() { - const size_t fftsize{1u << N}; - size_t ret_i{0}; + const std::size_t fftsize{1u << N}; + std::size_t ret_i{0}; /* Bit-reversal permutation applied to a sequence of fftsize items. */ - for(size_t idx{1u};idx < fftsize-1;++idx) + for(std::size_t idx{1u};idx < fftsize-1;++idx) { - size_t revidx{0u}, imask{idx}; - for(size_t i{0};i < N;++i) - { - revidx = (revidx<<1) | (imask&1); - imask >>= 1; - } + std::size_t revidx{idx}; + revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1); + revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2); + revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4); + revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8); + revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16); + revidx >>= 32-N; if(idx < revidx) { @@ -58,14 +61,13 @@ struct BitReverser { ++ret_i; } } - assert(ret_i == al::size(mData)); + assert(ret_i == std::size(mData)); } }; -/* These bit-reversal swap tables support up to 10-bit indices (1024 elements), - * which is the largest used by OpenAL Soft's filters and effects. Larger FFT - * requests, used by some utilities where performance is less important, will - * use a slower table-less path. +/* These bit-reversal swap tables support up to 11-bit indices (2048 elements), + * which is large enough for the filters and effects in OpenAL Soft. Larger FFT + * requests will use a slower table-less path. */ constexpr BitReverser<2> BitReverser2{}; constexpr BitReverser<3> BitReverser3{}; @@ -76,7 +78,8 @@ constexpr BitReverser<7> BitReverser7{}; constexpr BitReverser<8> BitReverser8{}; constexpr BitReverser<9> BitReverser9{}; constexpr BitReverser<10> BitReverser10{}; -constexpr std::array,11> gBitReverses{{ +constexpr BitReverser<11> BitReverser11{}; +constexpr std::array,12> gBitReverses{{ {}, {}, BitReverser2.mData, BitReverser3.mData, @@ -86,75 +89,124 @@ constexpr std::array,11> gBitReverses{{ BitReverser7.mData, BitReverser8.mData, BitReverser9.mData, - BitReverser10.mData + BitReverser10.mData, + BitReverser11.mData +}}; + +/* Lookup table for std::polar(1, pi / (1< +constexpr std::array,gBitReverses.size()-1> gArgAngle{{ + {static_cast(-1.00000000000000000e+00), static_cast(0.00000000000000000e+00)}, + {static_cast( 0.00000000000000000e+00), static_cast(1.00000000000000000e+00)}, + {static_cast( 7.07106781186547524e-01), static_cast(7.07106781186547524e-01)}, + {static_cast( 9.23879532511286756e-01), static_cast(3.82683432365089772e-01)}, + {static_cast( 9.80785280403230449e-01), static_cast(1.95090322016128268e-01)}, + {static_cast( 9.95184726672196886e-01), static_cast(9.80171403295606020e-02)}, + {static_cast( 9.98795456205172393e-01), static_cast(4.90676743274180143e-02)}, + {static_cast( 9.99698818696204220e-01), static_cast(2.45412285229122880e-02)}, + {static_cast( 9.99924701839144541e-01), static_cast(1.22715382857199261e-02)}, + {static_cast( 9.99981175282601143e-01), static_cast(6.13588464915447536e-03)}, + {static_cast( 9.99995293809576172e-01), static_cast(3.06795676296597627e-03)} }}; } // namespace -template -std::enable_if_t::value> -complex_fft(const al::span> buffer, const al::type_identity_t sign) +void complex_fft(const al::span> buffer, const double sign) { - const size_t fftsize{buffer.size()}; + const std::size_t fftsize{buffer.size()}; /* Get the number of bits used for indexing. Simplifies bit-reversal and * the main loop count. */ - const size_t log2_size{static_cast(al::countr_zero(fftsize))}; + const std::size_t log2_size{static_cast(al::countr_zero(fftsize))}; - if(log2_size >= gBitReverses.size()) UNLIKELY + if(log2_size < gBitReverses.size()) LIKELY { - for(size_t idx{1u};idx < fftsize-1;++idx) + for(auto &rev : gBitReverses[log2_size]) + std::swap(buffer[rev.first], buffer[rev.second]); + + /* Iterative form of Danielson-Lanczos lemma */ + for(std::size_t i{0};i < log2_size;++i) { - size_t revidx{0u}, imask{idx}; - for(size_t i{0};i < log2_size;++i) + const std::size_t step2{1_uz << i}; + const std::size_t step{2_uz << i}; + /* The first iteration of the inner loop would have u=1, which we + * can simplify to remove a number of complex multiplies. + */ + for(std::size_t k{0};k < fftsize;k+=step) { - revidx = (revidx<<1) | (imask&1); - imask >>= 1; - } - - if(idx < revidx) - std::swap(buffer[idx], buffer[revidx]); - } - } - else for(auto &rev : gBitReverses[log2_size]) - std::swap(buffer[rev.first], buffer[rev.second]); - - /* Iterative form of Danielson-Lanczos lemma */ - const Real pi{al::numbers::pi_v * sign}; - size_t step2{1u}; - for(size_t i{0};i < log2_size;++i) - { - const Real arg{pi / static_cast(step2)}; - - /* TODO: Would std::polar(1.0, arg) be any better? */ - const std::complex w{std::cos(arg), std::sin(arg)}; - std::complex u{1.0, 0.0}; - const size_t step{step2 << 1}; - for(size_t j{0};j < step2;j++) - { - for(size_t k{j};k < fftsize;k+=step) - { - std::complex temp{buffer[k+step2] * u}; + const complex_d temp{buffer[k+step2]}; buffer[k+step2] = buffer[k] - temp; buffer[k] += temp; } - u *= w; + const complex_d w{gArgAngle[i].real(), gArgAngle[i].imag()*sign}; + complex_d u{w}; + for(std::size_t j{1};j < step2;j++) + { + for(std::size_t k{j};k < fftsize;k+=step) + { + const complex_d temp{buffer[k+step2] * u}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; + } + u *= w; + } + } + } + else + { + assert(log2_size < 32); + + for(std::size_t idx{1u};idx < fftsize-1;++idx) + { + std::size_t revidx{idx}; + revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1); + revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2); + revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4); + revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8); + revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16); + revidx >>= 32-log2_size; + + if(idx < revidx) + std::swap(buffer[idx], buffer[revidx]); } - step2 <<= 1; + const double pi{al::numbers::pi * sign}; + for(std::size_t i{0};i < log2_size;++i) + { + const std::size_t step2{1_uz << i}; + const std::size_t step{2_uz << i}; + for(std::size_t k{0};k < fftsize;k+=step) + { + const complex_d temp{buffer[k+step2]}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; + } + + const double arg{pi / static_cast(step2)}; + const complex_d w{std::polar(1.0, arg)}; + complex_d u{w}; + for(std::size_t j{1};j < step2;j++) + { + for(std::size_t k{j};k < fftsize;k+=step) + { + const complex_d temp{buffer[k+step2] * u}; + buffer[k+step2] = buffer[k] - temp; + buffer[k] += temp; + } + u *= w; + } + } } } void complex_hilbert(const al::span> buffer) { - using namespace std::placeholders; - inverse_fft(buffer); const double inverse_size = 1.0/static_cast(buffer.size()); auto bufiter = buffer.begin(); - const auto halfiter = bufiter + (buffer.size()>>1); + const auto halfiter = bufiter + ptrdiff_t(buffer.size()>>1); *bufiter *= inverse_size; ++bufiter; bufiter = std::transform(bufiter, halfiter, bufiter, @@ -165,7 +217,3 @@ void complex_hilbert(const al::span> buffer) forward_fft(buffer); } - - -template void complex_fft<>(const al::span> buffer, const float sign); -template void complex_fft<>(const al::span> buffer, const double sign); diff --git a/Engine/lib/openal-soft/common/alcomplex.h b/Engine/lib/openal-soft/common/alcomplex.h index 794c35268..9f12cef32 100644 --- a/Engine/lib/openal-soft/common/alcomplex.h +++ b/Engine/lib/openal-soft/common/alcomplex.h @@ -2,7 +2,6 @@ #define ALCOMPLEX_H #include -#include #include "alspan.h" @@ -11,27 +10,21 @@ * FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to * the data supplied in the buffer, which MUST BE power of two. */ -template -std::enable_if_t::value> -complex_fft(const al::span> buffer, const al::type_identity_t sign); +void complex_fft(const al::span> buffer, const double sign); /** * Calculate the frequency-domain response of the time-domain signal in the * provided buffer, which MUST BE power of two. */ -template -std::enable_if_t::value> -forward_fft(const al::span,N> buffer) -{ complex_fft(buffer.subspan(0), -1); } +inline void forward_fft(const al::span> buffer) +{ complex_fft(buffer, -1.0); } /** * Calculate the time-domain signal of the frequency-domain response in the * provided buffer, which MUST BE power of two. */ -template -std::enable_if_t::value> -inverse_fft(const al::span,N> buffer) -{ complex_fft(buffer.subspan(0), 1); } +inline void inverse_fft(const al::span> buffer) +{ complex_fft(buffer, +1.0); } /** * Calculate the complex helical sequence (discrete-time analytical signal) of diff --git a/Engine/lib/openal-soft/common/aldeque.h b/Engine/lib/openal-soft/common/aldeque.h deleted file mode 100644 index 3f99bf007..000000000 --- a/Engine/lib/openal-soft/common/aldeque.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef ALDEQUE_H -#define ALDEQUE_H - -#include - -#include "almalloc.h" - - -namespace al { - -template -using deque = std::deque>; - -} // namespace al - -#endif /* ALDEQUE_H */ diff --git a/Engine/lib/openal-soft/common/alfstream.cpp b/Engine/lib/openal-soft/common/alfstream.cpp deleted file mode 100644 index 8991ce035..000000000 --- a/Engine/lib/openal-soft/common/alfstream.cpp +++ /dev/null @@ -1,26 +0,0 @@ - -#include "config.h" - -#include "alfstream.h" - -#include "strutils.h" - -#ifdef _WIN32 - -namespace al { - -ifstream::ifstream(const char *filename, std::ios_base::openmode mode) - : std::ifstream{utf8_to_wstr(filename).c_str(), mode} -{ } - -void ifstream::open(const char *filename, std::ios_base::openmode mode) -{ - std::wstring wstr{utf8_to_wstr(filename)}; - std::ifstream::open(wstr.c_str(), mode); -} - -ifstream::~ifstream() = default; - -} // namespace al - -#endif diff --git a/Engine/lib/openal-soft/common/alfstream.h b/Engine/lib/openal-soft/common/alfstream.h deleted file mode 100644 index 62b2e12c7..000000000 --- a/Engine/lib/openal-soft/common/alfstream.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef AL_FSTREAM_H -#define AL_FSTREAM_H - -#ifdef _WIN32 - -#include -#include - - -namespace al { - -// Inherit from std::ifstream to accept UTF-8 filenames -class ifstream final : public std::ifstream { -public: - explicit ifstream(const char *filename, std::ios_base::openmode mode=std::ios_base::in); - explicit ifstream(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in) - : ifstream{filename.c_str(), mode} { } - - explicit ifstream(const wchar_t *filename, std::ios_base::openmode mode=std::ios_base::in) - : std::ifstream{filename, mode} { } - explicit ifstream(const std::wstring &filename, std::ios_base::openmode mode=std::ios_base::in) - : ifstream{filename.c_str(), mode} { } - - void open(const char *filename, std::ios_base::openmode mode=std::ios_base::in); - void open(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in) - { open(filename.c_str(), mode); } - - ~ifstream() override; -}; - -} // namespace al - -#else /* _WIN32 */ - -#include - -namespace al { - -using ifstream = std::ifstream; - -} // namespace al - -#endif /* _WIN32 */ - -#endif /* AL_FSTREAM_H */ diff --git a/Engine/lib/openal-soft/common/almalloc.cpp b/Engine/lib/openal-soft/common/almalloc.cpp deleted file mode 100644 index ad1dc6be0..000000000 --- a/Engine/lib/openal-soft/common/almalloc.cpp +++ /dev/null @@ -1,61 +0,0 @@ - -#include "config.h" - -#include "almalloc.h" - -#include -#include -#include -#include -#include -#ifdef HAVE_MALLOC_H -#include -#endif - - -void *al_malloc(size_t alignment, size_t size) -{ - assert((alignment & (alignment-1)) == 0); - alignment = std::max(alignment, alignof(std::max_align_t)); - -#if defined(HAVE_POSIX_MEMALIGN) - void *ret{}; - if(posix_memalign(&ret, alignment, size) == 0) - return ret; - return nullptr; -#elif defined(HAVE__ALIGNED_MALLOC) - return _aligned_malloc(size, alignment); -#else - size_t total_size{size + alignment-1 + sizeof(void*)}; - void *base{std::malloc(total_size)}; - if(base != nullptr) - { - void *aligned_ptr{static_cast(base) + sizeof(void*)}; - total_size -= sizeof(void*); - - std::align(alignment, size, aligned_ptr, total_size); - *(static_cast(aligned_ptr)-1) = base; - base = aligned_ptr; - } - return base; -#endif -} - -void *al_calloc(size_t alignment, size_t size) -{ - void *ret{al_malloc(alignment, size)}; - if(ret) std::memset(ret, 0, size); - return ret; -} - -void al_free(void *ptr) noexcept -{ -#if defined(HAVE_POSIX_MEMALIGN) - std::free(ptr); -#elif defined(HAVE__ALIGNED_MALLOC) - _aligned_free(ptr); -#else - if(ptr != nullptr) - std::free(*(static_cast(ptr) - 1)); -#endif -} diff --git a/Engine/lib/openal-soft/common/almalloc.h b/Engine/lib/openal-soft/common/almalloc.h index a795fc3b7..e3a7bb521 100644 --- a/Engine/lib/openal-soft/common/almalloc.h +++ b/Engine/lib/openal-soft/common/almalloc.h @@ -3,49 +3,24 @@ #include #include -#include #include -#include #include #include #include - -#include "pragmadefs.h" +#include -void al_free(void *ptr) noexcept; -[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] -void *al_malloc(size_t alignment, size_t size); -[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]] -void *al_calloc(size_t alignment, size_t size); +namespace gsl { +template using owner = T; +} -#define DISABLE_ALLOC() \ +#define DISABLE_ALLOC \ void *operator new(size_t) = delete; \ void *operator new[](size_t) = delete; \ void operator delete(void*) noexcept = delete; \ void operator delete[](void*) noexcept = delete; -#define DEF_NEWDEL(T) \ - void *operator new(size_t size) \ - { \ - static_assert(&operator new == &T::operator new, \ - "Incorrect container type specified"); \ - if(void *ret{al_malloc(alignof(T), size)}) \ - return ret; \ - throw std::bad_alloc(); \ - } \ - void *operator new[](size_t size) { return operator new(size); } \ - void operator delete(void *block) noexcept { al_free(block); } \ - void operator delete[](void *block) noexcept { operator delete(block); } - -#define DEF_PLACE_NEWDEL() \ - void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \ - void *operator new[](size_t /*size*/, void *ptr) noexcept { return ptr; } \ - void operator delete(void *block, void*) noexcept { al_free(block); } \ - void operator delete(void *block) noexcept { al_free(block); } \ - void operator delete[](void *block, void*) noexcept { al_free(block); } \ - void operator delete[](void *block) noexcept { al_free(block); } enum FamCount : size_t { }; @@ -58,56 +33,64 @@ enum FamCount : size_t { }; sizeof(T)); \ } \ \ - void *operator new(size_t /*size*/, FamCount count) \ + gsl::owner operator new(size_t /*size*/, FamCount count) \ { \ - if(void *ret{al_malloc(alignof(T), T::Sizeof(count))}) \ - return ret; \ - throw std::bad_alloc(); \ + const auto alignment = std::align_val_t{alignof(T)}; \ + return ::operator new[](T::Sizeof(count), alignment); \ } \ + void operator delete(gsl::owner block, FamCount) noexcept \ + { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ + void operator delete(gsl::owner block) noexcept \ + { ::operator delete[](block, std::align_val_t{alignof(T)}); } \ void *operator new[](size_t /*size*/) = delete; \ - void operator delete(void *block, FamCount) { al_free(block); } \ - void operator delete(void *block) noexcept { al_free(block); } \ void operator delete[](void* /*block*/) = delete; namespace al { -template +template struct allocator { - static constexpr std::size_t alignment{std::max(Align, alignof(T))}; + static constexpr auto Alignment = std::max(AlignV, alignof(T)); + static constexpr auto AlignVal = std::align_val_t{Alignment}; - using value_type = T; - using reference = T&; - using const_reference = const T&; - using pointer = T*; - using const_pointer = const T*; + using value_type = std::remove_cv_t>; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using is_always_equal = std::true_type; - template + template = true> struct rebind { - using other = allocator; + using other = allocator; }; constexpr explicit allocator() noexcept = default; template - constexpr explicit allocator(const allocator&) noexcept { } + constexpr explicit allocator(const allocator&) noexcept + { static_assert(Alignment == allocator::Alignment); } - T *allocate(std::size_t n) + gsl::owner allocate(std::size_t n) { if(n > std::numeric_limits::max()/sizeof(T)) throw std::bad_alloc(); - if(auto p = al_malloc(alignment, n*sizeof(T))) return static_cast(p); - throw std::bad_alloc(); + return static_cast>(::operator new[](n*sizeof(T), AlignVal)); } - void deallocate(T *p, std::size_t) noexcept { al_free(p); } + void deallocate(gsl::owner p, std::size_t) noexcept + { ::operator delete[](gsl::owner{p}, AlignVal); } }; template -constexpr bool operator==(const allocator&, const allocator&) noexcept { return true; } +constexpr bool operator==(const allocator&, const allocator&) noexcept +{ return allocator::Alignment == allocator::Alignment; } template -constexpr bool operator!=(const allocator&, const allocator&) noexcept { return false; } +constexpr bool operator!=(const allocator&, const allocator&) noexcept +{ return allocator::Alignment != allocator::Alignment; } +#ifdef __cpp_lib_to_address +using std::to_address; +#else template constexpr T *to_address(T *p) noexcept { @@ -117,194 +100,55 @@ constexpr T *to_address(T *p) noexcept template constexpr auto to_address(const T &p) noexcept -{ return to_address(p.operator->()); } - +{ + return ::al::to_address(p.operator->()); +} +#endif template constexpr T* construct_at(T *ptr, Args&& ...args) - noexcept(std::is_nothrow_constructible::value) -{ return ::new(static_cast(ptr)) T{std::forward(args)...}; } - -/* At least VS 2015 complains that 'ptr' is unused when the given type's - * destructor is trivial (a no-op). So disable that warning for this call. - */ -DIAGNOSTIC_PUSH -msc_pragma(warning(disable : 4100)) -template -constexpr std::enable_if_t::value> -destroy_at(T *ptr) noexcept(std::is_nothrow_destructible::value) -{ ptr->~T(); } -DIAGNOSTIC_POP -template -constexpr std::enable_if_t::value> -destroy_at(T *ptr) noexcept(std::is_nothrow_destructible>::value) + noexcept(std::is_nothrow_constructible_v) { - for(auto &elem : *ptr) - al::destroy_at(std::addressof(elem)); -} - -template -constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) -{ - while(first != end) - { - al::destroy_at(std::addressof(*first)); - ++first; - } -} - -template -constexpr std::enable_if_t::value,T> -destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first)))) -{ - if(count != 0) - { - do { - al::destroy_at(std::addressof(*first)); - ++first; - } while(--count); - } - return first; + /* NOLINTBEGIN(cppcoreguidelines-owning-memory) construct_at doesn't + * necessarily handle the address from an owner, while placement new + * expects to. + */ + return ::new(static_cast(ptr)) T{std::forward(args)...}; + /* NOLINTEND(cppcoreguidelines-owning-memory) */ } -template -inline std::enable_if_t::value, -T> uninitialized_default_construct_n(T first, N count) -{ - using ValueT = typename std::iterator_traits::value_type; - T current{first}; - if(count != 0) +template +class out_ptr_t { + static_assert(!std::is_same_v); + + SP &mRes; + std::variant mPtr{}; + +public: + out_ptr_t(SP &res) : mRes{res} { } + ~out_ptr_t() { - try { - do { - ::new(static_cast(std::addressof(*current))) ValueT; - ++current; - } while(--count); - } - catch(...) { - al::destroy(first, current); - throw; - } + auto set_res = [this](auto &ptr) + { mRes.reset(static_cast(ptr)); }; + std::visit(set_res, mPtr); } - return current; -} + out_ptr_t(const out_ptr_t&) = delete; + out_ptr_t& operator=(const out_ptr_t&) = delete; + operator PT*() noexcept + { return &std::get(mPtr); } -/* Storage for flexible array data. This is trivially destructible if type T is - * trivially destructible. - */ -template::value> -struct FlexArrayStorage { - const size_t mSize; - union { - char mDummy; - alignas(alignment) T mArray[1]; - }; - - static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept - { - const size_t len{sizeof(T)*count}; - return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; - } - - FlexArrayStorage(size_t size) : mSize{size} - { al::uninitialized_default_construct_n(mArray, mSize); } - ~FlexArrayStorage() = default; - - FlexArrayStorage(const FlexArrayStorage&) = delete; - FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; + operator void**() noexcept + { return &mPtr.template emplace(); } }; -template -struct FlexArrayStorage { - const size_t mSize; - union { - char mDummy; - alignas(alignment) T mArray[1]; - }; - - static constexpr size_t Sizeof(size_t count, size_t base) noexcept - { - const size_t len{sizeof(T)*count}; - return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base; - } - - FlexArrayStorage(size_t size) : mSize{size} - { al::uninitialized_default_construct_n(mArray, mSize); } - ~FlexArrayStorage() { al::destroy_n(mArray, mSize); } - - FlexArrayStorage(const FlexArrayStorage&) = delete; - FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; -}; - -/* A flexible array type. Used either standalone or at the end of a parent - * struct, with placement new, to have a run-time-sized array that's embedded - * with its size. - */ -template -struct FlexArray { - using element_type = T; - using value_type = std::remove_cv_t; - using index_type = size_t; - using difference_type = ptrdiff_t; - - using pointer = T*; - using const_pointer = const T*; - using reference = T&; - using const_reference = const T&; - - using iterator = pointer; - using const_iterator = const_pointer; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - - using Storage_t_ = FlexArrayStorage; - - Storage_t_ mStore; - - static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept - { return Storage_t_::Sizeof(count, base); } - static std::unique_ptr Create(index_type count) - { - void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))}; - return std::unique_ptr{al::construct_at(static_cast(ptr), count)}; - } - - FlexArray(index_type size) : mStore{size} { } - ~FlexArray() = default; - - index_type size() const noexcept { return mStore.mSize; } - bool empty() const noexcept { return mStore.mSize == 0; } - - pointer data() noexcept { return mStore.mArray; } - const_pointer data() const noexcept { return mStore.mArray; } - - reference operator[](index_type i) noexcept { return mStore.mArray[i]; } - const_reference operator[](index_type i) const noexcept { return mStore.mArray[i]; } - - reference front() noexcept { return mStore.mArray[0]; } - const_reference front() const noexcept { return mStore.mArray[0]; } - - reference back() noexcept { return mStore.mArray[mStore.mSize-1]; } - const_reference back() const noexcept { return mStore.mArray[mStore.mSize-1]; } - - iterator begin() noexcept { return mStore.mArray; } - const_iterator begin() const noexcept { return mStore.mArray; } - const_iterator cbegin() const noexcept { return mStore.mArray; } - iterator end() noexcept { return mStore.mArray + mStore.mSize; } - const_iterator end() const noexcept { return mStore.mArray + mStore.mSize; } - const_iterator cend() const noexcept { return mStore.mArray + mStore.mSize; } - - reverse_iterator rbegin() noexcept { return end(); } - const_reverse_iterator rbegin() const noexcept { return end(); } - const_reverse_iterator crbegin() const noexcept { return cend(); } - reverse_iterator rend() noexcept { return begin(); } - const_reverse_iterator rend() const noexcept { return begin(); } - const_reverse_iterator crend() const noexcept { return cbegin(); } - - DEF_PLACE_NEWDEL() -}; +template +auto out_ptr(SP &res) +{ + using ptype = typename SP::element_type*; + return out_ptr_t{res}; +} } // namespace al diff --git a/Engine/lib/openal-soft/common/alnumbers.h b/Engine/lib/openal-soft/common/alnumbers.h index 37a554100..6d201ad4f 100644 --- a/Engine/lib/openal-soft/common/alnumbers.h +++ b/Engine/lib/openal-soft/common/alnumbers.h @@ -1,11 +1,9 @@ #ifndef COMMON_ALNUMBERS_H #define COMMON_ALNUMBERS_H -#include +#include -namespace al { - -namespace numbers { +namespace al::numbers { namespace detail_ { template @@ -13,24 +11,22 @@ namespace detail_ { } // detail_ template -static constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); +inline constexpr auto pi_v = detail_::as_fp(3.141592653589793238462643383279502884L); template -static constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); +inline constexpr auto inv_pi_v = detail_::as_fp(0.318309886183790671537767526745028724L); template -static constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); +inline constexpr auto sqrt2_v = detail_::as_fp(1.414213562373095048801688724209698079L); template -static constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); +inline constexpr auto sqrt3_v = detail_::as_fp(1.732050807568877293527446341505872367L); -static constexpr auto pi = pi_v; -static constexpr auto inv_pi = inv_pi_v; -static constexpr auto sqrt2 = sqrt2_v; -static constexpr auto sqrt3 = sqrt3_v; +inline constexpr auto pi = pi_v; +inline constexpr auto inv_pi = inv_pi_v; +inline constexpr auto sqrt2 = sqrt2_v; +inline constexpr auto sqrt3 = sqrt3_v; -} // namespace numbers - -} // namespace al +} // namespace al::numbers #endif /* COMMON_ALNUMBERS_H */ diff --git a/Engine/lib/openal-soft/common/alnumeric.h b/Engine/lib/openal-soft/common/alnumeric.h index d6919e401..0d091166b 100644 --- a/Engine/lib/openal-soft/common/alnumeric.h +++ b/Engine/lib/openal-soft/common/alnumeric.h @@ -2,9 +2,12 @@ #define AL_NUMERIC_H #include +#include #include #include #include +#include +#include #ifdef HAVE_INTRIN_H #include #endif @@ -12,75 +15,34 @@ #include #endif +#include "albit.h" #include "altraits.h" #include "opthelpers.h" -inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast(n); } -inline constexpr uint64_t operator "" _u64(unsigned long long int n) noexcept { return static_cast(n); } +constexpr auto operator "" _i64(unsigned long long n) noexcept { return static_cast(n); } +constexpr auto operator "" _u64(unsigned long long n) noexcept { return static_cast(n); } + +constexpr auto operator "" _z(unsigned long long n) noexcept +{ return static_cast>(n); } +constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_cast(n); } +constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast(n); } -constexpr inline float minf(float a, float b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline float maxf(float a, float b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline float clampf(float val, float min, float max) noexcept -{ return minf(max, maxf(min, val)); } - -constexpr inline double mind(double a, double b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline double maxd(double a, double b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline double clampd(double val, double min, double max) noexcept -{ return mind(max, maxd(min, val)); } - -constexpr inline unsigned int minu(unsigned int a, unsigned int b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline unsigned int maxu(unsigned int a, unsigned int b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline unsigned int clampu(unsigned int val, unsigned int min, unsigned int max) noexcept -{ return minu(max, maxu(min, val)); } - -constexpr inline int mini(int a, int b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline int maxi(int a, int b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline int clampi(int val, int min, int max) noexcept -{ return mini(max, maxi(min, val)); } - -constexpr inline int64_t mini64(int64_t a, int64_t b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline int64_t maxi64(int64_t a, int64_t b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline int64_t clampi64(int64_t val, int64_t min, int64_t max) noexcept -{ return mini64(max, maxi64(min, val)); } - -constexpr inline uint64_t minu64(uint64_t a, uint64_t b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline uint64_t maxu64(uint64_t a, uint64_t b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline uint64_t clampu64(uint64_t val, uint64_t min, uint64_t max) noexcept -{ return minu64(max, maxu64(min, val)); } - -constexpr inline size_t minz(size_t a, size_t b) noexcept -{ return ((a > b) ? b : a); } -constexpr inline size_t maxz(size_t a, size_t b) noexcept -{ return ((a > b) ? a : b); } -constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept -{ return minz(max, maxz(min, val)); } +constexpr auto GetCounterSuffix(size_t count) noexcept -> const char* +{ + auto &suffix = (((count%100)/10) == 1) ? "th" : + ((count%10) == 1) ? "st" : + ((count%10) == 2) ? "nd" : + ((count%10) == 3) ? "rd" : "th"; + return std::data(suffix); +} constexpr inline float lerpf(float val1, float val2, float mu) noexcept { return val1 + (val2-val1)*mu; } -constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept -{ - const float mu2{mu*mu}, mu3{mu2*mu}; - const float a0{-0.5f*mu3 + mu2 + -0.5f*mu}; - const float a1{ 1.5f*mu3 + -2.5f*mu2 + 1.0f}; - const float a2{-1.5f*mu3 + 2.0f*mu2 + 0.5f*mu}; - const float a3{ 0.5f*mu3 + -0.5f*mu2}; - return val1*a0 + val2*a1 + val3*a2 + val4*a3; -} +constexpr inline double lerpd(double val1, double val2, double mu) noexcept +{ return val1 + (val2-val1)*mu; } /** Find the next power-of-2 for non-power-of-2 numbers. */ @@ -125,21 +87,18 @@ inline int fastf2i(float f) noexcept #if defined(HAVE_SSE_INTRINSICS) return _mm_cvt_ss2si(_mm_set_ss(f)); -#elif defined(_MSC_VER) && defined(_M_IX86_FP) +#elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0 int i; __asm fld f __asm fistp i return i; -#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) +#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ + && !defined(__SSE_MATH__) int i; -#ifdef __SSE_MATH__ - __asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f)); -#else __asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st"); -#endif return i; #else @@ -159,21 +118,16 @@ inline int float2int(float f) noexcept #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \ || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE_MATH__)) - int sign, shift, mant; - union { - float f; - int i; - } conv; + const int conv_i{al::bit_cast(f)}; - conv.f = f; - sign = (conv.i>>31) | 1; - shift = ((conv.i>>23)&0xff) - (127+23); + const int sign{(conv_i>>31) | 1}; + const int shift{((conv_i>>23)&0xff) - (127+23)}; /* Over/underflow */ if(shift >= 31 || shift < -23) UNLIKELY return 0; - mant = (conv.i&0x7fffff) | 0x800000; + const int mant{(conv_i&0x7fffff) | 0x800000}; if(shift < 0) LIKELY return (mant >> -shift) * sign; return (mant << shift) * sign; @@ -195,25 +149,19 @@ inline int double2int(double d) noexcept #elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \ || ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \ && !defined(__SSE2_MATH__)) - int sign, shift; - int64_t mant; - union { - double d; - int64_t i64; - } conv; + const int64_t conv_i64{al::bit_cast(d)}; - conv.d = d; - sign = (conv.i64 >> 63) | 1; - shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52); + const int sign{static_cast(conv_i64 >> 63) | 1}; + const int shift{(static_cast(conv_i64 >> 52) & 0x7ff) - (1023 + 52)}; /* Over/underflow */ if(shift >= 63 || shift < -52) UNLIKELY return 0; - mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64; + const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64}; if(shift < 0) LIKELY - return (int)(mant >> -shift) * sign; - return (int)(mant << shift) * sign; + return static_cast(mant >> -shift) * sign; + return static_cast(mant << shift) * sign; #else @@ -246,19 +194,14 @@ inline float fast_roundf(float f) noexcept /* Integral limit, where sub-integral precision is not available for * floats. */ - static const float ilim[2]{ + static constexpr std::array ilim{ 8388608.0f /* 0x1.0p+23 */, -8388608.0f /* -0x1.0p+23 */ }; - unsigned int sign, expo; - union { - float f; - unsigned int i; - } conv; + const unsigned int conv_i{al::bit_cast(f)}; - conv.f = f; - sign = (conv.i>>31)&0x01; - expo = (conv.i>>23)&0xff; + const unsigned int sign{(conv_i>>31)&0x01}; + const unsigned int expo{(conv_i>>23)&0xff}; if(expo >= 150/*+23*/) UNLIKELY { @@ -275,20 +218,18 @@ inline float fast_roundf(float f) noexcept * optimize this out because of non-associative rules on floating-point * math (as long as you don't use -fassociative-math, * -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this - * may break). + * may break without __builtin_assoc_barrier support). */ +#if HAS_BUILTIN(__builtin_assoc_barrier) + return __builtin_assoc_barrier(f + ilim[sign]) - ilim[sign]; +#else f += ilim[sign]; return f - ilim[sign]; #endif +#endif } -template -constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept -{ - return std::min(std::max(value, min_value), max_value); -} - // Converts level (mB) to gain. inline float level_mb_to_gain(float x) { @@ -302,7 +243,7 @@ inline float gain_to_level_mb(float x) { if (x <= 0.0f) return -10'000.0f; - return maxf(std::log10(x) * 2'000.0f, -10'000.0f); + return std::max(std::log10(x) * 2'000.0f, -10'000.0f); } #endif /* AL_NUMERIC_H */ diff --git a/Engine/lib/openal-soft/common/aloptional.h b/Engine/lib/openal-soft/common/aloptional.h deleted file mode 100644 index 6de16799e..000000000 --- a/Engine/lib/openal-soft/common/aloptional.h +++ /dev/null @@ -1,353 +0,0 @@ -#ifndef AL_OPTIONAL_H -#define AL_OPTIONAL_H - -#include -#include -#include - -#include "almalloc.h" - -namespace al { - -struct nullopt_t { }; -struct in_place_t { }; - -constexpr nullopt_t nullopt{}; -constexpr in_place_t in_place{}; - -#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__)) - -namespace detail_ { -/* Base storage struct for an optional. Defines a trivial destructor, for types - * that can be trivially destructed. - */ -template::value> -struct optstore_base { - bool mHasValue{false}; - union { - char mDummy{}; - T mValue; - }; - - constexpr optstore_base() noexcept { } - template - constexpr explicit optstore_base(in_place_t, Args&& ...args) - noexcept(std::is_nothrow_constructible::value) - : mHasValue{true}, mValue{std::forward(args)...} - { } - ~optstore_base() = default; -}; - -/* Specialization needing a non-trivial destructor. */ -template -struct optstore_base { - bool mHasValue{false}; - union { - char mDummy{}; - T mValue; - }; - - constexpr optstore_base() noexcept { } - template - constexpr explicit optstore_base(in_place_t, Args&& ...args) - noexcept(std::is_nothrow_constructible::value) - : mHasValue{true}, mValue{std::forward(args)...} - { } - ~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); } -}; - -/* Next level of storage, which defines helpers to construct and destruct the - * stored object. - */ -template -struct optstore_helper : public optstore_base { - using optstore_base::optstore_base; - - template - constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible::value) - { - al::construct_at(std::addressof(this->mValue), std::forward(args)...); - this->mHasValue = true; - } - - constexpr void reset() noexcept - { - if(this->mHasValue) - al::destroy_at(std::addressof(this->mValue)); - this->mHasValue = false; - } - - constexpr void assign(const optstore_helper &rhs) - noexcept(std::is_nothrow_copy_constructible::value - && std::is_nothrow_copy_assignable::value) - { - if(!rhs.mHasValue) - this->reset(); - else if(this->mHasValue) - this->mValue = rhs.mValue; - else - this->construct(rhs.mValue); - } - - constexpr void assign(optstore_helper&& rhs) - noexcept(std::is_nothrow_move_constructible::value - && std::is_nothrow_move_assignable::value) - { - if(!rhs.mHasValue) - this->reset(); - else if(this->mHasValue) - this->mValue = std::move(rhs.mValue); - else - this->construct(std::move(rhs.mValue)); - } -}; - -/* Define copy and move constructors and assignment operators, which may or may - * not be trivial. - */ -template::value, - bool trivial_move = std::is_trivially_move_constructible::value, - /* Trivial assignment is dependent on trivial construction+destruction. */ - bool = trivial_copy && std::is_trivially_copy_assignable::value - && std::is_trivially_destructible::value, - bool = trivial_move && std::is_trivially_move_assignable::value - && std::is_trivially_destructible::value> -struct optional_storage; - -/* Some versions of GCC have issues with 'this' in the following noexcept(...) - * statements, so this macro is a workaround. - */ -#define _this std::declval() - -/* Completely trivial. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage&) = default; - constexpr optional_storage& operator=(optional_storage&&) = default; -}; - -/* Non-trivial move assignment. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage&) = default; - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial move construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) - { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } - constexpr optional_storage& operator=(const optional_storage&) = default; - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial copy assignment. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&&) = default; -}; - -/* Non-trivial copy construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) - { if(rhs.mHasValue) this->construct(rhs.mValue); } - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&&) = default; -}; - -/* Non-trivial assignment. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial assignment, non-trivial move construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage&) = default; - constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) - { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Non-trivial assignment, non-trivial copy construction. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) - { if(rhs.mHasValue) this->construct(rhs.mValue); } - constexpr optional_storage(optional_storage&&) = default; - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -/* Completely non-trivial. */ -template -struct optional_storage : public optstore_helper { - using optstore_helper::optstore_helper; - constexpr optional_storage() noexcept = default; - constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue)) - { if(rhs.mHasValue) this->construct(rhs.mValue); } - constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue))) - { if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); } - constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs)) - { this->assign(rhs); return *this; } - constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs))) - { this->assign(std::move(rhs)); return *this; } -}; - -#undef _this - -} // namespace detail_ - -#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true - -template -class optional { - using storage_t = detail_::optional_storage; - - storage_t mStore{}; - -public: - using value_type = T; - - constexpr optional() = default; - constexpr optional(const optional&) = default; - constexpr optional(optional&&) = default; - constexpr optional(nullopt_t) noexcept { } - template - constexpr explicit optional(in_place_t, Args&& ...args) - NOEXCEPT_AS(storage_t{al::in_place, std::forward(args)...}) - : mStore{al::in_place, std::forward(args)...} - { } - template::value - && !std::is_same, al::in_place_t>::value - && !std::is_same, optional>::value - && std::is_convertible::value)> - constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) - : mStore{al::in_place, std::forward(rhs)} - { } - template::value - && !std::is_same, al::in_place_t>::value - && !std::is_same, optional>::value - && !std::is_convertible::value)> - constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward(rhs)}) - : mStore{al::in_place, std::forward(rhs)} - { } - ~optional() = default; - - constexpr optional& operator=(const optional&) = default; - constexpr optional& operator=(optional&&) = default; - constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; } - template - constexpr std::enable_if_t::value - && std::is_assignable::value - && !std::is_same, optional>::value - && (!std::is_same, T>::value || !std::is_scalar::value), - optional&> operator=(U&& rhs) - { - if(mStore.mHasValue) - mStore.mValue = std::forward(rhs); - else - mStore.construct(std::forward(rhs)); - return *this; - } - - constexpr const T* operator->() const { return std::addressof(mStore.mValue); } - constexpr T* operator->() { return std::addressof(mStore.mValue); } - constexpr const T& operator*() const& { return mStore.mValue; } - constexpr T& operator*() & { return mStore.mValue; } - constexpr const T&& operator*() const&& { return std::move(mStore.mValue); } - constexpr T&& operator*() && { return std::move(mStore.mValue); } - - constexpr explicit operator bool() const noexcept { return mStore.mHasValue; } - constexpr bool has_value() const noexcept { return mStore.mHasValue; } - - constexpr T& value() & { return mStore.mValue; } - constexpr const T& value() const& { return mStore.mValue; } - constexpr T&& value() && { return std::move(mStore.mValue); } - constexpr const T&& value() const&& { return std::move(mStore.mValue); } - - template - constexpr T value_or(U&& defval) const& - { return bool(*this) ? **this : static_cast(std::forward(defval)); } - template - constexpr T value_or(U&& defval) && - { return bool(*this) ? std::move(**this) : static_cast(std::forward(defval)); } - - template - constexpr T& emplace(Args&& ...args) - { - mStore.reset(); - mStore.construct(std::forward(args)...); - return mStore.mValue; - } - template - constexpr std::enable_if_t&, Args&&...>::value, - T&> emplace(std::initializer_list il, Args&& ...args) - { - mStore.reset(); - mStore.construct(il, std::forward(args)...); - return mStore.mValue; - } - - constexpr void reset() noexcept { mStore.reset(); } -}; - -template -constexpr optional> make_optional(T&& arg) -{ return optional>{in_place, std::forward(arg)}; } - -template -constexpr optional make_optional(Args&& ...args) -{ return optional{in_place, std::forward(args)...}; } - -template -constexpr optional make_optional(std::initializer_list il, Args&& ...args) -{ return optional{in_place, il, std::forward(args)...}; } - -#undef REQUIRES -#undef NOEXCEPT_AS -} // namespace al - -#endif /* AL_OPTIONAL_H */ diff --git a/Engine/lib/openal-soft/common/threads.cpp b/Engine/lib/openal-soft/common/alsem.cpp similarity index 58% rename from Engine/lib/openal-soft/common/threads.cpp rename to Engine/lib/openal-soft/common/alsem.cpp index 19a6bbf04..2eeed5929 100644 --- a/Engine/lib/openal-soft/common/threads.cpp +++ b/Engine/lib/openal-soft/common/alsem.cpp @@ -20,8 +20,7 @@ #include "config.h" -#include "opthelpers.h" -#include "threads.h" +#include "alsem.h" #include @@ -32,44 +31,14 @@ #include -void althrd_setname(const char *name) -{ -#if defined(_MSC_VER) && !defined(_M_ARM) - -#define MS_VC_EXCEPTION 0x406D1388 -#pragma pack(push,8) - struct { - DWORD dwType; // Must be 0x1000. - LPCSTR szName; // Pointer to name (in user addr space). - DWORD dwThreadID; // Thread ID (-1=caller thread). - DWORD dwFlags; // Reserved for future use, must be zero. - } info; -#pragma pack(pop) - info.dwType = 0x1000; - info.szName = name; - info.dwThreadID = ~DWORD{0}; - info.dwFlags = 0; - - __try { - RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); - } - __except(EXCEPTION_CONTINUE_EXECUTION) { - } -#undef MS_VC_EXCEPTION - -#else - - (void)name; -#endif -} - namespace al { semaphore::semaphore(unsigned int initial) { if(initial > static_cast(std::numeric_limits::max())) throw std::system_error(std::make_error_code(std::errc::value_too_large)); - mSem = CreateSemaphore(nullptr, initial, std::numeric_limits::max(), nullptr); + mSem = CreateSemaphoreW(nullptr, static_cast(initial), std::numeric_limits::max(), + nullptr); if(mSem == nullptr) throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again)); } @@ -93,49 +62,8 @@ bool semaphore::try_wait() noexcept #else -#include -#ifdef HAVE_PTHREAD_NP_H -#include -#endif -#include - -namespace { - -using setname_t1 = int(*)(const char*); -using setname_t2 = int(*)(pthread_t, const char*); -using setname_t3 = void(*)(pthread_t, const char*); -using setname_t4 = int(*)(pthread_t, const char*, void*); - -void setname_caller(setname_t1 func, const char *name) -{ func(name); } - -void setname_caller(setname_t2 func, const char *name) -{ func(pthread_self(), name); } - -void setname_caller(setname_t3 func, const char *name) -{ func(pthread_self(), name); } - -void setname_caller(setname_t4 func, const char *name) -{ func(pthread_self(), "%s", static_cast(const_cast(name))); } - -} // namespace - -void althrd_setname(const char *name) -{ -#if defined(HAVE_PTHREAD_SET_NAME_NP) - setname_caller(pthread_set_name_np, name); -#elif defined(HAVE_PTHREAD_SETNAME_NP) - setname_caller(pthread_setname_np, name); -#endif - /* Avoid unused function/parameter warnings. */ - std::ignore = name; - std::ignore = static_cast(&setname_caller); - std::ignore = static_cast(&setname_caller); - std::ignore = static_cast(&setname_caller); - std::ignore = static_cast(&setname_caller); -} - -#ifdef __APPLE__ +/* Do not try using libdispatch on systems where it is absent. */ +#if defined(AL_APPLE_HAVE_DISPATCH) namespace al { diff --git a/Engine/lib/openal-soft/common/alsem.h b/Engine/lib/openal-soft/common/alsem.h new file mode 100644 index 000000000..90b393190 --- /dev/null +++ b/Engine/lib/openal-soft/common/alsem.h @@ -0,0 +1,43 @@ +#ifndef COMMON_ALSEM_H +#define COMMON_ALSEM_H + +#if defined(__APPLE__) +#include +#include +#if (((MAC_OS_X_VERSION_MIN_REQUIRED > 1050) && !defined(__ppc__)) || TARGET_OS_IOS || TARGET_OS_TV) +#include +#define AL_APPLE_HAVE_DISPATCH 1 +#else +#include /* Fallback option for Apple without a working libdispatch */ +#endif +#elif !defined(_WIN32) +#include +#endif + +namespace al { + +class semaphore { +#ifdef _WIN32 + using native_type = void*; +#elif defined(AL_APPLE_HAVE_DISPATCH) + using native_type = dispatch_semaphore_t; +#else + using native_type = sem_t; +#endif + native_type mSem{}; + +public: + semaphore(unsigned int initial=0); + semaphore(const semaphore&) = delete; + ~semaphore(); + + semaphore& operator=(const semaphore&) = delete; + + void post(); + void wait() noexcept; + bool try_wait() noexcept; +}; + +} // namespace al + +#endif /* COMMON_ALSEM_H */ diff --git a/Engine/lib/openal-soft/common/alspan.h b/Engine/lib/openal-soft/common/alspan.h index 1d6cdfe56..c60aeeb8d 100644 --- a/Engine/lib/openal-soft/common/alspan.h +++ b/Engine/lib/openal-soft/common/alspan.h @@ -1,180 +1,250 @@ #ifndef AL_SPAN_H #define AL_SPAN_H -#include +#include #include -#include #include +#include +#include #include +#include +#include "alassert.h" #include "almalloc.h" #include "altraits.h" namespace al { +/* This is here primarily to help ensure proper behavior for span's iterators, + * being an actual object with member functions instead of a raw pointer (which + * has requirements like + and - working with ptrdiff_t). This also helps + * silence clang-tidy's pointer arithmetic warnings for span and FlexArray + * iterators. It otherwise behaves like a plain pointer and should optimize + * accordingly. + * + * Shouldn't be needed once we use std::span in C++20. + */ template -constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size()) -{ return cont.size(); } +class ptr_wrapper { + static_assert(std::is_pointer_v); + T mPointer{}; -template -constexpr size_t size(const T (&)[N]) noexcept -{ return N; } +public: + using value_type = std::remove_pointer_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::random_access_iterator_tag; + + explicit constexpr ptr_wrapper(T ptr) : mPointer{ptr} { } + + /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + constexpr auto operator++() noexcept -> ptr_wrapper& { ++mPointer; return *this; } + constexpr auto operator--() noexcept -> ptr_wrapper& { --mPointer; return *this; } + constexpr auto operator++(int) noexcept -> ptr_wrapper + { + auto temp = *this; + ++*this; + return temp; + } + constexpr auto operator--(int) noexcept -> ptr_wrapper + { + auto temp = *this; + --*this; + return temp; + } + + constexpr + auto operator+=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer += n; return *this; } + constexpr + auto operator-=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer -= n; return *this; } + + [[nodiscard]] constexpr auto operator*() const noexcept -> value_type& { return *mPointer; } + [[nodiscard]] constexpr auto operator->() const noexcept -> value_type* { return mPointer; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) const noexcept -> value_type& {return mPointer[idx];} + + [[nodiscard]] friend constexpr + auto operator+(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper + { return ptr_wrapper{lhs.mPointer + n}; } + [[nodiscard]] friend constexpr + auto operator+(std::ptrdiff_t n, const ptr_wrapper &rhs) noexcept -> ptr_wrapper + { return ptr_wrapper{n + rhs.mPointer}; } + [[nodiscard]] friend constexpr + auto operator-(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper + { return ptr_wrapper{lhs.mPointer - n}; } + + [[nodiscard]] friend constexpr + auto operator-(const ptr_wrapper &lhs, const ptr_wrapper &rhs)noexcept->std::ptrdiff_t + { return lhs.mPointer - rhs.mPointer; } + + [[nodiscard]] friend constexpr + auto operator==(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer == rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator!=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer != rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator<=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer <= rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator>=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer >= rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator<(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer < rhs.mPointer; } + [[nodiscard]] friend constexpr + auto operator>(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool + { return lhs.mPointer > rhs.mPointer; } + /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ +}; -template -constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) -{ return cont.data(); } +inline constexpr std::size_t dynamic_extent{static_cast(-1)}; -template -constexpr auto data(const T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data()) -{ return cont.data(); } - -template -constexpr T* data(T (&arr)[N]) noexcept -{ return arr; } - -template -constexpr const T* data(std::initializer_list list) noexcept -{ return list.begin(); } - - -constexpr size_t dynamic_extent{static_cast(-1)}; - -template +template class span; namespace detail_ { - template - using void_t = void; - template struct is_span_ : std::false_type { }; - template + template struct is_span_> : std::true_type { }; template - constexpr bool is_span_v = is_span_>::value; + inline constexpr bool is_span_v = is_span_>::value; template struct is_std_array_ : std::false_type { }; - template + template struct is_std_array_> : std::true_type { }; template - constexpr bool is_std_array_v = is_std_array_>::value; + inline constexpr bool is_std_array_v = is_std_array_>::value; template - constexpr bool has_size_and_data = false; + inline constexpr bool has_size_and_data = false; template - constexpr bool has_size_and_data())), decltype(al::data(std::declval()))>> + inline constexpr bool has_size_and_data())),decltype(std::data(std::declval()))>> = true; + template + inline constexpr bool is_valid_container_type = !is_span_v && !is_std_array_v + && !std::is_array::value && has_size_and_data; + template - constexpr bool is_array_compatible = std::is_convertible::value; + inline constexpr bool is_array_compatible = std::is_convertible::value; /* NOLINT(*-avoid-c-arrays) */ template - constexpr bool is_valid_container = !is_span_v && !is_std_array_v - && !std::is_array::value && has_size_and_data - && is_array_compatible()))>,T>; + inline constexpr bool is_valid_container = is_valid_container_type + && is_array_compatible()))>,T>; } // namespace detail_ #define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true -template +template class span { public: using element_type = T; using value_type = std::remove_cv_t; - using index_type = size_t; - using difference_type = ptrdiff_t; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; using pointer = T*; using const_pointer = const T*; using reference = T&; using const_reference = const T&; - using iterator = pointer; - using const_iterator = const_pointer; + using iterator = ptr_wrapper; + using const_iterator = ptr_wrapper; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - static constexpr size_t extent{E}; + static constexpr std::size_t extent{E}; template constexpr span() noexcept { } template - constexpr explicit span(U iter, index_type) : mData{to_address(iter)} { } - template::value)> - constexpr explicit span(U first, V) : mData{to_address(first)} { } + constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)} + { alassert(size_ == extent); } + template::value)> + constexpr explicit span(U first, V last) : mData{::al::to_address(first)} + { alassert(static_cast(last-first) == extent); } - constexpr span(type_identity_t (&arr)[E]) noexcept - : span{al::data(arr), al::size(arr)} - { } - constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template::value)> - constexpr span(const std::array &arr) noexcept - : span{al::data(arr), al::size(arr)} - { } + template + constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ + : mData{std::data(arr)} + { static_assert(N == extent); } + template + constexpr span(std::array &arr) noexcept : mData{std::data(arr)} + { static_assert(N == extent); } + template::value)> + constexpr span(const std::array &arr) noexcept : mData{std::data(arr)} + { static_assert(N == extent); } template)> - constexpr explicit span(U&& cont) : span{al::data(cont), al::size(cont)} { } + constexpr explicit span(U&& cont) : span{std::data(cont), std::size(cont)} { } - template::value + template::value && detail_::is_array_compatible && N == dynamic_extent)> - constexpr explicit span(const span &span_) noexcept - : span{al::data(span_), al::size(span_)} - { } - template::value + constexpr explicit span(const span &span_) noexcept : mData{std::data(span_)} + { alassert(std::size(span_) == extent); } + template::value && detail_::is_array_compatible && N == extent)> - constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + constexpr span(const span &span_) noexcept : mData{std::data(span_)} { } constexpr span(const span&) noexcept = default; constexpr span& operator=(const span &rhs) noexcept = default; - constexpr reference front() const { return *mData; } - constexpr reference back() const { return *(mData+E-1); } - constexpr reference operator[](index_type idx) const { return mData[idx]; } - constexpr pointer data() const noexcept { return mData; } + [[nodiscard]] constexpr auto front() const -> reference { return mData[0]; } + [[nodiscard]] constexpr auto back() const -> reference { return mData[E-1]; } + [[nodiscard]] constexpr auto operator[](size_type idx) const -> reference { return mData[idx]; } + [[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; } - constexpr index_type size() const noexcept { return E; } - constexpr index_type size_bytes() const noexcept { return E * sizeof(value_type); } - constexpr bool empty() const noexcept { return E == 0; } + [[nodiscard]] constexpr auto size() const noexcept -> size_type { return E; } + [[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return E * sizeof(value_type); } + [[nodiscard]] constexpr auto empty() const noexcept -> bool { return E == 0; } - constexpr iterator begin() const noexcept { return mData; } - constexpr iterator end() const noexcept { return mData+E; } - constexpr const_iterator cbegin() const noexcept { return mData; } - constexpr const_iterator cend() const noexcept { return mData+E; } + [[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; } + [[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+E}; } + [[nodiscard]] constexpr + auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; } + [[nodiscard]] constexpr + auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; } - constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } - constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } - constexpr const_reverse_iterator crbegin() const noexcept - { return const_reverse_iterator{cend()}; } - constexpr const_reverse_iterator crend() const noexcept - { return const_reverse_iterator{cbegin()}; } + [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } + [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } + [[nodiscard]] constexpr + auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } - template - constexpr span first() const + template + [[nodiscard]] constexpr auto first() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData, C}; } - template - constexpr span last() const + template + [[nodiscard]] constexpr auto last() const noexcept -> span { static_assert(E >= C, "New size exceeds original capacity"); return span{mData+(E-C), C}; } - template - constexpr auto subspan() const -> std::enable_if_t> + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); static_assert(E-O >= C, "New size exceeds original capacity"); return span{mData+O, C}; } - template - constexpr auto subspan() const -> std::enable_if_t> + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> { static_assert(E >= O, "Offset exceeds extent"); return span{mData+O, E-O}; @@ -184,10 +254,13 @@ public: * defining the specialization. As a result, these methods need to be * defined later. */ - constexpr span first(size_t count) const; - constexpr span last(size_t count) const; - constexpr span subspan(size_t offset, - size_t count=dynamic_extent) const; + [[nodiscard]] constexpr + auto first(std::size_t count) const noexcept -> span; + [[nodiscard]] constexpr + auto last(std::size_t count) const noexcept -> span; + [[nodiscard]] constexpr + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept + -> span; private: pointer mData{nullptr}; @@ -198,7 +271,7 @@ class span { public: using element_type = T; using value_type = std::remove_cv_t; - using index_type = size_t; + using size_type = std::size_t; using difference_type = ptrdiff_t; using pointer = T*; @@ -206,146 +279,175 @@ public: using reference = T&; using const_reference = const T&; - using iterator = pointer; - using const_iterator = const_pointer; + using iterator = ptr_wrapper; + using const_iterator = ptr_wrapper; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - static constexpr size_t extent{dynamic_extent}; + static constexpr std::size_t extent{dynamic_extent}; constexpr span() noexcept = default; template - constexpr span(U iter, index_type count) - : mData{to_address(iter)}, mDataEnd{to_address(iter)+count} + constexpr span(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count} { } - template::value)> - constexpr span(U first, V last) : span{to_address(first), static_cast(last-first)} + template::value)> + constexpr span(U first, V last) + : span{::al::to_address(first), static_cast(last-first)} { } - template - constexpr span(type_identity_t (&arr)[N]) noexcept - : span{al::data(arr), al::size(arr)} + template + constexpr span(type_identity_t (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */ + : mData{std::data(arr)}, mDataLength{std::size(arr)} { } - template - constexpr span(std::array &arr) noexcept : span{al::data(arr), al::size(arr)} { } - template::value)> + template + constexpr span(std::array &arr) noexcept + : mData{std::data(arr)}, mDataLength{std::size(arr)} + { } + template::value)> constexpr span(const std::array &arr) noexcept - : span{al::data(arr), al::size(arr)} + : mData{std::data(arr)}, mDataLength{std::size(arr)} { } template)> - constexpr span(U&& cont) : span{al::data(cont), al::size(cont)} { } + constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { } - template::value || extent != N) - && detail_::is_array_compatible)> - constexpr span(const span &span_) noexcept : span{al::data(span_), al::size(span_)} { } + template + && (!std::is_same::value || extent != N))> + constexpr span(const span &span_) noexcept : span{std::data(span_), std::size(span_)} { } constexpr span(const span&) noexcept = default; constexpr span& operator=(const span &rhs) noexcept = default; - constexpr reference front() const { return *mData; } - constexpr reference back() const { return *(mDataEnd-1); } - constexpr reference operator[](index_type idx) const { return mData[idx]; } - constexpr pointer data() const noexcept { return mData; } + [[nodiscard]] constexpr auto front() const -> reference { return mData[0]; } + [[nodiscard]] constexpr auto back() const -> reference { return mData[mDataLength-1]; } + [[nodiscard]] constexpr auto operator[](size_type idx) const -> reference {return mData[idx];} + [[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; } - constexpr index_type size() const noexcept { return static_cast(mDataEnd-mData); } - constexpr index_type size_bytes() const noexcept - { return static_cast(mDataEnd-mData) * sizeof(value_type); } - constexpr bool empty() const noexcept { return mData == mDataEnd; } + [[nodiscard]] constexpr auto size() const noexcept -> size_type { return mDataLength; } + [[nodiscard]] constexpr + auto size_bytes() const noexcept -> size_type { return mDataLength * sizeof(value_type); } + [[nodiscard]] constexpr auto empty() const noexcept -> bool { return mDataLength == 0; } - constexpr iterator begin() const noexcept { return mData; } - constexpr iterator end() const noexcept { return mDataEnd; } - constexpr const_iterator cbegin() const noexcept { return mData; } - constexpr const_iterator cend() const noexcept { return mDataEnd; } + [[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; } + [[nodiscard]] constexpr + auto end() const noexcept -> iterator { return iterator{mData+mDataLength}; } + [[nodiscard]] constexpr + auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; } + [[nodiscard]] constexpr + auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; } - constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } - constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } - constexpr const_reverse_iterator crbegin() const noexcept - { return const_reverse_iterator{cend()}; } - constexpr const_reverse_iterator crend() const noexcept - { return const_reverse_iterator{cbegin()}; } + [[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); } + [[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); } + [[nodiscard]] constexpr + auto crbegin() const noexcept -> const_reverse_iterator { return cend(); } + [[nodiscard]] constexpr + auto crend() const noexcept -> const_reverse_iterator { return cbegin(); } - template - constexpr span first() const - { return span{mData, C}; } - - constexpr span first(size_t count) const - { return (count >= size()) ? *this : span{mData, mData+count}; } - - template - constexpr span last() const - { return span{mDataEnd-C, C}; } - - constexpr span last(size_t count) const - { return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; } - - template - constexpr auto subspan() const -> std::enable_if_t> - { return span{mData+O, C}; } - - template - constexpr auto subspan() const -> std::enable_if_t> - { return span{mData+O, mDataEnd}; } - - constexpr span subspan(size_t offset, size_t count=dynamic_extent) const + template + [[nodiscard]] constexpr auto first() const noexcept -> span { - return (offset > size()) ? span{} : - (count >= size()-offset) ? span{mData+offset, mDataEnd} : - span{mData+offset, mData+offset+count}; + assert(C <= mDataLength); + return span{mData, C}; + } + + [[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span + { + assert(count <= mDataLength); + return span{mData, count}; + } + + template + [[nodiscard]] constexpr auto last() const noexcept -> span + { + assert(C <= mDataLength); + return span{mData+mDataLength-C, C}; + } + + [[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span + { + assert(count <= mDataLength); + return span{mData+mDataLength-count, count}; + } + + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> + { + assert(O <= mDataLength); + assert(C <= mDataLength-O); + return span{mData+O, C}; + } + + template + [[nodiscard]] constexpr + auto subspan() const noexcept -> std::enable_if_t> + { + assert(O <= mDataLength); + return span{mData+O, mDataLength-O}; + } + + [[nodiscard]] constexpr + auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span + { + assert(offset <= mDataLength); + if(count != dynamic_extent) + { + assert(count <= mDataLength-offset); + return span{mData+offset, count}; + } + return span{mData+offset, mDataLength-offset}; } private: pointer mData{nullptr}; - pointer mDataEnd{nullptr}; + size_type mDataLength{0}; }; -template -constexpr inline auto span::first(size_t count) const -> span +template +[[nodiscard]] constexpr +auto span::first(std::size_t count) const noexcept -> span { - return (count >= size()) ? span{mData, extent} : - span{mData, count}; + assert(count <= size()); + return span{mData, count}; } -template -constexpr inline auto span::last(size_t count) const -> span +template +[[nodiscard]] constexpr +auto span::last(std::size_t count) const noexcept -> span { - return (count >= size()) ? span{mData, extent} : - span{mData+extent-count, count}; + assert(count <= size()); + return span{mData+size()-count, count}; } -template -constexpr inline auto span::subspan(size_t offset, size_t count) const +template +[[nodiscard]] constexpr +auto span::subspan(std::size_t offset, std::size_t count) const noexcept -> span { - return (offset > size()) ? span{} : - (count >= size()-offset) ? span{mData+offset, mData+extent} : - span{mData+offset, mData+offset+count}; + assert(offset <= size()); + if(count != dynamic_extent) + { + assert(count <= size()-offset); + return span{mData+offset, count}; + } + return span{mData+offset, size()-offset}; } -/* Helpers to deal with the lack of user-defined deduction guides (C++17). */ -template -constexpr auto as_span(T ptr, U count_or_end) -{ - using value_type = typename std::pointer_traits::element_type; - return span{ptr, count_or_end}; -} -template -constexpr auto as_span(T (&arr)[N]) noexcept { return span{al::data(arr), al::size(arr)}; } -template -constexpr auto as_span(std::array &arr) noexcept -{ return span{al::data(arr), al::size(arr)}; } -template -constexpr auto as_span(const std::array &arr) noexcept -{ return span,N>{al::data(arr), al::size(arr)}; } -template && !detail_::is_std_array_v - && !std::is_array::value && detail_::has_size_and_data)> -constexpr auto as_span(U&& cont) -{ - using value_type = std::remove_pointer_t()))>; - return span{al::data(cont), al::size(cont)}; -} -template -constexpr auto as_span(span span_) noexcept { return span_; } + +template +span(T, EndOrSize) -> span())>>; + +template +span(T (&)[N]) -> span; /* NOLINT(*-avoid-c-arrays) */ + +template +span(std::array&) -> span; + +template +span(const std::array&) -> span; + +template)> +span(C&&) -> span()))>>; #undef REQUIRES diff --git a/Engine/lib/openal-soft/common/alstring.cpp b/Engine/lib/openal-soft/common/alstring.cpp index 4a84be1db..945dbc3ae 100644 --- a/Engine/lib/openal-soft/common/alstring.cpp +++ b/Engine/lib/openal-soft/common/alstring.cpp @@ -3,43 +3,62 @@ #include "alstring.h" +#include #include +#include +#include #include -namespace { - -int to_upper(const char ch) -{ - using char8_traits = std::char_traits; - return std::toupper(char8_traits::to_int_type(ch)); -} - -} // namespace - namespace al { -int strcasecmp(const char *str0, const char *str1) noexcept +int case_compare(const std::string_view str0, const std::string_view str1) noexcept { - do { - const int diff{to_upper(*str0) - to_upper(*str1)}; - if(diff < 0) return -1; - if(diff > 0) return 1; - } while(*(str0++) && *(str1++)); + using Traits = std::string_view::traits_type; + + auto ch0 = str0.cbegin(); + auto ch1 = str1.cbegin(); + auto ch1end = ch1 + std::min(str0.size(), str1.size()); + while(ch1 != ch1end) + { + const int u0{std::toupper(Traits::to_int_type(*ch0))}; + const int u1{std::toupper(Traits::to_int_type(*ch1))}; + if(const int diff{u0-u1}) return diff; + ++ch0; ++ch1; + } + + if(str0.size() < str1.size()) return -1; + if(str0.size() > str1.size()) return 1; return 0; } +int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept +{ + using Traits = std::wstring_view::traits_type; + + auto ch0 = str0.cbegin(); + auto ch1 = str1.cbegin(); + auto ch1end = ch1 + std::min(str0.size(), str1.size()); + while(ch1 != ch1end) + { + const auto u0 = std::towupper(Traits::to_int_type(*ch0)); + const auto u1 = std::towupper(Traits::to_int_type(*ch1)); + if(const auto diff = static_cast(u0-u1)) return diff; + ++ch0; ++ch1; + } + + if(str0.size() < str1.size()) return -1; + if(str0.size() > str1.size()) return 1; + return 0; +} + +int strcasecmp(const char *str0, const char *str1) noexcept +{ return case_compare(str0, str1); } + int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept { - if(len > 0) - { - do { - const int diff{to_upper(*str0) - to_upper(*str1)}; - if(diff < 0) return -1; - if(diff > 0) return 1; - } while(--len && *(str0++) && *(str1++)); - } - return 0; + return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)}, + std::string_view{str1, std::min(std::strlen(str1), len)}); } } // namespace al diff --git a/Engine/lib/openal-soft/common/alstring.h b/Engine/lib/openal-soft/common/alstring.h index 6c5004ee1..3182889d2 100644 --- a/Engine/lib/openal-soft/common/alstring.h +++ b/Engine/lib/openal-soft/common/alstring.h @@ -1,16 +1,41 @@ #ifndef AL_STRING_H #define AL_STRING_H +#include #include -#include +#include +#include +#include namespace al { -/* These would be better served by using a string_view-like span/view with - * case-insensitive char traits. - */ +template +[[nodiscard]] constexpr +auto sizei(const std::basic_string_view str) noexcept -> int +{ return static_cast(std::min(str.size(), std::numeric_limits::max())); } + +[[nodiscard]] +constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept +{ return str0.find(str1) != std::string_view::npos; } + +[[nodiscard]] +constexpr bool starts_with(const std::string_view str0, const std::string_view str1) noexcept +{ return str0.substr(0, std::min(str0.size(), str1.size())) == str1; } + +[[nodiscard]] +constexpr bool ends_with(const std::string_view str0, const std::string_view str1) noexcept +{ return str0.substr(str0.size() - std::min(str0.size(), str1.size())) == str1; } + +[[nodiscard]] +int case_compare(const std::string_view str0, const std::string_view str1) noexcept; + +[[nodiscard]] +int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept; + +[[nodiscard]] int strcasecmp(const char *str0, const char *str1) noexcept; +[[nodiscard]] int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept; } // namespace al diff --git a/Engine/lib/openal-soft/common/althrd_setname.cpp b/Engine/lib/openal-soft/common/althrd_setname.cpp new file mode 100644 index 000000000..b92b05d1c --- /dev/null +++ b/Engine/lib/openal-soft/common/althrd_setname.cpp @@ -0,0 +1,77 @@ + +#include "config.h" + +#include "althrd_setname.h" + + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +void althrd_setname(const char *name [[maybe_unused]]) +{ +#if defined(_MSC_VER) && !defined(_M_ARM) + +#define MS_VC_EXCEPTION 0x406D1388 +#pragma pack(push,8) + struct InfoStruct { + DWORD dwType; // Must be 0x1000. + LPCSTR szName; // Pointer to name (in user addr space). + DWORD dwThreadID; // Thread ID (-1=caller thread). + DWORD dwFlags; // Reserved for future use, must be zero. + }; +#pragma pack(pop) + InfoStruct info{}; + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = ~DWORD{0}; + info.dwFlags = 0; + + /* FIXME: How to do this on MinGW? */ + __try { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info); + } + __except(EXCEPTION_CONTINUE_EXECUTION) { + } +#undef MS_VC_EXCEPTION +#endif +} + +#else + +#include +#ifdef HAVE_PTHREAD_NP_H +#include +#endif + +namespace { + +using setname_t1 = int(*)(const char*); +using setname_t2 = int(*)(pthread_t, const char*); +using setname_t3 = void(*)(pthread_t, const char*); +using setname_t4 = int(*)(pthread_t, const char*, void*); + +[[maybe_unused]] void setname_caller(setname_t1 func, const char *name) +{ func(name); } + +[[maybe_unused]] void setname_caller(setname_t2 func, const char *name) +{ func(pthread_self(), name); } + +[[maybe_unused]] void setname_caller(setname_t3 func, const char *name) +{ func(pthread_self(), name); } + +[[maybe_unused]] void setname_caller(setname_t4 func, const char *name) +{ func(pthread_self(), "%s", const_cast(name)); /* NOLINT(*-const-cast) */ } + +} // namespace + +void althrd_setname(const char *name [[maybe_unused]]) +{ +#if defined(HAVE_PTHREAD_SET_NAME_NP) + setname_caller(pthread_set_name_np, name); +#elif defined(HAVE_PTHREAD_SETNAME_NP) + setname_caller(pthread_setname_np, name); +#endif +} + +#endif diff --git a/Engine/lib/openal-soft/common/althrd_setname.h b/Engine/lib/openal-soft/common/althrd_setname.h new file mode 100644 index 000000000..0e22c0a92 --- /dev/null +++ b/Engine/lib/openal-soft/common/althrd_setname.h @@ -0,0 +1,6 @@ +#ifndef COMMON_ALTHRD_SETNAME_H +#define COMMON_ALTHRD_SETNAME_H + +void althrd_setname(const char *name); + +#endif /* COMMON_ALTHRD_SETNAME_H */ diff --git a/Engine/lib/openal-soft/common/althreads.h b/Engine/lib/openal-soft/common/althreads.h new file mode 100644 index 000000000..d84917067 --- /dev/null +++ b/Engine/lib/openal-soft/common/althreads.h @@ -0,0 +1,143 @@ +#ifndef AL_THREADS_H +#define AL_THREADS_H + +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include + +#elif defined(__APPLE__) + +#include + +#else + +#include +#endif + +#include "albit.h" + +namespace al { + +template +class tss { + static_assert(sizeof(T) <= sizeof(void*)); + static_assert(std::is_trivially_destructible_v && std::is_trivially_copy_constructible_v); + + [[nodiscard]] + static auto to_ptr(const T &value) noexcept -> void* + { + if constexpr(std::is_pointer_v) + { + if constexpr(std::is_const_v>) + return const_cast(static_cast(value)); /* NOLINT(*-const-cast) */ + else + return static_cast(value); + } + else if constexpr(sizeof(T) == sizeof(void*)) + return al::bit_cast(value); + else if constexpr(std::is_integral_v) + return al::bit_cast(static_cast(value)); + } + + [[nodiscard]] + static auto from_ptr(void *ptr) noexcept -> T + { + if constexpr(std::is_pointer_v) + return static_cast(ptr); + else if constexpr(sizeof(T) == sizeof(void*)) + return al::bit_cast(ptr); + else if constexpr(std::is_integral_v) + return static_cast(al::bit_cast(ptr)); + } + +#ifdef _WIN32 + DWORD mTss{TLS_OUT_OF_INDEXES}; + +public: + tss() : mTss{TlsAlloc()} + { + if(mTss == TLS_OUT_OF_INDEXES) + throw std::runtime_error{"al::tss::tss()"}; + } + explicit tss(const T &init) : tss{} + { + if(TlsSetValue(mTss, to_ptr(init)) == FALSE) + throw std::runtime_error{"al::tss::tss(T)"}; + } + ~tss() { TlsFree(mTss); } + + void set(const T &value) const + { + if(TlsSetValue(mTss, to_ptr(value)) == FALSE) + throw std::runtime_error{"al::tss::set(T)"}; + } + + [[nodiscard]] + auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); } + +#elif defined(__APPLE__) + + pthread_key_t mTss{}; + +public: + tss() + { + if(int res{pthread_key_create(&mTss, nullptr)}; res != 0) + throw std::runtime_error{"al::tss::tss()"}; + } + explicit tss(const T &init) : tss{} + { + if(int res{pthread_setspecific(mTss, to_ptr(init))}; res != 0) + throw std::runtime_error{"al::tss::tss(T)"}; + } + ~tss() { pthread_key_delete(mTss); } + + void set(const T &value) const + { + if(int res{pthread_setspecific(mTss, to_ptr(value))}; res != 0) + throw std::runtime_error{"al::tss::set(T)"}; + } + + [[nodiscard]] + auto get() const noexcept -> T { return from_ptr(pthread_getspecific(mTss)); } + +#else + + tss_t mTss{}; + +public: + tss() + { + if(int res{tss_create(&mTss, nullptr)}; res != thrd_success) + throw std::runtime_error{"al::tss::tss()"}; + } + explicit tss(const T &init) : tss{} + { + if(int res{tss_set(mTss, to_ptr(init))}; res != thrd_success) + throw std::runtime_error{"al::tss::tss(T)"}; + } + ~tss() { tss_delete(mTss); } + + void set(const T &value) const + { + if(int res{tss_set(mTss, to_ptr(value))}; res != thrd_success) + throw std::runtime_error{"al::tss::set(T)"}; + } + + [[nodiscard]] + auto get() const noexcept -> T { return from_ptr(tss_get(mTss)); } +#endif /* _WIN32 */ + + tss(const tss&) = delete; + tss(tss&&) = delete; + void operator=(const tss&) = delete; + void operator=(tss&&) = delete; +}; + +} // namespace al + +#endif /* AL_THREADS_H */ diff --git a/Engine/lib/openal-soft/common/atomic.h b/Engine/lib/openal-soft/common/atomic.h index 5e9b04c69..7075b1589 100644 --- a/Engine/lib/openal-soft/common/atomic.h +++ b/Engine/lib/openal-soft/common/atomic.h @@ -2,17 +2,17 @@ #define AL_ATOMIC_H #include +#include +#include +#include "almalloc.h" -using RefCount = std::atomic; - -inline void InitRef(RefCount &ref, unsigned int value) -{ ref.store(value, std::memory_order_relaxed); } -inline unsigned int ReadRef(RefCount &ref) -{ return ref.load(std::memory_order_acquire); } -inline unsigned int IncrementRef(RefCount &ref) +template +auto IncrementRef(std::atomic &ref) noexcept { return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; } -inline unsigned int DecrementRef(RefCount &ref) + +template +auto DecrementRef(std::atomic &ref) noexcept { return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; } @@ -30,4 +30,75 @@ inline void AtomicReplaceHead(std::atomic &head, T newhead) std::memory_order_acq_rel, std::memory_order_acquire)); } +namespace al { + +template> +class atomic_unique_ptr { + std::atomic> mPointer{}; + + using unique_ptr_t = std::unique_ptr; + +public: + atomic_unique_ptr() = default; + atomic_unique_ptr(const atomic_unique_ptr&) = delete; + explicit atomic_unique_ptr(std::nullptr_t) noexcept { } + explicit atomic_unique_ptr(gsl::owner ptr) noexcept : mPointer{ptr} { } + explicit atomic_unique_ptr(unique_ptr_t&& rhs) noexcept : mPointer{rhs.release()} { } + ~atomic_unique_ptr() + { + if(auto ptr = mPointer.exchange(nullptr, std::memory_order_relaxed)) + D{}(ptr); + } + + auto operator=(const atomic_unique_ptr&) -> atomic_unique_ptr& = delete; + auto operator=(std::nullptr_t) noexcept -> atomic_unique_ptr& + { + if(auto ptr = mPointer.exchange(nullptr)) + D{}(ptr); + return *this; + } + auto operator=(unique_ptr_t&& rhs) noexcept -> atomic_unique_ptr& + { + if(auto ptr = mPointer.exchange(rhs.release())) + D{}(ptr); + return *this; + } + + [[nodiscard]] + auto load(std::memory_order m=std::memory_order_seq_cst) const noexcept -> T* + { return mPointer.load(m); } + void store(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept + { + if(auto oldptr = mPointer.exchange(nullptr, m)) + D{}(oldptr); + } + void store(gsl::owner ptr, std::memory_order m=std::memory_order_seq_cst) noexcept + { + if(auto oldptr = mPointer.exchange(ptr, m)) + D{}(oldptr); + } + void store(unique_ptr_t&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept + { + if(auto oldptr = mPointer.exchange(ptr.release(), m)) + D{}(oldptr); + } + + [[nodiscard]] + auto exchange(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t + { return unique_ptr_t{mPointer.exchange(nullptr, m)}; } + [[nodiscard]] + auto exchange(gsl::owner ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t + { return unique_ptr_t{mPointer.exchange(ptr, m)}; } + [[nodiscard]] + auto exchange(std::unique_ptr&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t + { return unique_ptr_t{mPointer.exchange(ptr.release(), m)}; } + + [[nodiscard]] + auto is_lock_free() const noexcept -> bool { return mPointer.is_lock_free(); } + + static constexpr auto is_always_lock_free = std::atomic>::is_always_lock_free; +}; + +} // namespace al + #endif /* AL_ATOMIC_H */ diff --git a/Engine/lib/openal-soft/common/comptr.h b/Engine/lib/openal-soft/common/comptr.h index cdc6dec02..d3294396c 100644 --- a/Engine/lib/openal-soft/common/comptr.h +++ b/Engine/lib/openal-soft/common/comptr.h @@ -2,49 +2,86 @@ #define COMMON_COMPTR_H #include +#include +#include #include +#include -#include "opthelpers.h" +#define WIN32_LEAN_AND_MEAN +#include +#include + +struct ComWrapper { + HRESULT mStatus{}; + + ComWrapper(void *reserved, DWORD coinit) + : mStatus{CoInitializeEx(reserved, coinit)} + { } + ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED) + : mStatus{CoInitializeEx(nullptr, coinit)} + { } + ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); } + ComWrapper(const ComWrapper&) = delete; + ~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); } + + ComWrapper& operator=(ComWrapper&& rhs) + { + if(SUCCEEDED(mStatus)) + CoUninitialize(); + mStatus = std::exchange(rhs.mStatus, E_FAIL); + return *this; + } + ComWrapper& operator=(const ComWrapper&) = delete; + + [[nodiscard]] + HRESULT status() const noexcept { return mStatus; } + explicit operator bool() const noexcept { return SUCCEEDED(status()); } + + void uninit() + { + if(SUCCEEDED(mStatus)) + CoUninitialize(); + mStatus = E_FAIL; + } +}; template -class ComPtr { - T *mPtr{nullptr}; +struct ComPtr { + using element_type = T; + + static constexpr bool RefIsNoexcept{noexcept(std::declval().AddRef()) + && noexcept(std::declval().Release())}; -public: ComPtr() noexcept = default; - ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); } + ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr} + { if(mPtr) mPtr->AddRef(); } ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; } ComPtr(std::nullptr_t) noexcept { } explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { } ~ComPtr() { if(mPtr) mPtr->Release(); } - ComPtr& operator=(const ComPtr &rhs) + /* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */ + ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept) { - if(!rhs.mPtr) + if constexpr(RefIsNoexcept) { - if(mPtr) - mPtr->Release(); - mPtr = nullptr; + if(rhs.mPtr) rhs.mPtr->AddRef(); + if(mPtr) mPtr->Release(); + mPtr = rhs.mPtr; + return *this; } else { - rhs.mPtr->AddRef(); - try { - if(mPtr) - mPtr->Release(); - mPtr = rhs.mPtr; - } - catch(...) { - rhs.mPtr->Release(); - throw; - } + ComPtr tmp{rhs}; + if(mPtr) mPtr->Release(); + mPtr = tmp.release(); + return *this; } - return *this; } - ComPtr& operator=(ComPtr&& rhs) + ComPtr& operator=(ComPtr&& rhs) noexcept(RefIsNoexcept) { - if(&rhs != this) LIKELY + if(&rhs != this) { if(mPtr) mPtr->Release(); mPtr = std::exchange(rhs.mPtr, nullptr); @@ -52,17 +89,25 @@ public: return *this; } + void reset(T *ptr=nullptr) noexcept(RefIsNoexcept) + { + if(mPtr) mPtr->Release(); + mPtr = ptr; + } + explicit operator bool() const noexcept { return mPtr != nullptr; } T& operator*() const noexcept { return *mPtr; } T* operator->() const noexcept { return mPtr; } T* get() const noexcept { return mPtr; } - T** getPtr() noexcept { return &mPtr; } T* release() noexcept { return std::exchange(mPtr, nullptr); } void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); } void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } + +private: + T *mPtr{nullptr}; }; #endif diff --git a/Engine/lib/openal-soft/common/dynload.cpp b/Engine/lib/openal-soft/common/dynload.cpp index f1c2a7ebb..333a9435e 100644 --- a/Engine/lib/openal-soft/common/dynload.cpp +++ b/Engine/lib/openal-soft/common/dynload.cpp @@ -3,12 +3,12 @@ #include "dynload.h" -#include "strutils.h" - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include +#include "strutils.h" + void *LoadLib(const char *name) { std::wstring wname{utf8_to_wstr(name)}; diff --git a/Engine/lib/openal-soft/common/flexarray.h b/Engine/lib/openal-soft/common/flexarray.h new file mode 100644 index 000000000..afd6eaeee --- /dev/null +++ b/Engine/lib/openal-soft/common/flexarray.h @@ -0,0 +1,139 @@ +#ifndef AL_FLEXARRAY_H +#define AL_FLEXARRAY_H + +#include +#include +#include +#include +#include +#include + +#include "almalloc.h" +#include "alspan.h" + +namespace al { + +/* Storage for flexible array data. This is trivially destructible if type T is + * trivially destructible. + */ +template::value> +struct alignas(alignment) FlexArrayStorage : al::span { + /* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the + * sizeof(T) being suspicious when T is a pointer type, which it will be + * for flexible arrays of pointers. + */ + static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept + { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } + /* NOLINTEND(bugprone-sizeof-expression) */ + + /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) Flexible + * arrays store their payloads after the end of the object, which must be + * the last in the whole parent chain. + */ + FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + : al::span{::new(static_cast(this+1)) T[size], size} + { } + /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + ~FlexArrayStorage() = default; + + FlexArrayStorage(const FlexArrayStorage&) = delete; + FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; +}; + +template +struct alignas(alignment) FlexArrayStorage : al::span { + static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept + { return sizeof(FlexArrayStorage) + sizeof(T)*count + base; } + + /* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v) + : al::span{::new(static_cast(this+1)) T[size], size} + { } + /* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ + ~FlexArrayStorage() { std::destroy(this->begin(), this->end()); } + + FlexArrayStorage(const FlexArrayStorage&) = delete; + FlexArrayStorage& operator=(const FlexArrayStorage&) = delete; +}; + +/* A flexible array type. Used either standalone or at the end of a parent + * struct, to have a run-time-sized array that's embedded with its size. Should + * be used delicately, ensuring there's no additional data after the FlexArray + * member. + */ +template +struct FlexArray { + using element_type = T; + using value_type = std::remove_cv_t; + using index_type = size_t; + using difference_type = ptrdiff_t; + + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + + static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)}; + using Storage_t_ = FlexArrayStorage), StorageAlign)>; + + using iterator = typename Storage_t_::iterator; + using const_iterator = typename Storage_t_::const_iterator; + using reverse_iterator = typename Storage_t_::reverse_iterator; + using const_reverse_iterator = typename Storage_t_::const_reverse_iterator; + + const Storage_t_ mStore; + + static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept + { return Storage_t_::Sizeof(count, base); } + static std::unique_ptr Create(index_type count) + { return std::unique_ptr{new(FamCount{count}) FlexArray{count}}; } + + FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v) + : mStore{size} + { } + ~FlexArray() = default; + + [[nodiscard]] auto size() const noexcept -> index_type { return mStore.size(); } + [[nodiscard]] auto empty() const noexcept -> bool { return mStore.empty(); } + + [[nodiscard]] auto data() noexcept -> pointer { return mStore.data(); } + [[nodiscard]] auto data() const noexcept -> const_pointer { return mStore.data(); } + + [[nodiscard]] auto operator[](index_type i) noexcept -> reference { return mStore[i]; } + [[nodiscard]] auto operator[](index_type i) const noexcept -> const_reference { return mStore[i]; } + + [[nodiscard]] auto front() noexcept -> reference { return mStore.front(); } + [[nodiscard]] auto front() const noexcept -> const_reference { return mStore.front(); } + + [[nodiscard]] auto back() noexcept -> reference { return mStore.back(); } + [[nodiscard]] auto back() const noexcept -> const_reference { return mStore.back(); } + + [[nodiscard]] auto begin() noexcept -> iterator { return mStore.begin(); } + [[nodiscard]] auto begin() const noexcept -> const_iterator { return mStore.cbegin(); } + [[nodiscard]] auto cbegin() const noexcept -> const_iterator { return mStore.cbegin(); } + [[nodiscard]] auto end() noexcept -> iterator { return mStore.end(); } + [[nodiscard]] auto end() const noexcept -> const_iterator { return mStore.cend(); } + [[nodiscard]] auto cend() const noexcept -> const_iterator { return mStore.cend(); } + + [[nodiscard]] auto rbegin() noexcept -> reverse_iterator { return mStore.rbegin(); } + [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); } + [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); } + [[nodiscard]] auto rend() noexcept -> reverse_iterator { return mStore.rend(); } + [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { return mStore.crend(); } + [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { return mStore.crend(); } + + gsl::owner operator new(size_t, FamCount count) + { return ::operator new[](Sizeof(count), std::align_val_t{alignof(FlexArray)}); } + void operator delete(gsl::owner block, FamCount) noexcept + { ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); } + void operator delete(gsl::owner block) noexcept + { ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); } + + void *operator new(size_t size) = delete; + void *operator new[](size_t size) = delete; + void operator delete[](void *block) = delete; +}; + +} // namespace al + +#endif /* AL_FLEXARRAY_H */ diff --git a/Engine/lib/openal-soft/common/intrusive_ptr.h b/Engine/lib/openal-soft/common/intrusive_ptr.h index 270753479..b1fa742f0 100644 --- a/Engine/lib/openal-soft/common/intrusive_ptr.h +++ b/Engine/lib/openal-soft/common/intrusive_ptr.h @@ -1,6 +1,8 @@ #ifndef INTRUSIVE_PTR_H #define INTRUSIVE_PTR_H +#include +#include #include #include "atomic.h" @@ -11,7 +13,7 @@ namespace al { template class intrusive_ref { - RefCount mRef{1u}; + std::atomic mRef{1u}; public: unsigned int add_ref() noexcept { return IncrementRef(mRef); } @@ -60,6 +62,9 @@ public: explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { } ~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); } + /* NOLINTBEGIN(bugprone-unhandled-self-assignment) + * Self-assignment is handled properly here. + */ intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept { static_assert(noexcept(std::declval()->dec_ref()), "dec_ref must be noexcept"); @@ -69,6 +74,7 @@ public: mPtr = rhs.mPtr; return *this; } + /* NOLINTEND(bugprone-unhandled-self-assignment) */ intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept { if(&rhs != this) LIKELY @@ -81,9 +87,9 @@ public: explicit operator bool() const noexcept { return mPtr != nullptr; } - T& operator*() const noexcept { return *mPtr; } - T* operator->() const noexcept { return mPtr; } - T* get() const noexcept { return mPtr; } + [[nodiscard]] auto operator*() const noexcept -> T& { return *mPtr; } + [[nodiscard]] auto operator->() const noexcept -> T* { return mPtr; } + [[nodiscard]] auto get() const noexcept -> T* { return mPtr; } void reset(T *ptr=nullptr) noexcept { @@ -98,23 +104,6 @@ public: void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); } }; -#define AL_DECL_OP(op) \ -template \ -inline bool operator op(const intrusive_ptr &lhs, const T *rhs) noexcept \ -{ return lhs.get() op rhs; } \ -template \ -inline bool operator op(const T *lhs, const intrusive_ptr &rhs) noexcept \ -{ return lhs op rhs.get(); } - -AL_DECL_OP(==) -AL_DECL_OP(!=) -AL_DECL_OP(<=) -AL_DECL_OP(>=) -AL_DECL_OP(<) -AL_DECL_OP(>) - -#undef AL_DECL_OP - } // namespace al #endif /* INTRUSIVE_PTR_H */ diff --git a/Engine/lib/openal-soft/common/opthelpers.h b/Engine/lib/openal-soft/common/opthelpers.h index 596c24554..ae2611dad 100644 --- a/Engine/lib/openal-soft/common/opthelpers.h +++ b/Engine/lib/openal-soft/common/opthelpers.h @@ -19,10 +19,13 @@ #ifdef __GNUC__ #define force_inline [[gnu::always_inline]] inline +#define NOINLINE [[gnu::noinline]] #elif defined(_MSC_VER) #define force_inline __forceinline +#define NOINLINE __declspec(noinline) #else #define force_inline inline +#define NOINLINE #endif /* Unlike the likely attribute, ASSUME requires the condition to be true or @@ -39,7 +42,7 @@ #elif HAS_BUILTIN(__builtin_unreachable) #define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0) #else -#define ASSUME(x) ((void)0) +#define ASSUME(x) (static_cast(0)) #endif /* This shouldn't be needed since unknown attributes are ignored, but older diff --git a/Engine/lib/openal-soft/common/pffft.cpp b/Engine/lib/openal-soft/common/pffft.cpp new file mode 100644 index 000000000..6cddcf09a --- /dev/null +++ b/Engine/lib/openal-soft/common/pffft.cpp @@ -0,0 +1,2308 @@ +//$ nobt + +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + * Copyright (c) 2023 Christopher Robinson + * + * Based on original fortran 77 code from FFTPACKv4 from NETLIB + * (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber + * of NCAR, in 1985. + * + * As confirmed by the NCAR fftpack software curators, the following + * FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + * released under the same terms. + * + * FFTPACK license: + * + * http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + * + * Copyright (c) 2004 the University Corporation for Atmospheric + * Research ("UCAR"). All rights reserved. Developed by NCAR's + * Computational and Information Systems Laboratory, UCAR, + * www.cisl.ucar.edu. + * + * Redistribution and use of the Software in source and binary forms, + * with or without modification, is permitted provided that the + * following conditions are met: + * + * - Neither the names of NCAR's Computational and Information Systems + * Laboratory, the University Corporation for Atmospheric Research, + * nor the names of its sponsors or contributors may be used to + * endorse or promote products derived from this Software without + * specific prior written permission. + * + * - Redistributions of source code must retain the above copyright + * notices, this list of conditions, and the disclaimer below. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions, and the disclaimer below in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + * SOFTWARE. + * + * + * PFFFT : a Pretty Fast FFT. + * + * This file is largerly based on the original FFTPACK implementation, modified + * in order to take advantage of SIMD instructions of modern CPUs. + */ + +#include "pffft.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "albit.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" +#include "opthelpers.h" + + +using uint = unsigned int; + +namespace { + +#if defined(__GNUC__) || defined(_MSC_VER) +#define RESTRICT __restrict +#else +#define RESTRICT +#endif + +/* Vector support macros: the rest of the code is independent of + * SSE/Altivec/NEON -- adding support for other platforms with 4-element + * vectors should be limited to these macros + */ + +/* Define PFFFT_SIMD_DISABLE if you want to use scalar code instead of SIMD code */ +//#define PFFFT_SIMD_DISABLE + +#ifndef PFFFT_SIMD_DISABLE +/* + * Altivec support macros + */ +#if defined(__ppc__) || defined(__ppc64__) || defined(__powerpc__) || defined(__powerpc64__) +#include +using v4sf = vector float; +constexpr uint SimdSize{4}; +force_inline v4sf vzero() noexcept { return (vector float)vec_splat_u8(0); } +force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return vec_madd(a, b, vzero()); } +force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return vec_add(a, b); } +force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return vec_madd(a, b, c); } +force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return vec_sub(a, b); } +force_inline v4sf ld_ps1(float a) noexcept { return vec_splats(a); } + +force_inline v4sf vset4(float a, float b, float c, float d) noexcept +{ + /* There a more efficient way to do this? */ + alignas(16) std::array vals{{a, b, c, d}}; + return vec_ld(0, vals.data()); +} +force_inline v4sf vinsert0(v4sf v, float a) noexcept { return vec_insert(a, v, 0); } +force_inline float vextract0(v4sf v) noexcept { return vec_extract(v, 0); } + +force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = vec_mergeh(in1, in2); + out2 = vec_mergel(in1, in2); +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = vec_perm(in1, in2, (vector unsigned char){0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27}); + out2 = vec_perm(in1, in2, (vector unsigned char){4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31}); + out1 = tmp; +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ + v4sf y0{vec_mergeh(x0, x2)}; + v4sf y1{vec_mergel(x0, x2)}; + v4sf y2{vec_mergeh(x1, x3)}; + v4sf y3{vec_mergel(x1, x3)}; + x0 = vec_mergeh(y0, y2); + x1 = vec_mergel(y0, y2); + x2 = vec_mergeh(y1, y3); + x3 = vec_mergel(y1, y3); +} + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return vec_perm(a,b, (vector unsigned char){16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15}); } + +/* + * SSE1 support macros + */ +#elif defined(__x86_64__) || defined(__SSE__) || defined(_M_X64) || \ + (defined(_M_IX86_FP) && _M_IX86_FP >= 1) + +#include +using v4sf = __m128; +/* 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/ + * finalize functions anyway so you will have to work if you want to enable AVX + * with its 256-bit vectors. + */ +constexpr uint SimdSize{4}; +force_inline v4sf vzero() noexcept { return _mm_setzero_ps(); } +force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return _mm_mul_ps(a, b); } +force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return _mm_add_ps(a, b); } +force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return _mm_add_ps(_mm_mul_ps(a,b), c); } +force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return _mm_sub_ps(a, b); } +force_inline v4sf ld_ps1(float a) noexcept { return _mm_set1_ps(a); } + +force_inline v4sf vset4(float a, float b, float c, float d) noexcept +{ return _mm_setr_ps(a, b, c, d); } +force_inline v4sf vinsert0(const v4sf v, const float a) noexcept +{ return _mm_move_ss(v, _mm_set_ss(a)); } +force_inline float vextract0(v4sf v) noexcept +{ return _mm_cvtss_f32(v); } + +force_inline void interleave2(const v4sf in1, const v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = _mm_unpacklo_ps(in1, in2); + out2 = _mm_unpackhi_ps(in1, in2); +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); + out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ _MM_TRANSPOSE4_PS(x0, x1, x2, x3); } + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0)); } + +/* + * ARM NEON support macros + */ +#elif defined(__ARM_NEON) || defined(__aarch64__) || defined(__arm64) || defined(_M_ARM64) + +#include +using v4sf = float32x4_t; +constexpr uint SimdSize{4}; +force_inline v4sf vzero() noexcept { return vdupq_n_f32(0.0f); } +force_inline v4sf vmul(v4sf a, v4sf b) noexcept { return vmulq_f32(a, b); } +force_inline v4sf vadd(v4sf a, v4sf b) noexcept { return vaddq_f32(a, b); } +force_inline v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return vmlaq_f32(c, a, b); } +force_inline v4sf vsub(v4sf a, v4sf b) noexcept { return vsubq_f32(a, b); } +force_inline v4sf ld_ps1(float a) noexcept { return vdupq_n_f32(a); } + +force_inline v4sf vset4(float a, float b, float c, float d) noexcept +{ + float32x4_t ret{vmovq_n_f32(a)}; + ret = vsetq_lane_f32(b, ret, 1); + ret = vsetq_lane_f32(c, ret, 2); + ret = vsetq_lane_f32(d, ret, 3); + return ret; +} +force_inline v4sf vinsert0(v4sf v, float a) noexcept +{ return vsetq_lane_f32(a, v, 0); } +force_inline float vextract0(v4sf v) noexcept +{ return vgetq_lane_f32(v, 0); } + +force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + float32x4x2_t tmp{vzipq_f32(in1, in2)}; + out1 = tmp.val[0]; + out2 = tmp.val[1]; +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + float32x4x2_t tmp{vuzpq_f32(in1, in2)}; + out1 = tmp.val[0]; + out2 = tmp.val[1]; +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ + /* marginally faster version: + * asm("vtrn.32 %q0, %q1;\n" + * "vtrn.32 %q2, %q3\n + * "vswp %f0, %e2\n + * "vswp %f1, %e3" + * : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); + */ + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; +} + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return vcombine_f32(vget_low_f32(b), vget_high_f32(a)); } + +/* + * Generic GCC vector macros + */ +#elif defined(__GNUC__) + +using v4sf [[gnu::vector_size(16), gnu::aligned(16)]] = float; +constexpr uint SimdSize{4}; +force_inline constexpr v4sf vzero() noexcept { return v4sf{0.0f, 0.0f, 0.0f, 0.0f}; } +force_inline constexpr v4sf vmul(v4sf a, v4sf b) noexcept { return a * b; } +force_inline constexpr v4sf vadd(v4sf a, v4sf b) noexcept { return a + b; } +force_inline constexpr v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return a*b + c; } +force_inline constexpr v4sf vsub(v4sf a, v4sf b) noexcept { return a - b; } +force_inline constexpr v4sf ld_ps1(float a) noexcept { return v4sf{a, a, a, a}; } + +force_inline constexpr v4sf vset4(float a, float b, float c, float d) noexcept +{ return v4sf{a, b, c, d}; } +force_inline constexpr v4sf vinsert0(v4sf v, float a) noexcept +{ return v4sf{a, v[1], v[2], v[3]}; } +force_inline float vextract0(v4sf v) noexcept +{ return v[0]; } + +force_inline v4sf unpacklo(v4sf a, v4sf b) noexcept +{ return v4sf{a[0], b[0], a[1], b[1]}; } +force_inline v4sf unpackhi(v4sf a, v4sf b) noexcept +{ return v4sf{a[2], b[2], a[3], b[3]}; } + +force_inline void interleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = unpacklo(in1, in2); + out2 = unpackhi(in1, in2); +} +force_inline void uninterleave2(v4sf in1, v4sf in2, v4sf &out1, v4sf &out2) noexcept +{ + out1 = v4sf{in1[0], in1[2], in2[0], in2[2]}; + out2 = v4sf{in1[1], in1[3], in2[1], in2[3]}; +} + +force_inline void vtranspose4(v4sf &x0, v4sf &x1, v4sf &x2, v4sf &x3) noexcept +{ + v4sf tmp0{unpacklo(x0, x1)}; + v4sf tmp2{unpacklo(x2, x3)}; + v4sf tmp1{unpackhi(x0, x1)}; + v4sf tmp3{unpackhi(x2, x3)}; + x0 = v4sf{tmp0[0], tmp0[1], tmp2[0], tmp2[1]}; + x1 = v4sf{tmp0[2], tmp0[3], tmp2[2], tmp2[3]}; + x2 = v4sf{tmp1[0], tmp1[1], tmp3[0], tmp3[1]}; + x3 = v4sf{tmp1[2], tmp1[3], tmp3[2], tmp3[3]}; +} + +force_inline v4sf vswaphl(v4sf a, v4sf b) noexcept +{ return v4sf{b[0], b[1], a[2], a[3]}; } + +#else + +#warning "building with simd disabled !\n"; +#define PFFFT_SIMD_DISABLE // fallback to scalar code +#endif + +#endif /* PFFFT_SIMD_DISABLE */ + +// fallback mode for situations where SIMD is not available, use scalar mode instead +#ifdef PFFFT_SIMD_DISABLE +using v4sf = float; +constexpr uint SimdSize{1}; +force_inline constexpr v4sf vmul(v4sf a, v4sf b) noexcept { return a * b; } +force_inline constexpr v4sf vadd(v4sf a, v4sf b) noexcept { return a + b; } +force_inline constexpr v4sf vmadd(v4sf a, v4sf b, v4sf c) noexcept { return a*b + c; } +force_inline constexpr v4sf vsub(v4sf a, v4sf b) noexcept { return a - b; } +force_inline constexpr v4sf ld_ps1(float a) noexcept { return a; } + +#else + +[[maybe_unused]] inline +auto valigned(const float *ptr) noexcept -> bool +{ + static constexpr uintptr_t alignmask{SimdSize*sizeof(float) - 1}; + return (reinterpret_cast(ptr) & alignmask) == 0; +} +#endif + +// shortcuts for complex multiplications +force_inline void vcplxmul(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept +{ + v4sf tmp{vmul(ar, bi)}; + ar = vsub(vmul(ar, br), vmul(ai, bi)); + ai = vmadd(ai, br, tmp); +} +force_inline void vcplxmulconj(v4sf &ar, v4sf &ai, v4sf br, v4sf bi) noexcept +{ + v4sf tmp{vmul(ar, bi)}; + ar = vmadd(ai, bi, vmul(ar, br)); + ai = vsub(vmul(ai, br), tmp); +} + +#if !defined(PFFFT_SIMD_DISABLE) + +inline void assertv4(const al::span v_f [[maybe_unused]], + const float f0 [[maybe_unused]], const float f1 [[maybe_unused]], + const float f2 [[maybe_unused]], const float f3 [[maybe_unused]]) +{ assert(v_f[0] == f0 && v_f[1] == f1 && v_f[2] == f2 && v_f[3] == f3); } + +template +constexpr auto make_float_array(std::integer_sequence) +{ return std::array{static_cast(N)...}; } + +/* detect bugs with the vector support macros */ +[[maybe_unused]] void validate_pffft_simd() +{ + using float4 = std::array; + static constexpr auto f = make_float_array(std::make_index_sequence<16>{}); + + v4sf a0_v{vset4(f[ 0], f[ 1], f[ 2], f[ 3])}; + v4sf a1_v{vset4(f[ 4], f[ 5], f[ 6], f[ 7])}; + v4sf a2_v{vset4(f[ 8], f[ 9], f[10], f[11])}; + v4sf a3_v{vset4(f[12], f[13], f[14], f[15])}; + v4sf u_v{}; + + auto t_v = vzero(); + auto t_f = al::bit_cast(t_v); + printf("VZERO=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 0, 0, 0, 0); + + t_v = vadd(a1_v, a2_v); + t_f = al::bit_cast(t_v); + printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 12, 14, 16, 18); + + t_v = vmul(a1_v, a2_v); + t_f = al::bit_cast(t_v); + printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 32, 45, 60, 77); + + t_v = vmadd(a1_v, a2_v, a0_v); + t_f = al::bit_cast(t_v); + printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 32, 46, 62, 80); + + interleave2(a1_v, a2_v, t_v, u_v); + t_f = al::bit_cast(t_v); + auto u_f = al::bit_cast(u_v); + printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + assertv4(t_f, 4, 8, 5, 9); + assertv4(u_f, 6, 10, 7, 11); + + uninterleave2(a1_v, a2_v, t_v, u_v); + t_f = al::bit_cast(t_v); + u_f = al::bit_cast(u_v); + printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + t_f[0], t_f[1], t_f[2], t_f[3], u_f[0], u_f[1], u_f[2], u_f[3]); + assertv4(t_f, 4, 6, 8, 10); + assertv4(u_f, 5, 7, 9, 11); + + t_v = ld_ps1(f[15]); + t_f = al::bit_cast(t_v); + printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 15, 15, 15, 15); + + t_v = vswaphl(a1_v, a2_v); + t_f = al::bit_cast(t_v); + printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t_f[0], t_f[1], t_f[2], t_f[3]); + assertv4(t_f, 8, 9, 6, 7); + + vtranspose4(a0_v, a1_v, a2_v, a3_v); + auto a0_f = al::bit_cast(a0_v); + auto a1_f = al::bit_cast(a1_v); + auto a2_f = al::bit_cast(a2_v); + auto a3_f = al::bit_cast(a3_v); + printf("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n", + a0_f[0], a0_f[1], a0_f[2], a0_f[3], a1_f[0], a1_f[1], a1_f[2], a1_f[3], + a2_f[0], a2_f[1], a2_f[2], a2_f[3], a3_f[0], a3_f[1], a3_f[2], a3_f[3]); + assertv4(a0_f, 0, 4, 8, 12); + assertv4(a1_f, 1, 5, 9, 13); + assertv4(a2_f, 2, 6, 10, 14); + assertv4(a3_f, 3, 7, 11, 15); +} +#endif //!PFFFT_SIMD_DISABLE + +/* SSE and co like 16-bytes aligned pointers */ +/* with a 64-byte alignment, we are even aligned on L2 cache lines... */ +constexpr auto V4sfAlignment = size_t(64); +constexpr auto V4sfAlignVal = std::align_val_t(V4sfAlignment); + +/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + * FIXME: Converting this from raw pointers to spans or something will probably + * need significant work to maintain performance, given non-sequential range- + * checked accesses and lack of 'restrict' to indicate non-aliased memory. At + * least, some tests should be done to check the impact of using range-checked + * spans here before blindly switching. + */ +/* + passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2 +*/ +NOINLINE void passf2_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + const size_t l1ido{l1*ido}; + if(ido <= 2) + { + for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 2*ido) + { + ch[0] = vadd(cc[0], cc[ido+0]); + ch[l1ido] = vsub(cc[0], cc[ido+0]); + ch[1] = vadd(cc[1], cc[ido+1]); + ch[l1ido + 1] = vsub(cc[1], cc[ido+1]); + } + } + else + { + for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 2*ido) + { + for(size_t i{0};i < ido-1;i += 2) + { + v4sf tr2{vsub(cc[i+0], cc[i+ido+0])}; + v4sf ti2{vsub(cc[i+1], cc[i+ido+1])}; + v4sf wr{ld_ps1(wa1[i])}, wi{ld_ps1(wa1[i+1]*fsign)}; + ch[i] = vadd(cc[i+0], cc[i+ido+0]); + ch[i+1] = vadd(cc[i+1], cc[i+ido+1]); + vcplxmul(tr2, ti2, wr, wi); + ch[i+l1ido] = tr2; + ch[i+l1ido+1] = ti2; + } + } + } +} + +/* + passf3 and passb3 has been merged here, fsign = -1 for passf3, +1 for passb3 +*/ +NOINLINE void passf3_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + assert(ido > 2); + + const v4sf taur{ld_ps1(-0.5f)}; + const v4sf taui{ld_ps1(0.866025403784439f*fsign)}; + const size_t l1ido{l1*ido}; + const auto wa2 = wa1 + ido; + for(size_t k{0};k < l1ido;k += ido, cc += 3*ido, ch +=ido) + { + for(size_t i{0};i < ido-1;i += 2) + { + v4sf tr2{vadd(cc[i+ido], cc[i+2*ido])}; + v4sf cr2{vmadd(taur, tr2, cc[i])}; + ch[i] = vadd(tr2, cc[i]); + v4sf ti2{vadd(cc[i+ido+1], cc[i+2*ido+1])}; + v4sf ci2{vmadd(taur, ti2, cc[i+1])}; + ch[i+1] = vadd(cc[i+1], ti2); + v4sf cr3{vmul(taui, vsub(cc[i+ido], cc[i+2*ido]))}; + v4sf ci3{vmul(taui, vsub(cc[i+ido+1], cc[i+2*ido+1]))}; + v4sf dr2{vsub(cr2, ci3)}; + v4sf dr3{vadd(cr2, ci3)}; + v4sf di2{vadd(ci2, cr3)}; + v4sf di3{vsub(ci2, cr3)}; + float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}, wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; + vcplxmul(dr2, di2, ld_ps1(wr1), ld_ps1(wi1)); + ch[i+l1ido] = dr2; + ch[i+l1ido + 1] = di2; + vcplxmul(dr3, di3, ld_ps1(wr2), ld_ps1(wi2)); + ch[i+2*l1ido] = dr3; + ch[i+2*l1ido+1] = di3; + } + } +} /* passf3 */ + +NOINLINE void passf4_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + /* fsign == -1 for forward transform and +1 for backward transform */ + const v4sf vsign{ld_ps1(fsign)}; + const size_t l1ido{l1*ido}; + if(ido == 2) + { + for(size_t k{0};k < l1ido;k += ido, ch += ido, cc += 4*ido) + { + v4sf tr1{vsub(cc[0], cc[2*ido + 0])}; + v4sf tr2{vadd(cc[0], cc[2*ido + 0])}; + v4sf ti1{vsub(cc[1], cc[2*ido + 1])}; + v4sf ti2{vadd(cc[1], cc[2*ido + 1])}; + v4sf ti4{vmul(vsub(cc[1*ido + 0], cc[3*ido + 0]), vsign)}; + v4sf tr4{vmul(vsub(cc[3*ido + 1], cc[1*ido + 1]), vsign)}; + v4sf tr3{vadd(cc[ido + 0], cc[3*ido + 0])}; + v4sf ti3{vadd(cc[ido + 1], cc[3*ido + 1])}; + + ch[0*l1ido + 0] = vadd(tr2, tr3); + ch[0*l1ido + 1] = vadd(ti2, ti3); + ch[1*l1ido + 0] = vadd(tr1, tr4); + ch[1*l1ido + 1] = vadd(ti1, ti4); + ch[2*l1ido + 0] = vsub(tr2, tr3); + ch[2*l1ido + 1] = vsub(ti2, ti3); + ch[3*l1ido + 0] = vsub(tr1, tr4); + ch[3*l1ido + 1] = vsub(ti1, ti4); + } + } + else + { + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + for(size_t k{0};k < l1ido;k += ido, ch+=ido, cc += 4*ido) + { + for(size_t i{0};i < ido-1;i+=2) + { + v4sf tr1{vsub(cc[i + 0], cc[i + 2*ido + 0])}; + v4sf tr2{vadd(cc[i + 0], cc[i + 2*ido + 0])}; + v4sf ti1{vsub(cc[i + 1], cc[i + 2*ido + 1])}; + v4sf ti2{vadd(cc[i + 1], cc[i + 2*ido + 1])}; + v4sf tr4{vmul(vsub(cc[i + 3*ido + 1], cc[i + 1*ido + 1]), vsign)}; + v4sf ti4{vmul(vsub(cc[i + 1*ido + 0], cc[i + 3*ido + 0]), vsign)}; + v4sf tr3{vadd(cc[i + ido + 0], cc[i + 3*ido + 0])}; + v4sf ti3{vadd(cc[i + ido + 1], cc[i + 3*ido + 1])}; + + ch[i] = vadd(tr2, tr3); + v4sf cr3{vsub(tr2, tr3)}; + ch[i + 1] = vadd(ti2, ti3); + v4sf ci3{vsub(ti2, ti3)}; + + v4sf cr2{vadd(tr1, tr4)}; + v4sf cr4{vsub(tr1, tr4)}; + v4sf ci2{vadd(ti1, ti4)}; + v4sf ci4{vsub(ti1, ti4)}; + float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}; + vcplxmul(cr2, ci2, ld_ps1(wr1), ld_ps1(wi1)); + float wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; + ch[i + l1ido] = cr2; + ch[i + l1ido + 1] = ci2; + + vcplxmul(cr3, ci3, ld_ps1(wr2), ld_ps1(wi2)); + float wr3{wa3[i]}, wi3{fsign*wa3[i+1]}; + ch[i + 2*l1ido] = cr3; + ch[i + 2*l1ido + 1] = ci3; + + vcplxmul(cr4, ci4, ld_ps1(wr3), ld_ps1(wi3)); + ch[i + 3*l1ido] = cr4; + ch[i + 3*l1ido + 1] = ci4; + } + } + } +} /* passf4 */ + +/* + * passf5 and passb5 has been merged here, fsign = -1 for passf5, +1 for passb5 + */ +NOINLINE void passf5_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1, const float fsign) +{ + const v4sf tr11{ld_ps1(0.309016994374947f)}; + const v4sf tr12{ld_ps1(-0.809016994374947f)}; + const v4sf ti11{ld_ps1(0.951056516295154f*fsign)}; + const v4sf ti12{ld_ps1(0.587785252292473f*fsign)}; + + auto cc_ref = [&cc,ido](size_t a_1, size_t a_2) noexcept -> auto& + { return cc[(a_2-1)*ido + a_1 + 1]; }; + auto ch_ref = [&ch,ido,l1](size_t a_1, size_t a_3) noexcept -> auto& + { return ch[(a_3-1)*l1*ido + a_1 + 1]; }; + + assert(ido > 2); + + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + const auto wa4 = wa3 + ido; + for(size_t k{0};k < l1;++k, cc += 5*ido, ch += ido) + { + for(size_t i{0};i < ido-1;i += 2) + { + v4sf ti5{vsub(cc_ref(i , 2), cc_ref(i , 5))}; + v4sf ti2{vadd(cc_ref(i , 2), cc_ref(i , 5))}; + v4sf ti4{vsub(cc_ref(i , 3), cc_ref(i , 4))}; + v4sf ti3{vadd(cc_ref(i , 3), cc_ref(i , 4))}; + v4sf tr5{vsub(cc_ref(i-1, 2), cc_ref(i-1, 5))}; + v4sf tr2{vadd(cc_ref(i-1, 2), cc_ref(i-1, 5))}; + v4sf tr4{vsub(cc_ref(i-1, 3), cc_ref(i-1, 4))}; + v4sf tr3{vadd(cc_ref(i-1, 3), cc_ref(i-1, 4))}; + ch_ref(i-1, 1) = vadd(cc_ref(i-1, 1), vadd(tr2, tr3)); + ch_ref(i , 1) = vadd(cc_ref(i , 1), vadd(ti2, ti3)); + v4sf cr2{vadd(cc_ref(i-1, 1), vmadd(tr11, tr2, vmul(tr12, tr3)))}; + v4sf ci2{vadd(cc_ref(i , 1), vmadd(tr11, ti2, vmul(tr12, ti3)))}; + v4sf cr3{vadd(cc_ref(i-1, 1), vmadd(tr12, tr2, vmul(tr11, tr3)))}; + v4sf ci3{vadd(cc_ref(i , 1), vmadd(tr12, ti2, vmul(tr11, ti3)))}; + v4sf cr5{vmadd(ti11, tr5, vmul(ti12, tr4))}; + v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; + v4sf cr4{vsub(vmul(ti12, tr5), vmul(ti11, tr4))}; + v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; + v4sf dr3{vsub(cr3, ci4)}; + v4sf dr4{vadd(cr3, ci4)}; + v4sf di3{vadd(ci3, cr4)}; + v4sf di4{vsub(ci3, cr4)}; + v4sf dr5{vadd(cr2, ci5)}; + v4sf dr2{vsub(cr2, ci5)}; + v4sf di5{vsub(ci2, cr5)}; + v4sf di2{vadd(ci2, cr5)}; + float wr1{wa1[i]}, wi1{fsign*wa1[i+1]}, wr2{wa2[i]}, wi2{fsign*wa2[i+1]}; + float wr3{wa3[i]}, wi3{fsign*wa3[i+1]}, wr4{wa4[i]}, wi4{fsign*wa4[i+1]}; + vcplxmul(dr2, di2, ld_ps1(wr1), ld_ps1(wi1)); + ch_ref(i - 1, 2) = dr2; + ch_ref(i, 2) = di2; + vcplxmul(dr3, di3, ld_ps1(wr2), ld_ps1(wi2)); + ch_ref(i - 1, 3) = dr3; + ch_ref(i, 3) = di3; + vcplxmul(dr4, di4, ld_ps1(wr3), ld_ps1(wi3)); + ch_ref(i - 1, 4) = dr4; + ch_ref(i, 4) = di4; + vcplxmul(dr5, di5, ld_ps1(wr4), ld_ps1(wi4)); + ch_ref(i - 1, 5) = dr5; + ch_ref(i, 5) = di5; + } + } +} + +NOINLINE void radf2_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, + v4sf *RESTRICT ch, const float *const wa1) +{ + const size_t l1ido{l1*ido}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[k]}, b{cc[k + l1ido]}; + ch[2*k] = vadd(a, b); + ch[2*(k+ido)-1] = vsub(a, b); + } + if(ido < 2) + return; + if(ido != 2) + { + for(size_t k{0};k < l1ido;k += ido) + { + for(size_t i{2};i < ido;i += 2) + { + v4sf tr2{cc[i - 1 + k + l1ido]}, ti2{cc[i + k + l1ido]}; + v4sf br{cc[i - 1 + k]}, bi{cc[i + k]}; + vcplxmulconj(tr2, ti2, ld_ps1(wa1[i - 2]), ld_ps1(wa1[i - 1])); + ch[i + 2*k] = vadd(bi, ti2); + ch[2*(k+ido) - i] = vsub(ti2, bi); + ch[i - 1 + 2*k] = vadd(br, tr2); + ch[2*(k+ido) - i -1] = vsub(br, tr2); + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_one{ld_ps1(-1.0f)}; + for(size_t k{0};k < l1ido;k += ido) + { + ch[2*k + ido] = vmul(minus_one, cc[ido-1 + k + l1ido]); + ch[2*k + ido-1] = cc[k + ido-1]; + } +} /* radf2 */ + + +NOINLINE void radb2_ps(const size_t ido, const size_t l1, const v4sf *cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const size_t l1ido{l1*ido}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[2*k]}; + v4sf b{cc[2*(k+ido) - 1]}; + ch[k] = vadd(a, b); + ch[k + l1ido] = vsub(a, b); + } + if(ido < 2) + return; + if(ido != 2) + { + for(size_t k{0};k < l1ido;k += ido) + { + for(size_t i{2};i < ido;i += 2) + { + v4sf a{cc[i-1 + 2*k]}; + v4sf b{cc[2*(k + ido) - i - 1]}; + v4sf c{cc[i+0 + 2*k]}; + v4sf d{cc[2*(k + ido) - i + 0]}; + ch[i-1 + k] = vadd(a, b); + v4sf tr2{vsub(a, b)}; + ch[i+0 + k] = vsub(c, d); + v4sf ti2{vadd(c, d)}; + vcplxmul(tr2, ti2, ld_ps1(wa1[i - 2]), ld_ps1(wa1[i - 1])); + ch[i-1 + k + l1ido] = tr2; + ch[i+0 + k + l1ido] = ti2; + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_two{ld_ps1(-2.0f)}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[2*k + ido-1]}; + v4sf b{cc[2*k + ido]}; + ch[k + ido-1] = vadd(a,a); + ch[k + ido-1 + l1ido] = vmul(minus_two, b); + } +} /* radb2 */ + +void radf3_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const v4sf taur{ld_ps1(-0.5f)}; + const v4sf taui{ld_ps1(0.866025403784439f)}; + for(size_t k{0};k < l1;++k) + { + v4sf cr2{vadd(cc[(k + l1)*ido], cc[(k + 2*l1)*ido])}; + ch[ (3*k )*ido] = vadd(cc[k*ido], cr2); + ch[ (3*k + 2)*ido] = vmul(taui, vsub(cc[(k + l1*2)*ido], cc[(k + l1)*ido])); + ch[ido-1 + (3*k + 1)*ido] = vmadd(taur, cr2, cc[k*ido]); + } + if(ido == 1) + return; + + const auto wa2 = wa1 + ido; + for(size_t k{0};k < l1;++k) + { + for(size_t i{2};i < ido;i += 2) + { + const size_t ic{ido - i}; + v4sf wr1{ld_ps1(wa1[i - 2])}; + v4sf wi1{ld_ps1(wa1[i - 1])}; + v4sf dr2{cc[i - 1 + (k + l1)*ido]}; + v4sf di2{cc[i + (k + l1)*ido]}; + vcplxmulconj(dr2, di2, wr1, wi1); + + v4sf wr2{ld_ps1(wa2[i - 2])}; + v4sf wi2{ld_ps1(wa2[i - 1])}; + v4sf dr3{cc[i - 1 + (k + l1*2)*ido]}; + v4sf di3{cc[i + (k + l1*2)*ido]}; + vcplxmulconj(dr3, di3, wr2, wi2); + + v4sf cr2{vadd(dr2, dr3)}; + v4sf ci2{vadd(di2, di3)}; + ch[i - 1 + 3*k*ido] = vadd(cc[i - 1 + k*ido], cr2); + ch[i + 3*k*ido] = vadd(cc[i + k*ido], ci2); + v4sf tr2{vmadd(taur, cr2, cc[i - 1 + k*ido])}; + v4sf ti2{vmadd(taur, ci2, cc[i + k*ido])}; + v4sf tr3{vmul(taui, vsub(di2, di3))}; + v4sf ti3{vmul(taui, vsub(dr3, dr2))}; + ch[i - 1 + (3*k + 2)*ido] = vadd(tr2, tr3); + ch[ic - 1 + (3*k + 1)*ido] = vsub(tr2, tr3); + ch[i + (3*k + 2)*ido] = vadd(ti2, ti3); + ch[ic + (3*k + 1)*ido] = vsub(ti3, ti2); + } + } +} /* radf3 */ + + +void radb3_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + static constexpr float taur{-0.5f}; + static constexpr float taui{0.866025403784439f}; + static constexpr float taui_2{taui*2.0f}; + + const v4sf vtaur{ld_ps1(taur)}; + const v4sf vtaui_2{ld_ps1(taui_2)}; + for(size_t k{0};k < l1;++k) + { + v4sf tr2 = cc[ido-1 + (3*k + 1)*ido]; + tr2 = vadd(tr2,tr2); + v4sf cr2 = vmadd(vtaur, tr2, cc[3*k*ido]); + ch[k*ido] = vadd(cc[3*k*ido], tr2); + v4sf ci3 = vmul(vtaui_2, cc[(3*k + 2)*ido]); + ch[(k + l1)*ido] = vsub(cr2, ci3); + ch[(k + 2*l1)*ido] = vadd(cr2, ci3); + } + if(ido == 1) + return; + + const auto wa2 = wa1 + ido; + const v4sf vtaui{ld_ps1(taui)}; + for(size_t k{0};k < l1;++k) + { + for(size_t i{2};i < ido;i += 2) + { + const size_t ic{ido - i}; + v4sf tr2{vadd(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido])}; + v4sf cr2{vmadd(vtaur, tr2, cc[i - 1 + 3*k*ido])}; + ch[i - 1 + k*ido] = vadd(cc[i - 1 + 3*k*ido], tr2); + v4sf ti2{vsub(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido])}; + v4sf ci2{vmadd(vtaur, ti2, cc[i + 3*k*ido])}; + ch[i + k*ido] = vadd(cc[i + 3*k*ido], ti2); + v4sf cr3{vmul(vtaui, vsub(cc[i - 1 + (3*k + 2)*ido], cc[ic - 1 + (3*k + 1)*ido]))}; + v4sf ci3{vmul(vtaui, vadd(cc[i + (3*k + 2)*ido], cc[ic + (3*k + 1)*ido]))}; + v4sf dr2{vsub(cr2, ci3)}; + v4sf dr3{vadd(cr2, ci3)}; + v4sf di2{vadd(ci2, cr3)}; + v4sf di3{vsub(ci2, cr3)}; + vcplxmul(dr2, di2, ld_ps1(wa1[i-2]), ld_ps1(wa1[i-1])); + ch[i - 1 + (k + l1)*ido] = dr2; + ch[i + (k + l1)*ido] = di2; + vcplxmul(dr3, di3, ld_ps1(wa2[i-2]), ld_ps1(wa2[i-1])); + ch[i - 1 + (k + 2*l1)*ido] = dr3; + ch[i + (k + 2*l1)*ido] = di3; + } + } +} /* radb3 */ + +NOINLINE void radf4_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, + v4sf *RESTRICT ch, const float *const wa1) +{ + const size_t l1ido{l1*ido}; + { + const v4sf *RESTRICT cc_{cc}, *RESTRICT cc_end{cc + l1ido}; + v4sf *RESTRICT ch_{ch}; + while(cc != cc_end) + { + // this loop represents between 25% and 40% of total radf4_ps cost ! + v4sf a0{cc[0]}, a1{cc[l1ido]}; + v4sf a2{cc[2*l1ido]}, a3{cc[3*l1ido]}; + v4sf tr1{vadd(a1, a3)}; + v4sf tr2{vadd(a0, a2)}; + ch[2*ido-1] = vsub(a0, a2); + ch[2*ido ] = vsub(a3, a1); + ch[0 ] = vadd(tr1, tr2); + ch[4*ido-1] = vsub(tr2, tr1); + cc += ido; ch += 4*ido; + } + cc = cc_; + ch = ch_; + } + if(ido < 2) + return; + if(ido != 2) + { + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + + for(size_t k{0};k < l1ido;k += ido) + { + const v4sf *RESTRICT pc{cc + 1 + k}; + for(size_t i{2};i < ido;i += 2, pc += 2) + { + const size_t ic{ido - i}; + + v4sf cr2{pc[1*l1ido+0]}; + v4sf ci2{pc[1*l1ido+1]}; + v4sf wr{ld_ps1(wa1[i - 2])}; + v4sf wi{ld_ps1(wa1[i - 1])}; + vcplxmulconj(cr2,ci2,wr,wi); + + v4sf cr3{pc[2*l1ido+0]}; + v4sf ci3{pc[2*l1ido+1]}; + wr = ld_ps1(wa2[i-2]); + wi = ld_ps1(wa2[i-1]); + vcplxmulconj(cr3, ci3, wr, wi); + + v4sf cr4{pc[3*l1ido]}; + v4sf ci4{pc[3*l1ido+1]}; + wr = ld_ps1(wa3[i-2]); + wi = ld_ps1(wa3[i-1]); + vcplxmulconj(cr4, ci4, wr, wi); + + /* at this point, on SSE, five of "cr2 cr3 cr4 ci2 ci3 ci4" should be loaded in registers */ + + v4sf tr1{vadd(cr2,cr4)}; + v4sf tr4{vsub(cr4,cr2)}; + v4sf tr2{vadd(pc[0],cr3)}; + v4sf tr3{vsub(pc[0],cr3)}; + ch[i - 1 + 4*k ] = vadd(tr2,tr1); + ch[ic - 1 + 4*k + 3*ido] = vsub(tr2,tr1); // at this point tr1 and tr2 can be disposed + v4sf ti1{vadd(ci2,ci4)}; + v4sf ti4{vsub(ci2,ci4)}; + ch[i - 1 + 4*k + 2*ido] = vadd(tr3,ti4); + ch[ic - 1 + 4*k + 1*ido] = vsub(tr3,ti4); // dispose tr3, ti4 + v4sf ti2{vadd(pc[1],ci3)}; + v4sf ti3{vsub(pc[1],ci3)}; + ch[i + 4*k ] = vadd(ti1, ti2); + ch[ic + 4*k + 3*ido] = vsub(ti1, ti2); + ch[i + 4*k + 2*ido] = vadd(tr4, ti3); + ch[ic + 4*k + 1*ido] = vsub(tr4, ti3); + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_hsqt2{ld_ps1(al::numbers::sqrt2_v * -0.5f)}; + for(size_t k{0};k < l1ido;k += ido) + { + v4sf a{cc[ido-1 + k + l1ido]}, b{cc[ido-1 + k + 3*l1ido]}; + v4sf c{cc[ido-1 + k]}, d{cc[ido-1 + k + 2*l1ido]}; + v4sf ti1{vmul(minus_hsqt2, vadd(b, a))}; + v4sf tr1{vmul(minus_hsqt2, vsub(b, a))}; + ch[ido-1 + 4*k ] = vadd(c, tr1); + ch[ido-1 + 4*k + 2*ido] = vsub(c, tr1); + ch[ 4*k + 1*ido] = vsub(ti1, d); + ch[ 4*k + 3*ido] = vadd(ti1, d); + } +} /* radf4 */ + + +NOINLINE void radb4_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, + v4sf *RESTRICT ch, const float *const wa1) +{ + const v4sf two{ld_ps1(2.0f)}; + const size_t l1ido{l1*ido}; + { + const v4sf *RESTRICT cc_{cc}, *RESTRICT ch_end{ch + l1ido}; + v4sf *ch_{ch}; + while(ch != ch_end) + { + v4sf a{cc[0]}, b{cc[4*ido-1]}; + v4sf c{cc[2*ido]}, d{cc[2*ido-1]}; + v4sf tr3{vmul(two,d)}; + v4sf tr2{vadd(a,b)}; + v4sf tr1{vsub(a,b)}; + v4sf tr4{vmul(two,c)}; + ch[0*l1ido] = vadd(tr2, tr3); + ch[2*l1ido] = vsub(tr2, tr3); + ch[1*l1ido] = vsub(tr1, tr4); + ch[3*l1ido] = vadd(tr1, tr4); + + cc += 4*ido; ch += ido; + } + cc = cc_; ch = ch_; + } + if(ido < 2) + return; + if(ido != 2) + { + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + + for(size_t k{0};k < l1ido;k += ido) + { + const v4sf *RESTRICT pc{cc - 1 + 4*k}; + v4sf *RESTRICT ph{ch + k + 1}; + for(size_t i{2};i < ido;i += 2) + { + v4sf tr1{vsub(pc[ i], pc[4*ido - i])}; + v4sf tr2{vadd(pc[ i], pc[4*ido - i])}; + v4sf ti4{vsub(pc[2*ido + i], pc[2*ido - i])}; + v4sf tr3{vadd(pc[2*ido + i], pc[2*ido - i])}; + ph[0] = vadd(tr2, tr3); + v4sf cr3{vsub(tr2, tr3)}; + + v4sf ti3{vsub(pc[2*ido + i + 1], pc[2*ido - i + 1])}; + v4sf tr4{vadd(pc[2*ido + i + 1], pc[2*ido - i + 1])}; + v4sf cr2{vsub(tr1, tr4)}; + v4sf cr4{vadd(tr1, tr4)}; + + v4sf ti1{vadd(pc[i + 1], pc[4*ido - i + 1])}; + v4sf ti2{vsub(pc[i + 1], pc[4*ido - i + 1])}; + + ph[1] = vadd(ti2, ti3); ph += l1ido; + v4sf ci3{vsub(ti2, ti3)}; + v4sf ci2{vadd(ti1, ti4)}; + v4sf ci4{vsub(ti1, ti4)}; + vcplxmul(cr2, ci2, ld_ps1(wa1[i-2]), ld_ps1(wa1[i-1])); + ph[0] = cr2; + ph[1] = ci2; ph += l1ido; + vcplxmul(cr3, ci3, ld_ps1(wa2[i-2]), ld_ps1(wa2[i-1])); + ph[0] = cr3; + ph[1] = ci3; ph += l1ido; + vcplxmul(cr4, ci4, ld_ps1(wa3[i-2]), ld_ps1(wa3[i-1])); + ph[0] = cr4; + ph[1] = ci4; ph = ph - 3*l1ido + 2; + } + } + if((ido&1) == 1) + return; + } + const v4sf minus_sqrt2{ld_ps1(-1.414213562373095f)}; + for(size_t k{0};k < l1ido;k += ido) + { + const size_t i0{4*k + ido}; + v4sf c{cc[i0-1]}, d{cc[i0 + 2*ido-1]}; + v4sf a{cc[i0+0]}, b{cc[i0 + 2*ido+0]}; + v4sf tr1{vsub(c,d)}; + v4sf tr2{vadd(c,d)}; + v4sf ti1{vadd(b,a)}; + v4sf ti2{vsub(b,a)}; + ch[ido-1 + k + 0*l1ido] = vadd(tr2,tr2); + ch[ido-1 + k + 1*l1ido] = vmul(minus_sqrt2, vsub(ti1, tr1)); + ch[ido-1 + k + 2*l1ido] = vadd(ti2, ti2); + ch[ido-1 + k + 3*l1ido] = vmul(minus_sqrt2, vadd(ti1, tr1)); + } +} /* radb4 */ + +void radf5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const v4sf tr11{ld_ps1(0.309016994374947f)}; + const v4sf ti11{ld_ps1(0.951056516295154f)}; + const v4sf tr12{ld_ps1(-0.809016994374947f)}; + const v4sf ti12{ld_ps1(0.587785252292473f)}; + + auto cc_ref = [&cc,l1,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return cc[(a_3*l1 + a_2)*ido + a_1]; }; + auto ch_ref = [&ch,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return ch[(a_3*5 + a_2)*ido + a_1]; }; + + /* Parameter adjustments */ + ch -= 1 + ido * 6; + cc -= 1 + ido * (1 + l1); + + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + const auto wa4 = wa3 + ido; + + /* Function Body */ + for(size_t k{1};k <= l1;++k) + { + v4sf cr2{vadd(cc_ref(1, k, 5), cc_ref(1, k, 2))}; + v4sf ci5{vsub(cc_ref(1, k, 5), cc_ref(1, k, 2))}; + v4sf cr3{vadd(cc_ref(1, k, 4), cc_ref(1, k, 3))}; + v4sf ci4{vsub(cc_ref(1, k, 4), cc_ref(1, k, 3))}; + ch_ref(1, 1, k) = vadd(cc_ref(1, k, 1), vadd(cr2, cr3)); + ch_ref(ido, 2, k) = vadd(cc_ref(1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3))); + ch_ref(1, 3, k) = vmadd(ti11, ci5, vmul(ti12, ci4)); + ch_ref(ido, 4, k) = vadd(cc_ref(1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3))); + ch_ref(1, 5, k) = vsub(vmul(ti12, ci5), vmul(ti11, ci4)); + //printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4); + } + if(ido == 1) + return; + + const size_t idp2{ido + 2}; + for(size_t k{1};k <= l1;++k) + { + for(size_t i{3};i <= ido;i += 2) + { + const size_t ic{idp2 - i}; + v4sf dr2{ld_ps1(wa1[i-3])}; + v4sf di2{ld_ps1(wa1[i-2])}; + v4sf dr3{ld_ps1(wa2[i-3])}; + v4sf di3{ld_ps1(wa2[i-2])}; + v4sf dr4{ld_ps1(wa3[i-3])}; + v4sf di4{ld_ps1(wa3[i-2])}; + v4sf dr5{ld_ps1(wa4[i-3])}; + v4sf di5{ld_ps1(wa4[i-2])}; + vcplxmulconj(dr2, di2, cc_ref(i-1, k, 2), cc_ref(i, k, 2)); + vcplxmulconj(dr3, di3, cc_ref(i-1, k, 3), cc_ref(i, k, 3)); + vcplxmulconj(dr4, di4, cc_ref(i-1, k, 4), cc_ref(i, k, 4)); + vcplxmulconj(dr5, di5, cc_ref(i-1, k, 5), cc_ref(i, k, 5)); + v4sf cr2{vadd(dr2, dr5)}; + v4sf ci5{vsub(dr5, dr2)}; + v4sf cr5{vsub(di2, di5)}; + v4sf ci2{vadd(di2, di5)}; + v4sf cr3{vadd(dr3, dr4)}; + v4sf ci4{vsub(dr4, dr3)}; + v4sf cr4{vsub(di3, di4)}; + v4sf ci3{vadd(di3, di4)}; + ch_ref(i - 1, 1, k) = vadd(cc_ref(i - 1, k, 1), vadd(cr2, cr3)); + ch_ref(i, 1, k) = vsub(cc_ref(i, k, 1), vadd(ci2, ci3)); + v4sf tr2{vadd(cc_ref(i - 1, k, 1), vmadd(tr11, cr2, vmul(tr12, cr3)))}; + v4sf ti2{vsub(cc_ref(i, k, 1), vmadd(tr11, ci2, vmul(tr12, ci3)))}; + v4sf tr3{vadd(cc_ref(i - 1, k, 1), vmadd(tr12, cr2, vmul(tr11, cr3)))}; + v4sf ti3{vsub(cc_ref(i, k, 1), vmadd(tr12, ci2, vmul(tr11, ci3)))}; + v4sf tr5{vmadd(ti11, cr5, vmul(ti12, cr4))}; + v4sf ti5{vmadd(ti11, ci5, vmul(ti12, ci4))}; + v4sf tr4{vsub(vmul(ti12, cr5), vmul(ti11, cr4))}; + v4sf ti4{vsub(vmul(ti12, ci5), vmul(ti11, ci4))}; + ch_ref(i - 1, 3, k) = vsub(tr2, tr5); + ch_ref(ic - 1, 2, k) = vadd(tr2, tr5); + ch_ref(i , 3, k) = vadd(ti5, ti2); + ch_ref(ic , 2, k) = vsub(ti5, ti2); + ch_ref(i - 1, 5, k) = vsub(tr3, tr4); + ch_ref(ic - 1, 4, k) = vadd(tr3, tr4); + ch_ref(i , 5, k) = vadd(ti4, ti3); + ch_ref(ic , 4, k) = vsub(ti4, ti3); + } + } +} /* radf5 */ + +void radb5_ps(const size_t ido, const size_t l1, const v4sf *RESTRICT cc, v4sf *RESTRICT ch, + const float *const wa1) +{ + const v4sf tr11{ld_ps1(0.309016994374947f)}; + const v4sf ti11{ld_ps1(0.951056516295154f)}; + const v4sf tr12{ld_ps1(-0.809016994374947f)}; + const v4sf ti12{ld_ps1(0.587785252292473f)}; + + auto cc_ref = [&cc,ido](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return cc[(a_3*5 + a_2)*ido + a_1]; }; + auto ch_ref = [&ch,ido,l1](size_t a_1, size_t a_2, size_t a_3) noexcept -> auto& + { return ch[(a_3*l1 + a_2)*ido + a_1]; }; + + /* Parameter adjustments */ + ch -= 1 + ido*(1 + l1); + cc -= 1 + ido*6; + + const auto wa2 = wa1 + ido; + const auto wa3 = wa2 + ido; + const auto wa4 = wa3 + ido; + + /* Function Body */ + for(size_t k{1};k <= l1;++k) + { + v4sf ti5{vadd(cc_ref( 1, 3, k), cc_ref(1, 3, k))}; + v4sf ti4{vadd(cc_ref( 1, 5, k), cc_ref(1, 5, k))}; + v4sf tr2{vadd(cc_ref(ido, 2, k), cc_ref(ido, 2, k))}; + v4sf tr3{vadd(cc_ref(ido, 4, k), cc_ref(ido, 4, k))}; + ch_ref(1, k, 1) = vadd(cc_ref(1, 1, k), vadd(tr2, tr3)); + v4sf cr2{vadd(cc_ref(1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3)))}; + v4sf cr3{vadd(cc_ref(1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3)))}; + v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; + v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; + ch_ref(1, k, 2) = vsub(cr2, ci5); + ch_ref(1, k, 3) = vsub(cr3, ci4); + ch_ref(1, k, 4) = vadd(cr3, ci4); + ch_ref(1, k, 5) = vadd(cr2, ci5); + } + if(ido == 1) + return; + + const size_t idp2{ido + 2}; + for(size_t k{1};k <= l1;++k) + { + for(size_t i{3};i <= ido;i += 2) + { + const size_t ic{idp2 - i}; + v4sf ti5{vadd(cc_ref(i , 3, k), cc_ref(ic , 2, k))}; + v4sf ti2{vsub(cc_ref(i , 3, k), cc_ref(ic , 2, k))}; + v4sf ti4{vadd(cc_ref(i , 5, k), cc_ref(ic , 4, k))}; + v4sf ti3{vsub(cc_ref(i , 5, k), cc_ref(ic , 4, k))}; + v4sf tr5{vsub(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k))}; + v4sf tr2{vadd(cc_ref(i-1, 3, k), cc_ref(ic-1, 2, k))}; + v4sf tr4{vsub(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k))}; + v4sf tr3{vadd(cc_ref(i-1, 5, k), cc_ref(ic-1, 4, k))}; + ch_ref(i - 1, k, 1) = vadd(cc_ref(i-1, 1, k), vadd(tr2, tr3)); + ch_ref(i , k, 1) = vadd(cc_ref(i , 1, k), vadd(ti2, ti3)); + v4sf cr2{vadd(cc_ref(i-1, 1, k), vmadd(tr11, tr2, vmul(tr12, tr3)))}; + v4sf ci2{vadd(cc_ref(i , 1, k), vmadd(tr11, ti2, vmul(tr12, ti3)))}; + v4sf cr3{vadd(cc_ref(i-1, 1, k), vmadd(tr12, tr2, vmul(tr11, tr3)))}; + v4sf ci3{vadd(cc_ref(i , 1, k), vmadd(tr12, ti2, vmul(tr11, ti3)))}; + v4sf cr5{vmadd(ti11, tr5, vmul(ti12, tr4))}; + v4sf ci5{vmadd(ti11, ti5, vmul(ti12, ti4))}; + v4sf cr4{vsub(vmul(ti12, tr5), vmul(ti11, tr4))}; + v4sf ci4{vsub(vmul(ti12, ti5), vmul(ti11, ti4))}; + v4sf dr3{vsub(cr3, ci4)}; + v4sf dr4{vadd(cr3, ci4)}; + v4sf di3{vadd(ci3, cr4)}; + v4sf di4{vsub(ci3, cr4)}; + v4sf dr5{vadd(cr2, ci5)}; + v4sf dr2{vsub(cr2, ci5)}; + v4sf di5{vsub(ci2, cr5)}; + v4sf di2{vadd(ci2, cr5)}; + vcplxmul(dr2, di2, ld_ps1(wa1[i-3]), ld_ps1(wa1[i-2])); + vcplxmul(dr3, di3, ld_ps1(wa2[i-3]), ld_ps1(wa2[i-2])); + vcplxmul(dr4, di4, ld_ps1(wa3[i-3]), ld_ps1(wa3[i-2])); + vcplxmul(dr5, di5, ld_ps1(wa4[i-3]), ld_ps1(wa4[i-2])); + + ch_ref(i-1, k, 2) = dr2; ch_ref(i, k, 2) = di2; + ch_ref(i-1, k, 3) = dr3; ch_ref(i, k, 3) = di3; + ch_ref(i-1, k, 4) = dr4; ch_ref(i, k, 4) = di4; + ch_ref(i-1, k, 5) = dr5; ch_ref(i, k, 5) = di5; + } + } +} /* radb5 */ + +NOINLINE v4sf *rfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, + const float *wa, const al::span ifac) +{ + assert(work1 != work2); + + const v4sf *in{input_readonly}; + v4sf *out{in == work2 ? work1 : work2}; + const size_t nf{ifac[1]}; + size_t l2{n}; + size_t iw{n-1}; + size_t k1{1}; + while(true) + { + const size_t kh{nf - k1}; + const size_t ip{ifac[kh + 2]}; + const size_t l1{l2 / ip}; + const size_t ido{n / l2}; + iw -= (ip - 1)*ido; + switch(ip) + { + case 5: + radf5_ps(ido, l1, in, out, &wa[iw]); + break; + case 4: + radf4_ps(ido, l1, in, out, &wa[iw]); + break; + case 3: + radf3_ps(ido, l1, in, out, &wa[iw]); + break; + case 2: + radf2_ps(ido, l1, in, out, &wa[iw]); + break; + default: + assert(0); + } + if(++k1 > nf) + return out; + + l2 = l1; + if(out == work2) + { + out = work1; + in = work2; + } + else + { + out = work2; + in = work1; + } + } +} /* rfftf1 */ + +NOINLINE v4sf *rfftb1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, + const float *wa, const al::span ifac) +{ + assert(work1 != work2); + + const v4sf *in{input_readonly}; + v4sf *out{in == work2 ? work1 : work2}; + const size_t nf{ifac[1]}; + size_t l1{1}; + size_t iw{0}; + size_t k1{1}; + while(true) + { + const size_t ip{ifac[k1 + 1]}; + const size_t l2{ip*l1}; + const size_t ido{n / l2}; + switch(ip) + { + case 5: + radb5_ps(ido, l1, in, out, &wa[iw]); + break; + case 4: + radb4_ps(ido, l1, in, out, &wa[iw]); + break; + case 3: + radb3_ps(ido, l1, in, out, &wa[iw]); + break; + case 2: + radb2_ps(ido, l1, in, out, &wa[iw]); + break; + default: + assert(0); + } + if(++k1 > nf) + return out; + + l1 = l2; + iw += (ip - 1)*ido; + + if(out == work2) + { + out = work1; + in = work2; + } + else + { + out = work2; + in = work1; + } + } +} + +v4sf *cfftf1_ps(const size_t n, const v4sf *input_readonly, v4sf *work1, v4sf *work2, + const float *wa, const al::span ifac, const float fsign) +{ + assert(work1 != work2); + + const v4sf *in{input_readonly}; + v4sf *out{in == work2 ? work1 : work2}; + const size_t nf{ifac[1]}; + size_t l1{1}, iw{0}; + size_t k1{2}; + while(true) + { + const size_t ip{ifac[k1]}; + const size_t l2{ip*l1}; + const size_t ido{n / l2}; + const size_t idot{ido + ido}; + switch(ip) + { + case 5: + passf5_ps(idot, l1, in, out, &wa[iw], fsign); + break; + case 4: + passf4_ps(idot, l1, in, out, &wa[iw], fsign); + break; + case 3: + passf3_ps(idot, l1, in, out, &wa[iw], fsign); + break; + case 2: + passf2_ps(idot, l1, in, out, &wa[iw], fsign); + break; + default: + assert(0); + } + if(++k1 > nf+1) + return out; + + l1 = l2; + iw += (ip - 1)*idot; + if(out == work2) + { + out = work1; + in = work2; + } + else + { + out = work2; + in = work1; + } + } +} + + +uint decompose(const uint n, const al::span ifac, const al::span ntryh) +{ + uint nl{n}, nf{0}; + for(const uint ntry : ntryh) + { + while(nl != 1) + { + const uint nq{nl / ntry}; + const uint nr{nl % ntry}; + if(nr != 0) break; + + ifac[2+nf++] = ntry; + nl = nq; + if(ntry == 2 && nf != 1) + { + for(size_t i{2};i <= nf;++i) + { + size_t ib{nf - i + 2}; + ifac[ib + 1] = ifac[ib]; + } + ifac[2] = 2; + } + } + } + ifac[0] = n; + ifac[1] = nf; + return nf; +} + +void rffti1_ps(const uint n, float *wa, const al::span ifac) +{ + static constexpr std::array ntryh{4u,2u,3u,5u}; + + const uint nf{decompose(n, ifac, ntryh)}; + const double argh{2.0*al::numbers::pi / n}; + size_t is{0}; + size_t nfm1{nf - 1}; + size_t l1{1}; + for(size_t k1{0};k1 < nfm1;++k1) + { + const size_t ip{ifac[k1+2]}; + const size_t l2{l1*ip}; + const size_t ido{n / l2}; + const size_t ipm{ip - 1}; + size_t ld{0}; + for(size_t j{0};j < ipm;++j) + { + size_t i{is}; + ld += l1; + const double argld{static_cast(ld)*argh}; + double fi{0.0}; + for(size_t ii{2};ii < ido;ii += 2) + { + fi += 1.0; + wa[i++] = static_cast(std::cos(fi*argld)); + wa[i++] = static_cast(std::sin(fi*argld)); + } + is += ido; + } + l1 = l2; + } +} /* rffti1 */ + +void cffti1_ps(const uint n, float *wa, const al::span ifac) +{ + static constexpr std::array ntryh{5u,3u,4u,2u}; + + const uint nf{decompose(n, ifac, ntryh)}; + const double argh{2.0*al::numbers::pi / n}; + size_t i{1}; + size_t l1{1}; + for(size_t k1{0};k1 < nf;++k1) + { + const size_t ip{ifac[k1+2]}; + const size_t l2{l1*ip}; + const size_t ido{n / l2}; + const size_t idot{ido + ido + 2}; + const size_t ipm{ip - 1}; + size_t ld{0}; + for(size_t j{0};j < ipm;++j) + { + size_t i1{i}; + wa[i-1] = 1; + wa[i] = 0; + ld += l1; + const double argld{static_cast(ld)*argh}; + double fi{0.0}; + for(size_t ii{3};ii < idot;ii += 2) + { + fi += 1.0; + wa[++i] = static_cast(std::cos(fi*argld)); + wa[++i] = static_cast(std::sin(fi*argld)); + } + if(ip > 5) + { + wa[i1-1] = wa[i-1]; + wa[i1] = wa[i]; + } + } + l1 = l2; + } +} /* cffti1 */ + +} // namespace + +/* NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding) */ +struct PFFFT_Setup { + uint N{}; + uint Ncvec{}; /* nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL) */ + std::array ifac{}; + pffft_transform_t transform{}; + + float *twiddle{}; /* N/4 elements */ + al::span e; /* N/4*3 elements */ + + alignas(V4sfAlignment) std::byte end; +}; + +PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform) +{ + assert(transform == PFFFT_REAL || transform == PFFFT_COMPLEX); + assert(N > 0); + /* unfortunately, the fft size must be a multiple of 16 for complex FFTs + * and 32 for real FFTs -- a lot of stuff would need to be rewritten to + * handle other cases (or maybe just switch to a scalar fft, I don't know..) + */ + if(transform == PFFFT_REAL) + assert((N%(2*SimdSize*SimdSize)) == 0); + else + assert((N%(SimdSize*SimdSize)) == 0); + + const uint Ncvec{(transform == PFFFT_REAL ? N/2 : N) / SimdSize}; + + const size_t storelen{std::max(offsetof(PFFFT_Setup, end) + 2_zu*Ncvec*sizeof(v4sf), + sizeof(PFFFT_Setup))}; + auto storage = static_cast>(::operator new[](storelen, V4sfAlignVal)); + al::span extrastore{&storage[offsetof(PFFFT_Setup, end)], 2_zu*Ncvec*sizeof(v4sf)}; + + PFFFTSetupPtr s{::new(storage) PFFFT_Setup{}}; + s->N = N; + s->transform = transform; + s->Ncvec = Ncvec; + + const size_t ecount{2_zu*Ncvec*(SimdSize-1)/SimdSize}; + s->e = {std::launder(reinterpret_cast(extrastore.data())), ecount}; + s->twiddle = std::launder(reinterpret_cast(&extrastore[ecount*sizeof(v4sf)])); + + if constexpr(SimdSize > 1) + { + auto e = std::vector(s->e.size()*SimdSize, 0.0f); + for(size_t k{0};k < s->Ncvec;++k) + { + const size_t i{k / SimdSize}; + const size_t j{k % SimdSize}; + for(size_t m{0};m < SimdSize-1;++m) + { + const double A{-2.0*al::numbers::pi*static_cast((m+1)*k) / N}; + e[((i*3 + m)*2 + 0)*SimdSize + j] = static_cast(std::cos(A)); + e[((i*3 + m)*2 + 1)*SimdSize + j] = static_cast(std::sin(A)); + } + } + std::memcpy(s->e.data(), e.data(), e.size()*sizeof(float)); + } + if(transform == PFFFT_REAL) + rffti1_ps(N/SimdSize, s->twiddle, s->ifac); + else + cffti1_ps(N/SimdSize, s->twiddle, s->ifac); + + /* check that N is decomposable with allowed prime factors */ + size_t m{1}; + for(size_t k{0};k < s->ifac[1];++k) + m *= s->ifac[2+k]; + + if(m != N/SimdSize) + s = nullptr; + + return s; +} + + +void pffft_destroy_setup(gsl::owner s) noexcept +{ + std::destroy_at(s); + ::operator delete[](gsl::owner{s}, V4sfAlignVal); +} + +#if !defined(PFFFT_SIMD_DISABLE) + +namespace { + +/* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */ +void reversed_copy(const size_t N, const v4sf *in, const int in_stride, v4sf *RESTRICT out) +{ + v4sf g0, g1; + interleave2(in[0], in[1], g0, g1); + in += in_stride; + + *--out = vswaphl(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h] + for(size_t k{1};k < N;++k) + { + v4sf h0, h1; + interleave2(in[0], in[1], h0, h1); + in += in_stride; + *--out = vswaphl(g1, h0); + *--out = vswaphl(h0, h1); + g1 = h1; + } + *--out = vswaphl(g1, g0); +} + +void unreversed_copy(const size_t N, const v4sf *in, v4sf *RESTRICT out, const int out_stride) +{ + v4sf g0{in[0]}, g1{g0}; + ++in; + for(size_t k{1};k < N;++k) + { + v4sf h0{*in++}; v4sf h1{*in++}; + g1 = vswaphl(g1, h0); + h0 = vswaphl(h0, h1); + uninterleave2(h0, g1, out[0], out[1]); + out += out_stride; + g1 = h1; + } + v4sf h0{*in++}, h1{g0}; + g1 = vswaphl(g1, h0); + h0 = vswaphl(h0, h1); + uninterleave2(h0, g1, out[0], out[1]); +} + +void pffft_cplx_finalize(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) +{ + assert(in != out); + + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + for(size_t k{0};k < dk;++k) + { + v4sf r0{in[8*k+0]}, i0{in[8*k+1]}; + v4sf r1{in[8*k+2]}, i1{in[8*k+3]}; + v4sf r2{in[8*k+4]}, i2{in[8*k+5]}; + v4sf r3{in[8*k+6]}, i3{in[8*k+7]}; + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + vcplxmul(r1,i1,e[k*6+0],e[k*6+1]); + vcplxmul(r2,i2,e[k*6+2],e[k*6+3]); + vcplxmul(r3,i3,e[k*6+4],e[k*6+5]); + + v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0, r2)}; + v4sf sr1{vadd(r1,r3)}, dr1{vsub(r1, r3)}; + v4sf si0{vadd(i0,i2)}, di0{vsub(i0, i2)}; + v4sf si1{vadd(i1,i3)}, di1{vsub(i1, i3)}; + + /* transformation for each column is: + * + * [1 1 1 1 0 0 0 0] [r0] + * [1 0 -1 0 0 -1 0 1] [r1] + * [1 -1 1 -1 0 0 0 0] [r2] + * [1 0 -1 0 0 1 0 -1] [r3] + * [0 0 0 0 1 1 1 1] * [i0] + * [0 1 0 -1 1 0 -1 0] [i1] + * [0 0 0 0 1 -1 1 -1] [i2] + * [0 -1 0 1 1 0 -1 0] [i3] + */ + + r0 = vadd(sr0, sr1); i0 = vadd(si0, si1); + r1 = vadd(dr0, di1); i1 = vsub(di0, dr1); + r2 = vsub(sr0, sr1); i2 = vsub(si0, si1); + r3 = vsub(dr0, di1); i3 = vadd(di0, dr1); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + +void pffft_cplx_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, const v4sf *e) +{ + assert(in != out); + + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + for(size_t k{0};k < dk;++k) + { + v4sf r0{in[8*k+0]}, i0{in[8*k+1]}; + v4sf r1{in[8*k+2]}, i1{in[8*k+3]}; + v4sf r2{in[8*k+4]}, i2{in[8*k+5]}; + v4sf r3{in[8*k+6]}, i3{in[8*k+7]}; + + v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0, r2)}; + v4sf sr1{vadd(r1,r3)}, dr1{vsub(r1, r3)}; + v4sf si0{vadd(i0,i2)}, di0{vsub(i0, i2)}; + v4sf si1{vadd(i1,i3)}, di1{vsub(i1, i3)}; + + r0 = vadd(sr0, sr1); i0 = vadd(si0, si1); + r1 = vsub(dr0, di1); i1 = vadd(di0, dr1); + r2 = vsub(sr0, sr1); i2 = vsub(si0, si1); + r3 = vadd(dr0, di1); i3 = vsub(di0, dr1); + + vcplxmulconj(r1,i1,e[k*6+0],e[k*6+1]); + vcplxmulconj(r2,i2,e[k*6+2],e[k*6+3]); + vcplxmulconj(r3,i3,e[k*6+4],e[k*6+5]); + + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + + *out++ = r0; *out++ = i0; *out++ = r1; *out++ = i1; + *out++ = r2; *out++ = i2; *out++ = r3; *out++ = i3; + } +} + + +force_inline void pffft_real_finalize_4x4(const v4sf *in0, const v4sf *in1, const v4sf *in, + const v4sf *e, v4sf *RESTRICT out) +{ + v4sf r0{*in0}, i0{*in1}; + v4sf r1{*in++}; v4sf i1{*in++}; + v4sf r2{*in++}; v4sf i2{*in++}; + v4sf r3{*in++}; v4sf i3{*in++}; + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + + /* transformation for each column is: + * + * [1 1 1 1 0 0 0 0] [r0] + * [1 0 -1 0 0 -1 0 1] [r1] + * [1 0 -1 0 0 1 0 -1] [r2] + * [1 -1 1 -1 0 0 0 0] [r3] + * [0 0 0 0 1 1 1 1] * [i0] + * [0 -1 0 1 -1 0 1 0] [i1] + * [0 -1 0 1 1 0 -1 0] [i2] + * [0 0 0 0 -1 1 -1 1] [i3] + */ + + //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; + //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; + + vcplxmul(r1,i1,e[0],e[1]); + vcplxmul(r2,i2,e[2],e[3]); + vcplxmul(r3,i3,e[4],e[5]); + + //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n"; + //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n"; + + v4sf sr0{vadd(r0,r2)}, dr0{vsub(r0,r2)}; + v4sf sr1{vadd(r1,r3)}, dr1{vsub(r3,r1)}; + v4sf si0{vadd(i0,i2)}, di0{vsub(i0,i2)}; + v4sf si1{vadd(i1,i3)}, di1{vsub(i3,i1)}; + + r0 = vadd(sr0, sr1); + r3 = vsub(sr0, sr1); + i0 = vadd(si0, si1); + i3 = vsub(si1, si0); + r1 = vadd(dr0, di1); + r2 = vsub(dr0, di1); + i1 = vsub(dr1, di0); + i2 = vadd(dr1, di0); + + *out++ = r0; + *out++ = i0; + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; +} + +NOINLINE void pffft_real_finalize(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, + const v4sf *e) +{ + static constexpr float s{al::numbers::sqrt2_v/2.0f}; + + assert(in != out); + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + const v4sf zero{vzero()}; + const auto cr = al::bit_cast>(in[0]); + const auto ci = al::bit_cast>(in[Ncvec*2-1]); + pffft_real_finalize_4x4(&zero, &zero, in+1, e, out); + + /* [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3] + * + * [Xr(1)] ] [1 1 1 1 0 0 0 0] + * [Xr(N/4) ] [0 0 0 0 1 s 0 -s] + * [Xr(N/2) ] [1 0 -1 0 0 0 0 0] + * [Xr(3N/4)] [0 0 0 0 1 -s 0 s] + * [Xi(1) ] [1 -1 1 -1 0 0 0 0] + * [Xi(N/4) ] [0 0 0 0 0 -s -1 -s] + * [Xi(N/2) ] [0 -1 0 1 0 0 0 0] + * [Xi(3N/4)] [0 0 0 0 0 -s 1 -s] + */ + + const float xr0{(cr[0]+cr[2]) + (cr[1]+cr[3])}; out[0] = vinsert0(out[0], xr0); + const float xi0{(cr[0]+cr[2]) - (cr[1]+cr[3])}; out[1] = vinsert0(out[1], xi0); + const float xr2{(cr[0]-cr[2])}; out[4] = vinsert0(out[4], xr2); + const float xi2{(cr[3]-cr[1])}; out[5] = vinsert0(out[5], xi2); + const float xr1{ ci[0] + s*(ci[1]-ci[3])}; out[2] = vinsert0(out[2], xr1); + const float xi1{-ci[2] - s*(ci[1]+ci[3])}; out[3] = vinsert0(out[3], xi1); + const float xr3{ ci[0] - s*(ci[1]-ci[3])}; out[6] = vinsert0(out[6], xr3); + const float xi3{ ci[2] - s*(ci[1]+ci[3])}; out[7] = vinsert0(out[7], xi3); + + for(size_t k{1};k < dk;++k) + pffft_real_finalize_4x4(&in[8*k-1], &in[8*k+0], in + 8*k+1, e + k*6, out + k*8); +} + +force_inline void pffft_real_preprocess_4x4(const v4sf *in, const v4sf *e, v4sf *RESTRICT out, + const bool first) +{ + v4sf r0{in[0]}, i0{in[1]}, r1{in[2]}, i1{in[3]}; + v4sf r2{in[4]}, i2{in[5]}, r3{in[6]}, i3{in[7]}; + + /* transformation for each column is: + * + * [1 1 1 1 0 0 0 0] [r0] + * [1 0 0 -1 0 -1 -1 0] [r1] + * [1 -1 -1 1 0 0 0 0] [r2] + * [1 0 0 -1 0 1 1 0] [r3] + * [0 0 0 0 1 -1 1 -1] * [i0] + * [0 -1 1 0 1 0 0 1] [i1] + * [0 0 0 0 1 1 -1 -1] [i2] + * [0 1 -1 0 1 0 0 1] [i3] + */ + + v4sf sr0{vadd(r0,r3)}, dr0{vsub(r0,r3)}; + v4sf sr1{vadd(r1,r2)}, dr1{vsub(r1,r2)}; + v4sf si0{vadd(i0,i3)}, di0{vsub(i0,i3)}; + v4sf si1{vadd(i1,i2)}, di1{vsub(i1,i2)}; + + r0 = vadd(sr0, sr1); + r2 = vsub(sr0, sr1); + r1 = vsub(dr0, si1); + r3 = vadd(dr0, si1); + i0 = vsub(di0, di1); + i2 = vadd(di0, di1); + i1 = vsub(si0, dr1); + i3 = vadd(si0, dr1); + + vcplxmulconj(r1,i1,e[0],e[1]); + vcplxmulconj(r2,i2,e[2],e[3]); + vcplxmulconj(r3,i3,e[4],e[5]); + + vtranspose4(r0,r1,r2,r3); + vtranspose4(i0,i1,i2,i3); + + if(!first) + { + *out++ = r0; + *out++ = i0; + } + *out++ = r1; + *out++ = i1; + *out++ = r2; + *out++ = i2; + *out++ = r3; + *out++ = i3; +} + +NOINLINE void pffft_real_preprocess(const size_t Ncvec, const v4sf *in, v4sf *RESTRICT out, + const v4sf *e) +{ + static constexpr float sqrt2{al::numbers::sqrt2_v}; + + assert(in != out); + const size_t dk{Ncvec/SimdSize}; // number of 4x4 matrix blocks + /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */ + + std::array Xr{}, Xi{}; + for(size_t k{0};k < SimdSize;++k) + { + Xr[k] = vextract0(in[2*k]); + Xi[k] = vextract0(in[2*k + 1]); + } + + pffft_real_preprocess_4x4(in, e, out+1, true); // will write only 6 values + + /* [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3] + * + * [cr0] [1 0 2 0 1 0 0 0] + * [cr1] [1 0 0 0 -1 0 -2 0] + * [cr2] [1 0 -2 0 1 0 0 0] + * [cr3] [1 0 0 0 -1 0 2 0] + * [ci0] [0 2 0 2 0 0 0 0] + * [ci1] [0 s 0 -s 0 -s 0 -s] + * [ci2] [0 0 0 0 0 -2 0 2] + * [ci3] [0 -s 0 s 0 -s 0 -s] + */ + for(size_t k{1};k < dk;++k) + pffft_real_preprocess_4x4(in+8*k, e + k*6, out-1+k*8, false); + + const float cr0{(Xr[0]+Xi[0]) + 2*Xr[2]}; + const float cr1{(Xr[0]-Xi[0]) - 2*Xi[2]}; + const float cr2{(Xr[0]+Xi[0]) - 2*Xr[2]}; + const float cr3{(Xr[0]-Xi[0]) + 2*Xi[2]}; + out[0] = vset4(cr0, cr1, cr2, cr3); + const float ci0{ 2*(Xr[1]+Xr[3])}; + const float ci1{ sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3])}; + const float ci2{ 2*(Xi[3]-Xi[1])}; + const float ci3{-sqrt2*(Xr[1]-Xr[3]) - sqrt2*(Xi[1]+Xi[3])}; + out[2*Ncvec-1] = vset4(ci0, ci1, ci2, ci3); +} + + +void pffft_transform_internal(const PFFFT_Setup *setup, const v4sf *vinput, v4sf *voutput, + v4sf *scratch, const pffft_direction_t direction, const bool ordered) +{ + assert(scratch != nullptr); + assert(voutput != scratch); + + const size_t Ncvec{setup->Ncvec}; + const bool nf_odd{(setup->ifac[1]&1) != 0}; + + std::array buff{voutput, scratch}; + bool ib{nf_odd != ordered}; + if(direction == PFFFT_FORWARD) + { + /* Swap the initial work buffer for forward FFTs, which helps avoid an + * extra copy for output. + */ + ib = !ib; + if(setup->transform == PFFFT_REAL) + { + ib = (rfftf1_ps(Ncvec*2, vinput, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); + pffft_real_finalize(Ncvec, buff[ib], buff[!ib], setup->e.data()); + } + else + { + v4sf *tmp{buff[ib]}; + for(size_t k=0; k < Ncvec; ++k) + uninterleave2(vinput[k*2], vinput[k*2+1], tmp[k*2], tmp[k*2+1]); + + ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib], setup->twiddle, setup->ifac, -1.0f) == buff[1]); + pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib], setup->e.data()); + } + if(ordered) + pffft_zreorder(setup, reinterpret_cast(buff[!ib]), + reinterpret_cast(buff[ib]), PFFFT_FORWARD); + else + ib = !ib; + } + else + { + if(vinput == buff[ib]) + ib = !ib; // may happen when finput == foutput + + if(ordered) + { + pffft_zreorder(setup, reinterpret_cast(vinput), + reinterpret_cast(buff[ib]), PFFFT_BACKWARD); + vinput = buff[ib]; + ib = !ib; + } + if(setup->transform == PFFFT_REAL) + { + pffft_real_preprocess(Ncvec, vinput, buff[ib], setup->e.data()); + ib = (rfftb1_ps(Ncvec*2, buff[ib], buff[0], buff[1], setup->twiddle, setup->ifac) == buff[1]); + } + else + { + pffft_cplx_preprocess(Ncvec, vinput, buff[ib], setup->e.data()); + ib = (cfftf1_ps(Ncvec, buff[ib], buff[0], buff[1], setup->twiddle, setup->ifac, +1.0f) == buff[1]); + for(size_t k{0};k < Ncvec;++k) + interleave2(buff[ib][k*2], buff[ib][k*2+1], buff[ib][k*2], buff[ib][k*2+1]); + } + } + + if(buff[ib] != voutput) + { + /* extra copy required -- this situation should only happen when finput == foutput */ + assert(vinput==voutput); + for(size_t k{0};k < Ncvec;++k) + { + v4sf a{buff[ib][2*k]}, b{buff[ib][2*k+1]}; + voutput[2*k] = a; voutput[2*k+1] = b; + } + } +} + +} // namespace + +void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *out, + pffft_direction_t direction) +{ + assert(in != out); + + const size_t N{setup->N}, Ncvec{setup->Ncvec}; + const v4sf *vin{reinterpret_cast(in)}; + v4sf *RESTRICT vout{reinterpret_cast(out)}; + if(setup->transform == PFFFT_REAL) + { + const size_t dk{N/32}; + if(direction == PFFFT_FORWARD) + { + for(size_t k{0};k < dk;++k) + { + interleave2(vin[k*8 + 0], vin[k*8 + 1], vout[2*(0*dk + k) + 0], vout[2*(0*dk + k) + 1]); + interleave2(vin[k*8 + 4], vin[k*8 + 5], vout[2*(2*dk + k) + 0], vout[2*(2*dk + k) + 1]); + } + reversed_copy(dk, vin+2, 8, vout + N/SimdSize/2); + reversed_copy(dk, vin+6, 8, vout + N/SimdSize); + } + else + { + for(size_t k{0};k < dk;++k) + { + uninterleave2(vin[2*(0*dk + k) + 0], vin[2*(0*dk + k) + 1], vout[k*8 + 0], vout[k*8 + 1]); + uninterleave2(vin[2*(2*dk + k) + 0], vin[2*(2*dk + k) + 1], vout[k*8 + 4], vout[k*8 + 5]); + } + unreversed_copy(dk, vin + N/SimdSize/4, vout + N/SimdSize - 6, -8); + unreversed_copy(dk, vin + 3_uz*N/SimdSize/4, vout + N/SimdSize - 2, -8); + } + } + else + { + if(direction == PFFFT_FORWARD) + { + for(size_t k{0};k < Ncvec;++k) + { + size_t kk{(k/4) + (k%4)*(Ncvec/4)}; + interleave2(vin[k*2], vin[k*2+1], vout[kk*2], vout[kk*2+1]); + } + } + else + { + for(size_t k{0};k < Ncvec;++k) + { + size_t kk{(k/4) + (k%4)*(Ncvec/4)}; + uninterleave2(vin[kk*2], vin[kk*2+1], vout[k*2], vout[k*2+1]); + } + } + } +} + +void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, + float *ab, float scaling) +{ + const size_t Ncvec{s->Ncvec}; + const v4sf *RESTRICT va{reinterpret_cast(a)}; + const v4sf *RESTRICT vb{reinterpret_cast(b)}; + v4sf *RESTRICT vab{reinterpret_cast(ab)}; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(vab); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(vab+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(vab+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); + __builtin_prefetch(vab+6); +#ifndef __clang__ +#define ZCONVOLVE_USING_INLINE_NEON_ASM +#endif +#endif + + const float ar1{vextract0(va[0])}; + const float ai1{vextract0(va[1])}; + const float br1{vextract0(vb[0])}; + const float bi1{vextract0(vb[1])}; + const float abr1{vextract0(vab[0])}; + const float abi1{vextract0(vab[1])}; + +#ifdef ZCONVOLVE_USING_INLINE_ASM + /* Inline asm version, unfortunately miscompiled by clang 3.2, at least on + * Ubuntu. So this will be restricted to GCC. + * + * Does it still miscompile with Clang? Is it even needed with today's + * optimizers? + */ + const float *a_{a}, *b_{b}; float *ab_{ab}; + size_t N{Ncvec}; + asm volatile("mov r8, %2 \n" + "vdup.f32 q15, %4 \n" + "1: \n" + "pld [%0,#64] \n" + "pld [%1,#64] \n" + "pld [%2,#64] \n" + "pld [%0,#96] \n" + "pld [%1,#96] \n" + "pld [%2,#96] \n" + "vld1.f32 {q0,q1}, [%0,:128]! \n" + "vld1.f32 {q4,q5}, [%1,:128]! \n" + "vld1.f32 {q2,q3}, [%0,:128]! \n" + "vld1.f32 {q6,q7}, [%1,:128]! \n" + "vld1.f32 {q8,q9}, [r8,:128]! \n" + + "vmul.f32 q10, q0, q4 \n" + "vmul.f32 q11, q0, q5 \n" + "vmul.f32 q12, q2, q6 \n" + "vmul.f32 q13, q2, q7 \n" + "vmls.f32 q10, q1, q5 \n" + "vmla.f32 q11, q1, q4 \n" + "vld1.f32 {q0,q1}, [r8,:128]! \n" + "vmls.f32 q12, q3, q7 \n" + "vmla.f32 q13, q3, q6 \n" + "vmla.f32 q8, q10, q15 \n" + "vmla.f32 q9, q11, q15 \n" + "vmla.f32 q0, q12, q15 \n" + "vmla.f32 q1, q13, q15 \n" + "vst1.f32 {q8,q9},[%2,:128]! \n" + "vst1.f32 {q0,q1},[%2,:128]! \n" + "subs %3, #2 \n" + "bne 1b \n" + : "+r"(a_), "+r"(b_), "+r"(ab_), "+r"(N) : "r"(scaling) : "r8", "q0","q1","q2","q3","q4","q5","q6","q7","q8","q9", "q10","q11","q12","q13","q15","memory"); + +#else + + /* Default routine, works fine for non-arm cpus with current compilers. */ + const v4sf vscal{ld_ps1(scaling)}; + for(size_t i{0};i < Ncvec;i += 2) + { + v4sf ar4{va[2*i+0]}, ai4{va[2*i+1]}; + v4sf br4{vb[2*i+0]}, bi4{vb[2*i+1]}; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+0] = vmadd(ar4, vscal, vab[2*i+0]); + vab[2*i+1] = vmadd(ai4, vscal, vab[2*i+1]); + ar4 = va[2*i+2]; ai4 = va[2*i+3]; + br4 = vb[2*i+2]; bi4 = vb[2*i+3]; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+2] = vmadd(ar4, vscal, vab[2*i+2]); + vab[2*i+3] = vmadd(ai4, vscal, vab[2*i+3]); + } +#endif + + if(s->transform == PFFFT_REAL) + { + vab[0] = vinsert0(vab[0], abr1 + ar1*br1*scaling); + vab[1] = vinsert0(vab[1], abi1 + ai1*bi1*scaling); + } +} + +void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) +{ + const size_t Ncvec{s->Ncvec}; + const v4sf *RESTRICT va{reinterpret_cast(a)}; + const v4sf *RESTRICT vb{reinterpret_cast(b)}; + v4sf *RESTRICT vab{reinterpret_cast(ab)}; + +#ifdef __arm__ + __builtin_prefetch(va); + __builtin_prefetch(vb); + __builtin_prefetch(vab); + __builtin_prefetch(va+2); + __builtin_prefetch(vb+2); + __builtin_prefetch(vab+2); + __builtin_prefetch(va+4); + __builtin_prefetch(vb+4); + __builtin_prefetch(vab+4); + __builtin_prefetch(va+6); + __builtin_prefetch(vb+6); + __builtin_prefetch(vab+6); +#endif + + const float ar1{vextract0(va[0])}; + const float ai1{vextract0(va[1])}; + const float br1{vextract0(vb[0])}; + const float bi1{vextract0(vb[1])}; + const float abr1{vextract0(vab[0])}; + const float abi1{vextract0(vab[1])}; + + /* No inline assembly for this version. I'm not familiar enough with NEON + * assembly, and I don't know that it's needed with today's optimizers. + */ + for(size_t i{0};i < Ncvec;i += 2) + { + v4sf ar4{va[2*i+0]}, ai4{va[2*i+1]}; + v4sf br4{vb[2*i+0]}, bi4{vb[2*i+1]}; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+0] = vadd(ar4, vab[2*i+0]); + vab[2*i+1] = vadd(ai4, vab[2*i+1]); + ar4 = va[2*i+2]; ai4 = va[2*i+3]; + br4 = vb[2*i+2]; bi4 = vb[2*i+3]; + vcplxmul(ar4, ai4, br4, bi4); + vab[2*i+2] = vadd(ar4, vab[2*i+2]); + vab[2*i+3] = vadd(ai4, vab[2*i+3]); + } + + if(s->transform == PFFFT_REAL) + { + vab[0] = vinsert0(vab[0], abr1 + ar1*br1); + vab[1] = vinsert0(vab[1], abi1 + ai1*bi1); + } +} + + +void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, + pffft_direction_t direction) +{ + assert(valigned(input) && valigned(output) && valigned(work)); + pffft_transform_internal(setup, reinterpret_cast(al::assume_aligned<16>(input)), + reinterpret_cast(al::assume_aligned<16>(output)), + reinterpret_cast(al::assume_aligned<16>(work)), direction, false); +} + +void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, + float *work, pffft_direction_t direction) +{ + assert(valigned(input) && valigned(output) && valigned(work)); + pffft_transform_internal(setup, reinterpret_cast(al::assume_aligned<16>(input)), + reinterpret_cast(al::assume_aligned<16>(output)), + reinterpret_cast(al::assume_aligned<16>(work)), direction, true); +} + +#else // defined(PFFFT_SIMD_DISABLE) + +// standard routine using scalar floats, without SIMD stuff. + +namespace { + +void pffft_transform_internal(const PFFFT_Setup *setup, const float *input, float *output, + float *scratch, const pffft_direction_t direction, bool ordered) +{ + const size_t Ncvec{setup->Ncvec}; + const bool nf_odd{(setup->ifac[1]&1) != 0}; + + assert(scratch != nullptr); + + /* z-domain data for complex transforms is already ordered without SIMD. */ + if(setup->transform == PFFFT_COMPLEX) + ordered = false; + + float *buff[2]{output, scratch}; + bool ib{nf_odd != ordered}; + if(direction == PFFFT_FORWARD) + { + if(setup->transform == PFFFT_REAL) + ib = (rfftf1_ps(Ncvec*2, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); + else + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac, -1.0f) == buff[1]); + if(ordered) + { + pffft_zreorder(setup, buff[ib], buff[!ib], PFFFT_FORWARD); + ib = !ib; + } + } + else + { + if (input == buff[ib]) + ib = !ib; // may happen when finput == foutput + + if(ordered) + { + pffft_zreorder(setup, input, buff[ib], PFFFT_BACKWARD); + input = buff[ib]; + ib = !ib; + } + if(setup->transform == PFFFT_REAL) + ib = (rfftb1_ps(Ncvec*2, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac) == buff[1]); + else + ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib], setup->twiddle, setup->ifac, +1.0f) == buff[1]); + } + if(buff[ib] != output) + { + // extra copy required -- this situation should happens only when finput == foutput + assert(input==output); + for(size_t k{0};k < Ncvec;++k) + { + float a{buff[ib][2*k]}, b{buff[ib][2*k+1]}; + output[2*k] = a; output[2*k+1] = b; + } + } +} + +} // namespace + +void pffft_zreorder(const PFFFT_Setup *setup, const float *in, float *RESTRICT out, + pffft_direction_t direction) +{ + const size_t N{setup->N}; + if(setup->transform == PFFFT_COMPLEX) + { + for(size_t k{0};k < 2*N;++k) + out[k] = in[k]; + return; + } + else if(direction == PFFFT_FORWARD) + { + float x_N{in[N-1]}; + for(size_t k{N-1};k > 1;--k) + out[k] = in[k-1]; + out[0] = in[0]; + out[1] = x_N; + } + else + { + float x_N{in[1]}; + for(size_t k{1};k < N-1;++k) + out[k] = in[k+1]; + out[0] = in[0]; + out[N-1] = x_N; + } +} + +void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *s, const float *a, const float *b, + float *ab, float scaling) +{ + size_t Ncvec{s->Ncvec}; + + if(s->transform == PFFFT_REAL) + { + // take care of the fftpack ordering + ab[0] += a[0]*b[0]*scaling; + ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]*scaling; + ++ab; ++a; ++b; --Ncvec; + } + for(size_t i{0};i < Ncvec;++i) + { + float ar{a[2*i+0]}, ai{a[2*i+1]}; + const float br{b[2*i+0]}, bi{b[2*i+1]}; + vcplxmul(ar, ai, br, bi); + ab[2*i+0] += ar*scaling; + ab[2*i+1] += ai*scaling; + } +} + +void pffft_zconvolve_accumulate(const PFFFT_Setup *s, const float *a, const float *b, float *ab) +{ + size_t Ncvec{s->Ncvec}; + + if(s->transform == PFFFT_REAL) + { + // take care of the fftpack ordering + ab[0] += a[0]*b[0]; + ab[2*Ncvec-1] += a[2*Ncvec-1]*b[2*Ncvec-1]; + ++ab; ++a; ++b; --Ncvec; + } + for(size_t i{0};i < Ncvec;++i) + { + float ar{a[2*i+0]}, ai{a[2*i+1]}; + const float br{b[2*i+0]}, bi{b[2*i+1]}; + vcplxmul(ar, ai, br, bi); + ab[2*i+0] += ar; + ab[2*i+1] += ai; + } +} + + +void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, + pffft_direction_t direction) +{ + pffft_transform_internal(setup, input, output, work, direction, false); +} + +void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, + float *work, pffft_direction_t direction) +{ + pffft_transform_internal(setup, input, output, work, direction, true); +} + +#endif /* defined(PFFFT_SIMD_DISABLE) */ +/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ diff --git a/Engine/lib/openal-soft/common/pffft.h b/Engine/lib/openal-soft/common/pffft.h new file mode 100644 index 000000000..0eb321285 --- /dev/null +++ b/Engine/lib/openal-soft/common/pffft.h @@ -0,0 +1,212 @@ +/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com ) + + Based on original fortran 77 code from FFTPACKv4 from NETLIB, + authored by Dr Paul Swarztrauber of NCAR, in 1985. + + As confirmed by the NCAR fftpack software curators, the following + FFTPACKv5 license applies to FFTPACKv4 sources. My changes are + released under the same terms. + + FFTPACK license: + + http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html + + Copyright (c) 2004 the University Corporation for Atmospheric + Research ("UCAR"). All rights reserved. Developed by NCAR's + Computational and Information Systems Laboratory, UCAR, + www.cisl.ucar.edu. + + Redistribution and use of the Software in source and binary forms, + with or without modification, is permitted provided that the + following conditions are met: + + - Neither the names of NCAR's Computational and Information Systems + Laboratory, the University Corporation for Atmospheric Research, + nor the names of its sponsors or contributors may be used to + endorse or promote products derived from this Software without + specific prior written permission. + + - Redistributions of source code must retain the above copyright + notices, this list of conditions, and the disclaimer below. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer below in the + documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE + SOFTWARE. +*/ + +/* PFFFT : a Pretty Fast FFT. + * + * This is basically an adaptation of the single precision fftpack (v4) as + * found on netlib taking advantage of SIMD instructions found on CPUs such as + * Intel x86 (SSE1), PowerPC (Altivec), and Arm (NEON). + * + * For architectures where SIMD instructions aren't available, the code falls + * back to a scalar version. + * + * Restrictions: + * + * - 1D transforms only, with 32-bit single precision. + * + * - supports only transforms for inputs of length N of the form + * N=(2^a)*(3^b)*(5^c), given a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144, + * 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192. + * + * - all (float*) pointers for the functions below are expected to have a + * "SIMD-compatible" alignment, that is 16 bytes. + * + * You can allocate such buffers with the pffft_aligned_malloc function, and + * deallocate them with pffft_aligned_free (or with stuff like posix_memalign, + * aligned_alloc, etc). + * + * Note that for the z-domain data of real transforms, when in the canonical + * order (as interleaved complex numbers) both 0-frequency and half-frequency + * components, which are real, are assembled in the first entry as + * F(0)+i*F(n/2+1). The original fftpack placed F(n/2+1) at the end of the + * arrays instead. + */ + +#ifndef PFFFT_H +#define PFFFT_H + +#include +#include + +#include "almalloc.h" + + +/* opaque struct holding internal stuff (precomputed twiddle factors) this + * struct can be shared by many threads as it contains only read-only data. + */ +struct PFFFT_Setup; + +/* direction of the transform */ +enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD }; + +/* type of transform */ +enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX }; + +void pffft_destroy_setup(gsl::owner setup) noexcept; +struct PFFFTSetupDeleter { + void operator()(gsl::owner setup) const noexcept { pffft_destroy_setup(setup); } +}; +using PFFFTSetupPtr = std::unique_ptr; + +/** + * Prepare for performing transforms of size N -- the returned PFFFT_Setup + * structure is read-only so it can safely be shared by multiple concurrent + * threads. + */ +PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform); + +/** + * Perform a Fourier transform. The z-domain data is stored in the most + * efficient order for transforming back or using for convolution, and as + * such, there's no guarantee to the order of the values. If you need to have + * its content sorted in the usual way, that is as an array of interleaved + * complex numbers, either use pffft_transform_ordered, or call pffft_zreorder + * after the forward fft and before the backward fft. + * + * Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically + * you will want to scale the backward transform by 1/N. + * + * The 'work' pointer must point to an area of N (2*N for complex fft) floats, + * properly aligned. It cannot be NULL. + * + * The input and output parameters may alias. + */ +void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + +/** + * Similar to pffft_transform, but handles the complex values in the usual form + * (interleaved complex numbers). This is similar to calling + * pffft_transform(..., PFFFT_FORWARD) followed by + * pffft_zreorder(..., PFFFT_FORWARD), or + * pffft_zreorder(..., PFFFT_BACKWARD) followed by + * pffft_transform(..., PFFFT_BACKWARD), for the given direction. + * + * The input and output parameters may alias. + */ +void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction); + +/** + * Reorder the z-domain data. For PFFFT_FORWARD, it reorders from the internal + * representation to the "canonical" order (as interleaved complex numbers). + * For PFFFT_BACKWARD, it reorders from the canonical order to the internal + * order suitable for pffft_transform(..., PFFFT_BACKWARD) or + * pffft_zconvolve_accumulate. + * + * The input and output parameters should not alias. + */ +void pffft_zreorder(const PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction); + +/** + * Perform a multiplication of the z-domain data in dft_a and dft_b, and scale + * and accumulate into dft_ab. The arrays should have been obtained with + * pffft_transform(..., PFFFT_FORWARD) or pffft_zreorder(..., PFFFT_BACKWARD) + * and should *not* be in the usual order (otherwise just perform the operation + * yourself as the dft coeffs are stored as interleaved complex numbers). + * + * The operation performed is: dft_ab += (dft_a * dft_b)*scaling + * + * The dft_a, dft_b, and dft_ab parameters may alias. + */ +void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling); + +/** + * Perform a multiplication of the z-domain data in dft_a and dft_b, and + * accumulate into dft_ab. + * + * The operation performed is: dft_ab += dft_a * dft_b + * + * The dft_a, dft_b, and dft_ab parameters may alias. + */ +void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab); + + +struct PFFFTSetup { + PFFFTSetupPtr mSetup{}; + + PFFFTSetup() = default; + PFFFTSetup(const PFFFTSetup&) = delete; + PFFFTSetup(PFFFTSetup&& rhs) noexcept = default; + explicit PFFFTSetup(std::nullptr_t) noexcept { } + explicit PFFFTSetup(unsigned int n, pffft_transform_t transform) + : mSetup{pffft_new_setup(n, transform)} + { } + ~PFFFTSetup() = default; + + PFFFTSetup& operator=(const PFFFTSetup&) = delete; + PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default; + + [[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; } + + void transform(const float *input, float *output, float *work, pffft_direction_t direction) const + { pffft_transform(mSetup.get(), input, output, work, direction); } + + void transform_ordered(const float *input, float *output, float *work, + pffft_direction_t direction) const + { pffft_transform_ordered(mSetup.get(), input, output, work, direction); } + + void zreorder(const float *input, float *output, pffft_direction_t direction) const + { pffft_zreorder(mSetup.get(), input, output, direction); } + + void zconvolve_scale_accumulate(const float *dft_a, const float *dft_b, float *dft_ab, + float scaling) const + { pffft_zconvolve_scale_accumulate(mSetup.get(), dft_a, dft_b, dft_ab, scaling); } + + void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const + { pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); } +}; + +#endif // PFFFT_H diff --git a/Engine/lib/openal-soft/common/phase_shifter.h b/Engine/lib/openal-soft/common/phase_shifter.h index 0d4166bce..7febf3447 100644 --- a/Engine/lib/openal-soft/common/phase_shifter.h +++ b/Engine/lib/openal-soft/common/phase_shifter.h @@ -8,89 +8,51 @@ #endif #include -#include +#include +#include -#include "alcomplex.h" +#include "alnumbers.h" #include "alspan.h" +#include "opthelpers.h" /* Implements a wide-band +90 degree phase-shift. Note that this should be * given one sample less of a delay (FilterSize/2 - 1) compared to the direct * signal delay (FilterSize/2) to properly align. */ -template +template struct PhaseShifterT { static_assert(FilterSize >= 16, "FilterSize needs to be at least 16"); static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two"); alignas(16) std::array mCoeffs{}; - /* Some notes on this filter construction. - * - * A wide-band phase-shift filter needs a delay to maintain linearity. A - * dirac impulse in the center of a time-domain buffer represents a filter - * passing all frequencies through as-is with a pure delay. Converting that - * to the frequency domain, adjusting the phase of each frequency bin by - * +90 degrees, then converting back to the time domain, results in a FIR - * filter that applies a +90 degree wide-band phase-shift. - * - * A particularly notable aspect of the time-domain filter response is that - * every other coefficient is 0. This allows doubling the effective size of - * the filter, by storing only the non-0 coefficients and double-stepping - * over the input to apply it. - * - * Additionally, the resulting filter is independent of the sample rate. - * The same filter can be applied regardless of the device's sample rate - * and achieve the same effect. - */ PhaseShifterT() { - using complex_d = std::complex; - constexpr size_t fft_size{FilterSize}; - constexpr size_t half_size{fft_size / 2}; - - auto fftBuffer = std::make_unique(fft_size); - std::fill_n(fftBuffer.get(), fft_size, complex_d{}); - fftBuffer[half_size] = 1.0; - - forward_fft(al::as_span(fftBuffer.get(), fft_size)); - for(size_t i{0};i < half_size+1;++i) - fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()}; - for(size_t i{half_size+1};i < fft_size;++i) - fftBuffer[i] = std::conj(fftBuffer[fft_size - i]); - inverse_fft(al::as_span(fftBuffer.get(), fft_size)); - - auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1); - for(float &coeff : mCoeffs) + /* Every other coefficient is 0, so we only need to calculate and store + * the non-0 terms and double-step over the input to apply it. The + * calculated coefficients are in reverse to make applying in the time- + * domain more efficient. + */ + for(std::size_t i{0};i < FilterSize/2;++i) { - coeff = static_cast(fftiter->real() / double{fft_size}); - fftiter -= 2; + const int k{static_cast(i*2 + 1) - int{FilterSize/2}}; + + /* Calculate the Blackman window value for this coefficient. */ + const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) + / double{FilterSize}}; + const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) + - 0.0106411*std::cos(3.0*w)}; + + const double pk{al::numbers::pi * static_cast(k)}; + mCoeffs[i] = static_cast(window * (1.0-std::cos(pk)) / pk); } } - void process(al::span dst, const float *RESTRICT src) const; + void process(const al::span dst, const al::span src) const; private: #if defined(HAVE_NEON) - /* There doesn't seem to be NEON intrinsics to do this kind of stipple - * shuffling, so there's two custom methods for it. - */ - static auto shuffle_2020(float32x4_t a, float32x4_t b) - { - float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))}; - ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3); - return ret; - } - static auto shuffle_3131(float32x4_t a, float32x4_t b) - { - float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))}; - ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2); - ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3); - return ret; - } static auto unpacklo(float32x4_t a, float32x4_t b) { float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))}; @@ -109,105 +71,141 @@ private: ret = vsetq_lane_f32(d, ret, 3); return ret; } + static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) + { + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; + } #endif }; -template -inline void PhaseShifterT::process(al::span dst, const float *RESTRICT src) const +template +NOINLINE inline +void PhaseShifterT::process(const al::span dst, const al::span src) const { + auto in = src.begin(); #ifdef HAVE_SSE_INTRINSICS - if(size_t todo{dst.size()>>1}) + if(const std::size_t todo{dst.size()>>2}) { - auto *out = reinterpret_cast<__m64*>(dst.data()); - do { - __m128 r04{_mm_setzero_ps()}; - __m128 r14{_mm_setzero_ps()}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo}; + std::generate(out.begin(), out.end(), [&in,this] + { + __m128 r0{_mm_setzero_ps()}; + __m128 r1{_mm_setzero_ps()}; + __m128 r2{_mm_setzero_ps()}; + __m128 r3{_mm_setzero_ps()}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) { const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; - const __m128 s0{_mm_loadu_ps(&src[j*2])}; - const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])}; + const __m128 s0{_mm_loadu_ps(&in[j*2])}; + const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])}; + const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)}; + const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1), + reinterpret_cast(&in[j*2 + 8]))}; __m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))}; - r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs)); + r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs)); s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1)); - r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs)); + r1 = _mm_add_ps(r1, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(2, 0, 2, 0)); + r2 = _mm_add_ps(r2, _mm_mul_ps(s, coeffs)); + + s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(3, 1, 3, 1)); + r3 = _mm_add_ps(r3, _mm_mul_ps(s, coeffs)); } - src += 2; + in += 4; - __m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))}; - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - - _mm_storel_pi(out, r4); - ++out; - } while(--todo); + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); + }); } - if((dst.size()&1)) + if(const std::size_t todo{dst.size()&3}) { - __m128 r4{_mm_setzero_ps()}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&in,this] { - const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; - const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; - r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); - } - r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); - r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - - dst.back() = _mm_cvtss_f32(r4); + __m128 r4{_mm_setzero_ps()}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) + { + const __m128 coeffs{_mm_load_ps(&mCoeffs[j])}; + const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; + r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs)); + } + ++in; + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + return _mm_cvtss_f32(r4); + }); } #elif defined(HAVE_NEON) - size_t pos{0}; - if(size_t todo{dst.size()>>1}) + if(const std::size_t todo{dst.size()>>2}) { - do { - float32x4_t r04{vdupq_n_f32(0.0f)}; - float32x4_t r14{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = al::span{reinterpret_cast(dst.data()), todo}; + std::generate(out.begin(), out.end(), [&in,this] + { + float32x4_t r0{vdupq_n_f32(0.0f)}; + float32x4_t r1{vdupq_n_f32(0.0f)}; + float32x4_t r2{vdupq_n_f32(0.0f)}; + float32x4_t r3{vdupq_n_f32(0.0f)}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) { const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; - const float32x4_t s0{vld1q_f32(&src[j*2])}; - const float32x4_t s1{vld1q_f32(&src[j*2 + 4])}; + const float32x4_t s0{vld1q_f32(&in[j*2])}; + const float32x4_t s1{vld1q_f32(&in[j*2 + 4])}; + const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))}; + const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))}; + const float32x4x2_t values0{vuzpq_f32(s0, s1)}; + const float32x4x2_t values1{vuzpq_f32(s2, s3)}; - r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs); - r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs); + r0 = vmlaq_f32(r0, values0.val[0], coeffs); + r1 = vmlaq_f32(r1, values0.val[1], coeffs); + r2 = vmlaq_f32(r2, values1.val[0], coeffs); + r3 = vmlaq_f32(r3, values1.val[1], coeffs); } - src += 2; + in += 4; - float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))}; - float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))}; - - vst1_f32(&dst[pos], r2); - pos += 2; - } while(--todo); + vtranspose4(r0, r1, r2, r3); + return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); + }); } - if((dst.size()&1)) + if(const std::size_t todo{dst.size()&3}) { - float32x4_t r4{vdupq_n_f32(0.0f)}; - for(size_t j{0};j < mCoeffs.size();j+=4) + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&in,this] { - const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; - const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])}; - r4 = vmlaq_f32(r4, s, coeffs); - } - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + float32x4_t r4{vdupq_n_f32(0.0f)}; + for(std::size_t j{0};j < mCoeffs.size();j+=4) + { + const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])}; + const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])}; + r4 = vmlaq_f32(r4, s, coeffs); + } + ++in; + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + }); } #else - for(float &output : dst) + std::generate(dst.begin(), dst.end(), [&in,this] { float ret{0.0f}; - for(size_t j{0};j < mCoeffs.size();++j) - ret += src[j*2] * mCoeffs[j]; - - output = ret; - ++src; - } + for(std::size_t j{0};j < mCoeffs.size();++j) + ret += in[j*2] * mCoeffs[j]; + ++in; + return ret; + }); #endif } diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.cpp b/Engine/lib/openal-soft/common/polyphase_resampler.cpp index 14f7e40d8..6aed84edb 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.cpp +++ b/Engine/lib/openal-soft/common/polyphase_resampler.cpp @@ -3,16 +3,61 @@ #include #include +#include +#include +#include #include "alnumbers.h" #include "opthelpers.h" +using uint = unsigned int; + namespace { constexpr double Epsilon{1e-9}; -using uint = unsigned int; +#if __cpp_lib_math_special_functions >= 201603L +using std::cyl_bessel_i; + +#else + +/* The zero-order modified Bessel function of the first kind, used for the + * Kaiser window. + * + * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) + * = sum_{k=0}^inf ((x / 2)^k / k!)^2 + * + * This implementation only handles nu = 0, and isn't the most precise (it + * starts with the largest value and accumulates successively smaller values, + * compounding the rounding and precision error), but it's good enough. + */ +template +U cyl_bessel_i(T nu, U x) +{ + if(nu != T{0}) + throw std::runtime_error{"cyl_bessel_i: nu != 0"}; + + /* Start at k=1 since k=0 is trivial. */ + const double x2{x/2.0}; + double term{1.0}; + double sum{1.0}; + int k{1}; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + double last_sum{}; + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + return static_cast(sum); +} +#endif /* This is the normalized cardinal sine (sinc) function. * @@ -26,33 +71,6 @@ double Sinc(const double x) return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } -/* The zero-order modified Bessel function of the first kind, used for the - * Kaiser window. - * - * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) - * = sum_{k=0}^inf ((x / 2)^k / k!)^2 - */ -constexpr double BesselI_0(const double x) -{ - // Start at k=1 since k=0 is trivial. - const double x2{x/2.0}; - double term{1.0}; - double sum{1.0}; - int k{1}; - - // Let the integration converge until the term of the sum is no longer - // significant. - double last_sum{}; - do { - const double y{x2 / k}; - ++k; - last_sum = sum; - term *= y * y; - sum += term; - } while(sum != last_sum); - return sum; -} - /* Calculate a Kaiser window from the given beta value and a normalized k * [-1, 1]. * @@ -67,23 +85,11 @@ constexpr double BesselI_0(const double x) * * k = 2 i / M - 1, where 0 <= i <= M. */ -double Kaiser(const double b, const double k) +double Kaiser(const double beta, const double k, const double besseli_0_beta) { if(!(k >= -1.0 && k <= 1.0)) return 0.0; - return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b); -} - -// Calculates the greatest common divisor of a and b. -constexpr uint Gcd(uint x, uint y) -{ - while(y > 0) - { - const uint z{y}; - y = x % y; - x = z; - } - return x; + return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the size (order) of the Kaiser window. Rejection is in dB and @@ -124,11 +130,11 @@ constexpr double CalcKaiserBeta(const double rejection) * p -- gain compensation factor when sampling * f_t -- normalized center frequency (or cutoff; 0.5 is nyquist) */ -double SincFilter(const uint l, const double b, const double gain, const double cutoff, - const uint i) +double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain, + const double cutoff, const uint i) { const double x{static_cast(i) - l}; - return Kaiser(b, x / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); + return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x); } } // namespace @@ -137,7 +143,7 @@ double SincFilter(const uint l, const double b, const double gain, const double // that's used to cut frequencies above the destination nyquist. void PPhaseResampler::init(const uint srcRate, const uint dstRate) { - const uint gcd{Gcd(srcRate, dstRate)}; + const uint gcd{std::gcd(srcRate, dstRate)}; mP = dstRate / gcd; mQ = srcRate / gcd; @@ -145,78 +151,70 @@ void PPhaseResampler::init(const uint srcRate, const uint dstRate) * ends before the nyquist (0.5). Both are scaled by the downsampling * factor. */ - double cutoff, width; - if(mP > mQ) - { - cutoff = 0.475 / mP; - width = 0.05 / mP; - } - else - { - cutoff = 0.475 / mQ; - width = 0.05 / mQ; - } + const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP) + : std::make_tuple(0.475 / mQ, 0.05 / mQ); + // A rejection of -180 dB is used for the stop band. Round up when // calculating the left offset to avoid increasing the transition width. const uint l{(CalcKaiserOrder(180.0, width)+1) / 2}; const double beta{CalcKaiserBeta(180.0)}; + const double besseli_0_beta{cyl_bessel_i(0, beta)}; mM = l*2 + 1; mL = l; mF.resize(mM); for(uint i{0};i < mM;i++) - mF[i] = SincFilter(l, beta, mP, cutoff, i); + mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i); } // Perform the upsample-filter-downsample resampling operation using a // polyphase filter implementation. -void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out) +void PPhaseResampler::process(const al::span in, const al::span out) { - if(outN == 0) UNLIKELY + if(out.empty()) UNLIKELY return; // Handle in-place operation. std::vector workspace; - double *work{out}; - if(work == in) UNLIKELY + al::span work{out}; + if(work.data() == in.data()) UNLIKELY { - workspace.resize(outN); - work = workspace.data(); + workspace.resize(out.size()); + work = workspace; } // Resample the input. const uint p{mP}, q{mQ}, m{mM}, l{mL}; - const double *f{mF.data()}; - for(uint i{0};i < outN;i++) + const al::span f{mF}; + for(uint i{0};i < out.size();i++) { // Input starts at l to compensate for the filter delay. This will // drop any build-up from the first half of the filter. - size_t j_f{(l + q*i) % p}; - size_t j_s{(l + q*i) / p}; + std::size_t j_f{(l + q*i) % p}; + std::size_t j_s{(l + q*i) / p}; - // Only take input when 0 <= j_s < inN. + // Only take input when 0 <= j_s < in.size(). double r{0.0}; if(j_f < m) LIKELY { - size_t filt_len{(m-j_f+p-1) / p}; - if(j_s+1 > inN) LIKELY + std::size_t filt_len{(m-j_f+p-1) / p}; + if(j_s+1 > in.size()) LIKELY { - size_t skip{std::min(j_s+1 - inN, filt_len)}; + std::size_t skip{std::min(j_s+1 - in.size(), filt_len)}; j_f += p*skip; j_s -= skip; filt_len -= skip; } - if(size_t todo{std::min(j_s+1, filt_len)}) LIKELY + std::size_t todo{std::min(j_s+1, filt_len)}; + while(todo) { - do { - r += f[j_f] * in[j_s]; - j_f += p; - --j_s; - } while(--todo); + r += f[j_f] * in[j_s]; + j_f += p; --j_s; + --todo; } } work[i] = r; } // Clean up after in-place operation. - if(work != out) - std::copy_n(work, outN, out); + if(work.data() != out.data()) + std::copy(work.cbegin(), work.cend(), out.begin()); } diff --git a/Engine/lib/openal-soft/common/polyphase_resampler.h b/Engine/lib/openal-soft/common/polyphase_resampler.h index 557485bb2..0795c80d8 100644 --- a/Engine/lib/openal-soft/common/polyphase_resampler.h +++ b/Engine/lib/openal-soft/common/polyphase_resampler.h @@ -3,6 +3,8 @@ #include +#include "alspan.h" + using uint = unsigned int; @@ -35,12 +37,12 @@ using uint = unsigned int; struct PPhaseResampler { void init(const uint srcRate, const uint dstRate); - void process(const uint inN, const double *in, const uint outN, double *out); + void process(const al::span in, const al::span out); explicit operator bool() const noexcept { return !mF.empty(); } private: - uint mP, mQ, mM, mL; + uint mP{}, mQ{}, mM{}, mL{}; std::vector mF; }; diff --git a/Engine/lib/openal-soft/common/ringbuffer.cpp b/Engine/lib/openal-soft/common/ringbuffer.cpp index 0aec1d497..f4fa49bf9 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.cpp +++ b/Engine/lib/openal-soft/common/ringbuffer.cpp @@ -23,202 +23,150 @@ #include "ringbuffer.h" #include -#include +#include +#include #include +#include -#include "almalloc.h" +#include "alnumeric.h" -RingBufferPtr RingBuffer::Create(size_t sz, size_t elem_sz, int limit_writes) +auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr { - size_t power_of_two{0u}; + std::size_t power_of_two{0u}; if(sz > 0) { - power_of_two = sz; + power_of_two = sz - 1; power_of_two |= power_of_two>>1; power_of_two |= power_of_two>>2; power_of_two |= power_of_two>>4; power_of_two |= power_of_two>>8; power_of_two |= power_of_two>>16; -#if SIZE_MAX > UINT_MAX - power_of_two |= power_of_two>>32; -#endif + if constexpr(sizeof(size_t) > sizeof(uint32_t)) + power_of_two |= power_of_two>>32; } ++power_of_two; - if(power_of_two <= sz || power_of_two > std::numeric_limits::max()/elem_sz) + if(power_of_two < sz || power_of_two > std::numeric_limits::max()>>1 + || power_of_two > std::numeric_limits::max()/elem_sz) throw std::overflow_error{"Ring buffer size overflow"}; - const size_t bufbytes{power_of_two * elem_sz}; - RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{bufbytes}}; - rb->mWriteSize = limit_writes ? sz : (power_of_two-1); - rb->mSizeMask = power_of_two - 1; - rb->mElemSize = elem_sz; + const std::size_t bufbytes{power_of_two * elem_sz}; + RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{limit_writes ? sz : power_of_two, + power_of_two-1, elem_sz, bufbytes}}; return rb; } void RingBuffer::reset() noexcept { - mWritePtr.store(0, std::memory_order_relaxed); - mReadPtr.store(0, std::memory_order_relaxed); - std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{}); + mWriteCount.store(0, std::memory_order_relaxed); + mReadCount.store(0, std::memory_order_relaxed); + std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, std::byte{}); } -size_t RingBuffer::read(void *dest, size_t cnt) noexcept +auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t { - const size_t free_cnt{readSpace()}; - if(free_cnt == 0) return 0; + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + const std::size_t readable{w - r}; + if(readable == 0) return 0; - const size_t to_read{std::min(cnt, free_cnt)}; - size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask}; + const std::size_t to_read{std::min(count, readable)}; + const std::size_t read_idx{r & mSizeMask}; - size_t n1, n2; - const size_t cnt2{read_ptr + to_read}; - if(cnt2 > mSizeMask+1) - { - n1 = mSizeMask+1 - read_ptr; - n2 = cnt2 & mSizeMask; - } - else - { - n1 = to_read; - n2 = 0; - } + const std::size_t rdend{read_idx + to_read}; + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) + : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); - auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, - static_cast(dest)); - read_ptr += n1; + auto dstbytes = al::span{static_cast(dest), count*mElemSize}; + auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, + dstbytes.begin()); if(n2 > 0) - { std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); - read_ptr += n2; - } - mReadPtr.store(read_ptr, std::memory_order_release); + mReadCount.store(r+n1+n2, std::memory_order_release); return to_read; } -size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept +auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size_t { - const size_t free_cnt{readSpace()}; - if(free_cnt == 0) return 0; + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + const std::size_t readable{w - r}; + if(readable == 0) return 0; - const size_t to_read{std::min(cnt, free_cnt)}; - size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask}; + const std::size_t to_read{std::min(count, readable)}; + const std::size_t read_idx{r & mSizeMask}; - size_t n1, n2; - const size_t cnt2{read_ptr + to_read}; - if(cnt2 > mSizeMask+1) - { - n1 = mSizeMask+1 - read_ptr; - n2 = cnt2 & mSizeMask; - } - else - { - n1 = to_read; - n2 = 0; - } + const std::size_t rdend{read_idx + to_read}; + const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz) + : std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask); - auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize, - static_cast(dest)); + auto dstbytes = al::span{static_cast(dest), count*mElemSize}; + auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize, + dstbytes.begin()); if(n2 > 0) std::copy_n(mBuffer.begin(), n2*mElemSize, outiter); return to_read; } -size_t RingBuffer::write(const void *src, size_t cnt) noexcept +auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size_t { - const size_t free_cnt{writeSpace()}; - if(free_cnt == 0) return 0; + const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + const std::size_t writable{mWriteSize - (w - r)}; + if(writable == 0) return 0; - const size_t to_write{std::min(cnt, free_cnt)}; - size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask}; + const std::size_t to_write{std::min(count, writable)}; + const std::size_t write_idx{w & mSizeMask}; - size_t n1, n2; - const size_t cnt2{write_ptr + to_write}; - if(cnt2 > mSizeMask+1) - { - n1 = mSizeMask+1 - write_ptr; - n2 = cnt2 & mSizeMask; - } - else - { - n1 = to_write; - n2 = 0; - } + const std::size_t wrend{write_idx + to_write}; + const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz) + : std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask); - auto srcbytes = static_cast(src); - std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize); - write_ptr += n1; + auto srcbytes = al::span{static_cast(src), count*mElemSize}; + std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize)); if(n2 > 0) - { - std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin()); - write_ptr += n2; - } - mWritePtr.store(write_ptr, std::memory_order_release); + std::copy_n(srcbytes.cbegin() + ptrdiff_t(n1*mElemSize), n2*mElemSize, mBuffer.begin()); + mWriteCount.store(w+n1+n2, std::memory_order_release); return to_write; } -auto RingBuffer::getReadVector() const noexcept -> DataPair +auto RingBuffer::getReadVector() noexcept -> DataPair { - DataPair ret; + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + const std::size_t readable{w - r}; + const std::size_t read_idx{r & mSizeMask}; - size_t w{mWritePtr.load(std::memory_order_acquire)}; - size_t r{mReadPtr.load(std::memory_order_acquire)}; - w &= mSizeMask; - r &= mSizeMask; - const size_t free_cnt{(w-r) & mSizeMask}; - - const size_t cnt2{r + free_cnt}; - if(cnt2 > mSizeMask+1) + const std::size_t rdend{read_idx + readable}; + if(rdend > mSizeMask+1) { /* Two part vector: the rest of the buffer after the current read ptr, - * plus some from the start of the buffer. */ - ret.first.buf = const_cast(mBuffer.data() + r*mElemSize); - ret.first.len = mSizeMask+1 - r; - ret.second.buf = const_cast(mBuffer.data()); - ret.second.len = cnt2 & mSizeMask; + * plus some from the start of the buffer. + */ + return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx}, + {mBuffer.data(), rdend&mSizeMask}}; } - else - { - /* Single part vector: just the rest of the buffer */ - ret.first.buf = const_cast(mBuffer.data() + r*mElemSize); - ret.first.len = free_cnt; - ret.second.buf = nullptr; - ret.second.len = 0; - } - - return ret; + return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}}; } -auto RingBuffer::getWriteVector() const noexcept -> DataPair +auto RingBuffer::getWriteVector() noexcept -> DataPair { - DataPair ret; + const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + const std::size_t writable{mWriteSize - (w - r)}; + const std::size_t write_idx{w & mSizeMask}; - size_t w{mWritePtr.load(std::memory_order_acquire)}; - size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; - w &= mSizeMask; - r &= mSizeMask; - const size_t free_cnt{(r-w-1) & mSizeMask}; - - const size_t cnt2{w + free_cnt}; - if(cnt2 > mSizeMask+1) + const std::size_t wrend{write_idx + writable}; + if(wrend > mSizeMask+1) { /* Two part vector: the rest of the buffer after the current write ptr, - * plus some from the start of the buffer. */ - ret.first.buf = const_cast(mBuffer.data() + w*mElemSize); - ret.first.len = mSizeMask+1 - w; - ret.second.buf = const_cast(mBuffer.data()); - ret.second.len = cnt2 & mSizeMask; + * plus some from the start of the buffer. + */ + return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx}, + {mBuffer.data(), wrend&mSizeMask}}; } - else - { - ret.first.buf = const_cast(mBuffer.data() + w*mElemSize); - ret.first.len = free_cnt; - ret.second.buf = nullptr; - ret.second.len = 0; - } - - return ret; + return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}}; } diff --git a/Engine/lib/openal-soft/common/ringbuffer.h b/Engine/lib/openal-soft/common/ringbuffer.h index 2a3797b05..4493474cd 100644 --- a/Engine/lib/openal-soft/common/ringbuffer.h +++ b/Engine/lib/openal-soft/common/ringbuffer.h @@ -2,111 +2,133 @@ #define RINGBUFFER_H #include +#include +#include #include -#include +#include #include -#include "albyte.h" #include "almalloc.h" +#include "flexarray.h" /* NOTE: This lockless ringbuffer implementation is copied from JACK, extended * to include an element size. Consequently, parameters and return values for a - * size or count is in 'elements', not bytes. Additionally, it only supports + * size or count are in 'elements', not bytes. Additionally, it only supports * single-consumer/single-provider operation. */ struct RingBuffer { private: - std::atomic mWritePtr{0u}; - std::atomic mReadPtr{0u}; - size_t mWriteSize{0u}; - size_t mSizeMask{0u}; - size_t mElemSize{0u}; +#if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION) + static constexpr std::size_t sCacheAlignment{std::hardware_destructive_interference_size}; +#else + /* Assume a 64-byte cache line, the most common/likely value. */ + static constexpr std::size_t sCacheAlignment{64}; +#endif + alignas(sCacheAlignment) std::atomic mWriteCount{0u}; + alignas(sCacheAlignment) std::atomic mReadCount{0u}; - al::FlexArray mBuffer; + alignas(sCacheAlignment) const std::size_t mWriteSize; + const std::size_t mSizeMask; + const std::size_t mElemSize; + + al::FlexArray mBuffer; public: struct Data { - al::byte *buf; - size_t len; + std::byte *buf; + std::size_t len; }; using DataPair = std::pair; - - RingBuffer(const size_t count) : mBuffer{count} { } + RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize, + const std::size_t numbytes) + : mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numbytes} + { } /** Reset the read and write pointers to zero. This is not thread safe. */ - void reset() noexcept; + auto reset() noexcept -> void; + + /** + * Return the number of elements available for reading. This is the number + * of elements in front of the read pointer and behind the write pointer. + */ + [[nodiscard]] auto readSpace() const noexcept -> std::size_t + { + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + /* mWriteCount is never more than mWriteSize greater than mReadCount. */ + return w - r; + } + + /** + * The copying data reader. Copy at most `count' elements into `dest'. + * Returns the actual number of elements copied. + */ + [[nodiscard]] auto read(void *dest, std::size_t count) noexcept -> std::size_t; + /** + * The copying data reader w/o read pointer advance. Copy at most `count' + * elements into `dest'. Returns the actual number of elements copied. + */ + [[nodiscard]] auto peek(void *dest, std::size_t count) const noexcept -> std::size_t; /** * The non-copying data reader. Returns two ringbuffer data pointers that * hold the current readable data. If the readable data is in one segment * the second segment has zero length. */ - DataPair getReadVector() const noexcept; + [[nodiscard]] auto getReadVector() noexcept -> DataPair; + /** Advance the read pointer `count' places. */ + auto readAdvance(std::size_t count) noexcept -> void + { + const std::size_t w{mWriteCount.load(std::memory_order_acquire)}; + const std::size_t r{mReadCount.load(std::memory_order_relaxed)}; + [[maybe_unused]] const std::size_t readable{w - r}; + assert(readable >= count); + mReadCount.store(r+count, std::memory_order_release); + } + + + /** + * Return the number of elements available for writing. This is the total + * number of writable elements excluding what's readable (already written). + */ + [[nodiscard]] auto writeSpace() const noexcept -> std::size_t + { return mWriteSize - readSpace(); } + + /** + * The copying data writer. Copy at most `count' elements from `src'. Returns + * the actual number of elements copied. + */ + [[nodiscard]] auto write(const void *src, std::size_t count) noexcept -> std::size_t; + /** * The non-copying data writer. Returns two ringbuffer data pointers that * hold the current writeable data. If the writeable data is in one segment * the second segment has zero length. */ - DataPair getWriteVector() const noexcept; - - /** - * Return the number of elements available for reading. This is the number - * of elements in front of the read pointer and behind the write pointer. - */ - size_t readSpace() const noexcept + [[nodiscard]] auto getWriteVector() noexcept -> DataPair; + /** Advance the write pointer `count' places. */ + auto writeAdvance(std::size_t count) noexcept -> void { - const size_t w{mWritePtr.load(std::memory_order_acquire)}; - const size_t r{mReadPtr.load(std::memory_order_acquire)}; - return (w-r) & mSizeMask; + const std::size_t w{mWriteCount.load(std::memory_order_relaxed)}; + const std::size_t r{mReadCount.load(std::memory_order_acquire)}; + [[maybe_unused]] const std::size_t writable{mWriteSize - (w - r)}; + assert(writable >= count); + mWriteCount.store(w+count, std::memory_order_release); } - /** - * The copying data reader. Copy at most `cnt' elements into `dest'. - * Returns the actual number of elements copied. - */ - size_t read(void *dest, size_t cnt) noexcept; - /** - * The copying data reader w/o read pointer advance. Copy at most `cnt' - * elements into `dest'. Returns the actual number of elements copied. - */ - size_t peek(void *dest, size_t cnt) const noexcept; - /** Advance the read pointer `cnt' places. */ - void readAdvance(size_t cnt) noexcept - { mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); } - - - /** - * Return the number of elements available for writing. This is the number - * of elements in front of the write pointer and behind the read pointer. - */ - size_t writeSpace() const noexcept - { - const size_t w{mWritePtr.load(std::memory_order_acquire)}; - const size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask}; - return (r-w-1) & mSizeMask; - } - - /** - * The copying data writer. Copy at most `cnt' elements from `src'. Returns - * the actual number of elements copied. - */ - size_t write(const void *src, size_t cnt) noexcept; - /** Advance the write pointer `cnt' places. */ - void writeAdvance(size_t cnt) noexcept - { mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); } - - size_t getElemSize() const noexcept { return mElemSize; } + [[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; } /** * Create a new ringbuffer to hold at least `sz' elements of `elem_sz' - * bytes. The number of elements is rounded up to the next power of two - * (even if it is already a power of two, to ensure the requested amount - * can be written). + * bytes. The number of elements is rounded up to a power of two. If + * `limit_writes' is true, the writable space will be limited to `sz' + * elements regardless of the rounded size. */ - static std::unique_ptr Create(size_t sz, size_t elem_sz, int limit_writes); + [[nodiscard]] static + auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr; DEF_FAM_NEWDEL(RingBuffer, mBuffer) }; diff --git a/Engine/lib/openal-soft/common/strutils.cpp b/Engine/lib/openal-soft/common/strutils.cpp index d0418eff2..4bcd41194 100644 --- a/Engine/lib/openal-soft/common/strutils.cpp +++ b/Engine/lib/openal-soft/common/strutils.cpp @@ -5,36 +5,37 @@ #include - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include -std::string wstr_to_utf8(const WCHAR *wstr) +#include "alstring.h" + +std::string wstr_to_utf8(std::wstring_view wstr) { std::string ret; - int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr); + const int len{WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), nullptr, 0, + nullptr, nullptr)}; if(len > 0) { - ret.resize(len); - WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr); - ret.pop_back(); + ret.resize(static_cast(len)); + WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), ret.data(), len, + nullptr, nullptr); } return ret; } -std::wstring utf8_to_wstr(const char *str) +std::wstring utf8_to_wstr(std::string_view str) { std::wstring ret; - int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); + const int len{MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), nullptr, 0)}; if(len > 0) { - ret.resize(len); - MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len); - ret.pop_back(); + ret.resize(static_cast(len)); + MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), ret.data(), len); } return ret; @@ -43,21 +44,25 @@ std::wstring utf8_to_wstr(const char *str) namespace al { -al::optional getenv(const char *envname) +std::optional getenv(const char *envname) { +#ifdef _GAMING_XBOX + const char *str{::getenv(envname)}; +#else const char *str{std::getenv(envname)}; - if(str && str[0] != '\0') +#endif + if(str && *str != '\0') return str; - return al::nullopt; + return std::nullopt; } #ifdef _WIN32 -al::optional getenv(const WCHAR *envname) +std::optional getenv(const WCHAR *envname) { const WCHAR *str{_wgetenv(envname)}; - if(str && str[0] != L'\0') + if(str && *str != L'\0') return str; - return al::nullopt; + return std::nullopt; } #endif diff --git a/Engine/lib/openal-soft/common/strutils.h b/Engine/lib/openal-soft/common/strutils.h index 0c7a0e226..3644847c2 100644 --- a/Engine/lib/openal-soft/common/strutils.h +++ b/Engine/lib/openal-soft/common/strutils.h @@ -1,22 +1,22 @@ #ifndef AL_STRUTILS_H #define AL_STRUTILS_H +#include #include -#include "aloptional.h" - #ifdef _WIN32 -#include +#include +#include -std::string wstr_to_utf8(const wchar_t *wstr); -std::wstring utf8_to_wstr(const char *str); +std::string wstr_to_utf8(std::wstring_view wstr); +std::wstring utf8_to_wstr(std::string_view str); #endif namespace al { -al::optional getenv(const char *envname); +std::optional getenv(const char *envname); #ifdef _WIN32 -al::optional getenv(const wchar_t *envname); +std::optional getenv(const wchar_t *envname); #endif } // namespace al diff --git a/Engine/lib/openal-soft/common/threads.h b/Engine/lib/openal-soft/common/threads.h deleted file mode 100644 index 1cdb5d8f6..000000000 --- a/Engine/lib/openal-soft/common/threads.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef AL_THREADS_H -#define AL_THREADS_H - -#if defined(__GNUC__) && defined(__i386__) -/* force_align_arg_pointer is required for proper function arguments aligning - * when SSE code is used. Some systems (Windows, QNX) do not guarantee our - * thread functions will be properly aligned on the stack, even though GCC may - * generate code with the assumption that it is. */ -#define FORCE_ALIGN __attribute__((force_align_arg_pointer)) -#else -#define FORCE_ALIGN -#endif - -#if defined(__APPLE__) -#include -#elif !defined(_WIN32) -#include -#endif - -void althrd_setname(const char *name); - -namespace al { - -class semaphore { -#ifdef _WIN32 - using native_type = void*; -#elif defined(__APPLE__) - using native_type = dispatch_semaphore_t; -#else - using native_type = sem_t; -#endif - native_type mSem; - -public: - semaphore(unsigned int initial=0); - semaphore(const semaphore&) = delete; - ~semaphore(); - - semaphore& operator=(const semaphore&) = delete; - - void post(); - void wait() noexcept; - bool try_wait() noexcept; -}; - -} // namespace al - -#endif /* AL_THREADS_H */ diff --git a/Engine/lib/openal-soft/common/vecmat.h b/Engine/lib/openal-soft/common/vecmat.h index a45f262f6..90b5d1469 100644 --- a/Engine/lib/openal-soft/common/vecmat.h +++ b/Engine/lib/openal-soft/common/vecmat.h @@ -1,6 +1,7 @@ #ifndef COMMON_VECMAT_H #define COMMON_VECMAT_H +#include #include #include #include @@ -11,22 +12,24 @@ namespace alu { -template -class VectorR { - static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) T mVals[4]; +class Vector { + alignas(16) std::array mVals{}; public: - constexpr VectorR() noexcept = default; - constexpr VectorR(const VectorR&) noexcept = default; - constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { } + constexpr Vector() noexcept = default; + constexpr Vector(const Vector&) noexcept = default; + constexpr Vector(Vector&&) noexcept = default; + constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { } - constexpr VectorR& operator=(const VectorR&) noexcept = default; + constexpr auto operator=(const Vector&) noexcept -> Vector& = default; + constexpr auto operator=(Vector&&) noexcept -> Vector& = default; - constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; } - constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; } + [[nodiscard]] constexpr + auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; } - constexpr VectorR& operator+=(const VectorR &rhs) noexcept + constexpr auto operator+=(const Vector &rhs) noexcept -> Vector& { mVals[0] += rhs.mVals[0]; mVals[1] += rhs.mVals[1]; @@ -35,85 +38,85 @@ public: return *this; } - constexpr VectorR operator-(const VectorR &rhs) const noexcept + [[nodiscard]] constexpr + auto operator-(const Vector &rhs) const noexcept -> Vector { - return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], + return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1], mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]}; } - constexpr T normalize(T limit = std::numeric_limits::epsilon()) + constexpr auto normalize(float limit = std::numeric_limits::epsilon()) -> float { - limit = std::max(limit, std::numeric_limits::epsilon()); - const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; + limit = std::max(limit, std::numeric_limits::epsilon()); + const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]}; if(length_sqr > limit*limit) { - const T length{std::sqrt(length_sqr)}; - T inv_length{T{1}/length}; + const auto length = float{std::sqrt(length_sqr)}; + auto inv_length = float{1.0f / length}; mVals[0] *= inv_length; mVals[1] *= inv_length; mVals[2] *= inv_length; return length; } - mVals[0] = mVals[1] = mVals[2] = T{0}; - return T{0}; + mVals[0] = mVals[1] = mVals[2] = 0.0f; + return 0.0f; } - constexpr VectorR cross_product(const alu::VectorR &rhs) const noexcept + [[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector { - return VectorR{ + return Vector{ mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1], mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2], mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0], - T{0}}; + 0.0f}; } - constexpr T dot_product(const alu::VectorR &rhs) const noexcept + [[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float { return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; } }; -using Vector = VectorR; -template -class MatrixR { - static_assert(std::is_floating_point::value, "Must use floating-point types"); - alignas(16) T mVals[16]; +class Matrix { + alignas(16) std::array mVals{}; public: - constexpr MatrixR() noexcept = default; - constexpr MatrixR(const MatrixR&) noexcept = default; - constexpr explicit MatrixR( - T aa, T ab, T ac, T ad, - T ba, T bb, T bc, T bd, - T ca, T cb, T cc, T cd, - T da, T db, T dc, T dd) noexcept - : mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd} + constexpr Matrix() noexcept = default; + constexpr Matrix(const Matrix&) noexcept = default; + constexpr Matrix(Matrix&&) noexcept = default; + constexpr explicit Matrix( + float aa, float ab, float ac, float ad, + float ba, float bb, float bc, float bd, + float ca, float cb, float cc, float cd, + float da, float db, float dc, float dd) noexcept + : mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}} { } - constexpr MatrixR& operator=(const MatrixR&) noexcept = default; + constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default; + constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default; - constexpr auto operator[](size_t idx) noexcept { return al::span{&mVals[idx*4], 4}; } - constexpr auto operator[](size_t idx) const noexcept - { return al::span{&mVals[idx*4], 4}; } + [[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept + { return al::span{&mVals[idx*4], 4}; } + [[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept + { return al::span{&mVals[idx*4], 4}; } - static constexpr MatrixR Identity() noexcept + [[nodiscard]] static constexpr auto Identity() noexcept -> Matrix { - return MatrixR{ - T{1}, T{0}, T{0}, T{0}, - T{0}, T{1}, T{0}, T{0}, - T{0}, T{0}, T{1}, T{0}, - T{0}, T{0}, T{0}, T{1}}; + return Matrix{ + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + } + + [[nodiscard]] friend constexpr + auto operator*(const Matrix &mtx, const Vector &vec) noexcept -> Vector + { + return Vector{ + vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], + vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], + vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], + vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]}; } }; -using Matrix = MatrixR; - -template -constexpr VectorR operator*(const MatrixR &mtx, const VectorR &vec) noexcept -{ - return VectorR{ - vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0], - vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1], - vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2], - vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]}; -} } // namespace alu diff --git a/Engine/lib/openal-soft/common/vector.h b/Engine/lib/openal-soft/common/vector.h index 1b69d6a77..364735c67 100644 --- a/Engine/lib/openal-soft/common/vector.h +++ b/Engine/lib/openal-soft/common/vector.h @@ -1,13 +1,14 @@ #ifndef AL_VECTOR_H #define AL_VECTOR_H +#include #include #include "almalloc.h" namespace al { -template +template using vector = std::vector>; } // namespace al diff --git a/Engine/lib/openal-soft/common/win_main_utf8.h b/Engine/lib/openal-soft/common/win_main_utf8.h index 077af533d..81734242f 100644 --- a/Engine/lib/openal-soft/common/win_main_utf8.h +++ b/Engine/lib/openal-soft/common/win_main_utf8.h @@ -20,14 +20,20 @@ #define STATIC_CAST(...) static_cast<__VA_ARGS__> #define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__> +#define MAYBE_UNUSED [[maybe_unused]] #else #define STATIC_CAST(...) (__VA_ARGS__) #define REINTERPRET_CAST(...) (__VA_ARGS__) +#ifdef __GNUC__ +#define MAYBE_UNUSED __attribute__((__unused__)) +#else +#define MAYBE_UNUSED +#endif #endif -static FILE *my_fopen(const char *fname, const char *mode) +MAYBE_UNUSED static FILE *my_fopen(const char *fname, const char *mode) { wchar_t *wname=NULL, *wmode=NULL; int namelen, modelen; @@ -44,10 +50,11 @@ static FILE *my_fopen(const char *fname, const char *mode) } #ifdef __cplusplus - auto strbuf = std::make_unique(static_cast(namelen)+modelen); + auto strbuf = std::make_unique(static_cast(namelen) + + static_cast(modelen)); wname = strbuf.get(); #else - wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + modelen); + wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + (size_t)modelen); #endif wmode = wname + namelen; MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen); diff --git a/Engine/lib/openal-soft/config.h.in b/Engine/lib/openal-soft/config.h.in index 477d8c77b..9013c4731 100644 --- a/Engine/lib/openal-soft/config.h.in +++ b/Engine/lib/openal-soft/config.h.in @@ -1,21 +1,12 @@ -/* Define if deprecated EAX extensions are enabled */ -#cmakedefine ALSOFT_EAX +/* Define the alignment attribute for externally callable functions. */ +#define FORCE_ALIGN @ALSOFT_FORCE_ALIGN@ /* Define if HRTF data is embedded in the library */ #cmakedefine ALSOFT_EMBED_HRTF_DATA -/* Define if we have the posix_memalign function */ -#cmakedefine HAVE_POSIX_MEMALIGN - -/* Define if we have the _aligned_malloc function */ -#cmakedefine HAVE__ALIGNED_MALLOC - /* Define if we have the proc_pidpath function */ #cmakedefine HAVE_PROC_PIDPATH -/* Define if we have the getopt function */ -#cmakedefine HAVE_GETOPT - /* Define if we have DBus/RTKit */ #cmakedefine HAVE_RTKIT @@ -82,9 +73,6 @@ /* Define if we have pthread_np.h */ #cmakedefine HAVE_PTHREAD_NP_H -/* Define if we have malloc.h */ -#cmakedefine HAVE_MALLOC_H - /* Define if we have cpuid.h */ #cmakedefine HAVE_CPUID_H @@ -94,9 +82,6 @@ /* Define if we have guiddef.h */ #cmakedefine HAVE_GUIDDEF_H -/* Define if we have initguid.h */ -#cmakedefine HAVE_INITGUID_H - /* Define if we have GCC's __get_cpuid() */ #cmakedefine HAVE_GCC_GET_CPUID @@ -117,3 +102,6 @@ /* Define the installation data directory */ #cmakedefine ALSOFT_INSTALL_DATADIR "@ALSOFT_INSTALL_DATADIR@" + +/* Define whether build alsoft for winuwp */ +#cmakedefine ALSOFT_UWP diff --git a/Engine/lib/openal-soft/core/ambdec.cpp b/Engine/lib/openal-soft/core/ambdec.cpp index 8ca182c49..ba3a4b8b3 100644 --- a/Engine/lib/openal-soft/core/ambdec.cpp +++ b/Engine/lib/openal-soft/core/ambdec.cpp @@ -8,12 +8,13 @@ #include #include #include +#include +#include #include #include #include #include "albit.h" -#include "alfstream.h" #include "alspan.h" #include "opthelpers.h" @@ -42,21 +43,22 @@ enum class ReaderScope { HFMatrix, }; -#ifdef __USE_MINGW_ANSI_STDIO -[[gnu::format(gnu_printf,2,3)]] +#ifdef __MINGW32__ +[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else [[gnu::format(printf,2,3)]] #endif -al::optional make_error(size_t linenum, const char *fmt, ...) +std::optional make_error(size_t linenum, const char *fmt, ...) { - al::optional ret; + std::optional ret; auto &str = ret.emplace(); str.resize(256); - int printed{std::snprintf(const_cast(str.data()), str.length(), "Line %zu: ", linenum)}; + int printed{std::snprintf(str.data(), str.length(), "Line %zu: ", linenum)}; if(printed < 0) printed = 0; auto plen = std::min(static_cast(printed), str.length()); + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args, args2; va_start(args, fmt); va_copy(args2, args); @@ -68,6 +70,7 @@ al::optional make_error(size_t linenum, const char *fmt, ...) } va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ return ret; } @@ -77,9 +80,9 @@ al::optional make_error(size_t linenum, const char *fmt, ...) AmbDecConf::~AmbDecConf() = default; -al::optional AmbDecConf::load(const char *fname) noexcept +std::optional AmbDecConf::load(const char *fname) noexcept { - al::ifstream f{fname}; + std::ifstream f{std::filesystem::u8path(fname)}; if(!f.is_open()) return std::string("Failed to open file \"")+fname+"\""; @@ -111,7 +114,7 @@ al::optional AmbDecConf::load(const char *fname) noexcept { if(command == "add_spkr") { - if(speaker_pos == NumSpeakers) + if(speaker_pos == Speakers.size()) return make_error(linenum, "Too many speakers specified"); AmbDecConf::SpeakerConf &spkr = Speakers[speaker_pos++]; @@ -127,7 +130,7 @@ al::optional AmbDecConf::load(const char *fname) noexcept else if(scope == ReaderScope::LFMatrix || scope == ReaderScope::HFMatrix) { auto &gains = (scope == ReaderScope::LFMatrix) ? LFOrderGain : HFOrderGain; - auto *matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; + auto matrix = (scope == ReaderScope::LFMatrix) ? LFMatrix : HFMatrix; auto &pos = (scope == ReaderScope::LFMatrix) ? lfmatrix_pos : hfmatrix_pos; if(command == "order_gain") @@ -139,13 +142,13 @@ al::optional AmbDecConf::load(const char *fname) noexcept { --toread; istr >> value; - if(curgain < al::size(gains)) + if(curgain < std::size(gains)) gains[curgain++] = value; } } else if(command == "add_row") { - if(pos == NumSpeakers) + if(pos == Speakers.size()) return make_error(linenum, "Too many matrix rows specified"); unsigned int mask{ChanMask}; @@ -205,12 +208,13 @@ al::optional AmbDecConf::load(const char *fname) noexcept } else if(command == "/dec/speakers") { - if(NumSpeakers) + if(!Speakers.empty()) return make_error(linenum, "Duplicate speakers"); - istr >> NumSpeakers; - if(!NumSpeakers) - return make_error(linenum, "Invalid speakers: %zu", NumSpeakers); - Speakers = std::make_unique(NumSpeakers); + size_t numspeakers{}; + istr >> numspeakers; + if(!numspeakers) + return make_error(linenum, "Invalid speakers: %zu", numspeakers); + Speakers.resize(numspeakers); } else if(command == "/dec/coeff_scale") { @@ -243,22 +247,22 @@ al::optional AmbDecConf::load(const char *fname) noexcept } else if(command == "/speakers/{") { - if(!NumSpeakers) + if(Speakers.empty()) return make_error(linenum, "Speakers defined without a count"); scope = ReaderScope::Speakers; } else if(command == "/lfmatrix/{" || command == "/hfmatrix/{" || command == "/matrix/{") { - if(!NumSpeakers) + if(Speakers.empty()) return make_error(linenum, "Matrix defined without a speaker count"); if(!ChanMask) return make_error(linenum, "Matrix defined without a channel mask"); - if(!Matrix) + if(Matrix.empty()) { - Matrix = std::make_unique(NumSpeakers * FreqBands); - LFMatrix = Matrix.get(); - HFMatrix = LFMatrix + NumSpeakers*(FreqBands-1); + Matrix.resize(Speakers.size() * FreqBands); + LFMatrix = al::span{Matrix}.first(Speakers.size()); + HFMatrix = al::span{Matrix}.subspan(Speakers.size()*(FreqBands-1)); } if(FreqBands == 1) @@ -285,13 +289,13 @@ al::optional AmbDecConf::load(const char *fname) noexcept if(!is_at_end(buffer, endpos)) return make_error(linenum, "Extra junk on end: %s", buffer.substr(endpos).c_str()); - if(speaker_pos < NumSpeakers || hfmatrix_pos < NumSpeakers - || (FreqBands == 2 && lfmatrix_pos < NumSpeakers)) + if(speaker_pos < Speakers.size() || hfmatrix_pos < Speakers.size() + || (FreqBands == 2 && lfmatrix_pos < Speakers.size())) return make_error(linenum, "Incomplete decoder definition"); if(CoeffScale == AmbDecScale::Unset) return make_error(linenum, "No coefficient scaling defined"); - return al::nullopt; + return std::nullopt; } else return make_error(linenum, "Unexpected command: %s", command.c_str()); diff --git a/Engine/lib/openal-soft/core/ambdec.h b/Engine/lib/openal-soft/core/ambdec.h index 7f7397817..587a30c66 100644 --- a/Engine/lib/openal-soft/core/ambdec.h +++ b/Engine/lib/openal-soft/core/ambdec.h @@ -3,9 +3,11 @@ #include #include +#include #include +#include -#include "aloptional.h" +#include "alspan.h" #include "core/ambidefs.h" /* Helpers to read .ambdec configuration files. */ @@ -34,22 +36,21 @@ struct AmbDecConf { float Elevation{0.0f}; std::string Connection; }; - size_t NumSpeakers{0}; - std::unique_ptr Speakers; + std::vector Speakers; using CoeffArray = std::array; - std::unique_ptr Matrix; + std::vector Matrix; /* Unused when FreqBands == 1 */ - float LFOrderGain[MaxAmbiOrder+1]{}; - CoeffArray *LFMatrix; + std::array LFOrderGain{}; + al::span LFMatrix; - float HFOrderGain[MaxAmbiOrder+1]{}; - CoeffArray *HFMatrix; + std::array HFOrderGain{}; + al::span HFMatrix; ~AmbDecConf(); - al::optional load(const char *fname) noexcept; + std::optional load(const char *fname) noexcept; }; #endif /* CORE_AMBDEC_H */ diff --git a/Engine/lib/openal-soft/core/ambidefs.cpp b/Engine/lib/openal-soft/core/ambidefs.cpp index 70d6f356e..2389ce6bd 100644 --- a/Engine/lib/openal-soft/core/ambidefs.cpp +++ b/Engine/lib/openal-soft/core/ambidefs.cpp @@ -21,25 +21,25 @@ constexpr auto inv_sqrt3f = static_cast(1.0/al::numbers::sqrt3); * will result in that channel being subsequently decoded for second-order as * if it was a first-order decoder for that same speaker array. */ -constexpr std::array,MaxAmbiOrder+1> HFScales{{ - {{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, - {{ 4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, - {{ 2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f }}, - {{ 2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f }}, - /* 1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f */ -}}; +constexpr std::array HFScales{ + std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, + std::array{4.000000000e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, + std::array{2.981423970e+00f, 2.309401077e+00f, 1.192569588e+00f, 7.189495850e-01f}, + std::array{2.359168820e+00f, 2.031565936e+00f, 1.444598386e+00f, 7.189495850e-01f}, + /*std::array{1.947005434e+00f, 1.764337084e+00f, 1.424707344e+00f, 9.755104127e-01f, 4.784482742e-01f}, */ +}; /* Same as above, but using a 10-point horizontal-only speaker array. Should * only be used when the device is mixing in 2D B-Format for horizontal-only * output. */ -constexpr std::array,MaxAmbiOrder+1> HFScales2D{{ - {{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, - {{ 2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, - {{ 1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f }}, - {{ 1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f }}, - /* 1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f */ -}}; +constexpr std::array HFScales2D{ + std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, + std::array{2.236067977e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, + std::array{1.825741858e+00f, 1.581138830e+00f, 9.128709292e-01f, 6.050756345e-01f}, + std::array{1.581138830e+00f, 1.460781803e+00f, 1.118033989e+00f, 6.050756345e-01f}, + /*std::array{1.414213562e+00f, 1.344997024e+00f, 1.144122806e+00f, 8.312538756e-01f, 4.370160244e-01f}, */ +}; /* This calculates a first-order "upsampler" matrix. It combines a first-order @@ -49,17 +49,17 @@ constexpr std::array,MaxAmbiOrder+1> HFScales2D * signal. While not perfect, this should accurately encode a lower-order * signal into a higher-order signal. */ -constexpr std::array,8> FirstOrderDecoder{{ - {{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }}, - {{ 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }}, - {{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, }}, - {{ 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, }}, - {{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }}, - {{ 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }}, - {{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, }}, - {{ 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, }}, -}}; -constexpr std::array FirstOrderEncoder{{ +constexpr std::array FirstOrderDecoder{ + std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f}, + std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, 1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, 1.250000000e-01f}, + std::array{1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f, -1.250000000e-01f}, +}; +constexpr std::array FirstOrderEncoder{ CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( inv_sqrt3f, inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, inv_sqrt3f, inv_sqrt3f), @@ -68,25 +68,25 @@ constexpr std::array FirstOrderEncoder{{ CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs(-inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), -}}; +}; static_assert(FirstOrderDecoder.size() == FirstOrderEncoder.size(), "First-order mismatch"); /* This calculates a 2D first-order "upsampler" matrix. Same as the first-order * matrix, just using a more optimized speaker array for horizontal-only * content. */ -constexpr std::array,4> FirstOrder2DDecoder{{ - {{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f, }}, - {{ 2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f, }}, - {{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f, }}, - {{ 2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f, }}, -}}; -constexpr std::array FirstOrder2DEncoder{{ +constexpr std::array FirstOrder2DDecoder{ + std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, 2.041241452e-01f}, + std::array{2.500000000e-01f, 2.041241452e-01f, 0.0f, -2.041241452e-01f}, + std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, 2.041241452e-01f}, + std::array{2.500000000e-01f, -2.041241452e-01f, 0.0f, -2.041241452e-01f}, +}; +constexpr std::array FirstOrder2DEncoder{ CalcAmbiCoeffs( inv_sqrt2f, 0.0f, inv_sqrt2f), CalcAmbiCoeffs( inv_sqrt2f, 0.0f, -inv_sqrt2f), CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, inv_sqrt2f), CalcAmbiCoeffs(-inv_sqrt2f, 0.0f, -inv_sqrt2f), -}}; +}; static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-order 2D mismatch"); @@ -94,21 +94,21 @@ static_assert(FirstOrder2DDecoder.size() == FirstOrder2DEncoder.size(), "First-o * matrix, just using a slightly more dense speaker array suitable for second- * order content. */ -constexpr std::array,12> SecondOrderDecoder{{ - {{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, - {{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, - {{ 8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, - {{ 8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f, }}, - {{ 8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f, }}, - {{ 8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f, }}, -}}; -constexpr std::array SecondOrderEncoder{{ +constexpr std::array SecondOrderDecoder{ + std::array{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, -7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + std::array{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, -1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + std::array{8.333333333e-02f, 0.000000000e+00f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, 1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, -7.588274978e-02f, -1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, 7.588274978e-02f, 1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, + std::array{8.333333333e-02f, 0.000000000e+00f, 7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, -1.591525047e-02f, -1.443375673e-01f, 1.167715449e-01f}, + std::array{8.333333333e-02f, 1.227808683e-01f, 0.000000000e+00f, 7.588274978e-02f, 1.443375673e-01f, 0.000000000e+00f, -9.316949906e-02f, 0.000000000e+00f, -7.216878365e-02f}, + std::array{8.333333333e-02f, -7.588274978e-02f, -1.227808683e-01f, 0.000000000e+00f, 0.000000000e+00f, 1.443375673e-01f, 1.090847495e-01f, 0.000000000e+00f, -4.460276122e-02f}, +}; +constexpr std::array SecondOrderEncoder{ CalcAmbiCoeffs( 0.000000000e+00f, -5.257311121e-01f, 8.506508084e-01f), CalcAmbiCoeffs(-8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f), CalcAmbiCoeffs(-5.257311121e-01f, 8.506508084e-01f, 0.000000000e+00f), @@ -121,29 +121,29 @@ constexpr std::array SecondOrderEncoder{{ CalcAmbiCoeffs( 0.000000000e+00f, 5.257311121e-01f, -8.506508084e-01f), CalcAmbiCoeffs( 8.506508084e-01f, 0.000000000e+00f, 5.257311121e-01f), CalcAmbiCoeffs(-5.257311121e-01f, -8.506508084e-01f, 0.000000000e+00f), -}}; +}; static_assert(SecondOrderDecoder.size() == SecondOrderEncoder.size(), "Second-order mismatch"); /* This calculates a 2D second-order "upsampler" matrix. Same as the second- * order matrix, just using a more optimized speaker array for horizontal-only * content. */ -constexpr std::array,6> SecondOrder2DDecoder{{ - {{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, - {{ 1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }}, - {{ 1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, - {{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, - {{ 1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f, }}, - {{ 1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f, }}, -}}; -constexpr std::array SecondOrder2DEncoder{{ +constexpr std::array SecondOrder2DDecoder{ + std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, 1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, + std::array{1.666666667e-01f, -1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f}, + std::array{1.666666667e-01f, -9.622504486e-02f, 0.0f, -1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, -1.666666667e-01f, -1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, + std::array{1.666666667e-01f, 1.924500897e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.721325932e-01f}, + std::array{1.666666667e-01f, 9.622504486e-02f, 0.0f, 1.666666667e-01f, 1.490711985e-01f, 0.0f, 0.0f, 0.0f, 8.606629658e-02f}, +}; +constexpr std::array SecondOrder2DEncoder{ CalcAmbiCoeffs(-0.50000000000f, 0.0f, 0.86602540379f), CalcAmbiCoeffs(-1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs(-0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, -0.86602540379f), CalcAmbiCoeffs( 1.00000000000f, 0.0f, 0.00000000000f), CalcAmbiCoeffs( 0.50000000000f, 0.0f, 0.86602540379f), -}}; +}; static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(), "Second-order 2D mismatch"); @@ -152,29 +152,29 @@ static_assert(SecondOrder2DDecoder.size() == SecondOrder2DEncoder.size(), * matrix, just using a more dense speaker array suitable for third-order * content. */ -constexpr std::array,20> ThirdOrderDecoder{{ - {{ 5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f, }}, - {{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }}, - {{ 5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }}, - {{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f, }}, - {{ 5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f, }}, - {{ 5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f, }}, - {{ 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f, }}, -}}; -constexpr std::array ThirdOrderEncoder{{ +constexpr std::array ThirdOrderDecoder{ + std::array{5.000000000e-02f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, 7.944389175e-02f, 0.000000000e+00f, 2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, -1.256118221e-01f, 0.000000000e+00f, 1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, 6.454972244e-02f, 9.045084972e-02f, 0.000000000e+00f, -1.232790000e-02f, 1.256118221e-01f, 0.000000000e+00f, -1.126112056e-01f, -7.944389175e-02f, 0.000000000e+00f, -2.421151497e-02f, 0.000000000e+00f}, + std::array{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + std::array{5.000000000e-02f, 8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, -7.763237543e-02f, 0.000000000e+00f, -2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + std::array{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, 3.090169944e-02f, -6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, -1.497759251e-01f, 0.000000000e+00f, -7.763237543e-02f}, + std::array{5.000000000e-02f, -8.090169944e-02f, 0.000000000e+00f, -3.090169944e-02f, 6.454972244e-02f, 0.000000000e+00f, -5.590169944e-02f, 0.000000000e+00f, -7.216878365e-02f, 7.763237543e-02f, 0.000000000e+00f, 2.950836627e-02f, 0.000000000e+00f, 1.497759251e-01f, 0.000000000e+00f, 7.763237543e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, -6.779013272e-02f, 1.659481923e-01f, 4.797944664e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, 3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, 3.034486645e-02f, 6.779013272e-02f, 1.659481923e-01f, -4.797944664e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, 8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, -6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, -6.779013272e-02f, -1.659481923e-01f, 4.797944664e-02f}, + std::array{5.000000000e-02f, 0.000000000e+00f, -3.090169944e-02f, -8.090169944e-02f, 0.000000000e+00f, 0.000000000e+00f, -3.454915028e-02f, 6.454972244e-02f, 8.449668365e-02f, 0.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f, -3.034486645e-02f, 6.779013272e-02f, -1.659481923e-01f, -4.797944664e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, 1.011266756e-01f, -7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, -7.364853795e-02f, -1.011266756e-01f, -7.086833869e-02f, 1.482646439e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, 6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, -6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, 5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -6.454972244e-02f, -6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, 1.016220987e-01f, 6.338656910e-02f, -1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 5.000000000e-02f, -6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, -6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, 6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, 1.011266756e-01f, 7.086833869e-02f, -1.482646439e-02f}, + std::array{5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, -5.000000000e-02f, 6.454972244e-02f, 6.454972244e-02f, 0.000000000e+00f, 6.454972244e-02f, 0.000000000e+00f, -1.016220987e-01f, -6.338656910e-02f, 1.092600649e-02f, 7.364853795e-02f, -1.011266756e-01f, 7.086833869e-02f, 1.482646439e-02f}, +}; +constexpr std::array ThirdOrderEncoder{ CalcAmbiCoeffs( 0.35682208976f, 0.93417235897f, 0.00000000000f), CalcAmbiCoeffs(-0.35682208976f, 0.93417235897f, 0.00000000000f), CalcAmbiCoeffs( 0.35682208976f, -0.93417235897f, 0.00000000000f), @@ -195,24 +195,24 @@ constexpr std::array ThirdOrderEncoder{{ CalcAmbiCoeffs( inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, inv_sqrt3f), CalcAmbiCoeffs( -inv_sqrt3f, -inv_sqrt3f, -inv_sqrt3f), -}}; +}; static_assert(ThirdOrderDecoder.size() == ThirdOrderEncoder.size(), "Third-order mismatch"); /* This calculates a 2D third-order "upsampler" matrix. Same as the third-order * matrix, just using a more optimized speaker array for horizontal-only * content. */ -constexpr std::array,8> ThirdOrder2DDecoder{{ - {{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }}, - {{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }}, - {{ 1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }}, - {{ 1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }}, - {{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f, }}, - {{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f, }}, - {{ 1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f, }}, - {{ 1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f, }}, -}}; -constexpr std::array ThirdOrder2DEncoder{{ +constexpr std::array ThirdOrder2DDecoder{ + std::array{1.250000000e-01f, -5.523559567e-02f, 0.0f, 1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f}, + std::array{1.250000000e-01f, -1.333505242e-01f, 0.0f, 5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f}, + std::array{1.250000000e-01f, -1.333505242e-01f, 0.0f, -5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, 4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f}, + std::array{1.250000000e-01f, -5.523559567e-02f, 0.0f, -1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, -1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f}, + std::array{1.250000000e-01f, 5.523559567e-02f, 0.0f, -1.333505242e-01f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -4.573941867e-02f}, + std::array{1.250000000e-01f, 1.333505242e-01f, 0.0f, -5.523559567e-02f, -9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.104247249e-01f}, + std::array{1.250000000e-01f, 1.333505242e-01f, 0.0f, 5.523559567e-02f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, -9.128709292e-02f, -4.573941867e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.104247249e-01f}, + std::array{1.250000000e-01f, 5.523559567e-02f, 0.0f, 1.333505242e-01f, 9.128709292e-02f, 0.0f, 0.0f, 0.0f, 9.128709292e-02f, 1.104247249e-01f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 4.573941867e-02f}, +}; +constexpr std::array ThirdOrder2DEncoder{ CalcAmbiCoeffs(-0.38268343237f, 0.0f, 0.92387953251f), CalcAmbiCoeffs(-0.92387953251f, 0.0f, 0.38268343237f), CalcAmbiCoeffs(-0.92387953251f, 0.0f, -0.38268343237f), @@ -221,7 +221,7 @@ constexpr std::array ThirdOrder2DEncoder{{ CalcAmbiCoeffs( 0.92387953251f, 0.0f, -0.38268343237f), CalcAmbiCoeffs( 0.92387953251f, 0.0f, 0.38268343237f), CalcAmbiCoeffs( 0.38268343237f, 0.0f, 0.92387953251f), -}}; +}; static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-order 2D mismatch"); @@ -230,19 +230,19 @@ static_assert(ThirdOrder2DDecoder.size() == ThirdOrder2DEncoder.size(), "Third-o * the foreseeable future. This is only necessary for mixing horizontal-only * fourth-order content to 3D. */ -constexpr std::array,10> FourthOrder2DDecoder{{ - {{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, - {{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }}, - {{ 1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, - {{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, - {{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f, }}, - {{ 1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f, }}, - {{ 1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f, }}, -}}; -constexpr std::array FourthOrder2DEncoder{{ +constexpr std::array FourthOrder2DDecoder{ + std::array{1.000000000e-01f, 3.568220898e-02f, 0.0f, 1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, + std::array{1.000000000e-01f, 9.341723590e-02f, 0.0f, 6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, 1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, -9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f}, + std::array{1.000000000e-01f, 9.341723590e-02f, 0.0f, -6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, 2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, 3.568220898e-02f, 0.0f, -1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, 7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, + std::array{1.000000000e-01f, -3.568220898e-02f, 0.0f, -1.098185471e-01f, 6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -5.620301997e-02f, 8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, + std::array{1.000000000e-01f, -9.341723590e-02f, 0.0f, -6.787159473e-02f, 9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.093839659e-02f, -5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, -1.154700538e-01f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, -1.032795559e-01f, 9.561828875e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.000000000e+00f, 0.000000000e+00f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 9.014978717e-02f}, + std::array{1.000000000e-01f, -9.341723590e-02f, 0.0f, 6.787159473e-02f, -9.822469464e-02f, 0.0f, 0.0f, 0.0f, -3.191513794e-02f, -2.954767620e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -9.093839659e-02f, 5.298871540e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -7.293270986e-02f}, + std::array{1.000000000e-01f, -3.568220898e-02f, 0.0f, 1.098185471e-01f, -6.070619982e-02f, 0.0f, 0.0f, 0.0f, 8.355491589e-02f, -7.735682057e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 5.620301997e-02f, -8.573754253e-02f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.785781628e-02f}, +}; +constexpr std::array FourthOrder2DEncoder{ CalcAmbiCoeffs( 3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f), CalcAmbiCoeffs( 8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f), CalcAmbiCoeffs( 1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f), @@ -253,12 +253,12 @@ constexpr std::array FourthOrder2DEncoder{{ CalcAmbiCoeffs(-1.000000000e+00f, 0.000000000e+00f, 0.000000000e+00f), CalcAmbiCoeffs(-8.090169944e-01f, 0.000000000e+00f, 5.877852523e-01f), CalcAmbiCoeffs(-3.090169944e-01f, 0.000000000e+00f, 9.510565163e-01f), -}}; +}; static_assert(FourthOrder2DDecoder.size() == FourthOrder2DEncoder.size(), "Fourth-order 2D mismatch"); template -auto CalcAmbiUpsampler(const std::array,M> &decoder, +constexpr auto CalcAmbiUpsampler(const std::array,M> &decoder, const std::array &encoder) { std::array res{}; @@ -279,13 +279,13 @@ auto CalcAmbiUpsampler(const std::array,M> &decoder, } // namespace -const std::array AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)}; -const std::array AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)}; -const std::array AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)}; -const std::array AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)}; -const std::array AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)}; -const std::array AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)}; -const std::array AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)}; +const std::array,4> AmbiScale::FirstOrderUp{CalcAmbiUpsampler(FirstOrderDecoder, FirstOrderEncoder)}; +const std::array,4> AmbiScale::FirstOrder2DUp{CalcAmbiUpsampler(FirstOrder2DDecoder, FirstOrder2DEncoder)}; +const std::array,9> AmbiScale::SecondOrderUp{CalcAmbiUpsampler(SecondOrderDecoder, SecondOrderEncoder)}; +const std::array,9> AmbiScale::SecondOrder2DUp{CalcAmbiUpsampler(SecondOrder2DDecoder, SecondOrder2DEncoder)}; +const std::array,16> AmbiScale::ThirdOrderUp{CalcAmbiUpsampler(ThirdOrderDecoder, ThirdOrderEncoder)}; +const std::array,16> AmbiScale::ThirdOrder2DUp{CalcAmbiUpsampler(ThirdOrder2DDecoder, ThirdOrder2DEncoder)}; +const std::array,25> AmbiScale::FourthOrder2DUp{CalcAmbiUpsampler(FourthOrder2DDecoder, FourthOrder2DEncoder)}; std::array AmbiScale::GetHFOrderScales(const uint src_order, diff --git a/Engine/lib/openal-soft/core/ambidefs.h b/Engine/lib/openal-soft/core/ambidefs.h index b7d2bcd1f..1faf8eb68 100644 --- a/Engine/lib/openal-soft/core/ambidefs.h +++ b/Engine/lib/openal-soft/core/ambidefs.h @@ -2,8 +2,8 @@ #define CORE_AMBIDEFS_H #include -#include -#include +#include +#include #include "alnumbers.h" @@ -14,104 +14,88 @@ using uint = unsigned int; * needed will be (o+1)**2, thus zero-order has 1, first-order has 4, second- * order has 9, third-order has 16, and fourth-order has 25. */ -constexpr uint8_t MaxAmbiOrder{3}; -constexpr inline size_t AmbiChannelsFromOrder(size_t order) noexcept +inline constexpr auto MaxAmbiOrder = std::uint8_t{3}; +inline constexpr auto AmbiChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return (order+1) * (order+1); } -constexpr size_t MaxAmbiChannels{AmbiChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbiChannels = size_t{AmbiChannelsFromOrder(MaxAmbiOrder)}; /* A bitmask of ambisonic channels for 0 to 4th order. This only specifies up * to 4th order, which is the highest order a 32-bit mask value can specify (a * 64-bit mask could handle up to 7th order). */ -constexpr uint Ambi0OrderMask{0x00000001}; -constexpr uint Ambi1OrderMask{0x0000000f}; -constexpr uint Ambi2OrderMask{0x000001ff}; -constexpr uint Ambi3OrderMask{0x0000ffff}; -constexpr uint Ambi4OrderMask{0x01ffffff}; +inline constexpr uint Ambi0OrderMask{0x00000001}; +inline constexpr uint Ambi1OrderMask{0x0000000f}; +inline constexpr uint Ambi2OrderMask{0x000001ff}; +inline constexpr uint Ambi3OrderMask{0x0000ffff}; +inline constexpr uint Ambi4OrderMask{0x01ffffff}; /* A bitmask of ambisonic channels with height information. If none of these * channels are used/needed, there's no height (e.g. with most surround sound * speaker setups). This is ACN ordering, with bit 0 being ACN 0, etc. */ -constexpr uint AmbiPeriphonicMask{0xfe7ce4}; +inline constexpr uint AmbiPeriphonicMask{0xfe7ce4}; /* The maximum number of ambisonic channels for 2D (non-periphonic) * representation. This is 2 per each order above zero-order, plus 1 for zero- * order. Or simply, o*2 + 1. */ -constexpr inline size_t Ambi2DChannelsFromOrder(size_t order) noexcept +inline constexpr auto Ambi2DChannelsFromOrder(std::size_t order) noexcept -> std::size_t { return order*2 + 1; } -constexpr size_t MaxAmbi2DChannels{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; +inline constexpr auto MaxAmbi2DChannels = std::size_t{Ambi2DChannelsFromOrder(MaxAmbiOrder)}; /* NOTE: These are scale factors as applied to Ambisonics content. Decoder * coefficients should be divided by these values to get proper scalings. */ struct AmbiScale { - static auto& FromN3D() noexcept - { - static constexpr const std::array ret{{ - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f - }}; - return ret; - } - static auto& FromSN3D() noexcept - { - static constexpr const std::array ret{{ - 1.000000000f, /* ACN 0, sqrt(1) */ - 1.732050808f, /* ACN 1, sqrt(3) */ - 1.732050808f, /* ACN 2, sqrt(3) */ - 1.732050808f, /* ACN 3, sqrt(3) */ - 2.236067978f, /* ACN 4, sqrt(5) */ - 2.236067978f, /* ACN 5, sqrt(5) */ - 2.236067978f, /* ACN 6, sqrt(5) */ - 2.236067978f, /* ACN 7, sqrt(5) */ - 2.236067978f, /* ACN 8, sqrt(5) */ - 2.645751311f, /* ACN 9, sqrt(7) */ - 2.645751311f, /* ACN 10, sqrt(7) */ - 2.645751311f, /* ACN 11, sqrt(7) */ - 2.645751311f, /* ACN 12, sqrt(7) */ - 2.645751311f, /* ACN 13, sqrt(7) */ - 2.645751311f, /* ACN 14, sqrt(7) */ - 2.645751311f, /* ACN 15, sqrt(7) */ - }}; - return ret; - } - static auto& FromFuMa() noexcept - { - static constexpr const std::array ret{{ - 1.414213562f, /* ACN 0 (W), sqrt(2) */ - 1.732050808f, /* ACN 1 (Y), sqrt(3) */ - 1.732050808f, /* ACN 2 (Z), sqrt(3) */ - 1.732050808f, /* ACN 3 (X), sqrt(3) */ - 1.936491673f, /* ACN 4 (V), sqrt(15)/2 */ - 1.936491673f, /* ACN 5 (T), sqrt(15)/2 */ - 2.236067978f, /* ACN 6 (R), sqrt(5) */ - 1.936491673f, /* ACN 7 (S), sqrt(15)/2 */ - 1.936491673f, /* ACN 8 (U), sqrt(15)/2 */ - 2.091650066f, /* ACN 9 (Q), sqrt(35/8) */ - 1.972026594f, /* ACN 10 (O), sqrt(35)/3 */ - 2.231093404f, /* ACN 11 (M), sqrt(224/45) */ - 2.645751311f, /* ACN 12 (K), sqrt(7) */ - 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ - 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ - 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ - }}; - return ret; - } - static auto& FromUHJ() noexcept - { - static constexpr const std::array ret{{ - 1.000000000f, /* ACN 0 (W), sqrt(1) */ - 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ - 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ - 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ - /* Higher orders not relevant for UHJ. */ - 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, - }}; - return ret; - } + static inline constexpr std::array FromN3D{{ + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f + }}; + static inline constexpr std::array FromSN3D{{ + 1.000000000f, /* ACN 0, sqrt(1) */ + 1.732050808f, /* ACN 1, sqrt(3) */ + 1.732050808f, /* ACN 2, sqrt(3) */ + 1.732050808f, /* ACN 3, sqrt(3) */ + 2.236067978f, /* ACN 4, sqrt(5) */ + 2.236067978f, /* ACN 5, sqrt(5) */ + 2.236067978f, /* ACN 6, sqrt(5) */ + 2.236067978f, /* ACN 7, sqrt(5) */ + 2.236067978f, /* ACN 8, sqrt(5) */ + 2.645751311f, /* ACN 9, sqrt(7) */ + 2.645751311f, /* ACN 10, sqrt(7) */ + 2.645751311f, /* ACN 11, sqrt(7) */ + 2.645751311f, /* ACN 12, sqrt(7) */ + 2.645751311f, /* ACN 13, sqrt(7) */ + 2.645751311f, /* ACN 14, sqrt(7) */ + 2.645751311f, /* ACN 15, sqrt(7) */ + }}; + static inline constexpr std::array FromFuMa{{ + 1.414213562f, /* ACN 0 (W), sqrt(2) */ + 1.732050808f, /* ACN 1 (Y), sqrt(3) */ + 1.732050808f, /* ACN 2 (Z), sqrt(3) */ + 1.732050808f, /* ACN 3 (X), sqrt(3) */ + 1.936491673f, /* ACN 4 (V), sqrt(15)/2 */ + 1.936491673f, /* ACN 5 (T), sqrt(15)/2 */ + 2.236067978f, /* ACN 6 (R), sqrt(5) */ + 1.936491673f, /* ACN 7 (S), sqrt(15)/2 */ + 1.936491673f, /* ACN 8 (U), sqrt(15)/2 */ + 2.091650066f, /* ACN 9 (Q), sqrt(35/8) */ + 1.972026594f, /* ACN 10 (O), sqrt(35)/3 */ + 2.231093404f, /* ACN 11 (M), sqrt(224/45) */ + 2.645751311f, /* ACN 12 (K), sqrt(7) */ + 2.231093404f, /* ACN 13 (L), sqrt(224/45) */ + 1.972026594f, /* ACN 14 (N), sqrt(35)/3 */ + 2.091650066f, /* ACN 15 (P), sqrt(35/8) */ + }}; + static inline constexpr std::array FromUHJ{{ + 1.000000000f, /* ACN 0 (W), sqrt(1) */ + 1.224744871f, /* ACN 1 (Y), sqrt(3/2) */ + 1.224744871f, /* ACN 2 (Z), sqrt(3/2) */ + 1.224744871f, /* ACN 3 (X), sqrt(3/2) */ + /* Higher orders not relevant for UHJ. */ + 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, + }}; /* Retrieves per-order HF scaling factors for "upsampling" ambisonic data. */ static std::array GetHFOrderScales(const uint src_order, @@ -127,72 +111,49 @@ struct AmbiScale { }; struct AmbiIndex { - static auto& FromFuMa() noexcept - { - static constexpr const std::array ret{{ - 0, /* W */ - 3, /* X */ - 1, /* Y */ - 2, /* Z */ - 6, /* R */ - 7, /* S */ - 5, /* T */ - 8, /* U */ - 4, /* V */ - 12, /* K */ - 13, /* L */ - 11, /* M */ - 14, /* N */ - 10, /* O */ - 15, /* P */ - 9, /* Q */ - }}; - return ret; - } - static auto& FromFuMa2D() noexcept - { - static constexpr const std::array ret{{ - 0, /* W */ - 3, /* X */ - 1, /* Y */ - 8, /* U */ - 4, /* V */ - 15, /* P */ - 9, /* Q */ - }}; - return ret; - } + static inline constexpr std::array FromFuMa{{ + 0, /* W */ + 3, /* X */ + 1, /* Y */ + 2, /* Z */ + 6, /* R */ + 7, /* S */ + 5, /* T */ + 8, /* U */ + 4, /* V */ + 12, /* K */ + 13, /* L */ + 11, /* M */ + 14, /* N */ + 10, /* O */ + 15, /* P */ + 9, /* Q */ + }}; + static inline constexpr std::array FromFuMa2D{{ + 0, /* W */ + 3, /* X */ + 1, /* Y */ + 8, /* U */ + 4, /* V */ + 15, /* P */ + 9, /* Q */ + }}; - static auto& FromACN() noexcept - { - static constexpr const std::array ret{{ - 0, 1, 2, 3, 4, 5, 6, 7, - 8, 9, 10, 11, 12, 13, 14, 15 - }}; - return ret; - } - static auto& FromACN2D() noexcept - { - static constexpr const std::array ret{{ - 0, 1,3, 4,8, 9,15 - }}; - return ret; - } + static inline constexpr std::array FromACN{{ + 0, 1, 2, 3, 4, 5, 6, 7, + 8, 9, 10, 11, 12, 13, 14, 15 + }}; + static inline constexpr std::array FromACN2D{{ + 0, 1,3, 4,8, 9,15 + }}; - static auto& OrderFromChannel() noexcept - { - static constexpr const std::array ret{{ - 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, - }}; - return ret; - } - static auto& OrderFrom2DChannel() noexcept - { - static constexpr const std::array ret{{ - 0, 1,1, 2,2, 3,3, - }}; - return ret; - } + + static inline constexpr std::array OrderFromChannel{{ + 0, 1,1,1, 2,2,2,2,2, 3,3,3,3,3,3,3, + }}; + static inline constexpr std::array OrderFrom2DChannel{{ + 0, 1,1, 2,2, 3,3, + }}; }; diff --git a/Engine/lib/openal-soft/core/async_event.h b/Engine/lib/openal-soft/core/async_event.h index 5a2f5f91a..f865fd8a8 100644 --- a/Engine/lib/openal-soft/core/async_event.h +++ b/Engine/lib/openal-soft/core/async_event.h @@ -1,6 +1,11 @@ #ifndef CORE_EVENT_H #define CORE_EVENT_H +#include +#include +#include +#include + #include "almalloc.h" struct EffectState; @@ -8,48 +13,53 @@ struct EffectState; using uint = unsigned int; -struct AsyncEvent { - enum : uint { - /* User event types. */ - SourceStateChange, - BufferCompleted, - Disconnected, - UserEventCount, - - /* Internal events, always processed. */ - ReleaseEffectState = 128, - - /* End event thread processing. */ - KillThread, - }; - - enum class SrcState { - Reset, - Stop, - Play, - Pause - }; - - const uint EnumType; - union { - char dummy; - struct { - uint id; - SrcState state; - } srcstate; - struct { - uint id; - uint count; - } bufcomp; - struct { - char msg[244]; - } disconnect; - EffectState *mEffectState; - } u{}; - - constexpr AsyncEvent(uint type) noexcept : EnumType{type} { } - - DISABLE_ALLOC() +enum class AsyncEnableBits : std::uint8_t { + SourceState, + BufferCompleted, + Disconnected, + Count }; + +enum class AsyncSrcState : std::uint8_t { + Reset, + Stop, + Play, + Pause +}; + +using AsyncKillThread = std::monostate; + +struct AsyncSourceStateEvent { + uint mId; + AsyncSrcState mState; +}; + +struct AsyncBufferCompleteEvent { + uint mId; + uint mCount; +}; + +struct AsyncDisconnectEvent { + std::string msg; +}; + +struct AsyncEffectReleaseEvent { + EffectState *mEffectState; +}; + +using AsyncEvent = std::variant; + +template +auto &InitAsyncEvent(std::byte *evtbuf, Args&& ...args) +{ + auto *evt = al::construct_at(reinterpret_cast(evtbuf), std::in_place_type, + std::forward(args)...); + return std::get(*evt); +} + #endif diff --git a/Engine/lib/openal-soft/core/bformatdec.cpp b/Engine/lib/openal-soft/core/bformatdec.cpp index 129b99762..f572d4569 100644 --- a/Engine/lib/openal-soft/core/bformatdec.cpp +++ b/Engine/lib/openal-soft/core/bformatdec.cpp @@ -6,114 +6,122 @@ #include #include #include +#include #include -#include "almalloc.h" #include "alnumbers.h" +#include "bufferline.h" #include "filters/splitter.h" +#include "flexarray.h" #include "front_stablizer.h" #include "mixer.h" #include "opthelpers.h" +namespace { + +template +struct overloaded : Ts... { using Ts::operator()...; }; + +template +overloaded(Ts...) -> overloaded; + +} // namespace + BFormatDec::BFormatDec(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer) - : mStablizer{std::move(stablizer)}, mDualBand{!coeffslf.empty()}, mChannelDec{inchans} + : mStablizer{std::move(stablizer)} { - if(!mDualBand) + if(coeffslf.empty()) { - for(size_t j{0};j < mChannelDec.size();++j) + auto &decoder = mChannelDec.emplace>(inchans); + for(size_t j{0};j < decoder.size();++j) { - float *outcoeffs{mChannelDec[j].mGains.Single}; - for(const ChannelDec &incoeffs : coeffs) - *(outcoeffs++) = incoeffs[j]; + std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains.begin(), + [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } else { - mChannelDec[0].mXOver.init(xover_f0norm); - for(size_t j{1};j < mChannelDec.size();++j) - mChannelDec[j].mXOver = mChannelDec[0].mXOver; + auto &decoder = mChannelDec.emplace>(inchans); + decoder[0].mXOver.init(xover_f0norm); + for(size_t j{1};j < decoder.size();++j) + decoder[j].mXOver = decoder[0].mXOver; - for(size_t j{0};j < mChannelDec.size();++j) + for(size_t j{0};j < decoder.size();++j) { - float *outcoeffs{mChannelDec[j].mGains.Dual[sHFBand]}; - for(const ChannelDec &incoeffs : coeffs) - *(outcoeffs++) = incoeffs[j]; + std::transform(coeffs.cbegin(), coeffs.cend(), decoder[j].mGains[sHFBand].begin(), + [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); - outcoeffs = mChannelDec[j].mGains.Dual[sLFBand]; - for(const ChannelDec &incoeffs : coeffslf) - *(outcoeffs++) = incoeffs[j]; + std::transform(coeffslf.cbegin(), coeffslf.cend(), decoder[j].mGains[sLFBand].begin(), + [j](const ChannelDec &incoeffs) { return incoeffs[j]; }); } } } void BFormatDec::process(const al::span OutBuffer, - const FloatBufferLine *InSamples, const size_t SamplesToDo) + const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); - if(mDualBand) + auto decode_dualband = [=](std::vector &decoder) { - const al::span hfSamples{mSamples[sHFBand].data(), SamplesToDo}; - const al::span lfSamples{mSamples[sLFBand].data(), SamplesToDo}; - for(auto &chandec : mChannelDec) + auto input = InSamples.cbegin(); + const auto hfSamples = al::span{mSamples[sHFBand]}.first(SamplesToDo); + const auto lfSamples = al::span{mSamples[sLFBand]}.first(SamplesToDo); + for(auto &chandec : decoder) { - chandec.mXOver.process({InSamples->data(), SamplesToDo}, hfSamples.data(), - lfSamples.data()); - MixSamples(hfSamples, OutBuffer, chandec.mGains.Dual[sHFBand], - chandec.mGains.Dual[sHFBand], 0, 0); - MixSamples(lfSamples, OutBuffer, chandec.mGains.Dual[sLFBand], - chandec.mGains.Dual[sLFBand], 0, 0); - ++InSamples; + chandec.mXOver.process(al::span{*input++}.first(SamplesToDo), hfSamples, lfSamples); + MixSamples(hfSamples, OutBuffer, chandec.mGains[sHFBand], chandec.mGains[sHFBand],0,0); + MixSamples(lfSamples, OutBuffer, chandec.mGains[sLFBand], chandec.mGains[sLFBand],0,0); } - } - else + }; + auto decode_singleband = [=](std::vector &decoder) { - for(auto &chandec : mChannelDec) + auto input = InSamples.cbegin(); + for(auto &chandec : decoder) { - MixSamples({InSamples->data(), SamplesToDo}, OutBuffer, chandec.mGains.Single, - chandec.mGains.Single, 0, 0); - ++InSamples; + MixSamples(al::span{*input++}.first(SamplesToDo), OutBuffer, chandec.mGains, + chandec.mGains, 0, 0); } - } + }; + + std::visit(overloaded{decode_dualband, decode_singleband}, mChannelDec); } void BFormatDec::processStablize(const al::span OutBuffer, - const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, - const size_t SamplesToDo) + const al::span InSamples, const size_t lidx, const size_t ridx, + const size_t cidx, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); /* Move the existing direct L/R signal out so it doesn't get processed by * the stablizer. */ - float *RESTRICT mid{al::assume_aligned<16>(mStablizer->MidDirect.data())}; - float *RESTRICT side{al::assume_aligned<16>(mStablizer->Side.data())}; - for(size_t i{0};i < SamplesToDo;++i) - { - mid[i] = OutBuffer[lidx][i] + OutBuffer[ridx][i]; - side[i] = OutBuffer[lidx][i] - OutBuffer[ridx][i]; - } - std::fill_n(OutBuffer[lidx].begin(), SamplesToDo, 0.0f); - std::fill_n(OutBuffer[ridx].begin(), SamplesToDo, 0.0f); + const auto leftout = al::span{OutBuffer[lidx]}.first(SamplesToDo); + const auto rightout = al::span{OutBuffer[ridx]}.first(SamplesToDo); + const al::span mid{al::assume_aligned<16>(mStablizer->MidDirect.data()), SamplesToDo}; + const al::span side{al::assume_aligned<16>(mStablizer->Side.data()), SamplesToDo}; + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), mid.begin(),std::plus{}); + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), side.begin(),std::minus{}); + std::fill_n(leftout.begin(), leftout.size(), 0.0f); + std::fill_n(rightout.begin(), rightout.size(), 0.0f); /* Decode the B-Format input to OutBuffer. */ process(OutBuffer, InSamples, SamplesToDo); /* Include the decoded side signal with the direct side signal. */ for(size_t i{0};i < SamplesToDo;++i) - side[i] += OutBuffer[lidx][i] - OutBuffer[ridx][i]; + side[i] += leftout[i] - rightout[i]; /* Get the decoded mid signal and band-split it. */ - std::transform(OutBuffer[lidx].cbegin(), OutBuffer[lidx].cbegin()+SamplesToDo, - OutBuffer[ridx].cbegin(), mStablizer->Temp.begin(), - [](const float l, const float r) noexcept { return l + r; }); + const auto tmpsamples = al::span{mStablizer->Temp}.first(SamplesToDo); + std::transform(leftout.cbegin(), leftout.cend(), rightout.cbegin(), tmpsamples.begin(), + std::plus{}); - mStablizer->MidFilter.process({mStablizer->Temp.data(), SamplesToDo}, mStablizer->MidHF.data(), - mStablizer->MidLF.data()); + mStablizer->MidFilter.process(tmpsamples, mStablizer->MidHF, mStablizer->MidLF); /* Apply an all-pass to all channels to match the band-splitter's phase * shift. This is to keep the phase synchronized between the existing @@ -126,9 +134,9 @@ void BFormatDec::processStablize(const al::span OutBuffer, * and substitute the direct mid signal and direct+decoded side signal. */ if(i == lidx) - mStablizer->ChannelFilters[i].processAllPass({mid, SamplesToDo}); + mStablizer->ChannelFilters[i].processAllPass(mid); else if(i == ridx) - mStablizer->ChannelFilters[i].processAllPass({side, SamplesToDo}); + mStablizer->ChannelFilters[i].processAllPass(side); else mStablizer->ChannelFilters[i].processAllPass({OutBuffer[i].data(), SamplesToDo}); } @@ -142,6 +150,7 @@ void BFormatDec::processStablize(const al::span OutBuffer, const float cos_hf{std::cos(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; const float sin_lf{std::sin(1.0f/3.0f * (al::numbers::pi_v*0.5f))}; const float sin_hf{std::sin(1.0f/4.0f * (al::numbers::pi_v*0.5f))}; + const auto centerout = al::span{OutBuffer[cidx]}.first(SamplesToDo); for(size_t i{0};i < SamplesToDo;i++) { /* Add the direct mid signal to the processed mid signal so it can be @@ -154,9 +163,9 @@ void BFormatDec::processStablize(const al::span OutBuffer, /* The generated center channel signal adds to the existing signal, * while the modified left and right channels replace. */ - OutBuffer[lidx][i] = (m + s) * 0.5f; - OutBuffer[ridx][i] = (m - s) * 0.5f; - OutBuffer[cidx][i] += c * 0.5f; + leftout[i] = (m + s) * 0.5f; + rightout[i] = (m - s) * 0.5f; + centerout[i] += c * 0.5f; } } diff --git a/Engine/lib/openal-soft/core/bformatdec.h b/Engine/lib/openal-soft/core/bformatdec.h index 7a27a5a42..5bb2d3b40 100644 --- a/Engine/lib/openal-soft/core/bformatdec.h +++ b/Engine/lib/openal-soft/core/bformatdec.h @@ -4,16 +4,15 @@ #include #include #include +#include +#include -#include "almalloc.h" #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" #include "devformat.h" #include "filters/splitter.h" -#include "vector.h" - -struct FrontStablizer; +#include "front_stablizer.h" using ChannelDec = std::array; @@ -23,49 +22,40 @@ class BFormatDec { static constexpr size_t sLFBand{1}; static constexpr size_t sNumBands{2}; - struct ChannelDecoder { - union MatrixU { - float Dual[sNumBands][MAX_OUTPUT_CHANNELS]; - float Single[MAX_OUTPUT_CHANNELS]; - } mGains{}; - - /* NOTE: BandSplitter filter is unused with single-band decoding. */ - BandSplitter mXOver; + struct ChannelDecoderSingle { + std::array mGains{}; }; - alignas(16) std::array mSamples; + struct ChannelDecoderDual { + BandSplitter mXOver; + std::array,sNumBands> mGains{}; + }; + + alignas(16) std::array mSamples{}; const std::unique_ptr mStablizer; - const bool mDualBand{false}; - /* TODO: This should ideally be a FlexArray, since ChannelDecoder is rather - * small and only a few are needed (3, 4, 5, 7, typically). But that can - * only be used in a standard layout struct, and a std::unique_ptr member - * (mStablizer) causes GCC and Clang to warn it's not. - */ - al::vector mChannelDec; + std::variant,std::vector> mChannelDec; public: BFormatDec(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer); - bool hasStablizer() const noexcept { return mStablizer != nullptr; } + [[nodiscard]] auto hasStablizer() const noexcept -> bool { return mStablizer != nullptr; } /* Decodes the ambisonic input to the given output channels. */ - void process(const al::span OutBuffer, const FloatBufferLine *InSamples, - const size_t SamplesToDo); + void process(const al::span OutBuffer, + const al::span InSamples, const size_t SamplesToDo); /* Decodes the ambisonic input to the given output channels with stablization. */ void processStablize(const al::span OutBuffer, - const FloatBufferLine *InSamples, const size_t lidx, const size_t ridx, const size_t cidx, - const size_t SamplesToDo); + const al::span InSamples, const size_t lidx, const size_t ridx, + const size_t cidx, const size_t SamplesToDo); static std::unique_ptr Create(const size_t inchans, const al::span coeffs, const al::span coeffslf, const float xover_f0norm, std::unique_ptr stablizer); - - DEF_NEWDEL(BFormatDec) }; #endif /* CORE_BFORMATDEC_H */ diff --git a/Engine/lib/openal-soft/core/bs2b.cpp b/Engine/lib/openal-soft/core/bs2b.cpp index 303bf9bd9..6e292b147 100644 --- a/Engine/lib/openal-soft/core/bs2b.cpp +++ b/Engine/lib/openal-soft/core/bs2b.cpp @@ -26,72 +26,75 @@ #include #include #include +#include #include "alnumbers.h" +#include "alspan.h" #include "bs2b.h" +namespace { /* Set up all data. */ -static void init(struct bs2b *bs2b) +void init(Bs2b::bs2b *bs2b) { float Fc_lo, Fc_hi; float G_lo, G_hi; - float x, g; switch(bs2b->level) { - case BS2B_LOW_CLEVEL: /* Low crossfeed level */ + case Bs2b::LowCLevel: /* Low crossfeed level */ Fc_lo = 360.0f; Fc_hi = 501.0f; G_lo = 0.398107170553497f; G_hi = 0.205671765275719f; break; - case BS2B_MIDDLE_CLEVEL: /* Middle crossfeed level */ + case Bs2b::MiddleCLevel: /* Middle crossfeed level */ Fc_lo = 500.0f; Fc_hi = 711.0f; G_lo = 0.459726988530872f; G_hi = 0.228208484414988f; break; - case BS2B_HIGH_CLEVEL: /* High crossfeed level (virtual speakers are closer to itself) */ + case Bs2b::HighCLevel: /* High crossfeed level (virtual speakers are closer to itself) */ Fc_lo = 700.0f; Fc_hi = 1021.0f; G_lo = 0.530884444230988f; G_hi = 0.250105790667544f; break; - case BS2B_LOW_ECLEVEL: /* Low easy crossfeed level */ + case Bs2b::LowECLevel: /* Low easy crossfeed level */ Fc_lo = 360.0f; Fc_hi = 494.0f; G_lo = 0.316227766016838f; G_hi = 0.168236228897329f; break; - case BS2B_MIDDLE_ECLEVEL: /* Middle easy crossfeed level */ + case Bs2b::MiddleECLevel: /* Middle easy crossfeed level */ Fc_lo = 500.0f; Fc_hi = 689.0f; G_lo = 0.354813389233575f; G_hi = 0.187169483835901f; break; - default: /* High easy crossfeed level */ - bs2b->level = BS2B_HIGH_ECLEVEL; + case Bs2b::HighECLevel: /* High easy crossfeed level */ + default: + bs2b->level = Bs2b::HighECLevel; Fc_lo = 700.0f; Fc_hi = 975.0f; G_lo = 0.398107170553497f; G_hi = 0.205671765275719f; break; - } /* switch */ + } - g = 1.0f / (1.0f - G_hi + G_lo); + float g{1.0f / (1.0f - G_hi + G_lo)}; /* $fc = $Fc / $s; * $d = 1 / 2 / pi / $fc; * $x = exp(-1 / $d); */ - x = std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate)); + float x{ std::exp(-al::numbers::pi_v*2.0f*Fc_lo/static_cast(bs2b->srate))}; bs2b->b1_lo = x; bs2b->a0_lo = G_lo * (1.0f - x) * g; @@ -99,85 +102,88 @@ static void init(struct bs2b *bs2b) bs2b->b1_hi = x; bs2b->a0_hi = (1.0f - G_hi * (1.0f - x)) * g; bs2b->a1_hi = -x * g; -} /* init */ +} +} // namespace /* Exported functions. * See descriptions in "bs2b.h" */ +namespace Bs2b { -void bs2b_set_params(struct bs2b *bs2b, int level, int srate) +void bs2b::set_params(int level_, int srate_) { - if(srate <= 0) srate = 1; + if(srate_ < 1) + throw std::runtime_error{"BS2B srate < 1"}; - bs2b->level = level; - bs2b->srate = srate; - init(bs2b); -} /* bs2b_set_params */ + level = level_; + srate = srate_; + init(this); +} -int bs2b_get_level(struct bs2b *bs2b) +void bs2b::clear() { - return bs2b->level; -} /* bs2b_get_level */ + history.fill(bs2b::t_last_sample{}); +} -int bs2b_get_srate(struct bs2b *bs2b) +void bs2b::cross_feed(float *Left, float *Right, size_t SamplesToDo) { - return bs2b->srate; -} /* bs2b_get_srate */ + const float a0lo{a0_lo}; + const float b1lo{b1_lo}; + const float a0hi{a0_hi}; + const float a1hi{a1_hi}; + const float b1hi{b1_hi}; + std::array,128> samples{}; + al::span lsamples{Left, SamplesToDo}; + al::span rsamples{Right, SamplesToDo}; -void bs2b_clear(struct bs2b *bs2b) -{ - std::fill(std::begin(bs2b->history), std::end(bs2b->history), bs2b::t_last_sample{}); -} /* bs2b_clear */ - -void bs2b_cross_feed(struct bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo) -{ - const float a0_lo{bs2b->a0_lo}; - const float b1_lo{bs2b->b1_lo}; - const float a0_hi{bs2b->a0_hi}; - const float a1_hi{bs2b->a1_hi}; - const float b1_hi{bs2b->b1_hi}; - float lsamples[128][2]; - float rsamples[128][2]; - - for(size_t base{0};base < SamplesToDo;) + while(!lsamples.empty()) { - const size_t todo{std::min(128, SamplesToDo-base)}; + const size_t todo{std::min(samples.size(), lsamples.size())}; /* Process left input */ - float z_lo{bs2b->history[0].lo}; - float z_hi{bs2b->history[0].hi}; - for(size_t i{0};i < todo;i++) - { - lsamples[i][0] = a0_lo*Left[i] + z_lo; - z_lo = b1_lo*lsamples[i][0]; + float z_lo{history[0].lo}; + float z_hi{history[0].hi}; + std::transform(lsamples.cbegin(), lsamples.cbegin()+ptrdiff_t(todo), samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x) -> std::array + { + float y0{a0hi*x + z_hi}; + z_hi = a1hi*x + b1hi*y0; - lsamples[i][1] = a0_hi*Left[i] + z_hi; - z_hi = a1_hi*Left[i] + b1_hi*lsamples[i][1]; - } - bs2b->history[0].lo = z_lo; - bs2b->history[0].hi = z_hi; + float y1{a0lo*x + z_lo}; + z_lo = b1lo*y1; + + return {y0, y1}; + }); + history[0].lo = z_lo; + history[0].hi = z_hi; /* Process right input */ - z_lo = bs2b->history[1].lo; - z_hi = bs2b->history[1].hi; - for(size_t i{0};i < todo;i++) - { - rsamples[i][0] = a0_lo*Right[i] + z_lo; - z_lo = b1_lo*rsamples[i][0]; + z_lo = history[1].lo; + z_hi = history[1].hi; + std::transform(rsamples.cbegin(), rsamples.cbegin()+ptrdiff_t(todo), samples.begin(), + samples.begin(), + [a0hi,a1hi,b1hi,a0lo,b1lo,&z_lo,&z_hi](const float x, const std::array out) -> std::array + { + float y0{a0lo*x + z_lo}; + z_lo = b1lo*y0; - rsamples[i][1] = a0_hi*Right[i] + z_hi; - z_hi = a1_hi*Right[i] + b1_hi*rsamples[i][1]; - } - bs2b->history[1].lo = z_lo; - bs2b->history[1].hi = z_hi; + float y1{a0hi*x + z_hi}; + z_hi = a1hi*x + b1hi*y1; - /* Crossfeed */ - for(size_t i{0};i < todo;i++) - *(Left++) = lsamples[i][1] + rsamples[i][0]; - for(size_t i{0};i < todo;i++) - *(Right++) = rsamples[i][1] + lsamples[i][0]; + return {out[0]+y0, out[1]+y1}; + }); + history[1].lo = z_lo; + history[1].hi = z_hi; - base += todo; + auto iter = std::transform(samples.cbegin(), samples.cbegin()+todo, lsamples.begin(), + [](const std::array &in) { return in[0]; }); + lsamples = {iter, lsamples.end()}; + + iter = std::transform(samples.cbegin(), samples.cbegin()+todo, rsamples.begin(), + [](const std::array &in) { return in[1]; }); + rsamples = {iter, rsamples.end()}; } -} /* bs2b_cross_feed */ +} + +} // namespace Bs2b diff --git a/Engine/lib/openal-soft/core/bs2b.h b/Engine/lib/openal-soft/core/bs2b.h index 4d0b9dd8e..32c77e101 100644 --- a/Engine/lib/openal-soft/core/bs2b.h +++ b/Engine/lib/openal-soft/core/bs2b.h @@ -24,66 +24,66 @@ #ifndef CORE_BS2B_H #define CORE_BS2B_H -#include "almalloc.h" +#include +#include -/* Number of crossfeed levels */ -#define BS2B_CLEVELS 3 +namespace Bs2b { -/* Normal crossfeed levels */ -#define BS2B_HIGH_CLEVEL 3 -#define BS2B_MIDDLE_CLEVEL 2 -#define BS2B_LOW_CLEVEL 1 +enum { + /* Normal crossfeed levels */ + LowCLevel = 1, + MiddleCLevel = 2, + HighCLevel = 3, -/* Easy crossfeed levels */ -#define BS2B_HIGH_ECLEVEL BS2B_HIGH_CLEVEL + BS2B_CLEVELS -#define BS2B_MIDDLE_ECLEVEL BS2B_MIDDLE_CLEVEL + BS2B_CLEVELS -#define BS2B_LOW_ECLEVEL BS2B_LOW_CLEVEL + BS2B_CLEVELS + /* Easy crossfeed levels */ + LowECLevel = 4, + MiddleECLevel = 5, + HighECLevel = 6, -/* Default crossfeed levels */ -#define BS2B_DEFAULT_CLEVEL BS2B_HIGH_ECLEVEL -/* Default sample rate (Hz) */ -#define BS2B_DEFAULT_SRATE 44100 + DefaultCLevel = HighECLevel +}; struct bs2b { - int level; /* Crossfeed level */ - int srate; /* Sample rate (Hz) */ + int level{}; /* Crossfeed level */ + int srate{}; /* Sample rate (Hz) */ /* Lowpass IIR filter coefficients */ - float a0_lo; - float b1_lo; + float a0_lo{}; + float b1_lo{}; /* Highboost IIR filter coefficients */ - float a0_hi; - float a1_hi; - float b1_hi; + float a0_hi{}; + float a1_hi{}; + float b1_hi{}; /* Buffer of filter history * [0] - first channel, [1] - second channel */ struct t_last_sample { - float lo; - float hi; - } history[2]; + float lo{}; + float hi{}; + }; + std::array history{}; - DEF_NEWDEL(bs2b) + /* Clear buffers and set new coefficients with new crossfeed level and + * sample rate values. + * level - crossfeed level of *Level enum values. + * srate - sample rate by Hz. + */ + void set_params(int level, int srate); + + /* Return current crossfeed level value */ + [[nodiscard]] auto get_level() const noexcept -> int { return level; } + + /* Return current sample rate value */ + [[nodiscard]] auto get_srate() const noexcept -> int { return srate; } + + /* Clear buffer */ + void clear(); + + void cross_feed(float *Left, float *Right, std::size_t SamplesToDo); }; -/* Clear buffers and set new coefficients with new crossfeed level and sample - * rate values. - * level - crossfeed level of *LEVEL values. - * srate - sample rate by Hz. - */ -void bs2b_set_params(bs2b *bs2b, int level, int srate); - -/* Return current crossfeed level value */ -int bs2b_get_level(bs2b *bs2b); - -/* Return current sample rate value */ -int bs2b_get_srate(bs2b *bs2b); - -/* Clear buffer */ -void bs2b_clear(bs2b *bs2b); - -void bs2b_cross_feed(bs2b *bs2b, float *Left, float *Right, size_t SamplesToDo); +} // namespace Bs2b #endif /* CORE_BS2B_H */ diff --git a/Engine/lib/openal-soft/core/bsinc_tables.cpp b/Engine/lib/openal-soft/core/bsinc_tables.cpp index 693645f43..74f47a129 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.cpp +++ b/Engine/lib/openal-soft/core/bsinc_tables.cpp @@ -5,18 +5,63 @@ #include #include #include +#include #include -#include #include +#include #include "alnumbers.h" -#include "core/mixer/defs.h" +#include "alnumeric.h" +#include "alspan.h" +#include "bsinc_defs.h" +#include "resampler_limits.h" namespace { using uint = unsigned int; +#if __cpp_lib_math_special_functions >= 201603L +using std::cyl_bessel_i; + +#else + +/* The zero-order modified Bessel function of the first kind, used for the + * Kaiser window. + * + * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) + * = sum_{k=0}^inf ((x / 2)^k / k!)^2 + * + * This implementation only handles nu = 0, and isn't the most precise (it + * starts with the largest value and accumulates successively smaller values, + * compounding the rounding and precision error), but it's good enough. + */ +template +U cyl_bessel_i(T nu, U x) +{ + if(nu != T{0}) + throw std::runtime_error{"cyl_bessel_i: nu != 0"}; + + /* Start at k=1 since k=0 is trivial. */ + const double x2{x/2.0}; + double term{1.0}; + double sum{1.0}; + int k{1}; + + /* Let the integration converge until the term of the sum is no longer + * significant. + */ + double last_sum{}; + do { + const double y{x2 / k}; + ++k; + last_sum = sum; + term *= y * y; + sum += term; + } while(sum != last_sum); + return static_cast(sum); +} +#endif /* This is the normalized cardinal sine (sinc) function. * @@ -31,35 +76,6 @@ constexpr double Sinc(const double x) return std::sin(al::numbers::pi*x) / (al::numbers::pi*x); } -/* The zero-order modified Bessel function of the first kind, used for the - * Kaiser window. - * - * I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k) - * = sum_{k=0}^inf ((x / 2)^k / k!)^2 - */ -constexpr double BesselI_0(const double x) noexcept -{ - /* Start at k=1 since k=0 is trivial. */ - const double x2{x / 2.0}; - double term{1.0}; - double sum{1.0}; - double last_sum{}; - int k{1}; - - /* Let the integration converge until the term of the sum is no longer - * significant. - */ - do { - const double y{x2 / k}; - ++k; - last_sum = sum; - term *= y * y; - sum += term; - } while(sum != last_sum); - - return sum; -} - /* Calculate a Kaiser window from the given beta value and a normalized k * [-1, 1]. * @@ -78,7 +94,7 @@ constexpr double Kaiser(const double beta, const double k, const double besseli_ { if(!(k >= -1.0 && k <= 1.0)) return 0.0; - return BesselI_0(beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; + return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta; } /* Calculates the (normalized frequency) transition width of the Kaiser window. @@ -97,7 +113,7 @@ constexpr double CalcKaiserBeta(const double rejection) { if(rejection > 50.0) return 0.1102 * (rejection-8.7); - else if(rejection >= 21.0) + if(rejection >= 21.0) return (0.5842 * std::pow(rejection-21.0, 0.4)) + (0.07886 * (rejection-21.0)); return 0.0; } @@ -107,24 +123,18 @@ struct BSincHeader { double width{}; double beta{}; double scaleBase{}; - double scaleRange{}; - double besseli_0_beta{}; - uint a[BSincScaleCount]{}; + std::array a{}; uint total_size{}; constexpr BSincHeader(uint Rejection, uint Order) noexcept + : width{CalcKaiserWidth(Rejection, Order)}, beta{CalcKaiserBeta(Rejection)} + , scaleBase{width / 2.0} { - width = CalcKaiserWidth(Rejection, Order); - beta = CalcKaiserBeta(Rejection); - scaleBase = width / 2.0; - scaleRange = 1.0 - scaleBase; - besseli_0_beta = BesselI_0(beta); - uint num_points{Order+1}; for(uint si{0};si < BSincScaleCount;++si) { - const double scale{scaleBase + (scaleRange * (si+1) / BSincScaleCount)}; + const double scale{lerpd(scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; const uint a_{std::min(static_cast(num_points / 2.0 / scale), num_points)}; const uint m{2 * a_}; @@ -142,37 +152,19 @@ constexpr BSincHeader bsinc12_hdr{60, 11}; constexpr BSincHeader bsinc24_hdr{60, 23}; -/* NOTE: GCC 5 has an issue with BSincHeader objects being in an anonymous - * namespace while also being used as non-type template parameters. - */ -#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 - -/* The number of sample points is double the a value (rounded up to a multiple - * of 4), and scale index 0 includes the doubling for downsampling. bsinc24 is - * currently the highest quality filter, and will use the most sample points. - */ -constexpr uint BSincPointsMax{(bsinc24_hdr.a[0]*2 + 3) & ~3u}; -static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); - -template -struct BSincFilterArray { - alignas(16) std::array mTable; - const BSincHeader &hdr; - - BSincFilterArray(const BSincHeader &hdr_) : hdr{hdr_} - { -#else template struct BSincFilterArray { alignas(16) std::array mTable{}; BSincFilterArray() { - constexpr uint BSincPointsMax{(hdr.a[0]*2 + 3) & ~3u}; + static constexpr uint BSincPointsMax{(hdr.a[0]*2u + 3u) & ~3u}; static_assert(BSincPointsMax <= MaxResamplerPadding, "MaxResamplerPadding is too small"); -#endif - using filter_type = double[BSincPhaseCount+1][BSincPointsMax]; - auto filter = std::make_unique(BSincScaleCount); + + using filter_type = std::array,BSincPhaseCount>; + auto filter = std::vector(BSincScaleCount); + + const double besseli_0_beta{cyl_bessel_i(0, hdr.beta)}; /* Calculate the Kaiser-windowed Sinc filter coefficients for each * scale and phase index. @@ -181,22 +173,19 @@ struct BSincFilterArray { { const uint m{hdr.a[si] * 2}; const size_t o{(BSincPointsMax-m) / 2}; - const double scale{hdr.scaleBase + (hdr.scaleRange * (si+1) / BSincScaleCount)}; + const double scale{lerpd(hdr.scaleBase, 1.0, (si+1) / double{BSincScaleCount})}; const double cutoff{scale - (hdr.scaleBase * std::max(1.0, scale*2.0))}; const auto a = static_cast(hdr.a[si]); const double l{a - 1.0/BSincPhaseCount}; - /* Do one extra phase index so that the phase delta has a proper - * target for its last index. - */ - for(uint pi{0};pi <= BSincPhaseCount;++pi) + for(uint pi{0};pi < BSincPhaseCount;++pi) { const double phase{std::floor(l) + (pi/double{BSincPhaseCount})}; for(uint i{0};i < m;++i) { const double x{i - phase}; - filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, hdr.besseli_0_beta) * cutoff * + filter[si][pi][o+i] = Kaiser(hdr.beta, x/l, besseli_0_beta) * cutoff * Sinc(cutoff*x); } } @@ -219,59 +208,82 @@ struct BSincFilterArray { /* Linear interpolation between phases is simplified by pre- * calculating the delta (b - a) in: x = a + f (b - a) */ - for(size_t i{0};i < m;++i) + if(pi < BSincPhaseCount-1) { - const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; - mTable[idx++] = static_cast(phDelta); + for(size_t i{0};i < m;++i) + { + const double phDelta{filter[si][pi+1][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(phDelta); + } + } + else + { + /* The delta target for the last phase index is the first + * phase index with the coefficients offset by one. The + * first delta targets 0, as it represents a coefficient + * for a sample that won't be part of the filter. + */ + mTable[idx++] = static_cast(0.0 - filter[si][pi][o]); + for(size_t i{1};i < m;++i) + { + const double phDelta{filter[si][0][o+i-1] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(phDelta); + } } } - /* Calculate and write out each phase index's filter quality scale - * deltas. The last scale index doesn't have any scale or scale- - * phase deltas. + + /* Now write out each phase index's scale and phase+scale deltas, + * to complete the bilinear equation for the combination of phase + * and scale. */ - if(si == BSincScaleCount-1) + if(si < BSincScaleCount-1) { + for(size_t pi{0};pi < BSincPhaseCount;++pi) + { + for(size_t i{0};i < m;++i) + { + const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; + mTable[idx++] = static_cast(scDelta); + } + + if(pi < BSincPhaseCount-1) + { + for(size_t i{0};i < m;++i) + { + const double spDelta{(filter[si+1][pi+1][o+i]-filter[si+1][pi][o+i]) - + (filter[si][pi+1][o+i]-filter[si][pi][o+i])}; + mTable[idx++] = static_cast(spDelta); + } + } + else + { + mTable[idx++] = static_cast((0.0 - filter[si+1][pi][o]) - + (0.0 - filter[si][pi][o])); + for(size_t i{1};i < m;++i) + { + const double spDelta{(filter[si+1][0][o+i-1] - filter[si+1][pi][o+i]) - + (filter[si][0][o+i-1] - filter[si][pi][o+i])}; + mTable[idx++] = static_cast(spDelta); + } + } + } + } + else + { + /* The last scale index doesn't have scale-related deltas. */ for(size_t i{0};i < BSincPhaseCount*m*2;++i) mTable[idx++] = 0.0f; } - else for(size_t pi{0};pi < BSincPhaseCount;++pi) - { - /* Linear interpolation between scales is also simplified. - * - * Given a difference in the number of points between scales, - * the destination points will be 0, thus: x = a + f (-a) - */ - for(size_t i{0};i < m;++i) - { - const double scDelta{filter[si+1][pi][o+i] - filter[si][pi][o+i]}; - mTable[idx++] = static_cast(scDelta); - } - - /* This last simplification is done to complete the bilinear - * equation for the combination of phase and scale. - */ - for(size_t i{0};i < m;++i) - { - const double spDelta{(filter[si+1][pi+1][o+i] - filter[si+1][pi][o+i]) - - (filter[si][pi+1][o+i] - filter[si][pi][o+i])}; - mTable[idx++] = static_cast(spDelta); - } - } } assert(idx == hdr.total_size); } - constexpr const BSincHeader &getHeader() const noexcept { return hdr; } - constexpr const float *getTable() const noexcept { return &mTable.front(); } + [[nodiscard]] constexpr auto getHeader() const noexcept -> const BSincHeader& { return hdr; } + [[nodiscard]] constexpr auto getTable() const noexcept { return al::span{mTable}; } }; -#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 6 -const BSincFilterArray bsinc12_filter{bsinc12_hdr}; -const BSincFilterArray bsinc24_filter{bsinc24_hdr}; -#else const BSincFilterArray bsinc12_filter{}; const BSincFilterArray bsinc24_filter{}; -#endif template constexpr BSincTable GenerateBSincTable(const T &filter) @@ -279,7 +291,7 @@ constexpr BSincTable GenerateBSincTable(const T &filter) BSincTable ret{}; const BSincHeader &hdr = filter.getHeader(); ret.scaleBase = static_cast(hdr.scaleBase); - ret.scaleRange = static_cast(1.0 / hdr.scaleRange); + ret.scaleRange = static_cast(1.0 / (1.0 - hdr.scaleBase)); for(size_t i{0};i < BSincScaleCount;++i) ret.m[i] = ((hdr.a[i]*2) + 3) & ~3u; ret.filterOffset[0] = 0; diff --git a/Engine/lib/openal-soft/core/bsinc_tables.h b/Engine/lib/openal-soft/core/bsinc_tables.h index aca4b2744..eab894ce9 100644 --- a/Engine/lib/openal-soft/core/bsinc_tables.h +++ b/Engine/lib/openal-soft/core/bsinc_tables.h @@ -1,14 +1,17 @@ #ifndef CORE_BSINC_TABLES_H #define CORE_BSINC_TABLES_H +#include + +#include "alspan.h" #include "bsinc_defs.h" struct BSincTable { float scaleBase, scaleRange; - unsigned int m[BSincScaleCount]; - unsigned int filterOffset[BSincScaleCount]; - const float *Tab; + std::array m; + std::array filterOffset; + al::span Tab; }; extern const BSincTable gBSinc12; diff --git a/Engine/lib/openal-soft/core/buffer_storage.cpp b/Engine/lib/openal-soft/core/buffer_storage.cpp index 98ca2c1b3..780e388ad 100644 --- a/Engine/lib/openal-soft/core/buffer_storage.cpp +++ b/Engine/lib/openal-soft/core/buffer_storage.cpp @@ -3,79 +3,3 @@ #include "buffer_storage.h" -#include - - -const char *NameFromFormat(FmtType type) noexcept -{ - switch(type) - { - case FmtUByte: return "UInt8"; - case FmtShort: return "Int16"; - case FmtFloat: return "Float"; - case FmtDouble: return "Double"; - case FmtMulaw: return "muLaw"; - case FmtAlaw: return "aLaw"; - case FmtIMA4: return "IMA4 ADPCM"; - case FmtMSADPCM: return "MS ADPCM"; - } - return ""; -} - -const char *NameFromFormat(FmtChannels channels) noexcept -{ - switch(channels) - { - case FmtMono: return "Mono"; - case FmtStereo: return "Stereo"; - case FmtRear: return "Rear"; - case FmtQuad: return "Quadraphonic"; - case FmtX51: return "Surround 5.1"; - case FmtX61: return "Surround 6.1"; - case FmtX71: return "Surround 7.1"; - case FmtBFormat2D: return "B-Format 2D"; - case FmtBFormat3D: return "B-Format 3D"; - case FmtUHJ2: return "UHJ2"; - case FmtUHJ3: return "UHJ3"; - case FmtUHJ4: return "UHJ4"; - case FmtSuperStereo: return "Super Stereo"; - } - return ""; -} - -uint BytesFromFmt(FmtType type) noexcept -{ - switch(type) - { - case FmtUByte: return sizeof(uint8_t); - case FmtShort: return sizeof(int16_t); - case FmtFloat: return sizeof(float); - case FmtDouble: return sizeof(double); - case FmtMulaw: return sizeof(uint8_t); - case FmtAlaw: return sizeof(uint8_t); - case FmtIMA4: break; - case FmtMSADPCM: break; - } - return 0; -} - -uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept -{ - switch(chans) - { - case FmtMono: return 1; - case FmtStereo: return 2; - case FmtRear: return 2; - case FmtQuad: return 4; - case FmtX51: return 6; - case FmtX61: return 7; - case FmtX71: return 8; - case FmtBFormat2D: return (ambiorder*2) + 1; - case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); - case FmtUHJ2: return 2; - case FmtUHJ3: return 3; - case FmtUHJ4: return 4; - case FmtSuperStereo: return 2; - } - return 0; -} diff --git a/Engine/lib/openal-soft/core/buffer_storage.h b/Engine/lib/openal-soft/core/buffer_storage.h index 282d5b53e..ab097cf12 100644 --- a/Engine/lib/openal-soft/core/buffer_storage.h +++ b/Engine/lib/openal-soft/core/buffer_storage.h @@ -2,61 +2,16 @@ #define CORE_BUFFER_STORAGE_H #include +#include -#include "albyte.h" #include "alnumeric.h" #include "alspan.h" #include "ambidefs.h" +#include "storage_formats.h" using uint = unsigned int; -/* Storable formats */ -enum FmtType : unsigned char { - FmtUByte, - FmtShort, - FmtFloat, - FmtDouble, - FmtMulaw, - FmtAlaw, - FmtIMA4, - FmtMSADPCM, -}; -enum FmtChannels : unsigned char { - FmtMono, - FmtStereo, - FmtRear, - FmtQuad, - FmtX51, /* (WFX order) */ - FmtX61, /* (WFX order) */ - FmtX71, /* (WFX order) */ - FmtBFormat2D, - FmtBFormat3D, - FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ - FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ - FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ - FmtSuperStereo, /* Stereo processed with Super Stereo. */ -}; - -enum class AmbiLayout : unsigned char { - FuMa, - ACN, -}; -enum class AmbiScaling : unsigned char { - FuMa, - SN3D, - N3D, - UHJ, -}; - -const char *NameFromFormat(FmtType type) noexcept; -const char *NameFromFormat(FmtChannels channels) noexcept; - -uint BytesFromFmt(FmtType type) noexcept; -uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; -inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept -{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } - constexpr bool IsBFormat(FmtChannels chans) noexcept { return chans == FmtBFormat2D || chans == FmtBFormat3D; } @@ -85,7 +40,7 @@ struct BufferStorage { CallbackType mCallback{nullptr}; void *mUserData{nullptr}; - al::span mData; + al::span mData; uint mSampleRate{0u}; FmtChannels mChannels{FmtMono}; @@ -97,19 +52,20 @@ struct BufferStorage { AmbiScaling mAmbiScaling{AmbiScaling::FuMa}; uint mAmbiOrder{0u}; - inline uint bytesFromFmt() const noexcept { return BytesFromFmt(mType); } - inline uint channelsFromFmt() const noexcept + [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromFmt(mType); } + [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromFmt(mChannels, mAmbiOrder); } - inline uint frameSizeFromFmt() const noexcept { return channelsFromFmt() * bytesFromFmt(); } + [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint + { return channelsFromFmt() * bytesFromFmt(); } - inline uint blockSizeFromFmt() const noexcept + [[nodiscard]] auto blockSizeFromFmt() const noexcept -> uint { if(mType == FmtIMA4) return ((mBlockAlign-1)/2 + 4) * channelsFromFmt(); if(mType == FmtMSADPCM) return ((mBlockAlign-2)/2 + 7) * channelsFromFmt(); return frameSizeFromFmt(); }; - inline bool isBFormat() const noexcept { return IsBFormat(mChannels); } + [[nodiscard]] auto isBFormat() const noexcept -> bool { return IsBFormat(mChannels); } }; #endif /* CORE_BUFFER_STORAGE_H */ diff --git a/Engine/lib/openal-soft/core/bufferline.h b/Engine/lib/openal-soft/core/bufferline.h index 8b445f3ff..309fb778f 100644 --- a/Engine/lib/openal-soft/core/bufferline.h +++ b/Engine/lib/openal-soft/core/bufferline.h @@ -9,7 +9,7 @@ * more memory and are harder on cache, while smaller values may need more * iterations for mixing. */ -constexpr int BufferLineSize{1024}; +inline constexpr size_t BufferLineSize{1024}; using FloatBufferLine = std::array; using FloatBufferSpan = al::span; diff --git a/Engine/lib/openal-soft/core/context.cpp b/Engine/lib/openal-soft/core/context.cpp index d68d8327d..cff0e40cf 100644 --- a/Engine/lib/openal-soft/core/context.cpp +++ b/Engine/lib/openal-soft/core/context.cpp @@ -2,7 +2,11 @@ #include "config.h" #include +#include +#include #include +#include +#include #include "async_event.h" #include "context.h" @@ -23,52 +27,23 @@ ContextBase::ContextBase(DeviceBase *device) : mDevice{device} ContextBase::~ContextBase() { - size_t count{0}; - ContextProps *cprops{mParams.ContextUpdate.exchange(nullptr, std::memory_order_relaxed)}; - if(cprops) - { - ++count; - delete cprops; - } - cprops = mFreeContextProps.exchange(nullptr, std::memory_order_acquire); - while(cprops) - { - std::unique_ptr old{cprops}; - cprops = old->next.load(std::memory_order_relaxed); - ++count; - } - TRACE("Freed %zu context property object%s\n", count, (count==1)?"":"s"); - - count = 0; - EffectSlotProps *eprops{mFreeEffectslotProps.exchange(nullptr, std::memory_order_acquire)}; - while(eprops) - { - std::unique_ptr old{eprops}; - eprops = old->next.load(std::memory_order_relaxed); - ++count; - } - TRACE("Freed %zu AuxiliaryEffectSlot property object%s\n", count, (count==1)?"":"s"); - - if(EffectSlotArray *curarray{mActiveAuxSlots.exchange(nullptr, std::memory_order_relaxed)}) - { - al::destroy_n(curarray->end(), curarray->size()); - delete curarray; - } - - delete mVoices.exchange(nullptr, std::memory_order_relaxed); + mActiveAuxSlots.store(nullptr, std::memory_order_relaxed); + mVoices.store(nullptr, std::memory_order_relaxed); if(mAsyncEvents) { - count = 0; + size_t count{0}; auto evt_vec = mAsyncEvents->getReadVector(); if(evt_vec.first.len > 0) { - al::destroy_n(reinterpret_cast(evt_vec.first.buf), evt_vec.first.len); + std::destroy_n(std::launder(reinterpret_cast(evt_vec.first.buf)), + evt_vec.first.len); count += evt_vec.first.len; } if(evt_vec.second.len > 0) { - al::destroy_n(reinterpret_cast(evt_vec.second.buf), evt_vec.second.len); + std::destroy_n(std::launder(reinterpret_cast(evt_vec.second.buf)), + evt_vec.second.len); count += evt_vec.second.len; } if(count > 0) @@ -80,85 +55,131 @@ ContextBase::~ContextBase() void ContextBase::allocVoiceChanges() { - constexpr size_t clustersize{128}; + static constexpr size_t clustersize{std::tuple_size_v}; + + VoiceChangeCluster clusterptr{std::make_unique()}; + const auto cluster = al::span{*clusterptr}; - VoiceChangeCluster cluster{std::make_unique(clustersize)}; for(size_t i{1};i < clustersize;++i) cluster[i-1].mNext.store(std::addressof(cluster[i]), std::memory_order_relaxed); cluster[clustersize-1].mNext.store(mVoiceChangeTail, std::memory_order_relaxed); - mVoiceChangeClusters.emplace_back(std::move(cluster)); - mVoiceChangeTail = mVoiceChangeClusters.back().get(); + mVoiceChangeClusters.emplace_back(std::move(clusterptr)); + mVoiceChangeTail = mVoiceChangeClusters.back()->data(); } void ContextBase::allocVoiceProps() { - constexpr size_t clustersize{32}; + static constexpr size_t clustersize{std::tuple_size_v}; TRACE("Increasing allocated voice properties to %zu\n", (mVoicePropClusters.size()+1) * clustersize); - VoicePropsCluster cluster{std::make_unique(clustersize)}; + auto clusterptr = std::make_unique(); + auto cluster = al::span{*clusterptr}; for(size_t i{1};i < clustersize;++i) cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); - mVoicePropClusters.emplace_back(std::move(cluster)); + mVoicePropClusters.emplace_back(std::move(clusterptr)); VoicePropsItem *oldhead{mFreeVoiceProps.load(std::memory_order_acquire)}; do { - mVoicePropClusters.back()[clustersize-1].next.store(oldhead, std::memory_order_relaxed); - } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back().get(), + mVoicePropClusters.back()->back().next.store(oldhead, std::memory_order_relaxed); + } while(mFreeVoiceProps.compare_exchange_weak(oldhead, mVoicePropClusters.back()->data(), std::memory_order_acq_rel, std::memory_order_acquire) == false); } void ContextBase::allocVoices(size_t addcount) { - constexpr size_t clustersize{32}; + static constexpr size_t clustersize{std::tuple_size_v}; /* Convert element count to cluster count. */ addcount = (addcount+(clustersize-1)) / clustersize; + if(!addcount) + { + if(!mVoiceClusters.empty()) + return; + ++addcount; + } + if(addcount >= std::numeric_limits::max()/clustersize - mVoiceClusters.size()) throw std::runtime_error{"Allocating too many voices"}; const size_t totalcount{(mVoiceClusters.size()+addcount) * clustersize}; TRACE("Increasing allocated voices to %zu\n", totalcount); - auto newarray = VoiceArray::Create(totalcount); while(addcount) { - mVoiceClusters.emplace_back(std::make_unique(clustersize)); + mVoiceClusters.emplace_back(std::make_unique()); --addcount; } + auto newarray = VoiceArray::Create(totalcount); auto voice_iter = newarray->begin(); for(VoiceCluster &cluster : mVoiceClusters) - { - for(size_t i{0};i < clustersize;++i) - *(voice_iter++) = &cluster[i]; - } + voice_iter = std::transform(cluster->begin(), cluster->end(), voice_iter, + [](Voice &voice) noexcept -> Voice* { return &voice; }); - if(auto *oldvoices = mVoices.exchange(newarray.release(), std::memory_order_acq_rel)) - { - mDevice->waitForMix(); - delete oldvoices; - } + if(auto oldvoices = mVoices.exchange(std::move(newarray), std::memory_order_acq_rel)) + std::ignore = mDevice->waitForMix(); } +void ContextBase::allocEffectSlotProps() +{ + static constexpr size_t clustersize{std::tuple_size_v}; + + TRACE("Increasing allocated effect slot properties to %zu\n", + (mEffectSlotPropClusters.size()+1) * clustersize); + + auto clusterptr = std::make_unique(); + auto cluster = al::span{*clusterptr}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); + auto *newcluster = mEffectSlotPropClusters.emplace_back(std::move(clusterptr)).get(); + + EffectSlotProps *oldhead{mFreeEffectSlotProps.load(std::memory_order_acquire)}; + do { + newcluster->back().next.store(oldhead, std::memory_order_relaxed); + } while(mFreeEffectSlotProps.compare_exchange_weak(oldhead, newcluster->data(), + std::memory_order_acq_rel, std::memory_order_acquire) == false); +} + EffectSlot *ContextBase::getEffectSlot() { - for(auto& cluster : mEffectSlotClusters) + for(auto& clusterptr : mEffectSlotClusters) { - for(size_t i{0};i < EffectSlotClusterSize;++i) - { - if(!cluster[i].InUse) - return &cluster[i]; - } + const auto cluster = al::span{*clusterptr}; + auto iter = std::find_if_not(cluster.begin(), cluster.end(), + std::mem_fn(&EffectSlot::InUse)); + if(iter != cluster.end()) return al::to_address(iter); } - if(1 >= std::numeric_limits::max()/EffectSlotClusterSize - mEffectSlotClusters.size()) + auto clusterptr = std::make_unique(); + if(1 >= std::numeric_limits::max()/clusterptr->size() - mEffectSlotClusters.size()) throw std::runtime_error{"Allocating too many effect slots"}; - const size_t totalcount{(mEffectSlotClusters.size()+1) * EffectSlotClusterSize}; + const size_t totalcount{(mEffectSlotClusters.size()+1) * clusterptr->size()}; TRACE("Increasing allocated effect slots to %zu\n", totalcount); - mEffectSlotClusters.emplace_back(std::make_unique(EffectSlotClusterSize)); - return getEffectSlot(); + mEffectSlotClusters.emplace_back(std::move(clusterptr)); + return mEffectSlotClusters.back()->data(); +} + + +void ContextBase::allocContextProps() +{ + static constexpr size_t clustersize{std::tuple_size_v}; + + TRACE("Increasing allocated context properties to %zu\n", + (mContextPropClusters.size()+1) * clustersize); + + auto clusterptr = std::make_unique(); + auto cluster = al::span{*clusterptr}; + for(size_t i{1};i < clustersize;++i) + cluster[i-1].next.store(std::addressof(cluster[i]), std::memory_order_relaxed); + auto *newcluster = mContextPropClusters.emplace_back(std::move(clusterptr)).get(); + + ContextProps *oldhead{mFreeContextProps.load(std::memory_order_acquire)}; + do { + newcluster->back().next.store(oldhead, std::memory_order_relaxed); + } while(mFreeContextProps.compare_exchange_weak(oldhead, newcluster->data(), + std::memory_order_acq_rel, std::memory_order_acquire) == false); } diff --git a/Engine/lib/openal-soft/core/context.h b/Engine/lib/openal-soft/core/context.h index 9723eac38..1a1852e33 100644 --- a/Engine/lib/openal-soft/core/context.h +++ b/Engine/lib/openal-soft/core/context.h @@ -7,15 +7,16 @@ #include #include #include +#include #include "almalloc.h" +#include "alsem.h" #include "alspan.h" #include "async_event.h" #include "atomic.h" -#include "bufferline.h" -#include "threads.h" +#include "flexarray.h" +#include "opthelpers.h" #include "vecmat.h" -#include "vector.h" struct DeviceBase; struct EffectSlot; @@ -25,12 +26,10 @@ struct Voice; struct VoiceChange; struct VoicePropsItem; -using uint = unsigned int; +inline constexpr float SpeedOfSoundMetersPerSec{343.3f}; -constexpr float SpeedOfSoundMetersPerSec{343.3f}; - -constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ +inline constexpr float AirAbsorbGainHF{0.99426f}; /* -0.05dB */ enum class DistanceModel : unsigned char { Disable, @@ -58,8 +57,6 @@ struct ContextProps { DistanceModel mDistanceModel; std::atomic next; - - DEF_NEWDEL(ContextProps) }; struct ContextParams { @@ -87,7 +84,7 @@ struct ContextBase { /* Counter for the pre-mixing updates, in 31.1 fixed point (lowest bit * indicates if updates are currently happening). */ - RefCount mUpdateCount{0u}; + std::atomic mUpdateCount{0u}; std::atomic mHoldUpdates{false}; std::atomic mStopVoicesOnDisconnect{true}; @@ -98,7 +95,7 @@ struct ContextBase { */ std::atomic mFreeContextProps{nullptr}; std::atomic mFreeVoiceProps{nullptr}; - std::atomic mFreeEffectslotProps{nullptr}; + std::atomic mFreeEffectSlotProps{nullptr}; /* The voice change tail is the beginning of the "free" elements, up to and * *excluding* the current. If tail==current, there's no free elements and @@ -110,21 +107,22 @@ struct ContextBase { void allocVoiceChanges(); void allocVoiceProps(); - + void allocEffectSlotProps(); + void allocContextProps(); ContextParams mParams; using VoiceArray = al::FlexArray; - std::atomic mVoices{}; + al::atomic_unique_ptr mVoices{}; std::atomic mActiveVoiceCount{}; void allocVoices(size_t addcount); - al::span getVoicesSpan() const noexcept + [[nodiscard]] auto getVoicesSpan() const noexcept -> al::span { return {mVoices.load(std::memory_order_relaxed)->data(), mActiveVoiceCount.load(std::memory_order_relaxed)}; } - al::span getVoicesSpanAcquired() const noexcept + [[nodiscard]] auto getVoicesSpanAcquired() const noexcept -> al::span { return {mVoices.load(std::memory_order_acquire)->data(), mActiveVoiceCount.load(std::memory_order_acquire)}; @@ -132,12 +130,16 @@ struct ContextBase { using EffectSlotArray = al::FlexArray; - std::atomic mActiveAuxSlots{nullptr}; + /* This array is split in half. The front half is the list of activated + * effect slots as set by the app, and the back half is the same list but + * sorted to ensure later effect slots are fed by earlier ones. + */ + al::atomic_unique_ptr mActiveAuxSlots; std::thread mEventThread; al::semaphore mEventSem; std::unique_ptr mAsyncEvents; - using AsyncEventBitset = std::bitset; + using AsyncEventBitset = std::bitset; std::atomic mEnabledEvts{0u}; /* Asynchronous voice change actions are processed as a linked list of @@ -145,21 +147,29 @@ struct ContextBase { * However, to avoid allocating each object individually, they're allocated * in clusters that are stored in a vector for easy automatic cleanup. */ - using VoiceChangeCluster = std::unique_ptr; - al::vector mVoiceChangeClusters; + using VoiceChangeCluster = std::unique_ptr>; + std::vector mVoiceChangeClusters; - using VoiceCluster = std::unique_ptr; - al::vector mVoiceClusters; + using VoiceCluster = std::unique_ptr>; + std::vector mVoiceClusters; - using VoicePropsCluster = std::unique_ptr; - al::vector mVoicePropClusters; + using VoicePropsCluster = std::unique_ptr>; + std::vector mVoicePropClusters; - static constexpr size_t EffectSlotClusterSize{4}; EffectSlot *getEffectSlot(); - using EffectSlotCluster = std::unique_ptr; - al::vector mEffectSlotClusters; + using EffectSlotCluster = std::unique_ptr>; + std::vector mEffectSlotClusters; + + using EffectSlotPropsCluster = std::unique_ptr>; + std::vector mEffectSlotPropClusters; + + /* This could be greater than 2, but there should be no way there can be + * more than two context property updates in use simultaneously. + */ + using ContextPropsCluster = std::unique_ptr>; + std::vector mContextPropClusters; ContextBase(DeviceBase *device); diff --git a/Engine/lib/openal-soft/core/converter.cpp b/Engine/lib/openal-soft/core/converter.cpp index a51414484..97102536f 100644 --- a/Engine/lib/openal-soft/core/converter.cpp +++ b/Engine/lib/openal-soft/core/converter.cpp @@ -6,12 +6,12 @@ #include #include #include +#include #include #include -#include +#include #include "albit.h" -#include "albyte.h" #include "alnumeric.h" #include "fpu_ctrl.h" @@ -24,43 +24,46 @@ static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for Buffer static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); -/* Base template left undefined. Should be marked =delete, but Clang 3.8.1 - * chokes on that given the inline specializations. - */ template -inline float LoadSample(DevFmtType_t val) noexcept; +constexpr float LoadSample(DevFmtType_t val) noexcept = delete; -template<> inline float LoadSample(DevFmtType_t val) noexcept -{ return val * (1.0f/128.0f); } -template<> inline float LoadSample(DevFmtType_t val) noexcept -{ return val * (1.0f/32768.0f); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept +{ return float(val) * (1.0f/128.0f); } +template<> constexpr float LoadSample(DevFmtType_t val) noexcept +{ return float(val) * (1.0f/32768.0f); } +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return static_cast(val) * (1.0f/2147483648.0f); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return val; } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 128)); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 32768)); } -template<> inline float LoadSample(DevFmtType_t val) noexcept +template<> constexpr float LoadSample(DevFmtType_t val) noexcept { return LoadSample(static_cast(val - 2147483648u)); } template -inline void LoadSampleArray(float *RESTRICT dst, const void *src, const size_t srcstep, - const size_t samples) noexcept +inline void LoadSampleArray(const al::span dst, const void *src, const size_t channel, + const size_t srcstep) noexcept { - const DevFmtType_t *ssrc = static_cast*>(src); - for(size_t i{0u};i < samples;i++) - dst[i] = LoadSample(ssrc[i*srcstep]); + assert(channel < srcstep); + const auto srcspan = al::span{static_cast*>(src), dst.size()*srcstep}; + auto ssrc = srcspan.cbegin(); + std::generate(dst.begin(), dst.end(), [&ssrc,channel,srcstep] + { + const float ret{LoadSample(ssrc[channel])}; + ssrc += ptrdiff_t(srcstep); + return ret; + }); } -void LoadSamples(float *dst, const void *src, const size_t srcstep, const DevFmtType srctype, - const size_t samples) noexcept +void LoadSamples(const al::span dst, const void *src, const size_t channel, + const size_t srcstep, const DevFmtType srctype) noexcept { #define HANDLE_FMT(T) \ - case T: LoadSampleArray(dst, src, srcstep, samples); break + case T: LoadSampleArray(dst, src, channel, srcstep); break switch(srctype) { HANDLE_FMT(DevFmtByte); @@ -81,11 +84,11 @@ inline DevFmtType_t StoreSample(float) noexcept; template<> inline float StoreSample(float val) noexcept { return val; } template<> inline int32_t StoreSample(float val) noexcept -{ return fastf2i(clampf(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } +{ return fastf2i(std::clamp(val*2147483648.0f, -2147483648.0f, 2147483520.0f)); } template<> inline int16_t StoreSample(float val) noexcept -{ return static_cast(fastf2i(clampf(val*32768.0f, -32768.0f, 32767.0f))); } +{ return static_cast(fastf2i(std::clamp(val*32768.0f, -32768.0f, 32767.0f))); } template<> inline int8_t StoreSample(float val) noexcept -{ return static_cast(fastf2i(clampf(val*128.0f, -128.0f, 127.0f))); } +{ return static_cast(fastf2i(std::clamp(val*128.0f, -128.0f, 127.0f))); } /* Define unsigned output variations. */ template<> inline uint32_t StoreSample(float val) noexcept @@ -96,20 +99,25 @@ template<> inline uint8_t StoreSample(float val) noexcept { return static_cast(StoreSample(val) + 128); } template -inline void StoreSampleArray(void *dst, const float *RESTRICT src, const size_t dststep, - const size_t samples) noexcept +inline void StoreSampleArray(void *dst, const al::span src, const size_t channel, + const size_t dststep) noexcept { - DevFmtType_t *sdst = static_cast*>(dst); - for(size_t i{0u};i < samples;i++) - sdst[i*dststep] = StoreSample(src[i]); + assert(channel < dststep); + const auto dstspan = al::span{static_cast*>(dst), src.size()*dststep}; + auto sdst = dstspan.begin(); + std::for_each(src.cbegin(), src.cend(), [&sdst,channel,dststep](const float in) + { + sdst[channel] = StoreSample(in); + sdst += ptrdiff_t(dststep); + }); } -void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFmtType dsttype, - const size_t samples) noexcept +void StoreSamples(void *dst, const al::span src, const size_t channel, + const size_t dststep, const DevFmtType dsttype) noexcept { #define HANDLE_FMT(T) \ - case T: StoreSampleArray(dst, src, dststep, samples); break + case T: StoreSampleArray(dst, src, channel, dststep); break switch(dsttype) { HANDLE_FMT(DevFmtByte); @@ -125,30 +133,35 @@ void StoreSamples(void *dst, const float *src, const size_t dststep, const DevFm template -void Mono2Stereo(float *RESTRICT dst, const void *src, const size_t frames) noexcept +void Mono2Stereo(const al::span dst, const void *src) noexcept { - const DevFmtType_t *ssrc = static_cast*>(src); - for(size_t i{0u};i < frames;i++) - dst[i*2 + 1] = dst[i*2 + 0] = LoadSample(ssrc[i]) * 0.707106781187f; + const auto srcspan = al::span{static_cast*>(src), dst.size()>>1}; + auto sdst = dst.begin(); + std::for_each(srcspan.cbegin(), srcspan.cend(), [&sdst](const auto in) + { sdst = std::fill_n(sdst, 2, LoadSample(in)*0.707106781187f); }); } template -void Multi2Mono(uint chanmask, const size_t step, const float scale, float *RESTRICT dst, - const void *src, const size_t frames) noexcept +void Multi2Mono(uint chanmask, const size_t step, const float scale, const al::span dst, + const void *src) noexcept { - const DevFmtType_t *ssrc = static_cast*>(src); - std::fill_n(dst, frames, 0.0f); + const auto srcspan = al::span{static_cast*>(src), step*dst.size()}; + std::fill_n(dst.begin(), dst.size(), 0.0f); for(size_t c{0};chanmask;++c) { if((chanmask&1)) LIKELY { - for(size_t i{0u};i < frames;i++) - dst[i] += LoadSample(ssrc[i*step + c]); + auto ssrc = srcspan.cbegin(); + std::for_each(dst.begin(), dst.end(), [&ssrc,step,c](float &sample) + { + const float s{LoadSample(ssrc[c])}; + ssrc += ptrdiff_t(step); + sample += s; + }); } chanmask >>= 1; } - for(size_t i{0u};i < frames;i++) - dst[i] *= scale; + std::for_each(dst.begin(), dst.end(), [scale](float &sample) noexcept { sample *= scale; }); } } // namespace @@ -168,19 +181,19 @@ SampleConverterPtr SampleConverter::Create(DevFmtType srcType, DevFmtType dstTyp converter->mSrcPrepCount = MaxResamplerPadding; converter->mFracOffset = 0; for(auto &chan : converter->mChan) - { - const al::span buffer{chan.PrevSamples}; - std::fill(buffer.begin(), buffer.end(), 0.0f); - } + chan.PrevSamples.fill(0.0f); /* Have to set the mixer FPU mode since that's what the resampler code expects. */ FPUCtl mixer_mode{}; - auto step = static_cast( - mind(srcRate*double{MixerFracOne}/dstRate + 0.5, MaxPitch*MixerFracOne)); - converter->mIncrement = maxu(step, 1); + const auto step = std::min(std::round(srcRate*double{MixerFracOne}/dstRate), + MaxPitch*double{MixerFracOne}); + converter->mIncrement = std::max(static_cast(step), 1u); if(converter->mIncrement == MixerFracOne) - converter->mResample = [](const InterpState*, const float *RESTRICT src, uint, const uint, - const al::span dst) { std::copy_n(src, dst.size(), dst.begin()); }; + { + converter->mResample = [](const InterpState*, const al::span src, uint, + const uint, const al::span dst) + { std::copy_n(src.begin()+MaxResamplerEdge, dst.size(), dst.begin()); }; + } else converter->mResample = PrepareResampler(resampler, converter->mIncrement, &converter->mState); @@ -210,24 +223,25 @@ uint SampleConverter::availableOut(uint srcframes) const DataSize64 -= mFracOffset; /* If we have a full prep, we can generate at least one sample. */ - return static_cast(clampu64((DataSize64 + mIncrement-1)/mIncrement, 1, - std::numeric_limits::max())); + return static_cast(std::clamp((DataSize64 + mIncrement-1)/mIncrement, 1_u64, + uint64_t{std::numeric_limits::max()})); } uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint dstframes) { - const uint SrcFrameSize{static_cast(mChan.size()) * mSrcTypeSize}; - const uint DstFrameSize{static_cast(mChan.size()) * mDstTypeSize}; + const size_t SrcFrameSize{mChan.size() * mSrcTypeSize}; + const size_t DstFrameSize{mChan.size() * mDstTypeSize}; const uint increment{mIncrement}; - auto SamplesIn = static_cast(*src); uint NumSrcSamples{*srcframes}; + auto SamplesIn = al::span{static_cast(*src), NumSrcSamples*SrcFrameSize}; + auto SamplesOut = al::span{static_cast(dst), dstframes*DstFrameSize}; FPUCtl mixer_mode{}; uint pos{0}; while(pos < dstframes && NumSrcSamples > 0) { const uint prepcount{mSrcPrepCount}; - const uint readable{minu(NumSrcSamples, BufferLineSize - prepcount)}; + const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable) { @@ -235,16 +249,16 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint * what we're given for later. */ for(size_t chan{0u};chan < mChan.size();chan++) - LoadSamples(&mChan[chan].PrevSamples[prepcount], SamplesIn + mSrcTypeSize*chan, - mChan.size(), mSrcType, readable); + LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), + SamplesIn.data(), chan, mChan.size(), mSrcType); mSrcPrepCount = prepcount + readable; NumSrcSamples = 0; break; } - float *RESTRICT SrcData{mSrcSamples}; - float *RESTRICT DstData{mDstSamples}; + const auto SrcData = al::span{mSrcSamples}; + const auto DstData = al::span{mDstSamples}; uint DataPosFrac{mFracOffset}; uint64_t DataSize64{prepcount}; DataSize64 += readable; @@ -253,39 +267,36 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint DataSize64 -= DataPosFrac; /* If we have a full prep, we can generate at least one sample. */ - auto DstSize = static_cast( - clampu64((DataSize64 + increment-1)/increment, 1, BufferLineSize)); - DstSize = minu(DstSize, dstframes-pos); + auto DstSize = static_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, + uint64_t{BufferLineSize})); + DstSize = std::min(DstSize, dstframes-pos); const uint DataPosEnd{DstSize*increment + DataPosFrac}; const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; assert(prepcount+readable >= SrcDataEnd); - const uint nextprep{minu(prepcount + readable - SrcDataEnd, MaxResamplerPadding)}; + const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)}; for(size_t chan{0u};chan < mChan.size();chan++) { - const al::byte *SrcSamples{SamplesIn + mSrcTypeSize*chan}; - al::byte *DstSamples = static_cast(dst) + mDstTypeSize*chan; - /* Load the previous samples into the source data first, then the * new samples from the input buffer. */ - std::copy_n(mChan[chan].PrevSamples, prepcount, SrcData); - LoadSamples(SrcData + prepcount, SrcSamples, mChan.size(), mSrcType, readable); + std::copy_n(mChan[chan].PrevSamples.cbegin(), prepcount, SrcData.begin()); + LoadSamples(SrcData.subspan(prepcount, readable), SamplesIn.data(), chan, mChan.size(), + mSrcType); /* Store as many prep samples for next time as possible, given the * number of output samples being generated. */ - std::copy_n(SrcData+SrcDataEnd, nextprep, mChan[chan].PrevSamples); - std::fill(std::begin(mChan[chan].PrevSamples)+nextprep, - std::end(mChan[chan].PrevSamples), 0.0f); + auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep, + mChan[chan].PrevSamples.begin()); + std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); /* Now resample, and store the result in the output buffer. */ - mResample(&mState, SrcData+MaxResamplerEdge, DataPosFrac, increment, - {DstData, DstSize}); + mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); - StoreSamples(DstSamples, DstData, mChan.size(), mDstType, DstSize); + StoreSamples(SamplesOut.data(), DstData.first(DstSize), chan, mChan.size(), mDstType); } /* Update the number of prep samples still available, as well as the @@ -295,15 +306,115 @@ uint SampleConverter::convert(const void **src, uint *srcframes, void *dst, uint mFracOffset = DataPosEnd & MixerFracMask; /* Update the src and dst pointers in case there's still more to do. */ - const uint srcread{minu(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; - SamplesIn += SrcFrameSize*srcread; + const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; + SamplesIn = SamplesIn.subspan(SrcFrameSize*srcread); + NumSrcSamples -= srcread; + + SamplesOut = SamplesOut.subspan(DstFrameSize*DstSize); + pos += DstSize; + } + + *src = SamplesIn.data(); + *srcframes = NumSrcSamples; + + return pos; +} + +uint SampleConverter::convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) +{ + const auto srcs = al::span{src, mChan.size()}; + const auto dsts = al::span{dst, mChan.size()}; + const uint increment{mIncrement}; + uint NumSrcSamples{*srcframes}; + + FPUCtl mixer_mode{}; + uint pos{0}; + while(pos < dstframes && NumSrcSamples > 0) + { + const uint prepcount{mSrcPrepCount}; + const uint readable{std::min(NumSrcSamples, uint{BufferLineSize} - prepcount)}; + + if(prepcount < MaxResamplerPadding && MaxResamplerPadding-prepcount >= readable) + { + /* Not enough input samples to generate an output sample. Store + * what we're given for later. + */ + for(size_t chan{0u};chan < mChan.size();chan++) + { + auto samples = al::span{static_cast(srcs[chan]), + NumSrcSamples*size_t{mSrcTypeSize}}; + LoadSamples(al::span{mChan[chan].PrevSamples}.subspan(prepcount, readable), + samples.data(), 0, 1, mSrcType); + srcs[chan] = samples.subspan(size_t{mSrcTypeSize}*readable).data(); + } + + mSrcPrepCount = prepcount + readable; + NumSrcSamples = 0; + break; + } + + const auto SrcData = al::span{mSrcSamples}; + const auto DstData = al::span{mDstSamples}; + uint DataPosFrac{mFracOffset}; + uint64_t DataSize64{prepcount}; + DataSize64 += readable; + DataSize64 -= MaxResamplerPadding; + DataSize64 <<= MixerFracBits; + DataSize64 -= DataPosFrac; + + /* If we have a full prep, we can generate at least one sample. */ + auto DstSize = static_cast(std::clamp((DataSize64 + increment-1)/increment, 1_u64, + uint64_t{BufferLineSize})); + DstSize = std::min(DstSize, dstframes-pos); + + const uint DataPosEnd{DstSize*increment + DataPosFrac}; + const uint SrcDataEnd{DataPosEnd>>MixerFracBits}; + + assert(prepcount+readable >= SrcDataEnd); + const uint nextprep{std::min(prepcount+readable-SrcDataEnd, MaxResamplerPadding)}; + + for(size_t chan{0u};chan < mChan.size();chan++) + { + /* Load the previous samples into the source data first, then the + * new samples from the input buffer. + */ + auto srciter = std::copy_n(mChan[chan].PrevSamples.cbegin(),prepcount,SrcData.begin()); + LoadSamples({srciter, readable}, srcs[chan], 0, 1, mSrcType); + + /* Store as many prep samples for next time as possible, given the + * number of output samples being generated. + */ + auto previter = std::copy_n(SrcData.begin()+ptrdiff_t(SrcDataEnd), nextprep, + mChan[chan].PrevSamples.begin()); + std::fill(previter, mChan[chan].PrevSamples.end(), 0.0f); + + /* Now resample, and store the result in the output buffer. */ + mResample(&mState, SrcData, DataPosFrac, increment, DstData.first(DstSize)); + + auto DstSamples = al::span{static_cast(dsts[chan]), + size_t{mDstTypeSize}*dstframes}.subspan(pos*size_t{mDstTypeSize}); + StoreSamples(DstSamples.data(), DstData.first(DstSize), 0, 1, mDstType); + } + + /* Update the number of prep samples still available, as well as the + * fractional offset. + */ + mSrcPrepCount = nextprep; + mFracOffset = DataPosEnd & MixerFracMask; + + /* Update the src and dst pointers in case there's still more to do. */ + const uint srcread{std::min(NumSrcSamples, SrcDataEnd + mSrcPrepCount - prepcount)}; + std::for_each(srcs.begin(), srcs.end(), [this,NumSrcSamples,srcread](const void *&srcref) + { + auto srcspan = al::span{static_cast(srcref), + size_t{mSrcTypeSize}*NumSrcSamples}; + srcref = srcspan.subspan(size_t{mSrcTypeSize}*srcread).data(); + }); NumSrcSamples -= srcread; - dst = static_cast(dst) + DstFrameSize*DstSize; pos += DstSize; } - *src = SamplesIn; *srcframes = NumSrcSamples; return pos; @@ -317,7 +428,7 @@ void ChannelConverter::convert(const void *src, float *dst, uint frames) const const float scale{std::sqrt(1.0f / static_cast(al::popcount(mChanMask)))}; switch(mSrcType) { -#define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, scale, dst, src, frames); break +#define HANDLE_FMT(T) case T: Multi2Mono(mChanMask, mSrcStep, scale, {dst, frames}, src); break HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); @@ -332,7 +443,7 @@ void ChannelConverter::convert(const void *src, float *dst, uint frames) const { switch(mSrcType) { -#define HANDLE_FMT(T) case T: Mono2Stereo(dst, src, frames); break +#define HANDLE_FMT(T) case T: Mono2Stereo({dst, frames*2_uz}, src); break HANDLE_FMT(DevFmtByte); HANDLE_FMT(DevFmtUByte); HANDLE_FMT(DevFmtShort); diff --git a/Engine/lib/openal-soft/core/converter.h b/Engine/lib/openal-soft/core/converter.h index 01becea28..01649c361 100644 --- a/Engine/lib/openal-soft/core/converter.h +++ b/Engine/lib/openal-soft/core/converter.h @@ -7,7 +7,9 @@ #include "almalloc.h" #include "devformat.h" +#include "flexarray.h" #include "mixer/defs.h" +#include "resampler_limits.h" using uint = unsigned int; @@ -25,21 +27,22 @@ struct SampleConverter { InterpState mState{}; ResamplerFunc mResample{}; - alignas(16) float mSrcSamples[BufferLineSize]{}; - alignas(16) float mDstSamples[BufferLineSize]{}; + alignas(16) FloatBufferLine mSrcSamples{}; + alignas(16) FloatBufferLine mDstSamples{}; struct ChanSamples { - alignas(16) float PrevSamples[MaxResamplerPadding]; + alignas(16) std::array PrevSamples; }; al::FlexArray mChan; SampleConverter(size_t numchans) : mChan{numchans} { } - uint convert(const void **src, uint *srcframes, void *dst, uint dstframes); - uint availableOut(uint srcframes) const; + [[nodiscard]] auto convert(const void **src, uint *srcframes, void *dst, uint dstframes) -> uint; + [[nodiscard]] auto convertPlanar(const void **src, uint *srcframes, void *const*dst, uint dstframes) -> uint; + [[nodiscard]] auto availableOut(uint srcframes) const -> uint; using SampleOffset = std::chrono::duration>; - SampleOffset currentInputDelay() const noexcept + [[nodiscard]] auto currentInputDelay() const noexcept -> SampleOffset { const int64_t prep{int64_t{mSrcPrepCount} - MaxResamplerEdge}; return SampleOffset{(prep< bool { return mChanMask != 0; } void convert(const void *src, float *dst, uint frames) const; }; diff --git a/Engine/lib/openal-soft/core/cpu_caps.cpp b/Engine/lib/openal-soft/core/cpu_caps.cpp index d4b4d86c8..1a064cf46 100644 --- a/Engine/lib/openal-soft/core/cpu_caps.cpp +++ b/Engine/lib/openal-soft/core/cpu_caps.cpp @@ -17,6 +17,7 @@ #include #endif +#include #include #include #include @@ -50,14 +51,14 @@ inline std::array get_cpuid(unsigned int f) } // namespace -al::optional GetCPUInfo() +std::optional GetCPUInfo() { CPUInfo ret; #ifdef CAN_GET_CPUID auto cpuregs = get_cpuid(0); if(cpuregs[0] == 0) - return al::nullopt; + return std::nullopt; const reg_type maxfunc{cpuregs[0]}; diff --git a/Engine/lib/openal-soft/core/cpu_caps.h b/Engine/lib/openal-soft/core/cpu_caps.h index ffd671d0d..0826a49bd 100644 --- a/Engine/lib/openal-soft/core/cpu_caps.h +++ b/Engine/lib/openal-soft/core/cpu_caps.h @@ -1,10 +1,9 @@ #ifndef CORE_CPU_CAPS_H #define CORE_CPU_CAPS_H +#include #include -#include "aloptional.h" - extern int CPUCapFlags; enum { @@ -21,6 +20,6 @@ struct CPUInfo { int mCaps{0}; }; -al::optional GetCPUInfo(); +std::optional GetCPUInfo(); #endif /* CORE_CPU_CAPS_H */ diff --git a/Engine/lib/openal-soft/core/cubic_defs.h b/Engine/lib/openal-soft/core/cubic_defs.h index 33751c974..a38208164 100644 --- a/Engine/lib/openal-soft/core/cubic_defs.h +++ b/Engine/lib/openal-soft/core/cubic_defs.h @@ -1,13 +1,15 @@ #ifndef CORE_CUBIC_DEFS_H #define CORE_CUBIC_DEFS_H +#include + /* The number of distinct phase intervals within the cubic filter tables. */ constexpr unsigned int CubicPhaseBits{5}; constexpr unsigned int CubicPhaseCount{1 << CubicPhaseBits}; struct CubicCoefficients { - float mCoeffs[4]; - float mDeltas[4]; + alignas(16) std::array mCoeffs; + alignas(16) std::array mDeltas; }; #endif /* CORE_CUBIC_DEFS_H */ diff --git a/Engine/lib/openal-soft/core/cubic_tables.cpp b/Engine/lib/openal-soft/core/cubic_tables.cpp index 73ec6b3f6..cf469f9c0 100644 --- a/Engine/lib/openal-soft/core/cubic_tables.cpp +++ b/Engine/lib/openal-soft/core/cubic_tables.cpp @@ -1,59 +1,121 @@ #include "cubic_tables.h" -#include #include -#include #include -#include -#include -#include +#include #include "alnumbers.h" -#include "core/mixer/defs.h" - +#include "alnumeric.h" +#include "cubic_defs.h" +/* These gaussian filter tables are inspired by the gaussian-like filter found + * in the SNES. This is based on the public domain code developed by Near, with + * the help of Ryphecha and nocash, from the nesdev.org forums. + * + * + * + * Additional changes were made here, the most obvious being that is has full + * floating-point precision instead of 11-bit fixed-point, but also an offset + * adjustment for the coefficients to better preserve phase. + */ namespace { -using uint = unsigned int; - -struct SplineFilterArray { - alignas(16) CubicCoefficients mTable[CubicPhaseCount]{}; - - constexpr SplineFilterArray() - { - /* Fill in the main coefficients. */ - for(size_t pi{0};pi < CubicPhaseCount;++pi) - { - const double mu{static_cast(pi) / CubicPhaseCount}; - const double mu2{mu*mu}, mu3{mu2*mu}; - mTable[pi].mCoeffs[0] = static_cast(-0.5*mu3 + mu2 + -0.5*mu); - mTable[pi].mCoeffs[1] = static_cast( 1.5*mu3 + -2.5*mu2 + 1.0); - mTable[pi].mCoeffs[2] = static_cast(-1.5*mu3 + 2.0*mu2 + 0.5*mu); - mTable[pi].mCoeffs[3] = static_cast( 0.5*mu3 + -0.5*mu2); - } - - /* Fill in the coefficient deltas. */ - for(size_t pi{0};pi < CubicPhaseCount-1;++pi) - { - mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; - mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; - mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; - mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; - } - - const size_t pi{CubicPhaseCount - 1}; - mTable[pi].mDeltas[0] = -mTable[pi].mCoeffs[0]; - mTable[pi].mDeltas[1] = -mTable[pi].mCoeffs[1]; - mTable[pi].mDeltas[2] = 1.0f - mTable[pi].mCoeffs[2]; - mTable[pi].mDeltas[3] = -mTable[pi].mCoeffs[3]; - } - - constexpr auto getTable() const noexcept { return al::as_span(mTable); } -}; - -constexpr SplineFilterArray SplineFilter{}; +[[nodiscard]] +auto GetCoeff(double idx) noexcept -> double +{ + const double k{0.5 + idx}; + if(k > 512.0) return 0.0; + const double s{ std::sin(al::numbers::pi*1.280/1024 * k)}; + const double t{(std::cos(al::numbers::pi*2.000/1023 * k) - 1.0) * 0.50}; + const double u{(std::cos(al::numbers::pi*4.000/1023 * k) - 1.0) * 0.08}; + return s * (t + u + 1.0) / k; +} } // namespace -const CubicTable gCubicSpline{SplineFilter.getTable()}; +GaussianTable::GaussianTable() +{ + static constexpr double IndexScale{512.0 / double{CubicPhaseCount*2}}; + /* Fill in the main coefficients. */ + for(std::size_t pi{0};pi < CubicPhaseCount;++pi) + { + const double coeff0{GetCoeff(static_cast(CubicPhaseCount + pi)*IndexScale)}; + const double coeff1{GetCoeff(static_cast(pi)*IndexScale)}; + const double coeff2{GetCoeff(static_cast(CubicPhaseCount - pi)*IndexScale)}; + const double coeff3{GetCoeff(static_cast(CubicPhaseCount*2_uz-pi)*IndexScale)}; + + const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)}; + mTable[pi].mCoeffs[0] = static_cast(coeff0 * scale); + mTable[pi].mCoeffs[1] = static_cast(coeff1 * scale); + mTable[pi].mCoeffs[2] = static_cast(coeff2 * scale); + mTable[pi].mCoeffs[3] = static_cast(coeff3 * scale); + } + + /* Fill in the coefficient deltas. */ + for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) + { + mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; + } + + const std::size_t pi{CubicPhaseCount - 1}; + mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; +} + +SplineTable::SplineTable() +{ + /* This filter table is based on a Catmull-Rom spline. It retains more of + * the original high-frequency content, at the cost of increased harmonics. + */ + for(std::size_t pi{0};pi < CubicPhaseCount;++pi) + { + const double mu{static_cast(pi) / double{CubicPhaseCount}}; + const double mu2{mu*mu}, mu3{mu2*mu}; + mTable[pi].mCoeffs[0] = static_cast(-0.5*mu3 + mu2 + -0.5*mu); + mTable[pi].mCoeffs[1] = static_cast( 1.5*mu3 + -2.5*mu2 + 1.0); + mTable[pi].mCoeffs[2] = static_cast(-1.5*mu3 + 2.0*mu2 + 0.5*mu); + mTable[pi].mCoeffs[3] = static_cast( 0.5*mu3 + -0.5*mu2); + } + + for(std::size_t pi{0};pi < CubicPhaseCount-1;++pi) + { + mTable[pi].mDeltas[0] = mTable[pi+1].mCoeffs[0] - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[pi+1].mCoeffs[1] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[pi+1].mCoeffs[2] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[pi+1].mCoeffs[3] - mTable[pi].mCoeffs[3]; + } + + const std::size_t pi{CubicPhaseCount - 1}; + mTable[pi].mDeltas[0] = 0.0f - mTable[pi].mCoeffs[0]; + mTable[pi].mDeltas[1] = mTable[0].mCoeffs[0] - mTable[pi].mCoeffs[1]; + mTable[pi].mDeltas[2] = mTable[0].mCoeffs[1] - mTable[pi].mCoeffs[2]; + mTable[pi].mDeltas[3] = mTable[0].mCoeffs[2] - mTable[pi].mCoeffs[3]; +} + + +CubicFilter::CubicFilter() +{ + static constexpr double IndexScale{512.0 / double{sTableSteps*2}}; + /* Only half the coefficients need to be iterated here, since Coeff2 and + * Coeff3 are just Coeff1 and Coeff0 in reverse respectively. + */ + for(size_t i{0};i < sTableSteps/2 + 1;++i) + { + const double coeff0{GetCoeff(static_cast(sTableSteps + i)*IndexScale)}; + const double coeff1{GetCoeff(static_cast(i)*IndexScale)}; + const double coeff2{GetCoeff(static_cast(sTableSteps - i)*IndexScale)}; + const double coeff3{GetCoeff(static_cast(sTableSteps*2_uz - i)*IndexScale)}; + + const double scale{1.0 / (coeff0 + coeff1 + coeff2 + coeff3)}; + mFilter[sTableSteps + i] = static_cast(coeff0 * scale); + mFilter[i] = static_cast(coeff1 * scale); + mFilter[sTableSteps - i] = static_cast(coeff2 * scale); + mFilter[sTableSteps*2 - i] = static_cast(coeff3 * scale); + } +} diff --git a/Engine/lib/openal-soft/core/cubic_tables.h b/Engine/lib/openal-soft/core/cubic_tables.h index 88097ae2f..2106a1331 100644 --- a/Engine/lib/openal-soft/core/cubic_tables.h +++ b/Engine/lib/openal-soft/core/cubic_tables.h @@ -1,17 +1,41 @@ #ifndef CORE_CUBIC_TABLES_H #define CORE_CUBIC_TABLES_H -#include "alspan.h" +#include +#include + #include "cubic_defs.h" struct CubicTable { - al::span Tab; + std::array mTable{}; }; -/* A Catmull-Rom spline. The spline passes through the center two samples, - * ensuring no discontinuity while moving through a series of samples. - */ -extern const CubicTable gCubicSpline; +struct GaussianTable : CubicTable { GaussianTable(); }; +inline const GaussianTable gGaussianFilter; + +struct SplineTable : CubicTable { SplineTable(); }; +inline const SplineTable gSplineFilter; + + +struct CubicFilter { + static constexpr std::size_t sTableBits{8}; + static constexpr std::size_t sTableSteps{1 << sTableBits}; + static constexpr std::size_t sTableMask{sTableSteps - 1}; + + std::array mFilter{}; + + CubicFilter(); + + [[nodiscard]] constexpr + auto getCoeff0(std::size_t i) const noexcept -> float { return mFilter[sTableSteps+i]; } + [[nodiscard]] constexpr + auto getCoeff1(std::size_t i) const noexcept -> float { return mFilter[i]; } + [[nodiscard]] constexpr + auto getCoeff2(std::size_t i) const noexcept -> float { return mFilter[sTableSteps-i]; } + [[nodiscard]] constexpr + auto getCoeff3(std::size_t i) const noexcept -> float { return mFilter[sTableSteps*2-i]; } +}; +inline const CubicFilter gCubicTable; #endif /* CORE_CUBIC_TABLES_H */ diff --git a/Engine/lib/openal-soft/core/dbus_wrap.cpp b/Engine/lib/openal-soft/core/dbus_wrap.cpp index 7f2217066..05d9fc063 100644 --- a/Engine/lib/openal-soft/core/dbus_wrap.cpp +++ b/Engine/lib/openal-soft/core/dbus_wrap.cpp @@ -11,14 +11,16 @@ #include "logging.h" -void *dbus_handle{nullptr}; -#define DECL_FUNC(x) decltype(p##x) p##x{}; -DBUS_FUNCTIONS(DECL_FUNC) -#undef DECL_FUNC - void PrepareDBus() { - static constexpr char libname[] = "libdbus-1.so.3"; + const char *libname{"libdbus-1.so.3"}; + + dbus_handle = LoadLib(libname); + if(!dbus_handle) + { + WARN("Failed to load %s\n", libname); + return; + } auto load_func = [](auto &f, const char *name) -> void { f = reinterpret_cast>(GetSymbol(dbus_handle, name)); }; @@ -33,14 +35,8 @@ void PrepareDBus() } \ } while(0); - dbus_handle = LoadLib(libname); - if(!dbus_handle) - { - WARN("Failed to load %s\n", libname); - return; - } + DBUS_FUNCTIONS(LOAD_FUNC) -DBUS_FUNCTIONS(LOAD_FUNC) #undef LOAD_FUNC } #endif diff --git a/Engine/lib/openal-soft/core/dbus_wrap.h b/Engine/lib/openal-soft/core/dbus_wrap.h index 09eaacf93..afc27d842 100644 --- a/Engine/lib/openal-soft/core/dbus_wrap.h +++ b/Engine/lib/openal-soft/core/dbus_wrap.h @@ -28,8 +28,8 @@ MAGIC(dbus_message_iter_get_arg_type) \ MAGIC(dbus_message_iter_get_basic) \ MAGIC(dbus_set_error_from_message) -extern void *dbus_handle; -#define DECL_FUNC(x) extern decltype(x) *p##x; +inline void *dbus_handle{}; +#define DECL_FUNC(x) inline decltype(x) *p##x{}; DBUS_FUNCTIONS(DECL_FUNC) #undef DECL_FUNC @@ -70,9 +70,16 @@ namespace dbus { struct Error { Error() { dbus_error_init(&mError); } + Error(const Error&) = delete; + Error(Error&&) = delete; ~Error() { dbus_error_free(&mError); } + + void operator=(const Error&) = delete; + void operator=(Error&&) = delete; + DBusError* operator->() { return &mError; } DBusError &get() { return mError; } + private: DBusError mError{}; }; diff --git a/Engine/lib/openal-soft/core/devformat.cpp b/Engine/lib/openal-soft/core/devformat.cpp index acdabc4f1..57d1d8ea7 100644 --- a/Engine/lib/openal-soft/core/devformat.cpp +++ b/Engine/lib/openal-soft/core/devformat.cpp @@ -29,6 +29,7 @@ uint ChannelsFromDevFmt(DevFmtChannels chans, uint ambiorder) noexcept case DevFmtX61: return 7; case DevFmtX71: return 8; case DevFmtX714: return 12; + case DevFmtX7144: return 16; case DevFmtX3D71: return 8; case DevFmtAmbi3D: return (ambiorder+1) * (ambiorder+1); } @@ -60,6 +61,7 @@ const char *DevFmtChannelsString(DevFmtChannels chans) noexcept case DevFmtX61: return "6.1 Surround"; case DevFmtX71: return "7.1 Surround"; case DevFmtX714: return "7.1.4 Surround"; + case DevFmtX7144: return "7.1.4.4 Surround"; case DevFmtX3D71: return "3D7.1 Surround"; case DevFmtAmbi3D: return "Ambisonic 3D"; } diff --git a/Engine/lib/openal-soft/core/devformat.h b/Engine/lib/openal-soft/core/devformat.h index 485826a39..ca59df5e5 100644 --- a/Engine/lib/openal-soft/core/devformat.h +++ b/Engine/lib/openal-soft/core/devformat.h @@ -2,6 +2,7 @@ #define CORE_DEVFORMAT_H #include +#include using uint = unsigned int; @@ -25,6 +26,11 @@ enum Channel : unsigned char { TopBackCenter, TopBackRight, + BottomFrontLeft, + BottomFrontRight, + BottomBackLeft, + BottomBackRight, + Aux0, Aux1, Aux2, @@ -66,12 +72,13 @@ enum DevFmtChannels : unsigned char { DevFmtX61, DevFmtX71, DevFmtX714, + DevFmtX7144, DevFmtX3D71, DevFmtAmbi3D, DevFmtChannelsDefault = DevFmtStereo }; -#define MAX_OUTPUT_CHANNELS 16 +inline constexpr std::size_t MaxOutputChannels{16}; /* DevFmtType traits, providing the type, etc given a DevFmtType. */ template diff --git a/Engine/lib/openal-soft/core/device.cpp b/Engine/lib/openal-soft/core/device.cpp index 2766c5e4e..795a96016 100644 --- a/Engine/lib/openal-soft/core/device.cpp +++ b/Engine/lib/openal-soft/core/device.cpp @@ -9,15 +9,12 @@ #include "mastering.h" -al::FlexArray DeviceBase::sEmptyContextArray{0u}; +static_assert(std::atomic::is_always_lock_free); -DeviceBase::DeviceBase(DeviceType type) : Type{type}, mContexts{&sEmptyContextArray} +DeviceBase::DeviceBase(DeviceType type) + : Type{type}, mContexts{al::FlexArray::Create(0)} { } -DeviceBase::~DeviceBase() -{ - auto *oldarray = mContexts.exchange(nullptr, std::memory_order_relaxed); - if(oldarray != &sEmptyContextArray) delete oldarray; -} +DeviceBase::~DeviceBase() = default; diff --git a/Engine/lib/openal-soft/core/device.h b/Engine/lib/openal-soft/core/device.h index 9aaf7adb8..a62863189 100644 --- a/Engine/lib/openal-soft/core/device.h +++ b/Engine/lib/openal-soft/core/device.h @@ -1,14 +1,13 @@ #ifndef CORE_DEVICE_H #define CORE_DEVICE_H -#include - #include #include #include #include +#include +#include #include -#include #include #include "almalloc.h" @@ -18,6 +17,7 @@ #include "bufferline.h" #include "devformat.h" #include "filters/nfc.h" +#include "flexarray.h" #include "intrusive_ptr.h" #include "mixer/hrtfdefs.h" #include "opthelpers.h" @@ -26,8 +26,10 @@ #include "vector.h" class BFormatDec; +namespace Bs2b { struct bs2b; -struct Compressor; +} // namespace Bs2b +class Compressor; struct ContextBase; struct DirectHrtfState; struct HrtfStore; @@ -35,28 +37,28 @@ struct HrtfStore; using uint = unsigned int; -#define MIN_OUTPUT_RATE 8000 -#define MAX_OUTPUT_RATE 192000 -#define DEFAULT_OUTPUT_RATE 48000 +inline constexpr std::size_t MinOutputRate{8000}; +inline constexpr std::size_t MaxOutputRate{192000}; +inline constexpr std::size_t DefaultOutputRate{48000}; -#define DEFAULT_UPDATE_SIZE 960 /* 20ms */ -#define DEFAULT_NUM_UPDATES 3 +inline constexpr std::size_t DefaultUpdateSize{960}; /* 20ms */ +inline constexpr std::size_t DefaultNumUpdates{3}; -enum class DeviceType : unsigned char { +enum class DeviceType : std::uint8_t { Playback, Capture, Loopback }; -enum class RenderMode : unsigned char { +enum class RenderMode : std::uint8_t { Normal, Pairwise, Hrtf }; -enum class StereoEncoding : unsigned char { +enum class StereoEncoding : std::uint8_t { Basic, Uhj, Hrtf, @@ -78,24 +80,23 @@ struct DistanceComp { static constexpr uint MaxDelay{1024}; struct ChanData { + al::span Buffer{}; /* Valid size is [0...MaxDelay). */ float Gain{1.0f}; - uint Length{0u}; /* Valid range is [0...MaxDelay). */ - float *Buffer{nullptr}; }; - std::array mChannels; + std::array mChannels; al::FlexArray mSamples; - DistanceComp(size_t count) : mSamples{count} { } + DistanceComp(std::size_t count) : mSamples{count} { } - static std::unique_ptr Create(size_t numsamples) + static std::unique_ptr Create(std::size_t numsamples) { return std::unique_ptr{new(FamCount(numsamples)) DistanceComp{numsamples}}; } DEF_FAM_NEWDEL(DistanceComp, mSamples) }; -constexpr uint InvalidChannelIndex{~0u}; +constexpr auto InvalidChannelIndex = static_cast(~0u); struct BFChannelConfig { float Scale; @@ -113,24 +114,24 @@ struct MixParams { * source is expected to be a 3D ACN/N3D ambisonic buffer, and for each * channel [0...count), the given functor is called with the source channel * index, destination channel index, and the gain for that channel. If the - * destination channel is INVALID_CHANNEL_INDEX, the given source channel - * is not used for output. + * destination channel is InvalidChannelIndex, the given source channel is + * not used for output. */ template void setAmbiMixParams(const MixParams &inmix, const float gainbase, F func) const { - const size_t numIn{inmix.Buffer.size()}; - const size_t numOut{Buffer.size()}; - for(size_t i{0};i < numIn;++i) + const std::size_t numIn{inmix.Buffer.size()}; + const std::size_t numOut{Buffer.size()}; + for(std::size_t i{0};i < numIn;++i) { - auto idx = InvalidChannelIndex; - auto gain = 0.0f; + std::uint8_t idx{InvalidChannelIndex}; + float gain{0.0f}; - for(size_t j{0};j < numOut;++j) + for(std::size_t j{0};j < numOut;++j) { if(AmbiMap[j].Index == inmix.AmbiMap[i].Index) { - idx = static_cast(j); + idx = static_cast(j); gain = AmbiMap[j].Scale * gainbase; break; } @@ -142,7 +143,7 @@ struct MixParams { struct RealMixParams { al::span RemixMap; - std::array ChannelIndex{}; + std::array ChannelIndex{}; al::span Buffer; }; @@ -159,22 +160,26 @@ enum { // Specifies if the DSP is paused at user request DevicePaused, - // Specifies if the device is currently running - DeviceRunning, // Specifies if the output plays directly on/in ears (headphones, headset, // ear buds, etc). DirectEar, + /* Specifies if output is using speaker virtualization (e.g. Windows + * Spatial Audio). + */ + Virtualization, + DeviceFlagsCount }; -struct DeviceBase { - /* To avoid extraneous allocations, a 0-sized FlexArray is - * defined globally as a sharable object. - */ - static al::FlexArray sEmptyContextArray; +enum class DeviceState : std::uint8_t { + Unprepared, + Configured, + Playing +}; +struct DeviceBase { std::atomic Connected{true}; const DeviceType Type{}; @@ -198,6 +203,7 @@ struct DeviceBase { // Device flags std::bitset Flags{}; + DeviceState mDeviceState{DeviceState::Unprepared}; uint NumAuxSends{}; @@ -214,35 +220,31 @@ struct DeviceBase { */ NfcFilter mNFCtrlFilter{}; - uint SamplesDone{0u}; - std::chrono::nanoseconds ClockBase{0}; + std::atomic mSamplesDone{0u}; + std::atomic mClockBase{std::chrono::nanoseconds{}}; std::chrono::nanoseconds FixedLatency{0}; AmbiRotateMatrix mAmbiRotateMatrix{}; AmbiRotateMatrix mAmbiRotateMatrix2{}; /* Temp storage used for mixer processing. */ - static constexpr size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; - static constexpr size_t MixerChannelsMax{16}; - using MixerBufferLine = std::array; - alignas(16) std::array mSampleData; - alignas(16) std::array mResampleData; + static constexpr std::size_t MixerLineSize{BufferLineSize + DecoderBase::sMaxPadding}; + static constexpr std::size_t MixerChannelsMax{16}; + alignas(16) std::array mSampleData{}; + alignas(16) std::array mResampleData{}; - alignas(16) float FilteredData[BufferLineSize]; - union { - alignas(16) float HrtfSourceData[BufferLineSize + HrtfHistoryLength]; - alignas(16) float NfcSampleData[BufferLineSize]; - }; + alignas(16) std::array FilteredData{}; + alignas(16) std::array ExtraSampleData{}; /* Persistent storage for HRTF mixing. */ - alignas(16) float2 HrtfAccumData[BufferLineSize + HrirLength]; + alignas(16) std::array HrtfAccumData{}; /* Mixing buffer used by the Dry mix and Real output. */ al::vector MixBuffer; /* The "dry" path corresponds to the main output. */ MixParams Dry; - uint NumChannelsPerOrder[MaxAmbiOrder+1]{}; + std::array NumChannelsPerOrder{}; /* "Real" output, which will be written to the device buffer. May alias the * dry buffer. @@ -261,7 +263,7 @@ struct DeviceBase { std::unique_ptr AmbiDecoder; /* Stereo-to-binaural filter */ - std::unique_ptr Bs2b; + std::unique_ptr Bs2b; using PostProc = void(DeviceBase::*)(const size_t SamplesToDo); PostProc PostProcess{nullptr}; @@ -280,10 +282,10 @@ struct DeviceBase { * the end, so the bottom bit indicates if the device is currently mixing * and the upper bits indicates how many mixes have been done. */ - RefCount MixCount{0u}; + std::atomic mMixCount{0u}; // Contexts created on this device - std::atomic*> mContexts{nullptr}; + al::atomic_unique_ptr> mContexts; DeviceBase(DeviceType type); @@ -291,33 +293,69 @@ struct DeviceBase { DeviceBase& operator=(const DeviceBase&) = delete; ~DeviceBase(); - uint bytesFromFmt() const noexcept { return BytesFromDevFmt(FmtType); } - uint channelsFromFmt() const noexcept { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } - uint frameSizeFromFmt() const noexcept { return bytesFromFmt() * channelsFromFmt(); } + [[nodiscard]] auto bytesFromFmt() const noexcept -> uint { return BytesFromDevFmt(FmtType); } + [[nodiscard]] auto channelsFromFmt() const noexcept -> uint { return ChannelsFromDevFmt(FmtChans, mAmbiOrder); } + [[nodiscard]] auto frameSizeFromFmt() const noexcept -> uint { return bytesFromFmt() * channelsFromFmt(); } - uint waitForMix() const noexcept + struct MixLock { + DeviceBase *const self; + const uint mEndVal; + + MixLock(DeviceBase *device, const uint endval) noexcept : self{device}, mEndVal{endval} { } + MixLock(const MixLock&) = delete; + void operator=(const MixLock&) = delete; + /* Update the mix count when the lock goes out of scope to "release" it + * (lsb should be 0). + */ + ~MixLock() { self->mMixCount.store(mEndVal, std::memory_order_release); } + }; + auto getWriteMixLock() noexcept -> MixLock { - uint refcount; - while((refcount=MixCount.load(std::memory_order_acquire))&1) { - } + /* Increment the mix count at the start of mixing and writing clock + * info (lsb should be 1). + */ + auto mixCount = mMixCount.load(std::memory_order_relaxed); + mMixCount.store(++mixCount, std::memory_order_release); + return MixLock{this, ++mixCount}; + } + + /** Waits for the mixer to not be mixing or updating the clock. */ + [[nodiscard]] auto waitForMix() const noexcept -> uint + { + uint refcount{mMixCount.load(std::memory_order_acquire)}; + while((refcount&1)) refcount = mMixCount.load(std::memory_order_acquire); return refcount; } - void ProcessHrtf(const size_t SamplesToDo); - void ProcessAmbiDec(const size_t SamplesToDo); - void ProcessAmbiDecStablized(const size_t SamplesToDo); - void ProcessUhj(const size_t SamplesToDo); - void ProcessBs2b(const size_t SamplesToDo); + /** + * Helper to get the current clock time from the device's ClockBase, and + * SamplesDone converted from the sample rate. Should only be called while + * watching the MixCount. + */ + [[nodiscard]] auto getClockTime() const noexcept -> std::chrono::nanoseconds + { + using std::chrono::seconds; + using std::chrono::nanoseconds; - inline void postProcess(const size_t SamplesToDo) + auto ns = nanoseconds{seconds{mSamplesDone.load(std::memory_order_relaxed)}} / Frequency; + return mClockBase.load(std::memory_order_relaxed) + ns; + } + + void ProcessHrtf(const std::size_t SamplesToDo); + void ProcessAmbiDec(const std::size_t SamplesToDo); + void ProcessAmbiDecStablized(const std::size_t SamplesToDo); + void ProcessUhj(const std::size_t SamplesToDo); + void ProcessBs2b(const std::size_t SamplesToDo); + + inline void postProcess(const std::size_t SamplesToDo) { if(PostProcess) LIKELY (this->*PostProcess)(SamplesToDo); } void renderSamples(const al::span outBuffers, const uint numSamples); - void renderSamples(void *outBuffer, const uint numSamples, const size_t frameStep); + void renderSamples(void *outBuffer, const uint numSamples, const std::size_t frameStep); /* Caller must lock the device state, and the mixer must not be running. */ -#ifdef __USE_MINGW_ANSI_STDIO - [[gnu::format(gnu_printf,2,3)]] +#ifdef __MINGW32__ + [[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else [[gnu::format(printf,2,3)]] #endif @@ -325,21 +363,21 @@ struct DeviceBase { /** * Returns the index for the given channel name (e.g. FrontCenter), or - * INVALID_CHANNEL_INDEX if it doesn't exist. + * InvalidChannelIndex if it doesn't exist. */ - uint channelIdxByName(Channel chan) const noexcept + [[nodiscard]] auto channelIdxByName(Channel chan) const noexcept -> std::uint8_t { return RealOut.ChannelIndex[chan]; } - DISABLE_ALLOC() - private: uint renderSamples(const uint numSamples); }; /* Must be less than 15 characters (16 including terminating null) for * compatibility with pthread_setname_np limitations. */ -#define MIXER_THREAD_NAME "alsoft-mixer" +[[nodiscard]] constexpr +auto GetMixerThreadName() noexcept -> const char* { return "alsoft-mixer"; } -#define RECORD_THREAD_NAME "alsoft-record" +[[nodiscard]] constexpr +auto GetRecordThreadName() noexcept -> const char* { return "alsoft-record"; } #endif /* CORE_DEVICE_H */ diff --git a/Engine/lib/openal-soft/core/effects/base.h b/Engine/lib/openal-soft/core/effects/base.h index 4ee19f37c..b0d267167 100644 --- a/Engine/lib/openal-soft/core/effects/base.h +++ b/Engine/lib/openal-soft/core/effects/base.h @@ -1,12 +1,11 @@ #ifndef CORE_EFFECTS_BASE_H #define CORE_EFFECTS_BASE_H -#include +#include +#include +#include -#include "albyte.h" -#include "almalloc.h" #include "alspan.h" -#include "atomic.h" #include "core/bufferline.h" #include "intrusive_ptr.h" @@ -19,21 +18,21 @@ struct RealMixParams; /** Target gain for the reverb decay feedback reaching the decay time. */ -constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ +inline constexpr float ReverbDecayGain{0.001f}; /* -60 dB */ -constexpr float ReverbMaxReflectionsDelay{0.3f}; -constexpr float ReverbMaxLateReverbDelay{0.1f}; +inline constexpr float ReverbMaxReflectionsDelay{0.3f}; +inline constexpr float ReverbMaxLateReverbDelay{0.1f}; enum class ChorusWaveform { Sinusoid, Triangle }; -constexpr float ChorusMaxDelay{0.016f}; -constexpr float FlangerMaxDelay{0.004f}; +inline constexpr float ChorusMaxDelay{0.016f}; +inline constexpr float FlangerMaxDelay{0.004f}; -constexpr float EchoMaxDelay{0.207f}; -constexpr float EchoMaxLRDelay{0.404f}; +inline constexpr float EchoMaxDelay{0.207f}; +inline constexpr float EchoMaxLRDelay{0.404f}; enum class FShifterDirection { Down, @@ -59,115 +58,135 @@ enum class VMorpherWaveform { Sawtooth }; -union EffectProps { - struct { - float Density; - float Diffusion; - float Gain; - float GainHF; - float GainLF; - float DecayTime; - float DecayHFRatio; - float DecayLFRatio; - float ReflectionsGain; - float ReflectionsDelay; - float ReflectionsPan[3]; - float LateReverbGain; - float LateReverbDelay; - float LateReverbPan[3]; - float EchoTime; - float EchoDepth; - float ModulationTime; - float ModulationDepth; - float AirAbsorptionGainHF; - float HFReference; - float LFReference; - float RoomRolloffFactor; - bool DecayHFLimit; - } Reverb; - - struct { - float AttackTime; - float ReleaseTime; - float Resonance; - float PeakGain; - } Autowah; - - struct { - ChorusWaveform Waveform; - int Phase; - float Rate; - float Depth; - float Feedback; - float Delay; - } Chorus; /* Also Flanger */ - - struct { - bool OnOff; - } Compressor; - - struct { - float Edge; - float Gain; - float LowpassCutoff; - float EQCenter; - float EQBandwidth; - } Distortion; - - struct { - float Delay; - float LRDelay; - - float Damping; - float Feedback; - - float Spread; - } Echo; - - struct { - float LowCutoff; - float LowGain; - float Mid1Center; - float Mid1Gain; - float Mid1Width; - float Mid2Center; - float Mid2Gain; - float Mid2Width; - float HighCutoff; - float HighGain; - } Equalizer; - - struct { - float Frequency; - FShifterDirection LeftDirection; - FShifterDirection RightDirection; - } Fshifter; - - struct { - float Frequency; - float HighPassCutoff; - ModulatorWaveform Waveform; - } Modulator; - - struct { - int CoarseTune; - int FineTune; - } Pshifter; - - struct { - float Rate; - VMorpherPhenome PhonemeA; - VMorpherPhenome PhonemeB; - int PhonemeACoarseTuning; - int PhonemeBCoarseTuning; - VMorpherWaveform Waveform; - } Vmorpher; - - struct { - float Gain; - } Dedicated; +struct ReverbProps { + float Density; + float Diffusion; + float Gain; + float GainHF; + float GainLF; + float DecayTime; + float DecayHFRatio; + float DecayLFRatio; + float ReflectionsGain; + float ReflectionsDelay; + std::array ReflectionsPan; + float LateReverbGain; + float LateReverbDelay; + std::array LateReverbPan; + float EchoTime; + float EchoDepth; + float ModulationTime; + float ModulationDepth; + float AirAbsorptionGainHF; + float HFReference; + float LFReference; + float RoomRolloffFactor; + bool DecayHFLimit; }; +struct AutowahProps { + float AttackTime; + float ReleaseTime; + float Resonance; + float PeakGain; +}; + +struct ChorusProps { + ChorusWaveform Waveform; + int Phase; + float Rate; + float Depth; + float Feedback; + float Delay; +}; + +struct CompressorProps { + bool OnOff; +}; + +struct DistortionProps { + float Edge; + float Gain; + float LowpassCutoff; + float EQCenter; + float EQBandwidth; +}; + +struct EchoProps { + float Delay; + float LRDelay; + + float Damping; + float Feedback; + + float Spread; +}; + +struct EqualizerProps { + float LowCutoff; + float LowGain; + float Mid1Center; + float Mid1Gain; + float Mid1Width; + float Mid2Center; + float Mid2Gain; + float Mid2Width; + float HighCutoff; + float HighGain; +}; + +struct FshifterProps { + float Frequency; + FShifterDirection LeftDirection; + FShifterDirection RightDirection; +}; + +struct ModulatorProps { + float Frequency; + float HighPassCutoff; + ModulatorWaveform Waveform; +}; + +struct PshifterProps { + int CoarseTune; + int FineTune; +}; + +struct VmorpherProps { + float Rate; + VMorpherPhenome PhonemeA; + VMorpherPhenome PhonemeB; + int PhonemeACoarseTuning; + int PhonemeBCoarseTuning; + VMorpherWaveform Waveform; +}; + +struct DedicatedProps { + enum TargetType : bool { Dialog, Lfe }; + TargetType Target; + float Gain; +}; + +struct ConvolutionProps { + std::array OrientAt; + std::array OrientUp; +}; + +using EffectProps = std::variant; + struct EffectTarget { MixParams *Main; @@ -189,8 +208,14 @@ struct EffectState : public al::intrusive_ref { struct EffectStateFactory { + EffectStateFactory() = default; + EffectStateFactory(const EffectStateFactory&) = delete; + EffectStateFactory(EffectStateFactory&&) = delete; virtual ~EffectStateFactory() = default; + void operator=(const EffectStateFactory&) = delete; + void operator=(EffectStateFactory&&) = delete; + virtual al::intrusive_ptr create() = 0; }; diff --git a/Engine/lib/openal-soft/core/effectslot.cpp b/Engine/lib/openal-soft/core/effectslot.cpp index db8aa078c..d07c79a54 100644 --- a/Engine/lib/openal-soft/core/effectslot.cpp +++ b/Engine/lib/openal-soft/core/effectslot.cpp @@ -3,17 +3,13 @@ #include "effectslot.h" -#include +#include #include "almalloc.h" #include "context.h" -EffectSlotArray *EffectSlot::CreatePtrArray(size_t count) noexcept +std::unique_ptr EffectSlot::CreatePtrArray(size_t count) { - /* Allocate space for twice as many pointers, so the mixer has scratch - * space to store a sorted list during mixing. - */ - void *ptr{al_calloc(alignof(EffectSlotArray), EffectSlotArray::Sizeof(count*2))}; - return al::construct_at(static_cast(ptr), count); + return std::unique_ptr{new(FamCount{count}) EffectSlotArray(count)}; } diff --git a/Engine/lib/openal-soft/core/effectslot.h b/Engine/lib/openal-soft/core/effectslot.h index 2624ae5fd..70dbbbad4 100644 --- a/Engine/lib/openal-soft/core/effectslot.h +++ b/Engine/lib/openal-soft/core/effectslot.h @@ -2,10 +2,11 @@ #define CORE_EFFECTSLOT_H #include +#include -#include "almalloc.h" #include "device.h" #include "effects/base.h" +#include "flexarray.h" #include "intrusive_ptr.h" struct EffectSlot; @@ -18,20 +19,18 @@ enum class EffectSlotType : unsigned char { None, Reverb, Chorus, - Distortion, - Echo, - Flanger, - FrequencyShifter, - VocalMorpher, - PitchShifter, - RingModulator, Autowah, Compressor, + Convolution, + Dedicated, + Distortion, + Echo, Equalizer, - EAXReverb, - DedicatedLFE, - DedicatedDialog, - Convolution + Flanger, + FrequencyShifter, + PitchShifter, + RingModulator, + VocalMorpher, }; struct EffectSlotProps { @@ -45,8 +44,6 @@ struct EffectSlotProps { al::intrusive_ptr State; std::atomic next; - - DEF_NEWDEL(EffectSlotProps) }; @@ -81,9 +78,7 @@ struct EffectSlot { al::vector mWetBuffer; - static EffectSlotArray *CreatePtrArray(size_t count) noexcept; - - DEF_NEWDEL(EffectSlot) + static std::unique_ptr CreatePtrArray(size_t count); }; #endif /* CORE_EFFECTSLOT_H */ diff --git a/Engine/lib/openal-soft/core/except.cpp b/Engine/lib/openal-soft/core/except.cpp index 45fd4eb5f..f338e9aeb 100644 --- a/Engine/lib/openal-soft/core/except.cpp +++ b/Engine/lib/openal-soft/core/except.cpp @@ -13,18 +13,20 @@ namespace al { base_exception::~base_exception() = default; -void base_exception::setMessage(const char* msg, std::va_list args) +void base_exception::setMessage(const char *msg, std::va_list args) { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args2; va_copy(args2, args); int msglen{std::vsnprintf(nullptr, 0, msg, args)}; if(msglen > 0) LIKELY { mMessage.resize(static_cast(msglen)+1); - std::vsnprintf(const_cast(mMessage.data()), mMessage.length(), msg, args2); + std::vsnprintf(mMessage.data(), mMessage.length(), msg, args2); mMessage.pop_back(); } va_end(args2); + /* NOLINTEND(*-array-to-pointer-decay) */ } } // namespace al diff --git a/Engine/lib/openal-soft/core/except.h b/Engine/lib/openal-soft/core/except.h index 0e28e9dfe..1b3d634fc 100644 --- a/Engine/lib/openal-soft/core/except.h +++ b/Engine/lib/openal-soft/core/except.h @@ -13,19 +13,20 @@ class base_exception : public std::exception { std::string mMessage; protected: - base_exception() = default; - virtual ~base_exception(); - - void setMessage(const char *msg, std::va_list args); + auto setMessage(const char *msg, std::va_list args) -> void; public: - const char *what() const noexcept override { return mMessage.c_str(); } + base_exception() = default; + base_exception(const base_exception&) = default; + base_exception(base_exception&&) = default; + ~base_exception() override; + + auto operator=(const base_exception&) -> base_exception& = default; + auto operator=(base_exception&&) -> base_exception& = default; + + [[nodiscard]] auto what() const noexcept -> const char* override { return mMessage.c_str(); } }; } // namespace al -#define START_API_FUNC try - -#define END_API_FUNC catch(...) { std::terminate(); } - #endif /* CORE_EXCEPT_H */ diff --git a/Engine/lib/openal-soft/core/filters/biquad.cpp b/Engine/lib/openal-soft/core/filters/biquad.cpp index a0a62eb8b..c1faf5e9f 100644 --- a/Engine/lib/openal-soft/core/filters/biquad.cpp +++ b/Engine/lib/openal-soft/core/filters/biquad.cpp @@ -3,6 +3,7 @@ #include "biquad.h" +#include #include #include #include @@ -27,8 +28,8 @@ void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Rea const Real alpha{sin_w0/2.0f * rcpQ}; Real sqrtgain_alpha_2; - Real a[3]{ 1.0f, 0.0f, 0.0f }; - Real b[3]{ 1.0f, 0.0f, 0.0f }; + std::array a{{1.0f, 0.0f, 0.0f}}; + std::array b{{1.0f, 0.0f, 0.0f}}; /* Calculate filter coefficients depending on filter type */ switch(type) @@ -94,7 +95,7 @@ void BiquadFilterR::setParams(BiquadType type, Real f0norm, Real gain, Rea } template -void BiquadFilterR::process(const al::span src, Real *dst) +void BiquadFilterR::process(const al::span src, const al::span dst) { const Real b0{mB0}; const Real b1{mB1}; @@ -119,7 +120,7 @@ void BiquadFilterR::process(const al::span src, Real *dst) z2 = input*b2 - output*a2; return output; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); mZ1 = z1; mZ2 = z2; @@ -127,7 +128,7 @@ void BiquadFilterR::process(const al::span src, Real *dst) template void BiquadFilterR::dualProcess(BiquadFilterR &other, const al::span src, - Real *dst) + const al::span dst) { const Real b00{mB0}; const Real b01{mB1}; @@ -156,7 +157,7 @@ void BiquadFilterR::dualProcess(BiquadFilterR &other, const al::span src, Real *dst); + void process(const al::span src, const al::span dst); /** Processes this filter and the other at the same time. */ - void dualProcess(BiquadFilterR &other, const al::span src, Real *dst); + void dualProcess(BiquadFilterR &other, const al::span src, + const al::span dst); /* Rather hacky. It's just here to support "manual" processing. */ - std::pair getComponents() const noexcept { return {mZ1, mZ2}; } + [[nodiscard]] auto getComponents() const noexcept -> std::pair { return {mZ1, mZ2}; } void setComponents(Real z1, Real z2) noexcept { mZ1 = z1; mZ2 = z2; } - Real processOne(const Real in, Real &z1, Real &z2) const noexcept + [[nodiscard]] auto processOne(const Real in, Real &z1, Real &z2) const noexcept -> Real { const Real out{in*mB0 + z1}; z1 = in*mB1 - out*mA1 + z2; @@ -134,7 +135,7 @@ template struct DualBiquadR { BiquadFilterR &f0, &f1; - void process(const al::span src, Real *dst) + void process(const al::span src, const al::span dst) { f0.dualProcess(f1, src, dst); } }; diff --git a/Engine/lib/openal-soft/core/filters/nfc.cpp b/Engine/lib/openal-soft/core/filters/nfc.cpp index aa64c6130..c3c313db4 100644 --- a/Engine/lib/openal-soft/core/filters/nfc.cpp +++ b/Engine/lib/openal-soft/core/filters/nfc.cpp @@ -48,24 +48,22 @@ namespace { -constexpr float B[5][4] = { - { 0.0f }, - { 1.0f }, - { 3.0f, 3.0f }, - { 3.6778f, 6.4595f, 2.3222f }, - { 4.2076f, 11.4877f, 5.7924f, 9.1401f } +constexpr std::array B{ + std::array{ 0.0f, 0.0f, 0.0f, 0.0f}, + std::array{ 1.0f, 0.0f, 0.0f, 0.0f}, + std::array{ 3.0f, 3.0f, 0.0f, 0.0f}, + std::array{3.6778f, 6.4595f, 2.3222f, 0.0f}, + std::array{4.2076f, 11.4877f, 5.7924f, 9.1401f} }; NfcFilter1 NfcFilterCreate1(const float w0, const float w1) noexcept { NfcFilter1 nfc{}; - float b_00, g_0; - float r; /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_00 = B[1][0] * r; - g_0 = 1.0f + b_00; + float r{0.5f * w1}; + float b_00{B[1][0] * r}; + float g_0{1.0f + b_00}; nfc.base_gain = 1.0f / g_0; nfc.a1 = 2.0f * b_00 / g_0; @@ -95,14 +93,12 @@ void NfcFilterAdjust1(NfcFilter1 *nfc, const float w0) noexcept NfcFilter2 NfcFilterCreate2(const float w0, const float w1) noexcept { NfcFilter2 nfc{}; - float b_10, b_11, g_1; - float r; /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_10 = B[2][0] * r; - b_11 = B[2][1] * r * r; - g_1 = 1.0f + b_10 + b_11; + float r{0.5f * w1}; + float b_10{B[2][0] * r}; + float b_11{B[2][1] * r * r}; + float g_1{1.0f + b_10 + b_11}; nfc.base_gain = 1.0f / g_1; nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -137,17 +133,14 @@ void NfcFilterAdjust2(NfcFilter2 *nfc, const float w0) noexcept NfcFilter3 NfcFilterCreate3(const float w0, const float w1) noexcept { NfcFilter3 nfc{}; - float b_10, b_11, g_1; - float b_00, g_0; - float r; /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_10 = B[3][0] * r; - b_11 = B[3][1] * r * r; - b_00 = B[3][2] * r; - g_1 = 1.0f + b_10 + b_11; - g_0 = 1.0f + b_00; + float r{0.5f * w1}; + float b_10{B[3][0] * r}; + float b_11{B[3][1] * r * r}; + float b_00{B[3][2] * r}; + float g_1{1.0f + b_10 + b_11}; + float g_0{1.0f + b_00}; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -189,18 +182,15 @@ void NfcFilterAdjust3(NfcFilter3 *nfc, const float w0) noexcept NfcFilter4 NfcFilterCreate4(const float w0, const float w1) noexcept { NfcFilter4 nfc{}; - float b_10, b_11, g_1; - float b_00, b_01, g_0; - float r; /* Calculate bass-cut coefficients. */ - r = 0.5f * w1; - b_10 = B[4][0] * r; - b_11 = B[4][1] * r * r; - b_00 = B[4][2] * r; - b_01 = B[4][3] * r * r; - g_1 = 1.0f + b_10 + b_11; - g_0 = 1.0f + b_00 + b_01; + float r{0.5f * w1}; + float b_10{B[4][0] * r}; + float b_11{B[4][1] * r * r}; + float b_00{B[4][2] * r}; + float b_01{B[4][3] * r * r}; + float g_1{1.0f + b_10 + b_11}; + float g_0{1.0f + b_00 + b_01}; nfc.base_gain = 1.0f / (g_1 * g_0); nfc.a1 = (2.0f*b_10 + 4.0f*b_11) / g_1; @@ -262,7 +252,7 @@ void NfcFilter::adjust(const float w0) noexcept } -void NfcFilter::process1(const al::span src, float *RESTRICT dst) +void NfcFilter::process1(const al::span src, const al::span dst) { const float gain{first.gain}; const float b1{first.b1}; @@ -275,11 +265,11 @@ void NfcFilter::process1(const al::span src, float *RESTRICT dst) z1 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); first.z[0] = z1; } -void NfcFilter::process2(const al::span src, float *RESTRICT dst) +void NfcFilter::process2(const al::span src, const al::span dst) { const float gain{second.gain}; const float b1{second.b1}; @@ -296,12 +286,12 @@ void NfcFilter::process2(const al::span src, float *RESTRICT dst) z1 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); second.z[0] = z1; second.z[1] = z2; } -void NfcFilter::process3(const al::span src, float *RESTRICT dst) +void NfcFilter::process3(const al::span src, const al::span dst) { const float gain{third.gain}; const float b1{third.b1}; @@ -325,13 +315,13 @@ void NfcFilter::process3(const al::span src, float *RESTRICT dst) z3 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); third.z[0] = z1; third.z[1] = z2; third.z[2] = z3; } -void NfcFilter::process4(const al::span src, float *RESTRICT dst) +void NfcFilter::process4(const al::span src, const al::span dst) { const float gain{fourth.gain}; const float b1{fourth.b1}; @@ -359,7 +349,7 @@ void NfcFilter::process4(const al::span src, float *RESTRICT dst) z3 += y; return out; }; - std::transform(src.cbegin(), src.cend(), dst, proc_sample); + std::transform(src.cbegin(), src.cend(), dst.begin(), proc_sample); fourth.z[0] = z1; fourth.z[1] = z2; fourth.z[2] = z3; diff --git a/Engine/lib/openal-soft/core/filters/nfc.h b/Engine/lib/openal-soft/core/filters/nfc.h index 33f67a5f2..00ab4dd76 100644 --- a/Engine/lib/openal-soft/core/filters/nfc.h +++ b/Engine/lib/openal-soft/core/filters/nfc.h @@ -1,30 +1,31 @@ #ifndef CORE_FILTERS_NFC_H #define CORE_FILTERS_NFC_H +#include #include #include "alspan.h" struct NfcFilter1 { - float base_gain, gain; - float b1, a1; - float z[1]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, a1{}; + std::array z{}; }; struct NfcFilter2 { - float base_gain, gain; - float b1, b2, a1, a2; - float z[2]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, b2{}, a1{}, a2{}; + std::array z{}; }; struct NfcFilter3 { - float base_gain, gain; - float b1, b2, b3, a1, a2, a3; - float z[3]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, b2{}, b3{}, a1{}, a2{}, a3{}; + std::array z{}; }; struct NfcFilter4 { - float base_gain, gain; - float b1, b2, b3, b4, a1, a2, a3, a4; - float z[4]; + float base_gain{1.0f}, gain{1.0f}; + float b1{}, b2{}, b3{}, b4{}, a1{}, a2{}, a3{}, a4{}; + std::array z{}; }; class NfcFilter { @@ -39,7 +40,7 @@ public: * w1 = speed_of_sound / (control_distance * sample_rate); * * Generally speaking, the control distance should be approximately the - * average speaker distance, or based on the reference delay if outputing + * average speaker distance, or based on the reference delay if outputting * NFC-HOA. It must not be negative, 0, or infinite. The source distance * should not be too small relative to the control distance. */ @@ -48,16 +49,16 @@ public: void adjust(const float w0) noexcept; /* Near-field control filter for first-order ambisonic channels (1-3). */ - void process1(const al::span src, float *RESTRICT dst); + void process1(const al::span src, const al::span dst); /* Near-field control filter for second-order ambisonic channels (4-8). */ - void process2(const al::span src, float *RESTRICT dst); + void process2(const al::span src, const al::span dst); /* Near-field control filter for third-order ambisonic channels (9-15). */ - void process3(const al::span src, float *RESTRICT dst); + void process3(const al::span src, const al::span dst); /* Near-field control filter for fourth-order ambisonic channels (16-24). */ - void process4(const al::span src, float *RESTRICT dst); + void process4(const al::span src, const al::span dst); }; #endif /* CORE_FILTERS_NFC_H */ diff --git a/Engine/lib/openal-soft/core/filters/splitter.cpp b/Engine/lib/openal-soft/core/filters/splitter.cpp index 983ba36f1..fbb6b2b7e 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.cpp +++ b/Engine/lib/openal-soft/core/filters/splitter.cpp @@ -4,6 +4,7 @@ #include "splitter.h" #include +#include #include #include @@ -27,14 +28,17 @@ void BandSplitterR::init(Real f0norm) } template -void BandSplitterR::process(const al::span input, Real *hpout, Real *lpout) +void BandSplitterR::process(const al::span input, const al::span hpout, + const al::span lpout) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; Real lp_z1{mLpZ1}; Real lp_z2{mLpZ2}; Real ap_z1{mApZ1}; - auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpout](const Real in) noexcept -> Real + assert(lpout.size() <= input.size()); + auto lpiter = lpout.begin(); + auto proc_sample = [ap_coeff,lp_coeff,&lp_z1,&lp_z2,&ap_z1,&lpiter](const Real in) noexcept -> Real { /* Low-pass sample processing. */ Real d{(in - lp_z1) * lp_coeff}; @@ -45,7 +49,7 @@ void BandSplitterR::process(const al::span input, Real *hpout, lp_y = lp_z2 + d; lp_z2 = lp_y + d; - *(lpout++) = lp_y; + *(lpiter++) = lp_y; /* All-pass sample processing. */ Real ap_y{in*ap_coeff + ap_z1}; @@ -54,15 +58,15 @@ void BandSplitterR::process(const al::span input, Real *hpout, /* High-pass generated from removing low-passed output. */ return ap_y - lp_y; }; - std::transform(input.cbegin(), input.cend(), hpout, proc_sample); + std::transform(input.cbegin(), input.cend(), hpout.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; } template -void BandSplitterR::processHfScale(const al::span input, Real *RESTRICT output, - const Real hfscale) +void BandSplitterR::processHfScale(const al::span input, + const al::span output, const Real hfscale) { const Real ap_coeff{mCoeff}; const Real lp_coeff{mCoeff*0.5f + 0.5f}; @@ -89,7 +93,7 @@ void BandSplitterR::processHfScale(const al::span input, Real */ return (ap_y-lp_y)*hfscale + lp_y; }; - std::transform(input.begin(), input.end(), output, proc_sample); + std::transform(input.cbegin(), input.cend(), output.begin(), proc_sample); mLpZ1 = lp_z1; mLpZ2 = lp_z2; mApZ1 = ap_z1; diff --git a/Engine/lib/openal-soft/core/filters/splitter.h b/Engine/lib/openal-soft/core/filters/splitter.h index e853eb385..e3658c19a 100644 --- a/Engine/lib/openal-soft/core/filters/splitter.h +++ b/Engine/lib/openal-soft/core/filters/splitter.h @@ -22,9 +22,11 @@ public: void init(Real f0norm); void clear() noexcept { mLpZ1 = mLpZ2 = mApZ1 = 0.0f; } - void process(const al::span input, Real *hpout, Real *lpout); + void process(const al::span input, const al::span hpout, + const al::span lpout); - void processHfScale(const al::span input, Real *output, const Real hfscale); + void processHfScale(const al::span input, const al::span output, + const Real hfscale); void processHfScale(const al::span samples, const Real hfscale); void processScale(const al::span samples, const Real hfscale, const Real lfscale); diff --git a/Engine/lib/openal-soft/core/fmt_traits.cpp b/Engine/lib/openal-soft/core/fmt_traits.cpp index 054d87669..9d79287db 100644 --- a/Engine/lib/openal-soft/core/fmt_traits.cpp +++ b/Engine/lib/openal-soft/core/fmt_traits.cpp @@ -6,7 +6,7 @@ namespace al { -const int16_t muLawDecompressionTable[256] = { +const std::array muLawDecompressionTable{{ -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956, -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764, -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412, @@ -39,9 +39,9 @@ const int16_t muLawDecompressionTable[256] = { 244, 228, 212, 196, 180, 164, 148, 132, 120, 112, 104, 96, 88, 80, 72, 64, 56, 48, 40, 32, 24, 16, 8, 0 -}; +}}; -const int16_t aLawDecompressionTable[256] = { +const std::array aLawDecompressionTable{{ -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736, -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784, -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368, @@ -74,6 +74,6 @@ const int16_t aLawDecompressionTable[256] = { 1888, 1824, 2016, 1952, 1632, 1568, 1760, 1696, 688, 656, 752, 720, 560, 528, 624, 592, 944, 912, 1008, 976, 816, 784, 880, 848 -}; +}}; } // namespace al diff --git a/Engine/lib/openal-soft/core/fmt_traits.h b/Engine/lib/openal-soft/core/fmt_traits.h index f797f836f..e87ea57ce 100644 --- a/Engine/lib/openal-soft/core/fmt_traits.h +++ b/Engine/lib/openal-soft/core/fmt_traits.h @@ -1,17 +1,16 @@ #ifndef CORE_FMT_TRAITS_H #define CORE_FMT_TRAITS_H -#include -#include +#include +#include -#include "albyte.h" -#include "buffer_storage.h" +#include "storage_formats.h" namespace al { -extern const int16_t muLawDecompressionTable[256]; -extern const int16_t aLawDecompressionTable[256]; +extern const std::array muLawDecompressionTable; +extern const std::array aLawDecompressionTable; template @@ -19,63 +18,52 @@ struct FmtTypeTraits { }; template<> struct FmtTypeTraits { - using Type = uint8_t; + using Type = std::uint8_t; - template - static constexpr inline OutT to(const Type val) noexcept - { return val*OutT{1.0/128.0} - OutT{1.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(val)*(1.0f/128.0f) - 1.0f; } }; template<> struct FmtTypeTraits { - using Type = int16_t; + using Type = std::int16_t; - template - static constexpr inline OutT to(const Type val) noexcept { return val*OutT{1.0/32768.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(val) * (1.0f/32768.0f); } +}; +template<> +struct FmtTypeTraits { + using Type = std::int32_t; + + constexpr float operator()(const Type val) const noexcept + { return static_cast(val)*(1.0f/2147483648.0f); } }; template<> struct FmtTypeTraits { using Type = float; - template - static constexpr inline OutT to(const Type val) noexcept { return val; } + constexpr float operator()(const Type val) const noexcept { return val; } }; template<> struct FmtTypeTraits { using Type = double; - template - static constexpr inline OutT to(const Type val) noexcept { return static_cast(val); } + constexpr float operator()(const Type val) const noexcept { return static_cast(val); } }; template<> struct FmtTypeTraits { - using Type = uint8_t; + using Type = std::uint8_t; - template - static constexpr inline OutT to(const Type val) noexcept - { return muLawDecompressionTable[val] * OutT{1.0/32768.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(muLawDecompressionTable[val]) * (1.0f/32768.0f); } }; template<> struct FmtTypeTraits { - using Type = uint8_t; + using Type = std::uint8_t; - template - static constexpr inline OutT to(const Type val) noexcept - { return aLawDecompressionTable[val] * OutT{1.0/32768.0}; } + constexpr float operator()(const Type val) const noexcept + { return float(aLawDecompressionTable[val]) * (1.0f/32768.0f); } }; - -template -inline void LoadSampleArray(DstT *RESTRICT dst, const al::byte *src, const size_t srcstep, - const size_t samples) noexcept -{ - using TypeTraits = FmtTypeTraits; - using SampleType = typename TypeTraits::Type; - - const SampleType *RESTRICT ssrc{reinterpret_cast(src)}; - for(size_t i{0u};i < samples;i++) - dst[i] = TypeTraits::template to(ssrc[i*srcstep]); -} - } // namespace al #endif /* CORE_FMT_TRAITS_H */ diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.cpp b/Engine/lib/openal-soft/core/fpu_ctrl.cpp index 0cf0d6e72..28e60c042 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.cpp +++ b/Engine/lib/openal-soft/core/fpu_ctrl.cpp @@ -8,54 +8,80 @@ #endif #ifdef HAVE_SSE_INTRINSICS #include -#ifndef _MM_DENORMALS_ZERO_MASK +#elif defined(HAVE_SSE) +#include +#endif + +#if defined(HAVE_SSE) && !defined(_MM_DENORMALS_ZERO_MASK) /* Some headers seem to be missing these? */ #define _MM_DENORMALS_ZERO_MASK 0x0040u #define _MM_DENORMALS_ZERO_ON 0x0040u #endif -#endif #include "cpu_caps.h" +namespace { -void FPUCtl::enter() noexcept +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +[[gnu::target("sse")]] +#endif +[[maybe_unused]] +void disable_denormals(unsigned int *state [[maybe_unused]]) { - if(this->in_mode) return; - #if defined(HAVE_SSE_INTRINSICS) - this->sse_state = _mm_getcsr(); - unsigned int sseState{this->sse_state}; + *state = _mm_getcsr(); + unsigned int sseState{*state}; sseState &= ~(_MM_FLUSH_ZERO_MASK | _MM_DENORMALS_ZERO_MASK); sseState |= _MM_FLUSH_ZERO_ON | _MM_DENORMALS_ZERO_ON; _mm_setcsr(sseState); -#elif defined(__GNUC__) && defined(HAVE_SSE) +#elif defined(HAVE_SSE) - if((CPUCapFlags&CPU_CAP_SSE)) + *state = _mm_getcsr(); + unsigned int sseState{*state}; + sseState &= ~_MM_FLUSH_ZERO_MASK; + sseState |= _MM_FLUSH_ZERO_ON; + if((CPUCapFlags&CPU_CAP_SSE2)) { - __asm__ __volatile__("stmxcsr %0" : "=m" (*&this->sse_state)); - unsigned int sseState{this->sse_state}; - sseState |= 0x8000; /* set flush-to-zero */ - if((CPUCapFlags&CPU_CAP_SSE2)) - sseState |= 0x0040; /* set denormals-are-zero */ - __asm__ __volatile__("ldmxcsr %0" : : "m" (*&sseState)); + sseState &= ~_MM_DENORMALS_ZERO_MASK; + sseState |= _MM_DENORMALS_ZERO_ON; } + _mm_setcsr(sseState); #endif - - this->in_mode = true; } -void FPUCtl::leave() noexcept +#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +[[gnu::target("sse")]] +#endif +[[maybe_unused]] +void reset_fpu(unsigned int state [[maybe_unused]]) { - if(!this->in_mode) return; - -#if defined(HAVE_SSE_INTRINSICS) - _mm_setcsr(this->sse_state); - -#elif defined(__GNUC__) && defined(HAVE_SSE) - - if((CPUCapFlags&CPU_CAP_SSE)) - __asm__ __volatile__("ldmxcsr %0" : : "m" (*&this->sse_state)); +#if defined(HAVE_SSE_INTRINSICS) || defined(HAVE_SSE) + _mm_setcsr(state); +#endif +} + +} // namespace + + +unsigned int FPUCtl::Set() noexcept +{ + unsigned int state{}; +#if defined(HAVE_SSE_INTRINSICS) + disable_denormals(&state); +#elif defined(HAVE_SSE) + if((CPUCapFlags&CPU_CAP_SSE)) + disable_denormals(&state); +#endif + return state; +} + +void FPUCtl::Reset(unsigned int state [[maybe_unused]]) noexcept +{ +#if defined(HAVE_SSE_INTRINSICS) + reset_fpu(state); +#elif defined(HAVE_SSE) + if((CPUCapFlags&CPU_CAP_SSE)) + reset_fpu(state); #endif - this->in_mode = false; } diff --git a/Engine/lib/openal-soft/core/fpu_ctrl.h b/Engine/lib/openal-soft/core/fpu_ctrl.h index 9554313ae..d4f75ec31 100644 --- a/Engine/lib/openal-soft/core/fpu_ctrl.h +++ b/Engine/lib/openal-soft/core/fpu_ctrl.h @@ -2,20 +2,31 @@ #define CORE_FPU_CTRL_H class FPUCtl { -#if defined(HAVE_SSE_INTRINSICS) || (defined(__GNUC__) && defined(HAVE_SSE)) unsigned int sse_state{}; -#endif bool in_mode{}; + static unsigned int Set() noexcept; + static void Reset(unsigned int state) noexcept; + public: - FPUCtl() noexcept { enter(); in_mode = true; } - ~FPUCtl() { if(in_mode) leave(); } + FPUCtl() noexcept : sse_state{Set()}, in_mode{true} { } + ~FPUCtl() { if(in_mode) Reset(sse_state); } FPUCtl(const FPUCtl&) = delete; FPUCtl& operator=(const FPUCtl&) = delete; - void enter() noexcept; - void leave() noexcept; + void enter() noexcept + { + if(!in_mode) + sse_state = Set(); + in_mode = true; + } + void leave() noexcept + { + if(in_mode) + Reset(sse_state); + in_mode = false; + } }; #endif /* CORE_FPU_CTRL_H */ diff --git a/Engine/lib/openal-soft/core/front_stablizer.h b/Engine/lib/openal-soft/core/front_stablizer.h index 6825111a7..8eeb6d747 100644 --- a/Engine/lib/openal-soft/core/front_stablizer.h +++ b/Engine/lib/openal-soft/core/front_stablizer.h @@ -7,6 +7,7 @@ #include "almalloc.h" #include "bufferline.h" #include "filters/splitter.h" +#include "flexarray.h" struct FrontStablizer { diff --git a/Engine/lib/openal-soft/core/helpers.cpp b/Engine/lib/openal-soft/core/helpers.cpp index 99cf009c8..5e973bf28 100644 --- a/Engine/lib/openal-soft/core/helpers.cpp +++ b/Engine/lib/openal-soft/core/helpers.cpp @@ -3,102 +3,64 @@ #include "helpers.h" +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#include +#endif + #include -#include -#include #include -#include #include -#include +#include #include +#include +#include #include -#include +#include #include "almalloc.h" -#include "alfstream.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "logging.h" #include "strutils.h" -#include "vector.h" -/* Mixing thread piority level */ -int RTPrioLevel{1}; - -/* Allow reducing the process's RTTime limit for RTKit. */ -bool AllowRTTimeLimit{true}; - - -#ifdef _WIN32 - -#include - -const PathNamePair &GetProcBinary() -{ - static al::optional procbin; - if(procbin) return *procbin; - - auto fullpath = al::vector(256); - DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size()))}; - while(len == fullpath.size()) - { - fullpath.resize(fullpath.size() << 1); - len = GetModuleFileNameW(nullptr, fullpath.data(), static_cast(fullpath.size())); - } - if(len == 0) - { - ERR("Failed to get process name: error %lu\n", GetLastError()); - procbin.emplace(); - return *procbin; - } - - fullpath.resize(len); - if(fullpath.back() != 0) - fullpath.push_back(0); - - std::replace(fullpath.begin(), fullpath.end(), '/', '\\'); - auto sep = std::find(fullpath.rbegin()+1, fullpath.rend(), '\\'); - if(sep != fullpath.rend()) - { - *sep = 0; - procbin.emplace(wstr_to_utf8(fullpath.data()), wstr_to_utf8(al::to_address(sep.base()))); - } - else - procbin.emplace(std::string{}, wstr_to_utf8(fullpath.data())); - - TRACE("Got binary: %s, %s\n", procbin->path.c_str(), procbin->fname.c_str()); - return *procbin; -} - namespace { -void DirectorySearch(const char *path, const char *ext, al::vector *const results) -{ - std::string pathstr{path}; - pathstr += "\\*"; - pathstr += ext; - TRACE("Searching %s\n", pathstr.c_str()); +using namespace std::string_view_literals; - std::wstring wpath{utf8_to_wstr(pathstr.c_str())}; - WIN32_FIND_DATAW fdata; - HANDLE hdl{FindFirstFileW(wpath.c_str(), &fdata)}; - if(hdl == INVALID_HANDLE_VALUE) return; +std::mutex gSearchLock; + +void DirectorySearch(const std::filesystem::path &path, const std::string_view ext, + std::vector *const results) +{ + namespace fs = std::filesystem; const auto base = results->size(); - do { - results->emplace_back(); - std::string &str = results->back(); - str = path; - str += '\\'; - str += wstr_to_utf8(fdata.cFileName); - } while(FindNextFileW(hdl, &fdata)); - FindClose(hdl); + try { + auto fpath = path.lexically_normal(); + if(!fs::exists(fpath)) + return; - const al::span newlist{results->data()+base, results->size()-base}; + TRACE("Searching %s for *%.*s\n", fpath.u8string().c_str(), al::sizei(ext), ext.data()); + for(auto&& dirent : fs::directory_iterator{fpath}) + { + auto&& entrypath = dirent.path(); + if(!entrypath.has_extension()) + continue; + + if(fs::status(entrypath).type() == fs::file_type::regular + && al::case_compare(entrypath.extension().u8string(), ext) == 0) + results->emplace_back(entrypath.u8string()); + } + } + catch(std::exception& e) { + ERR("Exception enumerating files: %s\n", e.what()); + } + + const auto newlist = al::span{*results}.subspan(base); std::sort(newlist.begin(), newlist.end()); for(const auto &name : newlist) TRACE(" got %s\n", name.c_str()); @@ -106,83 +68,127 @@ void DirectorySearch(const char *path, const char *ext, al::vector } // namespace -al::vector SearchDataFiles(const char *ext, const char *subdir) -{ - auto is_slash = [](int c) noexcept -> int { return (c == '\\' || c == '/'); }; +#ifdef _WIN32 - static std::mutex search_lock; - std::lock_guard _{search_lock}; +#include +#include + +const PathNamePair &GetProcBinary() +{ + auto get_procbin = [] + { +#if !defined(ALSOFT_UWP) + DWORD pathlen{256}; + auto fullpath = std::wstring(pathlen, L'\0'); + DWORD len{GetModuleFileNameW(nullptr, fullpath.data(), pathlen)}; + while(len == fullpath.size()) + { + pathlen <<= 1; + if(pathlen == 0) + { + /* pathlen overflow (more than 4 billion characters??) */ + len = 0; + break; + } + fullpath.resize(pathlen); + len = GetModuleFileNameW(nullptr, fullpath.data(), pathlen); + } + if(len == 0) + { + ERR("Failed to get process name: error %lu\n", GetLastError()); + return PathNamePair{}; + } + + fullpath.resize(len); +#else + const WCHAR *exePath{__wargv[0]}; + if(!exePath) + { + ERR("Failed to get process name: __wargv[0] == nullptr\n"); + return PathNamePair{}; + } + std::wstring fullpath{exePath}; +#endif + std::replace(fullpath.begin(), fullpath.end(), L'/', L'\\'); + + PathNamePair res{}; + if(auto seppos = fullpath.rfind(L'\\'); seppos < fullpath.size()) + { + res.path = wstr_to_utf8(std::wstring_view{fullpath}.substr(0, seppos)); + res.fname = wstr_to_utf8(std::wstring_view{fullpath}.substr(seppos+1)); + } + else + res.fname = wstr_to_utf8(fullpath); + + TRACE("Got binary: %s, %s\n", res.path.c_str(), res.fname.c_str()); + return res; + }; + static const PathNamePair procbin{get_procbin()}; + return procbin; +} + +namespace { + +#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) +struct CoTaskMemDeleter { + void operator()(void *mem) const { CoTaskMemFree(mem); } +}; +#endif + +} // namespace + +std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) +{ + std::lock_guard srchlock{gSearchLock}; /* If the path is absolute, use it directly. */ - al::vector results; - if(isalpha(subdir[0]) && subdir[1] == ':' && is_slash(subdir[2])) + std::vector results; + auto path = std::filesystem::u8path(subdir); + if(path.is_absolute()) { - std::string path{subdir}; - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); + DirectorySearch(path, ext, &results); return results; } - if(subdir[0] == '\\' && subdir[1] == '\\' && subdir[2] == '?' && subdir[3] == '\\') - { - DirectorySearch(subdir, ext, &results); - return results; - } - - std::string path; /* Search the app-local directory. */ if(auto localpath = al::getenv(L"ALSOFT_LOCAL_PATH")) - { - path = wstr_to_utf8(localpath->c_str()); - if(is_slash(path.back())) - path.pop_back(); - } - else if(WCHAR *cwdbuf{_wgetcwd(nullptr, 0)}) - { - path = wstr_to_utf8(cwdbuf); - if(is_slash(path.back())) - path.pop_back(); - free(cwdbuf); - } - else - path = "."; - std::replace(path.begin(), path.end(), '/', '\\'); - DirectorySearch(path.c_str(), ext, &results); + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); +#if !defined(ALSOFT_UWP) && !defined(_GAMING_XBOX) /* Search the local and global data dirs. */ - static const int ids[2]{ CSIDL_APPDATA, CSIDL_COMMON_APPDATA }; - for(int id : ids) + for(const auto &folderid : std::array{FOLDERID_RoamingAppData, FOLDERID_ProgramData}) { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, id, FALSE) == FALSE) + std::unique_ptr buffer; + const HRESULT hr{SHGetKnownFolderPath(folderid, KF_FLAG_DONT_UNEXPAND, nullptr, + al::out_ptr(buffer))}; + if(FAILED(hr) || !buffer || !*buffer) continue; - path = wstr_to_utf8(buffer); - if(!is_slash(path.back())) - path += '\\'; - path += subdir; - std::replace(path.begin(), path.end(), '/', '\\'); - - DirectorySearch(path.c_str(), ext, &results); + DirectorySearch(std::filesystem::path{buffer.get()}/path, ext, &results); } +#endif return results; } -void SetRTPriority(void) +void SetRTPriority() { +#if !defined(ALSOFT_UWP) if(RTPrioLevel > 0) { if(!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) ERR("Failed to set priority level for thread\n"); } +#endif } #else -#include -#include +#include #include +#include #ifdef __FreeBSD__ #include #endif @@ -197,7 +203,6 @@ void SetRTPriority(void) #include #endif #ifdef HAVE_RTKIT -#include #include #include "dbus_wrap.h" @@ -209,184 +214,112 @@ void SetRTPriority(void) const PathNamePair &GetProcBinary() { - static al::optional procbin; - if(procbin) return *procbin; - - al::vector pathname; -#ifdef __FreeBSD__ - size_t pathlen; - int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 }; - if(sysctl(mib, 4, nullptr, &pathlen, nullptr, 0) == -1) - WARN("Failed to sysctl kern.proc.pathname: %s\n", strerror(errno)); - else + auto get_procbin = [] { - pathname.resize(pathlen + 1); - sysctl(mib, 4, pathname.data(), &pathlen, nullptr, 0); - pathname.resize(pathlen); - } + std::string pathname; +#ifdef __FreeBSD__ + size_t pathlen{}; + std::array mib{{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}}; + if(sysctl(mib.data(), mib.size(), nullptr, &pathlen, nullptr, 0) == -1) + WARN("Failed to sysctl kern.proc.pathname: %s\n", + std::generic_category().message(errno).c_str()); + else + { + auto procpath = std::vector(pathlen+1, '\0'); + sysctl(mib.data(), mib.size(), procpath.data(), &pathlen, nullptr, 0); + pathname = procpath.data(); + } #endif #ifdef HAVE_PROC_PIDPATH - if(pathname.empty()) - { - char procpath[PROC_PIDPATHINFO_MAXSIZE]{}; - const pid_t pid{getpid()}; - if(proc_pidpath(pid, procpath, sizeof(procpath)) < 1) - ERR("proc_pidpath(%d, ...) failed: %s\n", pid, strerror(errno)); - else - pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); - } + if(pathname.empty()) + { + std::array procpath{}; + const pid_t pid{getpid()}; + if(proc_pidpath(pid, procpath.data(), procpath.size()) < 1) + ERR("proc_pidpath(%d, ...) failed: %s\n", pid, + std::generic_category().message(errno).c_str()); + else + pathname = procpath.data(); + } #endif #ifdef __HAIKU__ - if(pathname.empty()) - { - char procpath[PATH_MAX]; - if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath, sizeof(procpath)) == B_OK) - pathname.insert(pathname.end(), procpath, procpath+strlen(procpath)); - } + if(pathname.empty()) + { + std::array procpath{}; + if(find_path(B_APP_IMAGE_SYMBOL, B_FIND_PATH_IMAGE_PATH, NULL, procpath.data(), procpath.size()) == B_OK) + pathname = procpath.data(); + } #endif #ifndef __SWITCH__ - if(pathname.empty()) - { - static const char SelfLinkNames[][32]{ - "/proc/self/exe", - "/proc/self/file", - "/proc/curproc/exe", - "/proc/curproc/file" - }; - - pathname.resize(256); - - const char *selfname{}; - ssize_t len{}; - for(const char *name : SelfLinkNames) + if(pathname.empty()) { - selfname = name; - len = readlink(selfname, pathname.data(), pathname.size()); - if(len >= 0 || errno != ENOENT) break; - } + const std::array SelfLinkNames{ + "/proc/self/exe"sv, + "/proc/self/file"sv, + "/proc/curproc/exe"sv, + "/proc/curproc/file"sv, + }; - while(len > 0 && static_cast(len) == pathname.size()) - { - pathname.resize(pathname.size() << 1); - len = readlink(selfname, pathname.data(), pathname.size()); + for(const std::string_view name : SelfLinkNames) + { + try { + if(!std::filesystem::exists(name)) + continue; + if(auto path = std::filesystem::read_symlink(name); !path.empty()) + { + pathname = path.u8string(); + break; + } + } + catch(std::exception& e) { + WARN("Exception getting symlink %.*s: %s\n", al::sizei(name), name.data(), + e.what()); + } + } } - if(len <= 0) - { - WARN("Failed to readlink %s: %s\n", selfname, strerror(errno)); - len = 0; - } - - pathname.resize(static_cast(len)); - } #endif - while(!pathname.empty() && pathname.back() == 0) - pathname.pop_back(); - auto sep = std::find(pathname.crbegin(), pathname.crend(), '/'); - if(sep != pathname.crend()) - procbin.emplace(std::string(pathname.cbegin(), sep.base()-1), - std::string(sep.base(), pathname.cend())); - else - procbin.emplace(std::string{}, std::string(pathname.cbegin(), pathname.cend())); + PathNamePair res{}; + if(auto seppos = pathname.rfind('/'); seppos < pathname.size()) + { + res.path = std::string_view{pathname}.substr(0, seppos); + res.fname = std::string_view{pathname}.substr(seppos+1); + } + else + res.fname = pathname; - TRACE("Got binary: \"%s\", \"%s\"\n", procbin->path.c_str(), procbin->fname.c_str()); - return *procbin; + TRACE("Got binary: \"%s\", \"%s\"\n", res.path.c_str(), res.fname.c_str()); + return res; + }; + static const PathNamePair procbin{get_procbin()}; + return procbin; } -namespace { - -void DirectorySearch(const char *path, const char *ext, al::vector *const results) +std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir) { - TRACE("Searching %s for *%s\n", path, ext); - DIR *dir{opendir(path)}; - if(!dir) return; + std::lock_guard srchlock{gSearchLock}; - const auto base = results->size(); - const size_t extlen{strlen(ext)}; - - while(struct dirent *dirent{readdir(dir)}) + std::vector results; + auto path = std::filesystem::u8path(subdir); + if(path.is_absolute()) { - if(strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0) - continue; - - const size_t len{strlen(dirent->d_name)}; - if(len <= extlen) continue; - if(al::strcasecmp(dirent->d_name+len-extlen, ext) != 0) - continue; - - results->emplace_back(); - std::string &str = results->back(); - str = path; - if(str.back() != '/') - str.push_back('/'); - str += dirent->d_name; - } - closedir(dir); - - const al::span newlist{results->data()+base, results->size()-base}; - std::sort(newlist.begin(), newlist.end()); - for(const auto &name : newlist) - TRACE(" got %s\n", name.c_str()); -} - -} // namespace - -al::vector SearchDataFiles(const char *ext, const char *subdir) -{ - static std::mutex search_lock; - std::lock_guard _{search_lock}; - - al::vector results; - if(subdir[0] == '/') - { - DirectorySearch(subdir, ext, &results); + DirectorySearch(path, ext, &results); return results; } /* Search the app-local directory. */ if(auto localpath = al::getenv("ALSOFT_LOCAL_PATH")) - DirectorySearch(localpath->c_str(), ext, &results); - else - { - al::vector cwdbuf(256); - while(!getcwd(cwdbuf.data(), cwdbuf.size())) - { - if(errno != ERANGE) - { - cwdbuf.clear(); - break; - } - cwdbuf.resize(cwdbuf.size() << 1); - } - if(cwdbuf.empty()) - DirectorySearch(".", ext, &results); - else - { - DirectorySearch(cwdbuf.data(), ext, &results); - cwdbuf.clear(); - } - } + DirectorySearch(*localpath, ext, &results); + else if(auto curpath = std::filesystem::current_path(); !curpath.empty()) + DirectorySearch(curpath, ext, &results); - // Search local data dir + /* Search local data dir */ if(auto datapath = al::getenv("XDG_DATA_HOME")) - { - std::string &path = *datapath; - if(path.back() != '/') - path += '/'; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } + DirectorySearch(std::filesystem::path{*datapath}/path, ext, &results); else if(auto homepath = al::getenv("HOME")) - { - std::string &path = *homepath; - if(path.back() == '/') - path.pop_back(); - path += "/.local/share/"; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } + DirectorySearch(std::filesystem::path{*homepath}/".local/share"/path, ext, &results); - // Search global data dirs + /* Search global data dirs */ std::string datadirs{al::getenv("XDG_DATA_DIRS").value_or("/usr/local/share/:/usr/share/")}; size_t curpos{0u}; @@ -394,30 +327,19 @@ al::vector SearchDataFiles(const char *ext, const char *subdir) { size_t nextpos{datadirs.find(':', curpos)}; - std::string path{(nextpos != std::string::npos) ? - datadirs.substr(curpos, nextpos++ - curpos) : datadirs.substr(curpos)}; + std::string_view pathname{(nextpos != std::string::npos) + ? std::string_view{datadirs}.substr(curpos, nextpos++ - curpos) + : std::string_view{datadirs}.substr(curpos)}; curpos = nextpos; - if(path.empty()) continue; - if(path.back() != '/') - path += '/'; - path += subdir; - - DirectorySearch(path.c_str(), ext, &results); + if(!pathname.empty()) + DirectorySearch(std::filesystem::path{pathname}/path, ext, &results); } #ifdef ALSOFT_INSTALL_DATADIR - // Search the installation data directory - { - std::string path{ALSOFT_INSTALL_DATADIR}; - if(!path.empty()) - { - if(path.back() != '/') - path += '/'; - path += subdir; - DirectorySearch(path.c_str(), ext, &results); - } - } + /* Search the installation data directory */ + if(auto instpath = std::filesystem::path{ALSOFT_INSTALL_DATADIR}; !instpath.empty()) + DirectorySearch(instpath/path, ext, &results); #endif return results; @@ -425,7 +347,7 @@ al::vector SearchDataFiles(const char *ext, const char *subdir) namespace { -bool SetRTPriorityPthread(int prio) +bool SetRTPriorityPthread(int prio [[maybe_unused]]) { int err{ENOTSUP}; #if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) @@ -438,23 +360,20 @@ bool SetRTPriorityPthread(int prio) rtmax = (rtmax-rtmin)/2 + rtmin; struct sched_param param{}; - param.sched_priority = clampi(prio, rtmin, rtmax); + param.sched_priority = std::clamp(prio, rtmin, rtmax); #ifdef SCHED_RESET_ON_FORK err = pthread_setschedparam(pthread_self(), SCHED_RR|SCHED_RESET_ON_FORK, ¶m); if(err == EINVAL) #endif err = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); if(err == 0) return true; - -#else - - std::ignore = prio; #endif - WARN("pthread_setschedparam failed: %s (%d)\n", std::strerror(err), err); + WARN("pthread_setschedparam failed: %s (%d)\n", std::generic_category().message(err).c_str(), + err); return false; } -bool SetRTPriorityRTKit(int prio) +bool SetRTPriorityRTKit(int prio [[maybe_unused]]) { #ifdef HAVE_RTKIT if(!HasDBus()) @@ -478,7 +397,7 @@ bool SetRTPriorityRTKit(int prio) if(err == -ENOENT) { err = std::abs(err); - ERR("Could not query RTKit: %s (%d)\n", std::strerror(err), err); + ERR("Could not query RTKit: %s (%d)\n", std::generic_category().message(err).c_str(), err); return false; } int rtmax{rtkit_get_max_realtime_priority(conn.get())}; @@ -514,19 +433,20 @@ bool SetRTPriorityRTKit(int prio) err = limit_rttime(conn.get()); if(err != 0) WARN("Failed to set RLIMIT_RTTIME for RTKit: %s (%d)\n", - std::strerror(err), err); + std::generic_category().message(err).c_str(), err); } /* Limit the maximum real-time priority to half. */ rtmax = (rtmax+1)/2; - prio = clampi(prio, 1, rtmax); + prio = std::clamp(prio, 1, rtmax); TRACE("Making real-time with priority %d (max: %d)\n", prio, rtmax); err = rtkit_make_realtime(conn.get(), 0, prio); if(err == 0) return true; err = std::abs(err); - WARN("Failed to set real-time priority: %s (%d)\n", std::strerror(err), err); + WARN("Failed to set real-time priority: %s (%d)\n", + std::generic_category().message(err).c_str(), err); } /* Don't try to set the niceness for non-Linux systems. Standard POSIX has * niceness as a per-process attribute, while the intent here is for the @@ -541,13 +461,13 @@ bool SetRTPriorityRTKit(int prio) if(err == 0) return true; err = std::abs(err); - WARN("Failed to set high priority: %s (%d)\n", std::strerror(err), err); + WARN("Failed to set high priority: %s (%d)\n", + std::generic_category().message(err).c_str(), err); } #endif /* __linux__ */ #else - std::ignore = prio; WARN("D-Bus not supported\n"); #endif return false; diff --git a/Engine/lib/openal-soft/core/helpers.h b/Engine/lib/openal-soft/core/helpers.h index f0bfcf1b5..3a987c9d1 100644 --- a/Engine/lib/openal-soft/core/helpers.h +++ b/Engine/lib/openal-soft/core/helpers.h @@ -2,17 +2,23 @@ #define CORE_HELPERS_H #include - -#include "vector.h" +#include +#include -struct PathNamePair { std::string path, fname; }; -const PathNamePair &GetProcBinary(void); +struct PathNamePair { + std::string path, fname; +}; +const PathNamePair &GetProcBinary(); -extern int RTPrioLevel; -extern bool AllowRTTimeLimit; -void SetRTPriority(void); +/* Mixing thread priority level */ +inline int RTPrioLevel{1}; -al::vector SearchDataFiles(const char *match, const char *subdir); +/* Allow reducing the process's RTTime limit for RTKit. */ +inline bool AllowRTTimeLimit{true}; + +void SetRTPriority(); + +std::vector SearchDataFiles(const std::string_view ext, const std::string_view subdir); #endif /* CORE_HELPERS_H */ diff --git a/Engine/lib/openal-soft/core/hrtf.cpp b/Engine/lib/openal-soft/core/hrtf.cpp index d5c7573a1..e2f0d893d 100644 --- a/Engine/lib/openal-soft/core/hrtf.cpp +++ b/Engine/lib/openal-soft/core/hrtf.cpp @@ -8,25 +8,28 @@ #include #include #include +#include #include #include #include +#include #include #include #include #include #include +#include +#include #include #include +#include #include "albit.h" -#include "albyte.h" -#include "alfstream.h" #include "almalloc.h" #include "alnumbers.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" +#include "alstring.h" #include "ambidefs.h" #include "filters/splitter.h" #include "helpers.h" @@ -34,15 +37,20 @@ #include "mixer/hrtfdefs.h" #include "opthelpers.h" #include "polyphase_resampler.h" -#include "vector.h" namespace { +using namespace std::string_view_literals; + struct HrtfEntry { std::string mDispName; std::string mFilename; + template + HrtfEntry(T&& dispname, U&& fname) + : mDispName{std::forward(dispname)}, mFilename{std::forward(fname)} + { } /* GCC warns when it tries to inline this. */ ~HrtfEntry(); }; @@ -50,11 +58,12 @@ HrtfEntry::~HrtfEntry() = default; struct LoadedHrtf { std::string mFilename; + uint mSampleRate{}; std::unique_ptr mEntry; template - LoadedHrtf(T&& name, U&& entry) - : mFilename{std::forward(name)}, mEntry{std::forward(entry)} + LoadedHrtf(T&& name, uint srate, U&& entry) + : mFilename{std::forward(name)}, mSampleRate{srate}, mEntry{std::forward(entry)} { } LoadedHrtf(LoadedHrtf&&) = default; /* GCC warns when it tries to inline this. */ @@ -86,24 +95,36 @@ constexpr uint HrirDelayFracBits{2}; constexpr uint HrirDelayFracOne{1 << HrirDelayFracBits}; constexpr uint HrirDelayFracHalf{HrirDelayFracOne >> 1}; +/* The sample rate is stored as a 24-bit integer, so 16MHz is the largest + * supported. + */ +constexpr uint MaxSampleRate{0xff'ff'ff}; + static_assert(MaxHrirDelay*HrirDelayFracOne < 256, "MAX_HRIR_DELAY or DELAY_FRAC too large"); -constexpr char magicMarker00[8]{'M','i','n','P','H','R','0','0'}; -constexpr char magicMarker01[8]{'M','i','n','P','H','R','0','1'}; -constexpr char magicMarker02[8]{'M','i','n','P','H','R','0','2'}; -constexpr char magicMarker03[8]{'M','i','n','P','H','R','0','3'}; + +[[nodiscard]] constexpr auto GetMarker00Name() noexcept { return "MinPHR00"sv; } +[[nodiscard]] constexpr auto GetMarker01Name() noexcept { return "MinPHR01"sv; } +[[nodiscard]] constexpr auto GetMarker02Name() noexcept { return "MinPHR02"sv; } +[[nodiscard]] constexpr auto GetMarker03Name() noexcept { return "MinPHR03"sv; } + /* First value for pass-through coefficients (remaining are 0), used for omni- * directional sounds. */ constexpr auto PassthruCoeff = static_cast(1.0/al::numbers::sqrt2); std::mutex LoadedHrtfLock; -al::vector LoadedHrtfs; +std::vector LoadedHrtfs; std::mutex EnumeratedHrtfLock; -al::vector EnumeratedHrtfs; +std::vector EnumeratedHrtfs; +/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) + * To access a memory buffer through the std::istream interface, a custom + * std::streambuf implementation is needed that has to do pointer manipulation + * for seeking. With C++23, we may be able to use std::spanstream instead. + */ class databuf final : public std::streambuf { int_type underflow() override { return traits_type::eof(); } @@ -113,34 +134,32 @@ class databuf final : public std::streambuf { if((mode&std::ios_base::out) || !(mode&std::ios_base::in)) return traits_type::eof(); - char_type *cur; switch(whence) { - case std::ios_base::beg: - if(offset < 0 || offset > egptr()-eback()) - return traits_type::eof(); - cur = eback() + offset; - break; - - case std::ios_base::cur: - if((offset >= 0 && offset > egptr()-gptr()) || - (offset < 0 && -offset > gptr()-eback())) - return traits_type::eof(); - cur = gptr() + offset; - break; - - case std::ios_base::end: - if(offset > 0 || -offset > egptr()-eback()) - return traits_type::eof(); - cur = egptr() + offset; - break; - - default: + case std::ios_base::beg: + if(offset < 0 || offset > egptr()-eback()) return traits_type::eof(); + setg(eback(), eback()+offset, egptr()); + break; + + case std::ios_base::cur: + if((offset >= 0 && offset > egptr()-gptr()) || + (offset < 0 && -offset > gptr()-eback())) + return traits_type::eof(); + setg(eback(), gptr()+offset, egptr()); + break; + + case std::ios_base::end: + if(offset > 0 || -offset > egptr()-eback()) + return traits_type::eof(); + setg(eback(), egptr()+offset, egptr()); + break; + + default: + return traits_type::eof(); } - setg(eback(), cur, egptr()); - return cur - eback(); + return gptr() - eback(); } pos_type seekpos(pos_type pos, std::ios_base::openmode mode) override @@ -152,24 +171,23 @@ class databuf final : public std::streambuf { if(pos < 0 || pos > egptr()-eback()) return traits_type::eof(); - setg(eback(), eback() + static_cast(pos), egptr()); + setg(eback(), eback()+static_cast(pos), egptr()); return pos; } public: - databuf(const char_type *start_, const char_type *end_) noexcept + databuf(const al::span data) noexcept { - setg(const_cast(start_), const_cast(start_), - const_cast(end_)); + setg(data.data(), data.data(), al::to_address(data.end())); } }; +/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ class idstream final : public std::istream { databuf mStreamBuf; public: - idstream(const char *start_, const char *end_) - : std::istream{nullptr}, mStreamBuf{start_, end_} + idstream(const al::span data) : std::istream{nullptr}, mStreamBuf{data} { init(&mStreamBuf); } }; @@ -184,7 +202,7 @@ IdxBlend CalcEvIndex(uint evcount, float ev) al::numbers::inv_pi_v; uint idx{float2uint(ev)}; - return IdxBlend{minu(idx, evcount-1), ev-static_cast(idx)}; + return IdxBlend{std::min(idx, evcount-1u), ev-static_cast(idx)}; } /* Calculate the azimuth index given the polar azimuth in radians. This will @@ -206,7 +224,7 @@ IdxBlend CalcAzIndex(uint azcount, float az) * and azimuth in radians. The coefficients are normalized. */ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float spread, - HrirArray &coeffs, const al::span delays) + const HrirSpan coeffs, const al::span delays) const { const float dirfact{1.0f - (al::numbers::inv_pi_v/2.0f * spread)}; @@ -222,7 +240,7 @@ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float /* Calculate the elevation indices. */ const auto elev0 = CalcEvIndex(field->evCount, elevation); - const size_t elev1_idx{minu(elev0.idx+1, field->evCount-1)}; + const size_t elev1_idx{std::min(elev0.idx+1u, field->evCount-1u)}; const size_t ir0offset{mElev[ebase + elev0.idx].irOffset}; const size_t ir1offset{mElev[ebase + elev1_idx].irOffset}; @@ -231,43 +249,43 @@ void HrtfStore::getCoeffs(float elevation, float azimuth, float distance, float const auto az1 = CalcAzIndex(mElev[ebase + elev1_idx].azCount, azimuth); /* Calculate the HRIR indices to blend. */ - const size_t idx[4]{ + const std::array idx{{ ir0offset + az0.idx, ir0offset + ((az0.idx+1) % mElev[ebase + elev0.idx].azCount), ir1offset + az1.idx, ir1offset + ((az1.idx+1) % mElev[ebase + elev1_idx].azCount) - }; + }}; /* Calculate bilinear blending weights, attenuated according to the * directional panning factor. */ - const float blend[4]{ + const std::array blend{{ (1.0f-elev0.blend) * (1.0f-az0.blend) * dirfact, (1.0f-elev0.blend) * ( az0.blend) * dirfact, ( elev0.blend) * (1.0f-az1.blend) * dirfact, ( elev0.blend) * ( az1.blend) * dirfact - }; + }}; /* Calculate the blended HRIR delays. */ - float d{mDelays[idx[0]][0]*blend[0] + mDelays[idx[1]][0]*blend[1] + mDelays[idx[2]][0]*blend[2] - + mDelays[idx[3]][0]*blend[3]}; + float d{float(mDelays[idx[0]][0])*blend[0] + float(mDelays[idx[1]][0])*blend[1] + + float(mDelays[idx[2]][0])*blend[2] + float(mDelays[idx[3]][0])*blend[3]}; delays[0] = fastf2u(d * float{1.0f/HrirDelayFracOne}); - d = mDelays[idx[0]][1]*blend[0] + mDelays[idx[1]][1]*blend[1] + mDelays[idx[2]][1]*blend[2] - + mDelays[idx[3]][1]*blend[3]; + d = float(mDelays[idx[0]][1])*blend[0] + float(mDelays[idx[1]][1])*blend[1] + + float(mDelays[idx[2]][1])*blend[2] + float(mDelays[idx[3]][1])*blend[3]; delays[1] = fastf2u(d * float{1.0f/HrirDelayFracOne}); /* Calculate the blended HRIR coefficients. */ - float *coeffout{al::assume_aligned<16>(coeffs[0].data())}; - coeffout[0] = PassthruCoeff * (1.0f-dirfact); - coeffout[1] = PassthruCoeff * (1.0f-dirfact); - std::fill_n(coeffout+2, size_t{HrirLength-1}*2, 0.0f); + auto coeffout = coeffs.begin(); + coeffout[0][0] = PassthruCoeff * (1.0f-dirfact); + coeffout[0][1] = PassthruCoeff * (1.0f-dirfact); + std::fill_n(coeffout+1, size_t{HrirLength-1}, std::array{0.0f, 0.0f}); for(size_t c{0};c < 4;c++) { - const float *srccoeffs{al::assume_aligned<16>(mCoeffs[idx[c]][0].data())}; const float mult{blend[c]}; - auto blend_coeffs = [mult](const float src, const float coeff) noexcept -> float - { return src*mult + coeff; }; - std::transform(srccoeffs, srccoeffs + HrirLength*2, coeffout, coeffout, blend_coeffs); + auto blend_coeffs = [mult](const float2 &src, const float2 &coeff) noexcept -> float2 + { return float2{{src[0]*mult + coeff[0], src[1]*mult + coeff[1]}}; }; + std::transform(mCoeffs[idx[c]].cbegin(), mCoeffs[idx[c]].cend(), coeffout, coeffout, + blend_coeffs); } } @@ -276,7 +294,8 @@ std::unique_ptr DirectHrtfState::Create(size_t num_chans) { return std::unique_ptr{new(FamCount(num_chans)) DirectHrtfState{num_chans}}; } void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, - const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + const al::span AmbiPoints, + const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain) { using double2 = std::array; @@ -287,27 +306,28 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool const double xover_norm{double{XOverFreq} / Hrtf->mSampleRate}; mChannels[0].mSplitter.init(static_cast(xover_norm)); - for(size_t i{0};i < mChannels.size();++i) + mChannels[0].mHfScale = AmbiOrderHFGain[0]; + for(size_t i{1};i < mChannels.size();++i) { - const size_t order{AmbiIndex::OrderFromChannel()[i]}; + const size_t order{AmbiIndex::OrderFromChannel[i]}; mChannels[i].mSplitter = mChannels[0].mSplitter; mChannels[i].mHfScale = AmbiOrderHFGain[order]; } uint min_delay{HrtfHistoryLength*HrirDelayFracOne}, max_delay{0}; - al::vector impres; impres.reserve(AmbiPoints.size()); + std::vector impres; impres.reserve(AmbiPoints.size()); auto calc_res = [Hrtf,&max_delay,&min_delay](const AngularPoint &pt) -> ImpulseResponse { auto &field = Hrtf->mFields[0]; const auto elev0 = CalcEvIndex(field.evCount, pt.Elev.value); - const size_t elev1_idx{minu(elev0.idx+1, field.evCount-1)}; + const size_t elev1_idx{std::min(elev0.idx+1u, field.evCount-1u)}; const size_t ir0offset{Hrtf->mElev[elev0.idx].irOffset}; const size_t ir1offset{Hrtf->mElev[elev1_idx].irOffset}; const auto az0 = CalcAzIndex(Hrtf->mElev[elev0.idx].azCount, pt.Azim.value); const auto az1 = CalcAzIndex(Hrtf->mElev[elev1_idx].azCount, pt.Azim.value); - const size_t idx[4]{ + const std::array idx{ ir0offset + az0.idx, ir0offset + ((az0.idx+1) % Hrtf->mElev[elev0.idx].azCount), ir1offset + az1.idx, @@ -319,8 +339,8 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool ImpulseResponse res{Hrtf->mCoeffs[irOffset], Hrtf->mDelays[irOffset][0], Hrtf->mDelays[irOffset][1]}; - min_delay = minu(min_delay, minu(res.ldelay, res.rdelay)); - max_delay = maxu(max_delay, maxu(res.ldelay, res.rdelay)); + min_delay = std::min(min_delay, std::min(res.ldelay, res.rdelay)); + max_delay = std::max(max_delay, std::max(res.ldelay, res.rdelay)); return res; }; @@ -331,40 +351,44 @@ void DirectHrtfState::build(const HrtfStore *Hrtf, const uint irSize, const bool TRACE("Min delay: %.2f, max delay: %.2f, FIR length: %u\n", min_delay/double{HrirDelayFracOne}, max_delay/double{HrirDelayFracOne}, irSize); - auto tmpres = al::vector>(mChannels.size()); + auto tmpres = std::vector>(mChannels.size()); max_delay = 0; - for(size_t c{0u};c < AmbiPoints.size();++c) + auto matrixline = AmbiMatrix.cbegin(); + for(auto &impulse : impres) { - const ConstHrirSpan hrir{impres[c].hrir}; - const uint base_delay{perHrirMin ? minu(impres[c].ldelay, impres[c].rdelay) : min_delay}; - const uint ldelay{hrir_delay_round(impres[c].ldelay - base_delay)}; - const uint rdelay{hrir_delay_round(impres[c].rdelay - base_delay)}; - max_delay = maxu(max_delay, maxu(impres[c].ldelay, impres[c].rdelay) - base_delay); + const ConstHrirSpan hrir{impulse.hrir}; + const uint base_delay{perHrirMin ? std::min(impulse.ldelay, impulse.rdelay) : min_delay}; + const uint ldelay{hrir_delay_round(impulse.ldelay - base_delay)}; + const uint rdelay{hrir_delay_round(impulse.rdelay - base_delay)}; + max_delay = std::max(max_delay, std::max(impulse.ldelay, impulse.rdelay) - base_delay); - for(size_t i{0u};i < mChannels.size();++i) + auto gains = matrixline->cbegin(); + ++matrixline; + for(auto &result : tmpres) { - const double mult{AmbiMatrix[c][i]}; - const size_t numirs{HrirLength - maxz(ldelay, rdelay)}; + const double mult{*(gains++)}; + const size_t numirs{HrirLength - std::max(ldelay, rdelay)}; size_t lidx{ldelay}, ridx{rdelay}; for(size_t j{0};j < numirs;++j) { - tmpres[i][lidx++][0] += hrir[j][0] * mult; - tmpres[i][ridx++][1] += hrir[j][1] * mult; + result[lidx++][0] += hrir[j][0] * mult; + result[ridx++][1] += hrir[j][1] * mult; } } } impres.clear(); - for(size_t i{0u};i < mChannels.size();++i) + auto output = mChannels.begin(); + for(auto &result : tmpres) { - auto copy_arr = [](const double2 &in) noexcept -> float2 + auto cast_array2 = [](const double2 &in) noexcept -> float2 { return float2{{static_cast(in[0]), static_cast(in[1])}}; }; - std::transform(tmpres[i].cbegin(), tmpres[i].cend(), mChannels[i].mCoeffs.begin(), - copy_arr); + std::transform(result.cbegin(), result.cend(), output->mCoeffs.begin(), cast_array2); + ++output; } tmpres.clear(); - const uint max_length{minu(hrir_delay_round(max_delay) + irSize, HrirLength)}; + const uint max_length{std::min(hrir_delay_round(max_delay) + irSize, HrirLength)}; TRACE("New max delay: %.2f, FIR length: %u\n", max_delay/double{HrirDelayFracOne}, max_length); mIrSize = max_length; @@ -376,8 +400,15 @@ namespace { std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, const al::span fields, const al::span elevs, const HrirArray *coeffs, - const ubyte2 *delays, const char *filename) + const ubyte2 *delays) { + static_assert(alignof(HrtfStore::Field) <= alignof(HrtfStore)); + static_assert(alignof(HrtfStore::Elevation) <= alignof(HrtfStore)); + static_assert(16 <= alignof(HrtfStore)); + + if(rate > MaxSampleRate) + throw std::runtime_error{"Sample rate is too large (max: "+std::to_string(MaxSampleRate)+"hz)"}; + const size_t irCount{size_t{elevs.back().azCount} + elevs.back().irOffset}; size_t total{sizeof(HrtfStore)}; total = RoundUp(total, alignof(HrtfStore::Field)); /* Align for field infos */ @@ -388,56 +419,54 @@ std::unique_ptr CreateHrtfStore(uint rate, uint8_t irSize, total += sizeof(std::declval().mCoeffs[0])*irCount; total += sizeof(std::declval().mDelays[0])*irCount; - std::unique_ptr Hrtf{}; - if(void *ptr{al_calloc(16, total)}) - { - Hrtf.reset(al::construct_at(static_cast(ptr))); - InitRef(Hrtf->mRef, 1u); - Hrtf->mSampleRate = rate; - Hrtf->mIrSize = irSize; + static constexpr auto AlignVal = std::align_val_t{alignof(HrtfStore)}; + std::unique_ptr Hrtf{::new(::operator new[](total, AlignVal)) HrtfStore{}}; + Hrtf->mRef.store(1u, std::memory_order_relaxed); + Hrtf->mSampleRate = rate & 0xff'ff'ff; + Hrtf->mIrSize = irSize; - /* Set up pointers to storage following the main HRTF struct. */ - char *base = reinterpret_cast(Hrtf.get()); - size_t offset{sizeof(HrtfStore)}; + /* Set up pointers to storage following the main HRTF struct. */ + auto storage = al::span{reinterpret_cast(Hrtf.get()), total}; + auto base = storage.begin(); + ptrdiff_t offset{sizeof(HrtfStore)}; - offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ - auto field_ = reinterpret_cast(base + offset); - offset += sizeof(field_[0])*fields.size(); + offset = RoundUp(offset, alignof(HrtfStore::Field)); /* Align for field infos */ + auto field_ = al::span{reinterpret_cast(al::to_address(base + offset)), + fields.size()}; + offset += ptrdiff_t(sizeof(field_[0])*fields.size()); - offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ - auto elev_ = reinterpret_cast(base + offset); - offset += sizeof(elev_[0])*elevs.size(); + offset = RoundUp(offset, alignof(HrtfStore::Elevation)); /* Align for elevation infos */ + auto elev_ = al::span{reinterpret_cast(al::to_address(base + offset)), + elevs.size()}; + offset += ptrdiff_t(sizeof(elev_[0])*elevs.size()); - offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ - auto coeffs_ = reinterpret_cast(base + offset); - offset += sizeof(coeffs_[0])*irCount; + offset = RoundUp(offset, 16); /* Align for coefficients using SIMD */ + auto coeffs_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; + offset += ptrdiff_t(sizeof(coeffs_[0])*irCount); - auto delays_ = reinterpret_cast(base + offset); - offset += sizeof(delays_[0])*irCount; + auto delays_ = al::span{reinterpret_cast(al::to_address(base + offset)), irCount}; + offset += ptrdiff_t(sizeof(delays_[0])*irCount); - if(offset != total) - throw std::runtime_error{"HrtfStore allocation size mismatch"}; + if(size_t(offset) != total) + throw std::runtime_error{"HrtfStore allocation size mismatch"}; - /* Copy input data to storage. */ - std::uninitialized_copy(fields.cbegin(), fields.cend(), field_); - std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_); - std::uninitialized_copy_n(coeffs, irCount, coeffs_); - std::uninitialized_copy_n(delays, irCount, delays_); + /* Copy input data to storage. */ + std::uninitialized_copy(fields.cbegin(), fields.cend(), field_.begin()); + std::uninitialized_copy(elevs.cbegin(), elevs.cend(), elev_.begin()); + std::uninitialized_copy_n(coeffs, irCount, coeffs_.begin()); + std::uninitialized_copy_n(delays, irCount, delays_.begin()); - /* Finally, assign the storage pointers. */ - Hrtf->mFields = al::as_span(field_, fields.size()); - Hrtf->mElev = elev_; - Hrtf->mCoeffs = coeffs_; - Hrtf->mDelays = delays_; - } - else - ERR("Out of memory allocating storage for %s.\n", filename); + /* Finally, assign the storage pointers. */ + Hrtf->mFields = field_; + Hrtf->mElev = elev_; + Hrtf->mCoeffs = coeffs_; + Hrtf->mDelays = delays_; return Hrtf; } -void MirrorLeftHrirs(const al::span elevs, HrirArray *coeffs, - ubyte2 *delays) +void MirrorLeftHrirs(const al::span elevs, al::span coeffs, + al::span delays) { for(const auto &elev : elevs) { @@ -477,11 +506,11 @@ T> readle(std::istream &data) static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); - T ret{}; - if(!data.read(reinterpret_cast(&ret), num_bits/8)) + alignas(T) std::array ret{}; + if(!data.read(ret.data(), num_bits/8)) return static_cast(EOF); - return fixsign(ret); + return fixsign(al::bit_cast(ret)); } template @@ -491,13 +520,12 @@ T> readle(std::istream &data) static_assert((num_bits&7) == 0, "num_bits must be a multiple of 8"); static_assert(num_bits <= sizeof(T)*8, "num_bits is too large for the type"); - T ret{}; - al::byte b[sizeof(T)]{}; - if(!data.read(reinterpret_cast(b), num_bits/8)) + alignas(T) std::array ret{}; + if(!data.read(ret.data(), num_bits/8)) return static_cast(EOF); - std::reverse_copy(std::begin(b), std::end(b), reinterpret_cast(&ret)); + std::reverse(ret.begin(), ret.end()); - return fixsign(ret); + return fixsign(al::bit_cast(ret)); } template<> @@ -505,17 +533,14 @@ inline uint8_t readle(std::istream &data) { return static_cast(data.get()); } -std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf00(std::istream &data) { uint rate{readle(data)}; ushort irCount{readle(data)}; ushort irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { @@ -529,14 +554,12 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) return nullptr; } - auto elevs = al::vector(evCount); + auto elevs = std::vector(evCount); for(auto &elev : elevs) elev.irOffset = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{1};i < evCount;i++) { if(elevs[i].irOffset <= elevs[i-1].irOffset) @@ -571,20 +594,18 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) return nullptr; } - auto coeffs = al::vector(irCount, HrirArray{}); - auto delays = al::vector(irCount); + auto coeffs = std::vector(irCount, HrirArray{}); + auto delays = std::vector(irCount); for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + for(auto &val : al::span{hrir}.first(irSize)) + val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irCount;i++) { if(delays[i][0] > MaxHrirDelay) @@ -596,23 +617,20 @@ std::unique_ptr LoadHrtf00(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); - const HrtfStore::Field field[1]{{0.0f, evCount}}; - return CreateHrtfStore(rate, static_cast(irSize), field, {elevs.data(), elevs.size()}, - coeffs.data(), delays.data(), filename); + const std::array field{HrtfStore::Field{0.0f, evCount}}; + return CreateHrtfStore(rate, static_cast(irSize), field, elevs, coeffs.data(), + delays.data()); } -std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf01(std::istream &data) { uint rate{readle(data)}; uint8_t irSize{readle(data)}; ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(irSize < MinIrLength || irSize > HrirLength) { @@ -626,14 +644,12 @@ std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) return nullptr; } - auto elevs = al::vector(evCount); + auto elevs = std::vector(evCount); for(auto &elev : elevs) elev.azCount = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < evCount;++i) { if(elevs[i].azCount < MinAzCount || elevs[i].azCount > MaxAzCount) @@ -649,20 +665,18 @@ std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) elevs[i].irOffset = static_cast(elevs[i-1].irOffset + elevs[i-1].azCount); const ushort irCount{static_cast(elevs.back().irOffset + elevs.back().azCount)}; - auto coeffs = al::vector(irCount, HrirArray{}); - auto delays = al::vector(irCount); + auto coeffs = std::vector(irCount, HrirArray{}); + auto delays = std::vector(irCount); for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + for(auto &val : al::span{hrir}.first(irSize)) + val[0] = float(readle(data)) / 32768.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irCount;i++) { if(delays[i][0] > MaxHrirDelay) @@ -674,19 +688,18 @@ std::unique_ptr LoadHrtf01(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); - const HrtfStore::Field field[1]{{0.0f, evCount}}; - return CreateHrtfStore(rate, irSize, field, {elevs.data(), elevs.size()}, coeffs.data(), - delays.data(), filename); + const std::array field{HrtfStore::Field{0.0f, evCount}}; + return CreateHrtfStore(rate, irSize, field, elevs, coeffs.data(), delays.data()); } -std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf02(std::istream &data) { - constexpr ubyte SampleType_S16{0}; - constexpr ubyte SampleType_S24{1}; - constexpr ubyte ChanType_LeftOnly{0}; - constexpr ubyte ChanType_LeftRight{1}; + static constexpr ubyte SampleType_S16{0}; + static constexpr ubyte SampleType_S24{1}; + static constexpr ubyte ChanType_LeftOnly{0}; + static constexpr ubyte ChanType_LeftRight{1}; uint rate{readle(data)}; ubyte sampleType{readle(data)}; @@ -694,10 +707,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) uint8_t irSize{readle(data)}; ubyte fdCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(sampleType > SampleType_S24) { @@ -722,17 +732,14 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) return nullptr; } - auto fields = al::vector(fdCount); - auto elevs = al::vector{}; + auto fields = std::vector(fdCount); + auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const ushort distance{readle(data)}; const ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { @@ -747,7 +754,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) return nullptr; } - fields[f].distance = distance / 1000.0f; + fields[f].distance = float(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && fields[f].distance <= fields[f-1].distance) { @@ -758,13 +765,10 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); - for(auto &elev : al::span(elevs.data()+ebase, evCount)) + for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t e{0};e < evCount;e++) { @@ -787,33 +791,31 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) }); const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); - auto coeffs = al::vector(irTotal, HrirArray{}); - auto delays = al::vector(irTotal); + auto coeffs = std::vector(irTotal, HrirArray{}); + auto delays = std::vector(irTotal); if(channelType == ChanType_LeftOnly) { if(sampleType == SampleType_S16) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) - val[0] = readle(data) / 32768.0f; + for(auto &val : al::span{hrir}.first(irSize)) + val[0] = float(readle(data)) / 32768.0f; } } else if(sampleType == SampleType_S24) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay) @@ -825,7 +827,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { @@ -833,10 +835,10 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { - val[0] = readle(data) / 32768.0f; - val[1] = readle(data) / 32768.0f; + val[0] = float(readle(data)) / 32768.0f; + val[1] = float(readle(data)) / 32768.0f; } } } @@ -844,7 +846,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; @@ -857,10 +859,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) val[1] = readle(data); } if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { @@ -881,10 +880,10 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) if(fdCount > 1) { - auto fields_ = al::vector(fields.size()); - auto elevs_ = al::vector(elevs.size()); - auto coeffs_ = al::vector(coeffs.size()); - auto delays_ = al::vector(delays.size()); + auto fields_ = std::vector(fields.size()); + auto elevs_ = std::vector(elevs.size()); + auto coeffs_ = std::vector(coeffs.size()); + auto delays_ = std::vector(delays.size()); /* Simple reverse for the per-field elements. */ std::reverse_copy(fields.cbegin(), fields.cend(), fields_.begin()); @@ -893,16 +892,16 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) * count. Reverse the order of the groups, keeping the relative order * of per-group azimuth counts. */ - auto elevs__end = elevs_.end(); - auto copy_azs = [&elevs,&elevs__end](const ptrdiff_t ebase, const HrtfStore::Field &field) + auto elevs_end = elevs_.end(); + auto copy_azs = [&elevs,&elevs_end](const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { auto elevs_src = elevs.begin()+ebase; - elevs__end = std::copy_backward(elevs_src, elevs_src+field.evCount, elevs__end); + elevs_end = std::copy_backward(elevs_src, elevs_src+field.evCount, elevs_end); return ebase + field.evCount; }; - (void)std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_azs); - assert(elevs_.begin() == elevs__end); + std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_azs); + assert(elevs_.begin() == elevs_end); /* Reestablish the IR offset for each elevation index, given the new * ordering of elevations. @@ -922,12 +921,13 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) auto copy_irs = [&elevs,&coeffs,&delays,&coeffs_end,&delays_end]( const ptrdiff_t ebase, const HrtfStore::Field &field) -> ptrdiff_t { - auto accum_az = [](int count, const HrtfStore::Elevation &elev) noexcept -> int + auto accum_az = [](const ptrdiff_t count, const HrtfStore::Elevation &elev) noexcept + -> ptrdiff_t { return count + elev.azCount; }; - const auto elevs_mid = elevs.cbegin() + ebase; - const auto elevs_end = elevs_mid + field.evCount; - const int abase{std::accumulate(elevs.cbegin(), elevs_mid, 0, accum_az)}; - const int num_azs{std::accumulate(elevs_mid, elevs_end, 0, accum_az)}; + const auto elev_mid = elevs.cbegin() + ebase; + const auto abase = std::accumulate(elevs.cbegin(), elev_mid, ptrdiff_t{0}, accum_az); + const auto num_azs = std::accumulate(elev_mid, elev_mid + field.evCount, ptrdiff_t{0}, + accum_az); coeffs_end = std::copy_backward(coeffs.cbegin() + abase, coeffs.cbegin() + (abase+num_azs), coeffs_end); @@ -936,7 +936,7 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) return ebase + field.evCount; }; - (void)std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_irs); + std::ignore = std::accumulate(fields.cbegin(), fields.cend(), ptrdiff_t{0}, copy_irs); assert(coeffs_.begin() == coeffs_end); assert(delays_.begin() == delays_end); @@ -946,24 +946,20 @@ std::unique_ptr LoadHrtf02(std::istream &data, const char *filename) delays = std::move(delays_); } - return CreateHrtfStore(rate, irSize, {fields.data(), fields.size()}, - {elevs.data(), elevs.size()}, coeffs.data(), delays.data(), filename); + return CreateHrtfStore(rate, irSize, fields, elevs, coeffs.data(), delays.data()); } -std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) +std::unique_ptr LoadHrtf03(std::istream &data) { - constexpr ubyte ChanType_LeftOnly{0}; - constexpr ubyte ChanType_LeftRight{1}; + static constexpr ubyte ChanType_LeftOnly{0}; + static constexpr ubyte ChanType_LeftRight{1}; uint rate{readle(data)}; ubyte channelType{readle(data)}; uint8_t irSize{readle(data)}; ubyte fdCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(channelType > ChanType_LeftRight) { @@ -983,17 +979,14 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) return nullptr; } - auto fields = al::vector(fdCount); - auto elevs = al::vector{}; + auto fields = std::vector(fdCount); + auto elevs = std::vector{}; for(size_t f{0};f < fdCount;f++) { const ushort distance{readle(data)}; const ubyte evCount{readle(data)}; if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; if(distance < MinFdDistance || distance > MaxFdDistance) { @@ -1008,7 +1001,7 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) return nullptr; } - fields[f].distance = distance / 1000.0f; + fields[f].distance = float(distance) / 1000.0f; fields[f].evCount = evCount; if(f > 0 && fields[f].distance > fields[f-1].distance) { @@ -1019,13 +1012,10 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) const size_t ebase{elevs.size()}; elevs.resize(ebase + evCount); - for(auto &elev : al::span(elevs.data()+ebase, evCount)) + for(auto &elev : al::span{elevs}.subspan(ebase, evCount)) elev.azCount = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t e{0};e < evCount;e++) { @@ -1048,22 +1038,20 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) }); const auto irTotal = static_cast(elevs.back().azCount + elevs.back().irOffset); - auto coeffs = al::vector(irTotal, HrirArray{}); - auto delays = al::vector(irTotal); + auto coeffs = std::vector(irTotal, HrirArray{}); + auto delays = std::vector(irTotal); if(channelType == ChanType_LeftOnly) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) val[0] = static_cast(readle(data)) / 8388608.0f; } for(auto &val : delays) val[0] = readle(data); if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; + for(size_t i{0};i < irTotal;++i) { if(delays[i][0] > MaxHrirDelay< LoadHrtf03(std::istream &data, const char *filename) } /* Mirror the left ear responses to the right ear. */ - MirrorLeftHrirs({elevs.data(), elevs.size()}, coeffs.data(), delays.data()); + MirrorLeftHrirs(elevs, coeffs, delays); } else if(channelType == ChanType_LeftRight) { for(auto &hrir : coeffs) { - for(auto &val : al::span{hrir.data(), irSize}) + for(auto &val : al::span{hrir}.first(irSize)) { val[0] = static_cast(readle(data)) / 8388608.0f; val[1] = static_cast(readle(data)) / 8388608.0f; @@ -1093,10 +1081,7 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) val[1] = readle(data); } if(!data || data.eof()) - { - ERR("Failed reading %s\n", filename); - return nullptr; - } + throw std::runtime_error{"Premature end of file"}; for(size_t i{0};i < irTotal;++i) { @@ -1115,39 +1100,38 @@ std::unique_ptr LoadHrtf03(std::istream &data, const char *filename) } } - return CreateHrtfStore(rate, irSize, {fields.data(), fields.size()}, - {elevs.data(), elevs.size()}, coeffs.data(), delays.data(), filename); + return CreateHrtfStore(rate, irSize, fields, elevs, coeffs.data(), delays.data()); } -bool checkName(const std::string &name) +bool checkName(const std::string_view name) { - auto match_name = [&name](const HrtfEntry &entry) -> bool { return name == entry.mDispName; }; + auto match_name = [name](const HrtfEntry &entry) -> bool { return name == entry.mDispName; }; auto &enum_names = EnumeratedHrtfs; return std::find_if(enum_names.cbegin(), enum_names.cend(), match_name) != enum_names.cend(); } -void AddFileEntry(const std::string &filename) +void AddFileEntry(const std::string_view filename) { /* Check if this file has already been enumerated. */ auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), - [&filename](const HrtfEntry &entry) -> bool + [filename](const HrtfEntry &entry) -> bool { return entry.mFilename == filename; }); if(enum_iter != EnumeratedHrtfs.cend()) { - TRACE("Skipping duplicate file entry %s\n", filename.c_str()); + TRACE("Skipping duplicate file entry %.*s\n", al::sizei(filename), filename.data()); return; } /* TODO: Get a human-readable name from the HRTF data (possibly coming in a * format update). */ - size_t namepos{filename.find_last_of('/')+1}; - if(!namepos) namepos = filename.find_last_of('\\')+1; + size_t namepos{filename.rfind('/')+1}; + if(!namepos) namepos = filename.rfind('\\')+1; - size_t extpos{filename.find_last_of('.')}; + size_t extpos{filename.rfind('.')}; if(extpos <= namepos) extpos = std::string::npos; - const std::string basename{(extpos == std::string::npos) ? + const std::string_view basename{(extpos == std::string::npos) ? filename.substr(namepos) : filename.substr(namepos, extpos-namepos)}; std::string newname{basename}; int count{1}; @@ -1157,8 +1141,7 @@ void AddFileEntry(const std::string &filename) newname += " #"; newname += std::to_string(++count); } - EnumeratedHrtfs.emplace_back(HrtfEntry{newname, filename}); - const HrtfEntry &entry = EnumeratedHrtfs.back(); + const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(newname, filename); TRACE("Adding file entry \"%s\"\n", entry.mFilename.c_str()); } @@ -1166,9 +1149,10 @@ void AddFileEntry(const std::string &filename) /* Unfortunate that we have to duplicate AddFileEntry to take a memory buffer * for input instead of opening the given filename. */ -void AddBuiltInEntry(const std::string &dispname, uint residx) +void AddBuiltInEntry(const std::string_view dispname, uint residx) { - const std::string filename{'!'+std::to_string(residx)+'_'+dispname}; + std::string filename{'!'+std::to_string(residx)+'_'}; + filename += dispname; auto enum_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), [&filename](const HrtfEntry &entry) -> bool @@ -1190,8 +1174,7 @@ void AddBuiltInEntry(const std::string &dispname, uint residx) newname += " #"; newname += std::to_string(++count); } - EnumeratedHrtfs.emplace_back(HrtfEntry{newname, filename}); - const HrtfEntry &entry = EnumeratedHrtfs.back(); + const HrtfEntry &entry = EnumeratedHrtfs.emplace_back(std::move(newname), std::move(filename)); TRACE("Adding built-in entry \"%s\"\n", entry.mFilename.c_str()); } @@ -1206,6 +1189,7 @@ al::span GetResource(int /*name*/) #else +/* NOLINTNEXTLINE(*-avoid-c-arrays) */ constexpr unsigned char hrtf_default[]{ #include "default_hrtf.txt" }; @@ -1221,56 +1205,52 @@ al::span GetResource(int name) } // namespace -al::vector EnumerateHrtf(al::optional pathopt) +std::vector EnumerateHrtf(std::optional pathopt) { - std::lock_guard _{EnumeratedHrtfLock}; + std::lock_guard enumlock{EnumeratedHrtfLock}; EnumeratedHrtfs.clear(); bool usedefaults{true}; if(pathopt) { - const char *pathlist{pathopt->c_str()}; - while(pathlist && *pathlist) + std::string_view pathlist{*pathopt}; + while(!pathlist.empty()) { - const char *next, *end; + while(!pathlist.empty() && (std::isspace(pathlist.front()) || pathlist.front() == ',')) + pathlist.remove_prefix(1); + if(pathlist.empty()) + break; - while(isspace(*pathlist) || *pathlist == ',') - pathlist++; - if(*pathlist == '\0') - continue; - - next = strchr(pathlist, ','); - if(next) - end = next++; + auto endpos = std::min(pathlist.find(','), pathlist.size()); + auto entry = pathlist.substr(0, endpos); + if(endpos < pathlist.size()) + pathlist.remove_prefix(++endpos); else { - end = pathlist + strlen(pathlist); + pathlist.remove_prefix(endpos); usedefaults = false; } - while(end != pathlist && isspace(*(end-1))) - --end; - if(end != pathlist) + while(!entry.empty() && std::isspace(entry.back())) + entry.remove_suffix(1); + if(!entry.empty()) { - const std::string pname{pathlist, end}; - for(const auto &fname : SearchDataFiles(".mhr", pname.c_str())) + for(const auto &fname : SearchDataFiles(".mhr"sv, entry)) AddFileEntry(fname); } - - pathlist = next; } } if(usedefaults) { - for(const auto &fname : SearchDataFiles(".mhr", "openal/hrtf")) + for(const auto &fname : SearchDataFiles(".mhr"sv, "openal/hrtf"sv)) AddFileEntry(fname); if(!GetResource(IDR_DEFAULT_HRTF_MHR).empty()) AddBuiltInEntry("Built-In HRTF", IDR_DEFAULT_HRTF_MHR); } - al::vector list; + std::vector list; list.reserve(EnumeratedHrtfs.size()); for(auto &entry : EnumeratedHrtfs) list.emplace_back(entry.mDispName); @@ -1278,28 +1258,35 @@ al::vector EnumerateHrtf(al::optional pathopt) return list; } -HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) -{ - std::lock_guard _{EnumeratedHrtfLock}; +HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate) +try { + if(devrate > MaxSampleRate) + { + WARN("Device sample rate too large for HRTF (%uhz > %uhz)\n", devrate, MaxSampleRate); + return nullptr; + } + std::lock_guard enumlock{EnumeratedHrtfLock}; auto entry_iter = std::find_if(EnumeratedHrtfs.cbegin(), EnumeratedHrtfs.cend(), - [&name](const HrtfEntry &entry) -> bool { return entry.mDispName == name; }); + [name](const HrtfEntry &entry) -> bool { return entry.mDispName == name; }); if(entry_iter == EnumeratedHrtfs.cend()) return nullptr; const std::string &fname = entry_iter->mFilename; - std::lock_guard __{LoadedHrtfLock}; - auto hrtf_lt_fname = [](LoadedHrtf &hrtf, const std::string &filename) -> bool - { return hrtf.mFilename < filename; }; - auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, hrtf_lt_fname); - while(handle != LoadedHrtfs.end() && handle->mFilename == fname) + std::lock_guard loadlock{LoadedHrtfLock}; + auto hrtf_lt_fname = [devrate](LoadedHrtf &hrtf, const std::string_view filename) -> bool { - HrtfStore *hrtf{handle->mEntry.get()}; - if(hrtf && hrtf->mSampleRate == devrate) + return hrtf.mSampleRate < devrate + || (hrtf.mSampleRate == devrate && hrtf.mFilename < filename); + }; + auto handle = std::lower_bound(LoadedHrtfs.begin(), LoadedHrtfs.end(), fname, hrtf_lt_fname); + if(handle != LoadedHrtfs.end() && handle->mSampleRate == devrate && handle->mFilename == fname) + { + if(HrtfStore *hrtf{handle->mEntry.get()}) { + assert(hrtf->mSampleRate == devrate); hrtf->add_ref(); return HrtfStorePtr{hrtf}; } - ++handle; } std::unique_ptr stream; @@ -1311,15 +1298,17 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) al::span res{GetResource(residx)}; if(res.empty()) { - ERR("Could not get resource %u, %s\n", residx, name.c_str()); + ERR("Could not get resource %u, %.*s\n", residx, al::sizei(name), name.data()); return nullptr; } - stream = std::make_unique(res.begin(), res.end()); + /* NOLINTNEXTLINE(*-const-cast) */ + stream = std::make_unique(al::span{const_cast(res.data()), res.size()}); } else { TRACE("Loading %s...\n", fname.c_str()); - auto fstr = std::make_unique(fname.c_str(), std::ios::binary); + auto fstr = std::make_unique(std::filesystem::u8path(fname), + std::ios::binary); if(!fstr->is_open()) { ERR("Could not open %s\n", fname.c_str()); @@ -1329,63 +1318,62 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) } std::unique_ptr hrtf; - char magic[sizeof(magicMarker03)]; - stream->read(magic, sizeof(magic)); - if(stream->gcount() < static_cast(sizeof(magicMarker03))) - ERR("%s data is too short (%zu bytes)\n", name.c_str(), stream->gcount()); - else if(memcmp(magic, magicMarker03, sizeof(magicMarker03)) == 0) + std::array magic{}; + stream->read(magic.data(), magic.size()); + if(stream->gcount() < static_cast(GetMarker03Name().size())) + ERR("%.*s data is too short (%zu bytes)\n", al::sizei(name),name.data(), stream->gcount()); + else if(GetMarker03Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v3\n"); - hrtf = LoadHrtf03(*stream, name.c_str()); + hrtf = LoadHrtf03(*stream); } - else if(memcmp(magic, magicMarker02, sizeof(magicMarker02)) == 0) + else if(GetMarker02Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v2\n"); - hrtf = LoadHrtf02(*stream, name.c_str()); + hrtf = LoadHrtf02(*stream); } - else if(memcmp(magic, magicMarker01, sizeof(magicMarker01)) == 0) + else if(GetMarker01Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v1\n"); - hrtf = LoadHrtf01(*stream, name.c_str()); + hrtf = LoadHrtf01(*stream); } - else if(memcmp(magic, magicMarker00, sizeof(magicMarker00)) == 0) + else if(GetMarker00Name() == std::string_view{magic.data(), magic.size()}) { TRACE("Detected data set format v0\n"); - hrtf = LoadHrtf00(*stream, name.c_str()); + hrtf = LoadHrtf00(*stream); } else - ERR("Invalid header in %s: \"%.8s\"\n", name.c_str(), magic); + ERR("Invalid header in %.*s: \"%.8s\"\n", al::sizei(name), name.data(), magic.data()); stream.reset(); if(!hrtf) - { - ERR("Failed to load %s\n", name.c_str()); return nullptr; - } if(hrtf->mSampleRate != devrate) { - TRACE("Resampling HRTF %s (%uhz -> %uhz)\n", name.c_str(), hrtf->mSampleRate, devrate); + TRACE("Resampling HRTF %.*s (%uhz -> %uhz)\n", al::sizei(name), name.data(), + hrtf->mSampleRate, devrate); /* Calculate the last elevation's index and get the total IR count. */ - const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), size_t{0}, + const size_t lastEv{std::accumulate(hrtf->mFields.begin(), hrtf->mFields.end(), 0_uz, [](const size_t curval, const HrtfStore::Field &field) noexcept -> size_t { return curval + field.evCount; } ) - 1}; const size_t irCount{size_t{hrtf->mElev[lastEv].irOffset} + hrtf->mElev[lastEv].azCount}; /* Resample all the IRs. */ - std::array,2> inout; + std::array,2> inout{}; PPhaseResampler rs; rs.init(hrtf->mSampleRate, devrate); for(size_t i{0};i < irCount;++i) { - HrirArray &coeffs = const_cast(hrtf->mCoeffs[i]); + /* NOLINTNEXTLINE(*-const-cast) */ + auto coeffs = al::span{const_cast(hrtf->mCoeffs[i])}; for(size_t j{0};j < 2;++j) { std::transform(coeffs.cbegin(), coeffs.cend(), inout[0].begin(), [j](const float2 &in) noexcept -> double { return in[j]; }); - rs.process(HrirLength, inout[0].data(), HrirLength, inout[1].data()); + rs.process(inout[0], inout[1]); for(size_t k{0};k < HrirLength;++k) coeffs[k][j] = static_cast(inout[1][k]); } @@ -1394,15 +1382,15 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) /* Scale the delays for the new sample rate. */ float max_delay{0.0f}; - auto new_delays = al::vector(irCount); + auto new_delays = std::vector(irCount); const float rate_scale{static_cast(devrate)/static_cast(hrtf->mSampleRate)}; for(size_t i{0};i < irCount;++i) { for(size_t j{0};j < 2;++j) { - const float new_delay{std::round(hrtf->mDelays[i][j] * rate_scale) / + const float new_delay{std::round(float(hrtf->mDelays[i][j]) * rate_scale) / float{HrirDelayFracOne}}; - max_delay = maxf(max_delay, new_delay); + max_delay = std::max(max_delay, new_delay); new_delays[i][j] = new_delay; } } @@ -1420,25 +1408,31 @@ HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate) for(size_t i{0};i < irCount;++i) { - ubyte2 &delays = const_cast(hrtf->mDelays[i]); - for(size_t j{0};j < 2;++j) - delays[j] = static_cast(float2int(new_delays[i][j]*delay_scale + 0.5f)); + /* NOLINTNEXTLINE(*-const-cast) */ + auto delays = al::span{const_cast(hrtf->mDelays[i])}; + std::transform(new_delays[i].cbegin(), new_delays[i].cend(), delays.begin(), + [delay_scale](const float delay) + { return static_cast(float2int(delay*delay_scale + 0.5f)); }); } /* Scale the IR size for the new sample rate and update the stored * sample rate. */ const float newIrSize{std::round(static_cast(hrtf->mIrSize) * rate_scale)}; - hrtf->mIrSize = static_cast(minf(HrirLength, newIrSize)); - hrtf->mSampleRate = devrate; + hrtf->mIrSize = static_cast(std::min(float{HrirLength}, newIrSize)); + hrtf->mSampleRate = devrate & 0xff'ff'ff; } - TRACE("Loaded HRTF %s for sample rate %uhz, %u-sample filter\n", name.c_str(), - hrtf->mSampleRate, hrtf->mIrSize); - handle = LoadedHrtfs.emplace(handle, fname, std::move(hrtf)); + handle = LoadedHrtfs.emplace(handle, fname, devrate, std::move(hrtf)); + TRACE("Loaded HRTF %.*s for sample rate %uhz, %u-sample filter\n", al::sizei(name),name.data(), + handle->mEntry->mSampleRate, handle->mEntry->mIrSize); return HrtfStorePtr{handle->mEntry.get()}; } +catch(std::exception& e) { + ERR("Failed to load %.*s: %s\n", al::sizei(name), name.data(), e.what()); + return nullptr; +} void HrtfStore::add_ref() @@ -1453,15 +1447,15 @@ void HrtfStore::dec_ref() TRACE("HrtfStore %p decreasing refcount to %u\n", decltype(std::declval()){this}, ref); if(ref == 0) { - std::lock_guard _{LoadedHrtfLock}; + std::lock_guard loadlock{LoadedHrtfLock}; /* Go through and remove all unused HRTFs. */ auto remove_unused = [](LoadedHrtf &hrtf) -> bool { HrtfStore *entry{hrtf.mEntry.get()}; - if(entry && ReadRef(entry->mRef) == 0) + if(entry && entry->mRef.load() == 0) { - TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.data()); + TRACE("Unloading unused HRTF %s\n", hrtf.mFilename.c_str()); hrtf.mEntry = nullptr; return true; } diff --git a/Engine/lib/openal-soft/core/hrtf.h b/Engine/lib/openal-soft/core/hrtf.h index eb18682a5..e93fddaab 100644 --- a/Engine/lib/openal-soft/core/hrtf.h +++ b/Engine/lib/openal-soft/core/hrtf.h @@ -4,21 +4,23 @@ #include #include #include +#include #include +#include +#include #include "almalloc.h" -#include "aloptional.h" #include "alspan.h" #include "atomic.h" #include "ambidefs.h" #include "bufferline.h" -#include "mixer/hrtfdefs.h" +#include "flexarray.h" #include "intrusive_ptr.h" -#include "vector.h" +#include "mixer/hrtfdefs.h" -struct HrtfStore { - RefCount mRef; +struct alignas(16) HrtfStore { + std::atomic mRef; uint mSampleRate : 24; uint mIrSize : 8; @@ -36,17 +38,24 @@ struct HrtfStore { ushort azCount; ushort irOffset; }; - Elevation *mElev; - const HrirArray *mCoeffs; - const ubyte2 *mDelays; + al::span mElev; + al::span mCoeffs; + al::span mDelays; - void getCoeffs(float elevation, float azimuth, float distance, float spread, HrirArray &coeffs, - const al::span delays); + void getCoeffs(float elevation, float azimuth, float distance, float spread, + const HrirSpan coeffs, const al::span delays) const; void add_ref(); void dec_ref(); - DEF_PLACE_NEWDEL() + void *operator new(size_t) = delete; + void *operator new[](size_t) = delete; + void operator delete[](void*) noexcept = delete; + + void operator delete(gsl::owner block, void*) noexcept + { ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); } + void operator delete(gsl::owner block) noexcept + { ::operator delete[](block, std::align_val_t{alignof(HrtfStore)}); } }; using HrtfStorePtr = al::intrusive_ptr; @@ -60,7 +69,7 @@ struct AngularPoint { struct DirectHrtfState { - std::array mTemp; + std::array mTemp{}; /* HRTF filter state for dry buffer content */ uint mIrSize{0}; @@ -74,7 +83,8 @@ struct DirectHrtfState { * are ordered and scaled according to the matrix input. */ void build(const HrtfStore *Hrtf, const uint irSize, const bool perHrirMin, - const al::span AmbiPoints, const float (*AmbiMatrix)[MaxAmbiChannels], + const al::span AmbiPoints, + const al::span> AmbiMatrix, const float XOverFreq, const al::span AmbiOrderHFGain); static std::unique_ptr Create(size_t num_chans); @@ -83,7 +93,7 @@ struct DirectHrtfState { }; -al::vector EnumerateHrtf(al::optional pathopt); -HrtfStorePtr GetLoadedHrtf(const std::string &name, const uint devrate); +std::vector EnumerateHrtf(std::optional pathopt); +HrtfStorePtr GetLoadedHrtf(const std::string_view name, const uint devrate); #endif /* CORE_HRTF_H */ diff --git a/Engine/lib/openal-soft/core/logging.cpp b/Engine/lib/openal-soft/core/logging.cpp index 34a95e5ad..c0ff45c02 100644 --- a/Engine/lib/openal-soft/core/logging.cpp +++ b/Engine/lib/openal-soft/core/logging.cpp @@ -3,13 +3,19 @@ #include "logging.h" +#include +#include #include #include +#include +#include +#include #include +#include #include "alspan.h" +#include "opthelpers.h" #include "strutils.h" -#include "vector.h" #if defined(_WIN32) @@ -19,47 +25,108 @@ #include #endif -void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) + +FILE *gLogFile{stderr}; +#ifdef _DEBUG +LogLevel gLogLevel{LogLevel::Warning}; +#else +LogLevel gLogLevel{LogLevel::Error}; +#endif + + +namespace { + +enum class LogState : uint8_t { + FirstRun, + Ready, + Disable +}; + +std::mutex LogCallbackMutex; +LogState gLogState{LogState::FirstRun}; + +LogCallbackFunc gLogCallback{}; +void *gLogCallbackPtr{}; + +constexpr auto GetLevelCode(LogLevel level) noexcept -> std::optional { + switch(level) + { + case LogLevel::Disable: break; + case LogLevel::Error: return 'E'; + case LogLevel::Warning: return 'W'; + case LogLevel::Trace: return 'I'; + } + return std::nullopt; +} + +} // namespace + +void al_set_log_callback(LogCallbackFunc callback, void *userptr) +{ + auto cblock = std::lock_guard{LogCallbackMutex}; + gLogCallback = callback; + gLogCallbackPtr = callback ? userptr : nullptr; + if(gLogState == LogState::FirstRun) + { + auto extlogopt = al::getenv("ALSOFT_DISABLE_LOG_CALLBACK"); + if(!extlogopt || *extlogopt != "1") + gLogState = LogState::Ready; + else + gLogState = LogState::Disable; + } +} + +void al_print(LogLevel level, const char *fmt, ...) noexcept +try { /* Kind of ugly since string literals are const char arrays with a size * that includes the null terminator, which we want to exclude from the * span. */ - auto prefix = al::as_span("[ALSOFT] (--) ").first<14>(); + auto prefix = al::span{"[ALSOFT] (--) "}.first<14>(); switch(level) { case LogLevel::Disable: break; - case LogLevel::Error: prefix = al::as_span("[ALSOFT] (EE) ").first<14>(); break; - case LogLevel::Warning: prefix = al::as_span("[ALSOFT] (WW) ").first<14>(); break; - case LogLevel::Trace: prefix = al::as_span("[ALSOFT] (II) ").first<14>(); break; + case LogLevel::Error: prefix = al::span{"[ALSOFT] (EE) "}.first<14>(); break; + case LogLevel::Warning: prefix = al::span{"[ALSOFT] (WW) "}.first<14>(); break; + case LogLevel::Trace: prefix = al::span{"[ALSOFT] (II) "}.first<14>(); break; } - al::vector dynmsg; + std::vector dynmsg; std::array stcmsg{}; char *str{stcmsg.data()}; auto prefend1 = std::copy_n(prefix.begin(), prefix.size(), stcmsg.begin()); al::span msg{prefend1, stcmsg.end()}; + /* NOLINTBEGIN(*-array-to-pointer-decay) */ std::va_list args, args2; va_start(args, fmt); va_copy(args2, args); const int msglen{std::vsnprintf(msg.data(), msg.size(), fmt, args)}; - if(msglen >= 0 && static_cast(msglen) >= msg.size()) UNLIKELY + if(msglen >= 0) { - dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); + if(static_cast(msglen) >= msg.size()) UNLIKELY + { + dynmsg.resize(static_cast(msglen)+prefix.size() + 1u); - str = dynmsg.data(); - auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); - msg = {prefend2, dynmsg.end()}; + str = dynmsg.data(); + auto prefend2 = std::copy_n(prefix.begin(), prefix.size(), dynmsg.begin()); + msg = {prefend2, dynmsg.end()}; - std::vsnprintf(msg.data(), msg.size(), fmt, args2); + std::vsnprintf(msg.data(), msg.size(), fmt, args2); + } + msg = msg.first(static_cast(msglen)); } + else + msg = {msg.data(), std::strlen(msg.data())}; va_end(args2); va_end(args); + /* NOLINTEND(*-array-to-pointer-decay) */ if(gLogLevel >= level) { + auto logfile = gLogFile; fputs(str, logfile); fflush(logfile); } @@ -86,4 +153,24 @@ void al_print(LogLevel level, FILE *logfile, const char *fmt, ...) }; __android_log_print(android_severity(level), "openal", "%s", str); #endif + + auto cblock = std::lock_guard{LogCallbackMutex}; + if(gLogState != LogState::Disable) + { + while(!msg.empty() && std::isspace(msg.back())) + { + msg.back() = '\0'; + msg = msg.first(msg.size()-1); + } + if(auto logcode = GetLevelCode(level); logcode && !msg.empty()) + { + if(gLogCallback) + gLogCallback(gLogCallbackPtr, *logcode, msg.data(), static_cast(msg.size())); + else if(gLogState == LogState::FirstRun) + gLogState = LogState::Disable; + } + } +} +catch(...) { + /* Swallow any exceptions */ } diff --git a/Engine/lib/openal-soft/core/logging.h b/Engine/lib/openal-soft/core/logging.h index f4b6ab562..527e79540 100644 --- a/Engine/lib/openal-soft/core/logging.h +++ b/Engine/lib/openal-soft/core/logging.h @@ -1,9 +1,7 @@ #ifndef CORE_LOGGING_H #define CORE_LOGGING_H -#include - -#include "opthelpers.h" +#include enum class LogLevel { @@ -16,36 +14,23 @@ extern LogLevel gLogLevel; extern FILE *gLogFile; -#ifdef __USE_MINGW_ANSI_STDIO -[[gnu::format(gnu_printf,3,4)]] + +using LogCallbackFunc = void(*)(void *userptr, char level, const char *message, int length) noexcept; + +void al_set_log_callback(LogCallbackFunc callback, void *userptr); + + +#ifdef __MINGW32__ +[[gnu::format(__MINGW_PRINTF_FORMAT,2,3)]] #else -[[gnu::format(printf,3,4)]] +[[gnu::format(printf,2,3)]] #endif -void al_print(LogLevel level, FILE *logfile, const char *fmt, ...); +void al_print(LogLevel level, const char *fmt, ...) noexcept; -#if (!defined(_WIN32) || defined(NDEBUG)) && !defined(__ANDROID__) -#define TRACE(...) do { \ - if(gLogLevel >= LogLevel::Trace) UNLIKELY \ - al_print(LogLevel::Trace, gLogFile, __VA_ARGS__); \ -} while(0) +#define TRACE(...) al_print(LogLevel::Trace, __VA_ARGS__) -#define WARN(...) do { \ - if(gLogLevel >= LogLevel::Warning) UNLIKELY \ - al_print(LogLevel::Warning, gLogFile, __VA_ARGS__); \ -} while(0) +#define WARN(...) al_print(LogLevel::Warning, __VA_ARGS__) -#define ERR(...) do { \ - if(gLogLevel >= LogLevel::Error) UNLIKELY \ - al_print(LogLevel::Error, gLogFile, __VA_ARGS__); \ -} while(0) - -#else - -#define TRACE(...) al_print(LogLevel::Trace, gLogFile, __VA_ARGS__) - -#define WARN(...) al_print(LogLevel::Warning, gLogFile, __VA_ARGS__) - -#define ERR(...) al_print(LogLevel::Error, gLogFile, __VA_ARGS__) -#endif +#define ERR(...) al_print(LogLevel::Error, __VA_ARGS__) #endif /* CORE_LOGGING_H */ diff --git a/Engine/lib/openal-soft/core/mastering.cpp b/Engine/lib/openal-soft/core/mastering.cpp index 97a4008e1..069a21cea 100644 --- a/Engine/lib/openal-soft/core/mastering.cpp +++ b/Engine/lib/openal-soft/core/mastering.cpp @@ -11,7 +11,6 @@ #include #include -#include "almalloc.h" #include "alnumeric.h" #include "alspan.h" #include "opthelpers.h" @@ -21,8 +20,8 @@ static_assert((BufferLineSize & (BufferLineSize-1)) == 0, "BufferLineSize is not a power of 2"); struct SlidingHold { - alignas(16) float mValues[BufferLineSize]; - uint mExpiries[BufferLineSize]; + alignas(16) FloatBufferLine mValues; + std::array mExpiries; uint mLowerIndex; uint mUpperIndex; uint mLength; @@ -31,7 +30,9 @@ struct SlidingHold { namespace { -using namespace std::placeholders; +template +constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span +{ return al::span{al::assume_aligned(s.data()), s.size()}; } /* This sliding hold follows the input level with an instant attack and a * fixed duration hold before an instant release to the next highest level. @@ -44,8 +45,8 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) { static constexpr uint mask{BufferLineSize - 1}; const uint length{Hold->mLength}; - float (&values)[BufferLineSize] = Hold->mValues; - uint (&expiries)[BufferLineSize] = Hold->mExpiries; + const al::span values{Hold->mValues}; + const al::span expiries{Hold->mExpiries}; uint lowerIndex{Hold->mLowerIndex}; uint upperIndex{Hold->mUpperIndex}; @@ -60,14 +61,16 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) } else { - do { + auto findLowerIndex = [&lowerIndex,in,values]() noexcept -> bool + { do { if(!(in >= values[lowerIndex])) - goto found_place; + return true; } while(lowerIndex--); + return false; + }; + while(!findLowerIndex()) lowerIndex = mask; - } while(true); - found_place: lowerIndex = (lowerIndex + 1) & mask; values[lowerIndex] = in; @@ -82,38 +85,41 @@ float UpdateSlidingHold(SlidingHold *Hold, const uint i, const float in) void ShiftSlidingHold(SlidingHold *Hold, const uint n) { - auto exp_begin = std::begin(Hold->mExpiries) + Hold->mUpperIndex; - auto exp_last = std::begin(Hold->mExpiries) + Hold->mLowerIndex; - if(exp_last-exp_begin < 0) + auto exp_upper = Hold->mExpiries.begin() + Hold->mUpperIndex; + if(Hold->mLowerIndex < Hold->mUpperIndex) { - std::transform(exp_begin, std::end(Hold->mExpiries), exp_begin, - [n](uint e){ return e - n; }); - exp_begin = std::begin(Hold->mExpiries); + std::transform(exp_upper, Hold->mExpiries.end(), exp_upper, + [n](const uint e) noexcept { return e - n; }); + exp_upper = Hold->mExpiries.begin(); } - std::transform(exp_begin, exp_last+1, exp_begin, [n](uint e){ return e - n; }); + const auto exp_lower = Hold->mExpiries.begin() + Hold->mLowerIndex; + std::transform(exp_upper, exp_lower+1, exp_upper, + [n](const uint e) noexcept { return e - n; }); } +} // namespace /* Multichannel compression is linked via the absolute maximum of all * channels. */ -void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLine *OutBuffer) +void Compressor::linkChannels(const uint SamplesToDo, + const al::span OutBuffer) { - const size_t numChans{Comp->mNumChans}; - ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); + ASSUME(SamplesToDo <= BufferLineSize); - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::fill(side_begin, side_begin+SamplesToDo, 0.0f); + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::fill_n(sideChain.begin(), sideChain.size(), 0.0f); - auto fill_max = [SamplesToDo,side_begin](const FloatBufferLine &input) -> void + auto fill_max = [sideChain](const FloatBufferLine &input) -> void { - const float *RESTRICT buffer{al::assume_aligned<16>(input.data())}; - auto max_abs = std::bind(maxf, _1, std::bind(static_cast(std::fabs), _2)); - std::transform(side_begin, side_begin+SamplesToDo, buffer, side_begin, max_abs); + const auto buffer = assume_aligned_span<16>(al::span{input}); + auto max_abs = [](const float s0, const float s1) noexcept -> float + { return std::max(s0, std::fabs(s1)); }; + std::transform(sideChain.begin(), sideChain.end(), buffer.begin(), sideChain.begin(), + max_abs); }; - std::for_each(OutBuffer, OutBuffer+numChans, fill_max); + std::for_each(OutBuffer.begin(), OutBuffer.end(), fill_max); } /* This calculates the squared crest factor of the control signal for the @@ -121,60 +127,63 @@ void LinkChannels(Compressor *Comp, const uint SamplesToDo, const FloatBufferLin * it uses an instantaneous squared peak detector and a squared RMS detector * both with 200ms release times. */ -void CrestDetector(Compressor *Comp, const uint SamplesToDo) +void Compressor::crestDetector(const uint SamplesToDo) { - const float a_crest{Comp->mCrestCoeff}; - float y2_peak{Comp->mLastPeakSq}; - float y2_rms{Comp->mLastRmsSq}; + const float a_crest{mCrestCoeff}; + float y2_peak{mLastPeakSq}; + float y2_rms{mLastRmsSq}; ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); auto calc_crest = [&y2_rms,&y2_peak,a_crest](const float x_abs) noexcept -> float { - const float x2{clampf(x_abs * x_abs, 0.000001f, 1000000.0f)}; + const float x2{std::clamp(x_abs*x_abs, 0.000001f, 1000000.0f)}; - y2_peak = maxf(x2, lerpf(x2, y2_peak, a_crest)); + y2_peak = std::max(x2, lerpf(x2, y2_peak, a_crest)); y2_rms = lerpf(x2, y2_rms, a_crest); return y2_peak / y2_rms; }; - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, std::begin(Comp->mCrestFactor), calc_crest); + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), mCrestFactor.begin(), calc_crest); - Comp->mLastPeakSq = y2_peak; - Comp->mLastRmsSq = y2_rms; + mLastPeakSq = y2_peak; + mLastRmsSq = y2_rms; } /* The side-chain starts with a simple peak detector (based on the absolute * value of the incoming signal) and performs most of its operations in the * log domain. */ -void PeakDetector(Compressor *Comp, const uint SamplesToDo) +void Compressor::peakDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - /* Clamp the minimum amplitude to near-zero and convert to logarithm. */ - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, side_begin, - [](float s) { return std::log(maxf(0.000001f, s)); }); + /* Clamp the minimum amplitude to near-zero and convert to logarithmic. */ + const auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), + [](float s) { return std::log(std::max(0.000001f, s)); }); } /* An optional hold can be used to extend the peak detector so it can more * solidly detect fast transients. This is best used when operating as a * limiter. */ -void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo) +void Compressor::peakHoldDetector(const uint SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - SlidingHold *hold{Comp->mHold}; + SlidingHold *hold{mHold.get()}; uint i{0}; auto detect_peak = [&i,hold](const float x_abs) -> float { - const float x_G{std::log(maxf(0.000001f, x_abs))}; + const float x_G{std::log(std::max(0.000001f, x_abs))}; return UpdateSlidingHold(hold, i++, x_G); }; - auto side_begin = std::begin(Comp->mSideChain) + Comp->mLookAhead; - std::transform(side_begin, side_begin+SamplesToDo, side_begin, detect_peak); + auto sideChain = al::span{mSideChain}.subspan(mLookAhead, SamplesToDo); + std::transform(sideChain.cbegin(), sideChain.cend(), sideChain.begin(), detect_peak); ShiftSlidingHold(hold, SamplesToDo); } @@ -184,46 +193,46 @@ void PeakHoldDetector(Compressor *Comp, const uint SamplesToDo) * to knee width, attack/release times, make-up/post gain, and clipping * reduction. */ -void GainCompressor(Compressor *Comp, const uint SamplesToDo) +void Compressor::gainCompressor(const uint SamplesToDo) { - const bool autoKnee{Comp->mAuto.Knee}; - const bool autoAttack{Comp->mAuto.Attack}; - const bool autoRelease{Comp->mAuto.Release}; - const bool autoPostGain{Comp->mAuto.PostGain}; - const bool autoDeclip{Comp->mAuto.Declip}; - const uint lookAhead{Comp->mLookAhead}; - const float threshold{Comp->mThreshold}; - const float slope{Comp->mSlope}; - const float attack{Comp->mAttack}; - const float release{Comp->mRelease}; - const float c_est{Comp->mGainEstimate}; - const float a_adp{Comp->mAdaptCoeff}; - const float *crestFactor{Comp->mCrestFactor}; - float postGain{Comp->mPostGain}; - float knee{Comp->mKnee}; + const bool autoKnee{mAuto.Knee}; + const bool autoAttack{mAuto.Attack}; + const bool autoRelease{mAuto.Release}; + const bool autoPostGain{mAuto.PostGain}; + const bool autoDeclip{mAuto.Declip}; + const float threshold{mThreshold}; + const float slope{mSlope}; + const float attack{mAttack}; + const float release{mRelease}; + const float c_est{mGainEstimate}; + const float a_adp{mAdaptCoeff}; + auto lookAhead = mSideChain.cbegin() + mLookAhead; + auto crestFactor = mCrestFactor.cbegin(); + float postGain{mPostGain}; + float knee{mKnee}; float t_att{attack}; float t_rel{release - attack}; float a_att{std::exp(-1.0f / t_att)}; float a_rel{std::exp(-1.0f / t_rel)}; - float y_1{Comp->mLastRelease}; - float y_L{Comp->mLastAttack}; - float c_dev{Comp->mLastGainDev}; + float y_1{mLastRelease}; + float y_L{mLastAttack}; + float c_dev{mLastGainDev}; ASSUME(SamplesToDo > 0); - for(float &sideChain : al::span{Comp->mSideChain, SamplesToDo}) + auto process = [&](const float input) -> float { if(autoKnee) - knee = maxf(0.0f, 2.5f * (c_dev + c_est)); + knee = std::max(0.0f, 2.5f * (c_dev + c_est)); const float knee_h{0.5f * knee}; /* This is the gain computer. It applies a static compression curve * to the control signal. */ - const float x_over{std::addressof(sideChain)[lookAhead] - threshold}; + const float x_over{*(lookAhead++) - threshold}; const float y_G{ (x_over <= -knee_h) ? 0.0f : - (std::fabs(x_over) < knee_h) ? (x_over + knee_h) * (x_over + knee_h) / (2.0f * knee) : + (std::fabs(x_over) < knee_h) ? (x_over+knee_h) * (x_over+knee_h) / (2.0f * knee) : x_over}; const float y2_crest{*(crestFactor++)}; @@ -243,7 +252,7 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * above to compensate for the chained operating mode. */ const float x_L{-slope * y_G}; - y_1 = maxf(x_L, lerpf(x_L, y_1, a_rel)); + y_1 = std::max(x_L, lerpf(x_L, y_1, a_rel)); y_L = lerpf(y_1, y_L, a_att); /* Knee width and make-up gain automation make use of a smoothed @@ -262,17 +271,19 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * same output level. */ if(autoDeclip) - c_dev = maxf(c_dev, sideChain - y_L - threshold - c_est); + c_dev = std::max(c_dev, input - y_L - threshold - c_est); postGain = -(c_dev + c_est); } - sideChain = std::exp(postGain - y_L); - } + return std::exp(postGain - y_L); + }; + auto sideChain = al::span{mSideChain}.first(SamplesToDo); + std::transform(sideChain.begin(), sideChain.end(), sideChain.begin(), process); - Comp->mLastRelease = y_1; - Comp->mLastAttack = y_L; - Comp->mLastGainDev = c_dev; + mLastRelease = y_1; + mLastAttack = y_L; + mLastGainDev = c_dev; } /* Combined with the hold time, a look-ahead delay can improve handling of @@ -280,36 +291,35 @@ void GainCompressor(Compressor *Comp, const uint SamplesToDo) * reaching the offending impulse. This is best used when operating as a * limiter. */ -void SignalDelay(Compressor *Comp, const uint SamplesToDo, FloatBufferLine *OutBuffer) +void Compressor::signalDelay(const uint SamplesToDo, const al::span OutBuffer) { - const size_t numChans{Comp->mNumChans}; - const uint lookAhead{Comp->mLookAhead}; + const auto lookAhead = mLookAhead; ASSUME(SamplesToDo > 0); - ASSUME(numChans > 0); + ASSUME(SamplesToDo <= BufferLineSize); ASSUME(lookAhead > 0); + ASSUME(lookAhead < BufferLineSize); - for(size_t c{0};c < numChans;c++) + auto delays = mDelay.begin(); + for(auto &buffer : OutBuffer) { - float *inout{al::assume_aligned<16>(OutBuffer[c].data())}; - float *delaybuf{al::assume_aligned<16>(Comp->mDelay[c].data())}; + const auto inout = al::span{buffer}.first(SamplesToDo); + const auto delaybuf = al::span{*(delays++)}.first(lookAhead); - auto inout_end = inout + SamplesToDo; - if(SamplesToDo >= lookAhead) LIKELY + if(SamplesToDo >= delaybuf.size()) LIKELY { - auto delay_end = std::rotate(inout, inout_end - lookAhead, inout_end); - std::swap_ranges(inout, delay_end, delaybuf); + const auto inout_start = inout.end() - ptrdiff_t(delaybuf.size()); + const auto delay_end = std::rotate(inout.begin(), inout_start, inout.end()); + std::swap_ranges(inout.begin(), delay_end, delaybuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, delaybuf); - std::rotate(delaybuf, delay_start, delaybuf + lookAhead); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), delaybuf.begin()); + std::rotate(delaybuf.begin(), delay_start, delaybuf.end()); } } } -} // namespace - std::unique_ptr Compressor::Create(const size_t NumChans, const float SampleRate, const bool AutoKnee, const bool AutoAttack, const bool AutoRelease, const bool AutoPostGain, @@ -317,24 +327,12 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa const float PostGainDb, const float ThresholdDb, const float Ratio, const float KneeDb, const float AttackTime, const float ReleaseTime) { - const auto lookAhead = static_cast( - clampf(std::round(LookAheadTime*SampleRate), 0.0f, BufferLineSize-1)); - const auto hold = static_cast( - clampf(std::round(HoldTime*SampleRate), 0.0f, BufferLineSize-1)); + const auto lookAhead = static_cast(std::clamp(std::round(LookAheadTime*SampleRate), 0.0f, + BufferLineSize-1.0f)); + const auto hold = static_cast(std::clamp(std::round(HoldTime*SampleRate), 0.0f, + BufferLineSize-1.0f)); - size_t size{sizeof(Compressor)}; - if(lookAhead > 0) - { - size += sizeof(*Compressor::mDelay) * NumChans; - /* The sliding hold implementation doesn't handle a length of 1. A 1- - * sample hold is useless anyway, it would only ever give back what was - * just given to it. - */ - if(hold > 1) - size += sizeof(*Compressor::mHold); - } - - auto Comp = CompressorPtr{al::construct_at(static_cast(al_calloc(16, size)))}; + auto Comp = CompressorPtr{new Compressor{}}; Comp->mNumChans = NumChans; Comp->mAuto.Knee = AutoKnee; Comp->mAuto.Attack = AutoAttack; @@ -343,12 +341,12 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa Comp->mAuto.Declip = AutoPostGain && AutoDeclip; Comp->mLookAhead = lookAhead; Comp->mPreGain = std::pow(10.0f, PreGainDb / 20.0f); - Comp->mPostGain = PostGainDb * std::log(10.0f) / 20.0f; - Comp->mThreshold = ThresholdDb * std::log(10.0f) / 20.0f; - Comp->mSlope = 1.0f / maxf(1.0f, Ratio) - 1.0f; - Comp->mKnee = maxf(0.0f, KneeDb * std::log(10.0f) / 20.0f); - Comp->mAttack = maxf(1.0f, AttackTime * SampleRate); - Comp->mRelease = maxf(1.0f, ReleaseTime * SampleRate); + Comp->mPostGain = std::log(10.0f)/20.0f * PostGainDb; + Comp->mThreshold = std::log(10.0f)/20.0f * ThresholdDb; + Comp->mSlope = 1.0f / std::max(1.0f, Ratio) - 1.0f; + Comp->mKnee = std::max(0.0f, std::log(10.0f)/20.0f * KneeDb); + Comp->mAttack = std::max(1.0f, AttackTime * SampleRate); + Comp->mRelease = std::max(1.0f, ReleaseTime * SampleRate); /* Knee width automation actually treats the compressor as a limiter. By * varying the knee width, it can effectively be seen as applying @@ -359,17 +357,18 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa if(lookAhead > 0) { + /* The sliding hold implementation doesn't handle a length of 1. A 1- + * sample hold is useless anyway, it would only ever give back what was + * just given to it. + */ if(hold > 1) { - Comp->mHold = al::construct_at(reinterpret_cast(Comp.get() + 1)); + Comp->mHold = std::make_unique(); Comp->mHold->mValues[0] = -std::numeric_limits::infinity(); Comp->mHold->mExpiries[0] = hold; Comp->mHold->mLength = hold; - Comp->mDelay = reinterpret_cast(Comp->mHold + 1); } - else - Comp->mDelay = reinterpret_cast(Comp.get() + 1); - std::uninitialized_fill_n(Comp->mDelay, NumChans, FloatBufferLine{}); + Comp->mDelay.resize(NumChans, FloatBufferLine{}); } Comp->mCrestCoeff = std::exp(-1.0f / (0.200f * SampleRate)); // 200ms @@ -379,15 +378,7 @@ std::unique_ptr Compressor::Create(const size_t NumChans, const floa return Comp; } -Compressor::~Compressor() -{ - if(mHold) - al::destroy_at(mHold); - mHold = nullptr; - if(mDelay) - al::destroy_n(mDelay, mNumChans); - mDelay = nullptr; -} +Compressor::~Compressor() = default; void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) @@ -395,45 +386,46 @@ void Compressor::process(const uint SamplesToDo, FloatBufferLine *OutBuffer) const size_t numChans{mNumChans}; ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); ASSUME(numChans > 0); + const auto output = al::span{OutBuffer, numChans}; const float preGain{mPreGain}; if(preGain != 1.0f) { auto apply_gain = [SamplesToDo,preGain](FloatBufferLine &input) noexcept -> void { - float *buffer{al::assume_aligned<16>(input.data())}; - std::transform(buffer, buffer+SamplesToDo, buffer, - [preGain](float s) { return s * preGain; }); + const auto buffer = assume_aligned_span<16>(al::span{input}.first(SamplesToDo)); + std::transform(buffer.cbegin(), buffer.cend(), buffer.begin(), + [preGain](const float s) noexcept { return s * preGain; }); }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_gain); + std::for_each(output.begin(), output.end(), apply_gain); } - LinkChannels(this, SamplesToDo, OutBuffer); + linkChannels(SamplesToDo, output); if(mAuto.Attack || mAuto.Release) - CrestDetector(this, SamplesToDo); + crestDetector(SamplesToDo); if(mHold) - PeakHoldDetector(this, SamplesToDo); + peakHoldDetector(SamplesToDo); else - PeakDetector(this, SamplesToDo); + peakDetector(SamplesToDo); - GainCompressor(this, SamplesToDo); + gainCompressor(SamplesToDo); - if(mDelay) - SignalDelay(this, SamplesToDo, OutBuffer); + if(!mDelay.empty()) + signalDelay(SamplesToDo, output); - const float (&sideChain)[BufferLineSize*2] = mSideChain; - auto apply_comp = [SamplesToDo,&sideChain](FloatBufferLine &input) noexcept -> void + const auto gains = assume_aligned_span<16>(al::span{mSideChain}.first(SamplesToDo)); + auto apply_comp = [gains](const FloatBufferSpan input) noexcept -> void { - float *buffer{al::assume_aligned<16>(input.data())}; - const float *gains{al::assume_aligned<16>(&sideChain[0])}; - std::transform(gains, gains+SamplesToDo, buffer, buffer, - [](float g, float s) { return g * s; }); + const auto buffer = assume_aligned_span<16>(input); + std::transform(gains.cbegin(), gains.cend(), buffer.cbegin(), buffer.begin(), + std::multiplies{}); }; - std::for_each(OutBuffer, OutBuffer+numChans, apply_comp); + std::for_each(output.begin(), output.end(), apply_comp); - auto side_begin = std::begin(mSideChain) + SamplesToDo; - std::copy(side_begin, side_begin+mLookAhead, std::begin(mSideChain)); + const auto delayedGains = al::span{mSideChain}.subspan(SamplesToDo, mLookAhead); + std::copy(delayedGains.begin(), delayedGains.end(), mSideChain.begin()); } diff --git a/Engine/lib/openal-soft/core/mastering.h b/Engine/lib/openal-soft/core/mastering.h index 1a36937ca..032eae082 100644 --- a/Engine/lib/openal-soft/core/mastering.h +++ b/Engine/lib/openal-soft/core/mastering.h @@ -1,10 +1,14 @@ #ifndef CORE_MASTERING_H #define CORE_MASTERING_H +#include #include #include "almalloc.h" +#include "alnumeric.h" +#include "alspan.h" #include "bufferline.h" +#include "vector.h" struct SlidingHold; @@ -21,16 +25,17 @@ using uint = unsigned int; * * http://c4dm.eecs.qmul.ac.uk/audioengineering/compressors/ */ -struct Compressor { +class Compressor { size_t mNumChans{0u}; - struct { + struct AutoFlags { bool Knee : 1; bool Attack : 1; bool Release : 1; bool PostGain : 1; bool Declip : 1; - } mAuto{}; + }; + AutoFlags mAuto{}; uint mLookAhead{0}; @@ -44,11 +49,11 @@ struct Compressor { float mAttack{0.0f}; float mRelease{0.0f}; - alignas(16) float mSideChain[2*BufferLineSize]{}; - alignas(16) float mCrestFactor[BufferLineSize]{}; + alignas(16) std::array mSideChain{}; + alignas(16) std::array mCrestFactor{}; - SlidingHold *mHold{nullptr}; - FloatBufferLine *mDelay{nullptr}; + std::unique_ptr mHold; + al::vector mDelay; float mCrestCoeff{0.0f}; float mGainEstimate{0.0f}; @@ -60,12 +65,19 @@ struct Compressor { float mLastAttack{0.0f}; float mLastGainDev{0.0f}; + Compressor() = default; + void linkChannels(const uint SamplesToDo, const al::span OutBuffer); + void crestDetector(const uint SamplesToDo); + void peakDetector(const uint SamplesToDo); + void peakHoldDetector(const uint SamplesToDo); + void gainCompressor(const uint SamplesToDo); + void signalDelay(const uint SamplesToDo, const al::span OutBuffer); + +public: ~Compressor(); void process(const uint SamplesToDo, FloatBufferLine *OutBuffer); - int getLookAhead() const noexcept { return static_cast(mLookAhead); } - - DEF_PLACE_NEWDEL() + [[nodiscard]] auto getLookAhead() const noexcept -> uint { return mLookAhead; } /** * The compressor is initialized with the following settings: diff --git a/Engine/lib/openal-soft/core/mixer.cpp b/Engine/lib/openal-soft/core/mixer.cpp index 066c57bd7..bba7ae206 100644 --- a/Engine/lib/openal-soft/core/mixer.cpp +++ b/Engine/lib/openal-soft/core/mixer.cpp @@ -3,10 +3,12 @@ #include "mixer.h" +#include #include +#include #include "alnumbers.h" -#include "devformat.h" +#include "core/ambidefs.h" #include "device.h" #include "mixer/defs.h" @@ -82,14 +84,13 @@ std::array CalcAmbiCoeffs(const float y, const float z, c return coeffs; } -void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, - const al::span gains) +void ComputePanGains(const MixParams *mix, const al::span coeffs, + const float ingain, const al::span gains) { - auto ambimap = mix->AmbiMap.cbegin(); + auto ambimap = al::span{std::as_const(mix->AmbiMap)}.first(mix->Buffer.size()); - auto iter = std::transform(ambimap, ambimap+mix->Buffer.size(), gains.begin(), + auto iter = std::transform(ambimap.begin(), ambimap.end(), gains.begin(), [coeffs,ingain](const BFChannelConfig &chanmap) noexcept -> float - { return chanmap.Scale * coeffs[chanmap.Index] * ingain; } - ); + { return chanmap.Scale * coeffs[chanmap.Index] * ingain; }); std::fill(iter, gains.end(), 0.0f); } diff --git a/Engine/lib/openal-soft/core/mixer.h b/Engine/lib/openal-soft/core/mixer.h index aa7597bba..b5f1b9aa1 100644 --- a/Engine/lib/openal-soft/core/mixer.h +++ b/Engine/lib/openal-soft/core/mixer.h @@ -3,34 +3,32 @@ #include #include -#include -#include +#include #include "alspan.h" #include "ambidefs.h" #include "bufferline.h" -#include "devformat.h" struct MixParams; /* Mixer functions that handle one input and multiple output channels. */ using MixerOutFunc = void(*)(const al::span InSamples, - const al::span OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos); + const al::span OutBuffer, const al::span CurrentGains, + const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos); extern MixerOutFunc MixSamplesOut; inline void MixSamples(const al::span InSamples, - const al::span OutBuffer, float *CurrentGains, const float *TargetGains, - const size_t Counter, const size_t OutPos) + const al::span OutBuffer, const al::span CurrentGains, + const al::span TargetGains, const std::size_t Counter, const std::size_t OutPos) { MixSamplesOut(InSamples, OutBuffer, CurrentGains, TargetGains, Counter, OutPos); } /* Mixer functions that handle one input and one output channel. */ -using MixerOneFunc = void(*)(const al::span InSamples, float *OutBuffer, - float &CurrentGain, const float TargetGain, const size_t Counter); +using MixerOneFunc = void(*)(const al::span InSamples,const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const std::size_t Counter); extern MixerOneFunc MixSamplesOne; -inline void MixSamples(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +inline void MixSamples(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const std::size_t Counter) { MixSamplesOne(InSamples, OutBuffer, CurrentGain, TargetGain, Counter); } @@ -58,7 +56,7 @@ std::array CalcAmbiCoeffs(const float y, const float z, c * vector must be normalized (unit length), and the spread is the angular width * of the sound (0...tau). */ -inline std::array CalcDirectionCoeffs(const float (&dir)[3], +inline std::array CalcDirectionCoeffs(const al::span dir, const float spread) { /* Convert from OpenAL coords to Ambisonics. */ @@ -71,7 +69,7 @@ inline std::array CalcDirectionCoeffs(const float (&dir)[ * Calculates ambisonic coefficients based on an OpenAL direction vector. The * vector must be normalized (unit length). */ -constexpr std::array CalcDirectionCoeffs(const float (&dir)[3]) +constexpr std::array CalcDirectionCoeffs(const al::span dir) { /* Convert from OpenAL coords to Ambisonics. */ return CalcAmbiCoeffs(-dir[0], dir[1], -dir[2]); @@ -103,7 +101,7 @@ inline std::array CalcAngleCoeffs(const float azimuth, * coeffs are a 'slice' of a transform matrix for the input channel, used to * scale and orient the sound samples. */ -void ComputePanGains(const MixParams *mix, const float*RESTRICT coeffs, const float ingain, - const al::span gains); +void ComputePanGains(const MixParams *mix, const al::span coeffs, + const float ingain, const al::span gains); #endif /* CORE_MIXER_H */ diff --git a/Engine/lib/openal-soft/core/mixer/defs.h b/Engine/lib/openal-soft/core/mixer/defs.h index 48daca9b9..f19217c80 100644 --- a/Engine/lib/openal-soft/core/mixer/defs.h +++ b/Engine/lib/openal-soft/core/mixer/defs.h @@ -2,13 +2,15 @@ #define CORE_MIXER_DEFS_H #include -#include +#include +#include +#include +#include #include "alspan.h" #include "core/bufferline.h" -#include "core/resampler_limits.h" +#include "core/cubic_defs.h" -struct CubicCoefficients; struct HrtfChannelState; struct HrtfFilter; struct MixHrtfFilter; @@ -17,18 +19,19 @@ using uint = unsigned int; using float2 = std::array; -constexpr int MixerFracBits{16}; -constexpr int MixerFracOne{1 << MixerFracBits}; -constexpr int MixerFracMask{MixerFracOne - 1}; -constexpr int MixerFracHalf{MixerFracOne >> 1}; +inline constexpr int MixerFracBits{16}; +inline constexpr int MixerFracOne{1 << MixerFracBits}; +inline constexpr int MixerFracMask{MixerFracOne - 1}; +inline constexpr int MixerFracHalf{MixerFracOne >> 1}; -constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ +inline constexpr float GainSilenceThreshold{0.00001f}; /* -100dB */ -enum class Resampler : uint8_t { +enum class Resampler : std::uint8_t { Point, Linear, - Cubic, + Spline, + Gaussian, FastBSinc12, BSinc12, FastBSinc24, @@ -49,56 +52,59 @@ struct BsincState { * delta coefficients. Starting at phase index 0, each subsequent phase * index follows contiguously. */ - const float *filter; + al::span filter; }; struct CubicState { /* Filter coefficients, and coefficient deltas. Starting at phase index 0, * each subsequent phase index follows contiguously. */ - const CubicCoefficients *filter; + al::span filter; + CubicState(al::span f) : filter{f} { } }; -union InterpState { - CubicState cubic; - BsincState bsinc; -}; +using InterpState = std::variant; -using ResamplerFunc = void(*)(const InterpState *state, const float *RESTRICT src, uint frac, +using ResamplerFunc = void(*)(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); ResamplerFunc PrepareResampler(Resampler resampler, uint increment, InterpState *state); template -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst); template void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos); + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos); template -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter); +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter); template -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize); +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo); template -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize); +void MixHrtfBlend_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t SamplesToDo); template void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize); + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo); /* Vectorized resampler helpers */ template -inline void InitPosArrays(uint frac, uint increment, uint (&frac_arr)[N], uint (&pos_arr)[N]) +constexpr void InitPosArrays(uint pos, uint frac, const uint increment, + const al::span frac_arr, const al::span pos_arr) { - pos_arr[0] = 0; + static_assert(pos_arr.size() == frac_arr.size()); + pos_arr[0] = pos; frac_arr[0] = frac; - for(size_t i{1};i < N;i++) + for(size_t i{1};i < pos_arr.size();++i) { const uint frac_tmp{frac_arr[i-1] + increment}; pos_arr[i] = pos_arr[i-1] + (frac_tmp>>MixerFracBits); diff --git a/Engine/lib/openal-soft/core/mixer/hrtfbase.h b/Engine/lib/openal-soft/core/mixer/hrtfbase.h index 36f88e496..703bfab9d 100644 --- a/Engine/lib/openal-soft/core/mixer/hrtfbase.h +++ b/Engine/lib/openal-soft/core/mixer/hrtfbase.h @@ -4,21 +4,23 @@ #include #include -#include "almalloc.h" +#include "defs.h" #include "hrtfdefs.h" #include "opthelpers.h" using uint = unsigned int; -using ApplyCoeffsT = void(&)(float2 *RESTRICT Values, const size_t irSize, +using ApplyCoeffsT = void(const al::span Values, const size_t irSize, const ConstHrirSpan Coeffs, const float left, const float right); template -inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, const size_t IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) +inline void MixHrtfBase(const al::span InSamples, const al::span AccumSamples, + const size_t IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); const ConstHrirSpan Coeffs{hrtfparams->Coeffs}; const float gainstep{hrtfparams->GainStep}; @@ -27,26 +29,28 @@ inline void MixHrtfBase(const float *InSamples, float2 *RESTRICT AccumSamples, c size_t ldelay{HrtfHistoryLength - hrtfparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - hrtfparams->Delay[1]}; float stepcount{0.0f}; - for(size_t i{0u};i < BufferSize;++i) + for(size_t i{0u};i < SamplesToDo;++i) { const float g{gain + gainstep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, left, right); stepcount += 1.0f; } } template -inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSamples, - const size_t IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, - const size_t BufferSize) +inline void MixHrtfBlendBase(const al::span InSamples, + const al::span AccumSamples, const size_t IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); const ConstHrirSpan OldCoeffs{oldparams->Coeffs}; - const float oldGainStep{oldparams->Gain / static_cast(BufferSize)}; + const float oldGainStep{oldparams->Gain / static_cast(SamplesToDo)}; const ConstHrirSpan NewCoeffs{newparams->Coeffs}; const float newGainStep{newparams->GainStep}; @@ -54,29 +58,29 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl { size_t ldelay{HrtfHistoryLength - oldparams->Delay[0]}; size_t rdelay{HrtfHistoryLength - oldparams->Delay[1]}; - auto stepcount = static_cast(BufferSize); - for(size_t i{0u};i < BufferSize;++i) + auto stepcount = static_cast(SamplesToDo); + for(size_t i{0u};i < SamplesToDo;++i) { const float g{oldGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, OldCoeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, OldCoeffs, left, right); stepcount -= 1.0f; } } - if(newGainStep*static_cast(BufferSize) > GainSilenceThreshold) LIKELY + if(newGainStep*static_cast(SamplesToDo) > GainSilenceThreshold) LIKELY { size_t ldelay{HrtfHistoryLength+1 - newparams->Delay[0]}; size_t rdelay{HrtfHistoryLength+1 - newparams->Delay[1]}; float stepcount{1.0f}; - for(size_t i{1u};i < BufferSize;++i) + for(size_t i{1u};i < SamplesToDo;++i) { const float g{newGainStep*stepcount}; const float left{InSamples[ldelay++] * g}; const float right{InSamples[rdelay++] * g}; - ApplyCoeffs(AccumSamples+i, IrSize, NewCoeffs, left, right); + ApplyCoeffs(AccumSamples.subspan(i), IrSize, NewCoeffs, left, right); stepcount += 1.0f; } @@ -85,45 +89,52 @@ inline void MixHrtfBlendBase(const float *InSamples, float2 *RESTRICT AccumSampl template inline void MixDirectHrtfBase(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *RESTRICT AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChannelState, + const size_t IrSize, const size_t SamplesToDo) { - ASSUME(BufferSize > 0); + ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); + ASSUME(IrSize <= HrirLength); + assert(ChannelState.size() == InSamples.size()); + auto ChanState = ChannelState.begin(); for(const FloatBufferLine &input : InSamples) { /* For dual-band processing, the signal needs extra scaling applied to * the high frequency response. The band-splitter applies this scaling * with a consistent phase shift regardless of the scale amount. */ - ChanState->mSplitter.processHfScale({input.data(), BufferSize}, TempBuf, + ChanState->mSplitter.processHfScale(al::span{input}.first(SamplesToDo), TempBuf, ChanState->mHfScale); /* Now apply the HRIR coefficients to this channel. */ - const float *RESTRICT tempbuf{al::assume_aligned<16>(TempBuf)}; const ConstHrirSpan Coeffs{ChanState->mCoeffs}; - for(size_t i{0u};i < BufferSize;++i) + for(size_t i{0u};i < SamplesToDo;++i) { - const float insample{tempbuf[i]}; - ApplyCoeffs(AccumSamples+i, IrSize, Coeffs, insample, insample); + const float insample{TempBuf[i]}; + ApplyCoeffs(AccumSamples.subspan(i), IrSize, Coeffs, insample, insample); } ++ChanState; } /* Add the HRTF signal to the existing "direct" signal. */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut.data())}; - float *RESTRICT right{al::assume_aligned<16>(RightOut.data())}; - for(size_t i{0u};i < BufferSize;++i) - left[i] += AccumSamples[i][0]; - for(size_t i{0u};i < BufferSize;++i) - right[i] += AccumSamples[i][1]; + const auto left = al::span{al::assume_aligned<16>(LeftOut.data()), SamplesToDo}; + std::transform(left.cbegin(), left.cend(), AccumSamples.cbegin(), left.begin(), + [](const float sample, const float2 &accum) noexcept -> float + { return sample + accum[0]; }); + const auto right = al::span{al::assume_aligned<16>(RightOut.data()), SamplesToDo}; + std::transform(right.cbegin(), right.cend(), AccumSamples.cbegin(), right.begin(), + [](const float sample, const float2 &accum) noexcept -> float + { return sample + accum[1]; }); /* Copy the new in-progress accumulation values to the front and clear the * following samples for the next mix. */ - auto accum_iter = std::copy_n(AccumSamples+BufferSize, HrirLength, AccumSamples); - std::fill_n(accum_iter, BufferSize, float2{}); + const auto accum_inprog = AccumSamples.subspan(SamplesToDo, HrirLength); + auto accum_iter = std::copy(accum_inprog.cbegin(), accum_inprog.cend(), AccumSamples.begin()); + std::fill_n(accum_iter, SamplesToDo, float2{}); } #endif /* CORE_MIXER_HRTFBASE_H */ diff --git a/Engine/lib/openal-soft/core/mixer/mixer_c.cpp b/Engine/lib/openal-soft/core/mixer/mixer_c.cpp index 28a92ef7e..e14454d26 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_c.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_c.cpp @@ -1,14 +1,21 @@ #include "config.h" -#include -#include +#include +#include +#include #include +#include #include "alnumeric.h" +#include "alspan.h" #include "core/bsinc_defs.h" +#include "core/bufferline.h" #include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct CTag; struct PointTag; @@ -28,191 +35,246 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; -inline float do_point(const InterpState&, const float *RESTRICT vals, const uint) -{ return vals[0]; } -inline float do_lerp(const InterpState&, const float *RESTRICT vals, const uint frac) -{ return lerpf(vals[0], vals[1], static_cast(frac)*(1.0f/MixerFracOne)); } -inline float do_cubic(const InterpState &istate, const float *RESTRICT vals, const uint frac) +using SamplerNST = float(const al::span, const size_t, const uint) noexcept; + +template +using SamplerT = float(const T&,const al::span,const size_t,const uint) noexcept; + +[[nodiscard]] constexpr +auto do_point(const al::span vals, const size_t pos, const uint) noexcept -> float +{ return vals[pos]; } +[[nodiscard]] constexpr +auto do_lerp(const al::span vals, const size_t pos, const uint frac) noexcept -> float +{ return lerpf(vals[pos+0], vals[pos+1], static_cast(frac)*(1.0f/MixerFracOne)); } +[[nodiscard]] constexpr +auto do_cubic(const CubicState &istate, const al::span vals, const size_t pos, + const uint frac) noexcept -> float { /* Calculate the phase index and factor. */ - const uint pi{frac >> CubicPhaseDiffBits}; + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; - const float *RESTRICT fil{al::assume_aligned<16>(istate.cubic.filter[pi].mCoeffs)}; - const float *RESTRICT phd{al::assume_aligned<16>(istate.cubic.filter[pi].mDeltas)}; + const auto fil = al::span{istate.filter[pi].mCoeffs}; + const auto phd = al::span{istate.filter[pi].mDeltas}; /* Apply the phase interpolated filter. */ - return (fil[0] + pf*phd[0])*vals[0] + (fil[1] + pf*phd[1])*vals[1] - + (fil[2] + pf*phd[2])*vals[2] + (fil[3] + pf*phd[3])*vals[3]; + return (fil[0] + pf*phd[0])*vals[pos+0] + (fil[1] + pf*phd[1])*vals[pos+1] + + (fil[2] + pf*phd[2])*vals[pos+2] + (fil[3] + pf*phd[3])*vals[pos+3]; } -inline float do_bsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) +[[nodiscard]] constexpr +auto do_fastbsinc(const BsincState &bsinc, const al::span vals, const size_t pos, + const uint frac) noexcept -> float { - const size_t m{istate.bsinc.m}; + const size_t m{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); /* Calculate the phase index and factor. */ - const uint pi{frac >> BsincPhaseDiffBits}; + const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; - const float *RESTRICT fil{istate.bsinc.filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; + const auto fil = bsinc.filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + + /* Apply the phase interpolated filter. */ + float r{0.0f}; + for(size_t j_f{0};j_f < m;++j_f) + r += (fil[j_f] + pf*phd[j_f]) * vals[pos+j_f]; + return r; +} +[[nodiscard]] constexpr +auto do_bsinc(const BsincState &bsinc, const al::span vals, const size_t pos, + const uint frac) noexcept -> float +{ + const size_t m{bsinc.m}; + ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); + + /* Calculate the phase index and factor. */ + const uint pi{frac >> BsincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); + const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; + + const auto fil = bsinc.filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(BSincPhaseCount*2_uz*m); + const auto spd = scd.subspan(m); /* Apply the scale and phase interpolated filter. */ float r{0.0f}; - for(size_t j_f{0};j_f < m;j_f++) - r += (fil[j_f] + istate.bsinc.sf*scd[j_f] + pf*(phd[j_f] + istate.bsinc.sf*spd[j_f])) * vals[j_f]; - return r; -} -inline float do_fastbsinc(const InterpState &istate, const float *RESTRICT vals, const uint frac) -{ - const size_t m{istate.bsinc.m}; - ASSUME(m > 0); - - /* Calculate the phase index and factor. */ - const uint pi{frac >> BsincPhaseDiffBits}; - const float pf{static_cast(frac&BsincPhaseDiffMask) * (1.0f/BsincPhaseDiffOne)}; - - const float *RESTRICT fil{istate.bsinc.filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - - /* Apply the phase interpolated filter. */ - float r{0.0f}; - for(size_t j_f{0};j_f < m;j_f++) - r += (fil[j_f] + pf*phd[j_f]) * vals[j_f]; + for(size_t j_f{0};j_f < m;++j_f) + r += (fil[j_f] + bsinc.sf*scd[j_f] + pf*(phd[j_f] + bsinc.sf*spd[j_f])) * vals[pos+j_f]; return r; } -using SamplerT = float(&)(const InterpState&, const float*RESTRICT, const uint); -template -void DoResample(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +template +void DoResample(const al::span src, uint frac, const uint increment, + const al::span dst) { - const InterpState istate{*state}; ASSUME(frac < MixerFracOne); - for(float &out : dst) + size_t pos{0}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment]() -> float { - out = Sampler(istate, src, frac); - + const float output{Sampler(src, pos, frac)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +template Sampler> +void DoResample(const U istate, const al::span src, uint frac, const uint increment, + const al::span dst) +{ + ASSUME(frac < MixerFracOne); + size_t pos{0}; + std::generate(dst.begin(), dst.end(), [istate,src,&pos,&frac,increment]() -> float + { + const float output{Sampler(istate, src, pos, frac)}; + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); +} + +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) noexcept { ASSUME(IrSize >= MinIrLength); - for(size_t c{0};c < IrSize;++c) - { - Values[c][0] += Coeffs[c][0] * left; - Values[c][1] += Coeffs[c][1] * right; - } + ASSUME(IrSize <= HrirLength); + + auto mix_impulse = [left,right](const float2 &value, const float2 &coeff) noexcept -> float2 + { return float2{{value[0] + coeff[0]*left, value[1] + coeff[1]*right}}; }; + std::transform(Values.cbegin(), Values.cbegin()+ptrdiff_t(IrSize), Coeffs.cbegin(), + Values.begin(), mix_impulse); } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, +force_inline void MixLine(al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const float step{(TargetGain-CurrentGain) * delta}; - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + auto output = dst.begin(); + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; - for(;pos != min_len;++pos) - { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; - } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; - } - CurrentGain = gain; + auto input = InSamples.first(fade_len); + InSamples = InSamples.subspan(fade_len); - if(!(std::abs(gain) > GainSilenceThreshold)) + const float gain{CurrentGain}; + float step_count{0.0f}; + output = std::transform(input.begin(), input.end(), output, output, + [gain,step,&step_count](const float in, float out) noexcept -> float + { + out += in * (gain + step*step_count); + step_count += 1.0f; + return out; + }); + + if(fade_len < Counter) + { + CurrentGain = gain + step*step_count; + return; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; - for(;pos != InSamples.size();++pos) - dst[pos] += InSamples[pos] * gain; + + std::transform(InSamples.begin(), InSamples.end(), output, output, + [TargetGain](const float in, const float out) noexcept -> float + { return out + in*TargetGain; }); } } // namespace template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src, frac, increment, dst); } +{ DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src, frac, increment, dst); } +{ DoResample(src.subspan(MaxResamplerEdge), frac, increment, dst); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src-1, frac, increment, dst); } +{ + DoResample(std::get(*state), src.subspan(MaxResamplerEdge-1), + frac, increment, dst); +} template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + const auto istate = std::get(*state); + ASSUME(istate.l <= MaxResamplerEdge); + DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, + increment, dst); +} + +template<> +void Resample_(const InterpState *state, const al::span src, uint frac, const uint increment, const al::span dst) -{ DoResample(state, src-state->bsinc.l, frac, increment, dst); } - -template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) -{ DoResample(state, src-state->bsinc.l, frac, increment, dst); } +{ + const auto istate = std::get(*state); + ASSUME(istate.l <= MaxResamplerEdge); + DoResample(istate, src.subspan(MaxResamplerEdge-istate.l), frac, + increment, dst); +} template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples,const al::span AccumSamples, + const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, + const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); + const auto fade_len = std::min(Counter, InSamples.size()); + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); + const auto fade_len = std::min(Counter, InSamples.size()); - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, - TargetGain, delta, min_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp index ef2936b32..bbbe44708 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_neon.cpp @@ -2,14 +2,22 @@ #include -#include +#include +#include +#include #include +#include #include "alnumeric.h" +#include "alspan.h" #include "core/bsinc_defs.h" +#include "core/bufferline.h" #include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct NEONTag; struct LerpTag; @@ -22,6 +30,8 @@ struct FastBSincTag; #pragma GCC target("fpu=neon") #endif +using uint = unsigned int; + namespace { constexpr uint BSincPhaseDiffBits{MixerFracBits - BSincPhaseBits}; @@ -32,6 +42,19 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; +force_inline +void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3) noexcept +{ + float32x4x2_t t0_{vzipq_f32(x0, x2)}; + float32x4x2_t t1_{vzipq_f32(x1, x3)}; + float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])}; + float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])}; + x0 = u0_.val[0]; + x1 = u0_.val[1]; + x2 = u1_.val[0]; + x3 = u1_.val[1]; +} + inline float32x4_t set_f4(float l0, float l1, float l2, float l3) { float32x4_t ret{vmovq_n_f32(l0)}; @@ -41,60 +64,59 @@ inline float32x4_t set_f4(float l0, float l1, float l2, float l3) return ret; } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) { - float32x4_t leftright4; - { - float32x2_t leftright2{vmov_n_f32(left)}; - leftright2 = vset_lane_f32(right, leftright2, 1); - leftright4 = vcombine_f32(leftright2, leftright2); - } - ASSUME(IrSize >= MinIrLength); - for(size_t c{0};c < IrSize;c += 2) + ASSUME(IrSize <= HrirLength); + + auto dup_samples = [left,right]() -> float32x4_t { - float32x4_t vals = vld1q_f32(&Values[c][0]); - float32x4_t coefs = vld1q_f32(&Coeffs[c][0]); + float32x2_t leftright2{vset_lane_f32(right, vmov_n_f32(left), 1)}; + return vcombine_f32(leftright2, leftright2); + }; + const auto leftright4 = dup_samples(); + const auto count4 = size_t{(IrSize+1) >> 1}; - vals = vmlaq_f32(vals, coefs, leftright4); - - vst1q_f32(&Values[c][0], vals); - } + const auto vals4 = al::span{reinterpret_cast(Values[0].data()), count4}; + const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; + std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), + [leftright4](const float32x4_t &val, const float32x4_t &coeff) -> float32x4_t + { return vmlaq_f32(val, coeff, leftright4); }); } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, - const size_t aligned_len, size_t Counter) +force_inline void MixLine(const al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, + const size_t realign_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const auto step = float{(TargetGain-CurrentGain) * delta}; - size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + auto pos = size_t{0}; + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; + const auto gain = float{CurrentGain}; + auto step_count = float{0.0f}; /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_len >> 2}) + if(const size_t todo{fade_len >> 2}) { - const float32x4_t four4{vdupq_n_f32(4.0f)}; - const float32x4_t step4{vdupq_n_f32(step)}; - const float32x4_t gain4{vdupq_n_f32(gain)}; - float32x4_t step_count4{vdupq_n_f32(0.0f)}; - step_count4 = vsetq_lane_f32(1.0f, step_count4, 1); - step_count4 = vsetq_lane_f32(2.0f, step_count4, 2); - step_count4 = vsetq_lane_f32(3.0f, step_count4, 3); + const auto four4 = vdupq_n_f32(4.0f); + const auto step4 = vdupq_n_f32(step); + const auto gain4 = vdupq_n_f32(gain); + auto step_count4 = set_f4(0.0f, 1.0f, 2.0f, 3.0f); + + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.first(todo); + const auto out4 = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4,step4,four4,&step_count4](const float32x4_t val4, float32x4_t dry4) + { + /* dry += val * (gain + step*step_count) */ + dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); + step_count4 = vaddq_f32(step_count4, four4); + return dry4; + }); + pos += in4.size()*4; - do { - const float32x4_t val4 = vld1q_f32(&InSamples[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, vmlaq_f32(gain4, step4, step_count4)); - step_count4 = vaddq_f32(step_count4, four4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); /* NOTE: step_count4 now represents the next four counts after the * last four mixed samples, so the lowest element represents the * next step count to apply. @@ -102,152 +124,242 @@ force_inline void MixLine(const al::span InSamples, float *RESTRICT step_count = vgetq_lane_f32(step_count4, 0); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(size_t leftover{min_len&3};leftover;++pos,--leftover) + if(const size_t leftover{fade_len&3}) { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [gain,step,&step_count](const float val, float dry) noexcept -> float + { + dry += val * (gain + step*step_count); + step_count += 1.0f; + return dry; + }); + pos += leftover; + } + if(pos < Counter) + { + CurrentGain = gain + step*step_count; + return; } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; /* Mix until pos is aligned with 4 or the mix is done. */ - for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } - CurrentGain = gain; + if(const size_t leftover{realign_len&3}) + { + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); - if(!(std::abs(gain) > GainSilenceThreshold)) - return; - if(size_t todo{(InSamples.size()-pos) >> 2}) - { - const float32x4_t gain4 = vdupq_n_f32(gain); - do { - const float32x4_t val4 = vld1q_f32(&InSamples[pos]); - float32x4_t dry4 = vld1q_f32(&dst[pos]); - dry4 = vmlaq_f32(dry4, val4, gain4); - vst1q_f32(&dst[pos], dry4); - pos += 4; - } while(--todo); + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); + pos += leftover; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) + return; + if(const size_t todo{(InSamples.size()-pos) >> 2}) + { + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.last(todo); + const auto out = dst.subspan(pos); + const auto out4 = al::span{reinterpret_cast(out.data()), out.size()/4}; + + const auto gain4 = vdupq_n_f32(TargetGain); + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4](const float32x4_t val4, const float32x4_t dry4) -> float32x4_t + { return vmlaq_f32(dry4, val4, gain4); }); + pos += in4.size()*4; + } + if(const size_t leftover{(InSamples.size()-pos)&3}) + { + const auto in = InSamples.last(leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; } } // namespace template<> -void Resample_(const InterpState*, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - const int32x4_t increment4 = vdupq_n_s32(static_cast(increment*4)); + const uint32x4_t increment4 = vdupq_n_u32(increment*4u); const float32x4_t fracOne4 = vdupq_n_f32(1.0f/MixerFracOne); - const int32x4_t fracMask4 = vdupq_n_s32(MixerFracMask); - alignas(16) uint pos_[4], frac_[4]; - int32x4_t pos4, frac4; + const uint32x4_t fracMask4 = vdupq_n_u32(MixerFracMask); - InitPosArrays(frac, increment, frac_, pos_); - frac4 = vld1q_s32(reinterpret_cast(frac_)); - pos4 = vld1q_s32(reinterpret_cast(pos_)); + alignas(16) std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); + uint32x4_t frac4 = vld1q_u32(frac_.data()); + uint32x4_t pos4 = vld1q_u32(pos_.data()); - auto dst_iter = dst.begin(); - for(size_t todo{dst.size()>>2};todo;--todo) + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> float32x4_t { - const int pos0{vgetq_lane_s32(pos4, 0)}; - const int pos1{vgetq_lane_s32(pos4, 1)}; - const int pos2{vgetq_lane_s32(pos4, 2)}; - const int pos3{vgetq_lane_s32(pos4, 3)}; + const uint pos0{vgetq_lane_u32(pos4, 0)}; + const uint pos1{vgetq_lane_u32(pos4, 1)}; + const uint pos2{vgetq_lane_u32(pos4, 2)}; + const uint pos3{vgetq_lane_u32(pos4, 3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); const float32x4_t val1{set_f4(src[pos0], src[pos1], src[pos2], src[pos3])}; - const float32x4_t val2{set_f4(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; + const float32x4_t val2{set_f4(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* val1 + (val2-val1)*mu */ const float32x4_t r0{vsubq_f32(val2, val1)}; - const float32x4_t mu{vmulq_f32(vcvtq_f32_s32(frac4), fracOne4)}; + const float32x4_t mu{vmulq_f32(vcvtq_f32_u32(frac4), fracOne4)}; const float32x4_t out{vmlaq_f32(val1, mu, r0)}; - vst1q_f32(dst_iter, out); - dst_iter += 4; - - frac4 = vaddq_s32(frac4, increment4); - pos4 = vaddq_s32(pos4, vshrq_n_s32(frac4, MixerFracBits)); - frac4 = vandq_s32(frac4, fracMask4); - } + frac4 = vaddq_u32(frac4, increment4); + pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits)); + frac4 = vandq_u32(frac4, fracMask4); + return out; + }); if(size_t todo{dst.size()&3}) { - src += static_cast(vgetq_lane_s32(pos4, 0)); - frac = static_cast(vgetq_lane_s32(frac4, 0)); + auto pos = size_t{vgetq_lane_u32(pos4, 0)}; + frac = vgetq_lane_u32(frac4, 0); - do { - *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + const auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] + { + const float output{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return output; + }); } } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter); + const auto filter = std::get(*state).filter; - src -= 1; - for(float &out_sample : dst) + const uint32x4_t increment4{vdupq_n_u32(increment*4u)}; + const uint32x4_t fracMask4{vdupq_n_u32(MixerFracMask)}; + const float32x4_t fracDiffOne4{vdupq_n_f32(1.0f/CubicPhaseDiffOne)}; + const uint32x4_t fracDiffMask4{vdupq_n_u32(CubicPhaseDiffMask)}; + + alignas(16) std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); + uint32x4_t frac4{vld1q_u32(frac_.data())}; + uint32x4_t pos4{vld1q_u32(pos_.data())}; + + auto vecout = al::span{reinterpret_cast(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { - const uint pi{frac >> CubicPhaseDiffBits}; - const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; - const float32x4_t pf4{vdupq_n_f32(pf)}; + const uint pos0{vgetq_lane_u32(pos4, 0)}; + const uint pos1{vgetq_lane_u32(pos4, 1)}; + const uint pos2{vgetq_lane_u32(pos4, 2)}; + const uint pos3{vgetq_lane_u32(pos4, 3)}; + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const float32x4_t val0{vld1q_f32(&src[pos0])}; + const float32x4_t val1{vld1q_f32(&src[pos1])}; + const float32x4_t val2{vld1q_f32(&src[pos2])}; + const float32x4_t val3{vld1q_f32(&src[pos3])}; - /* Apply the phase interpolated filter. */ + const uint32x4_t pi4{vshrq_n_u32(frac4, CubicPhaseDiffBits)}; + const uint pi0{vgetq_lane_u32(pi4, 0)}; ASSUME(pi0 < CubicPhaseCount); + const uint pi1{vgetq_lane_u32(pi4, 1)}; ASSUME(pi1 < CubicPhaseCount); + const uint pi2{vgetq_lane_u32(pi4, 2)}; ASSUME(pi2 < CubicPhaseCount); + const uint pi3{vgetq_lane_u32(pi4, 3)}; ASSUME(pi3 < CubicPhaseCount); - /* f = fil + pf*phd */ - const float32x4_t f4 = vmlaq_f32(vld1q_f32(filter[pi].mCoeffs), pf4, - vld1q_f32(filter[pi].mDeltas)); - /* r = f*src */ - float32x4_t r4{vmulq_f32(f4, vld1q_f32(src))}; + const float32x4_t pf4{vmulq_f32(vcvtq_f32_u32(vandq_u32(frac4, fracDiffMask4)), + fracDiffOne4)}; - r4 = vaddq_f32(r4, vrev64q_f32(r4)); - out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + float32x4_t r0{vmulq_f32(val0, + vmlaq_f32(vld1q_f32(filter[pi0].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 0), + vld1q_f32(filter[pi0].mDeltas.data())))}; + float32x4_t r1{vmulq_f32(val1, + vmlaq_f32(vld1q_f32(filter[pi1].mCoeffs.data()), vdupq_lane_f32(vget_low_f32(pf4), 1), + vld1q_f32(filter[pi1].mDeltas.data())))}; + float32x4_t r2{vmulq_f32(val2, + vmlaq_f32(vld1q_f32(filter[pi2].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 0), + vld1q_f32(filter[pi2].mDeltas.data())))}; + float32x4_t r3{vmulq_f32(val3, + vmlaq_f32(vld1q_f32(filter[pi3].mCoeffs.data()), vdupq_lane_f32(vget_high_f32(pf4), 1), + vld1q_f32(filter[pi3].mDeltas.data())))}; - frac += increment; - src += frac>>MixerFracBits; - frac &= MixerFracMask; + vtranspose4(r0, r1, r2, r3); + r0 = vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3)); + + frac4 = vaddq_u32(frac4, increment4); + pos4 = vaddq_u32(pos4, vshrq_n_u32(frac4, MixerFracBits)); + frac4 = vandq_u32(frac4, fracMask4); + return r0; + }); + + if(const size_t todo{dst.size()&3}) + { + auto pos = size_t{vgetq_lane_u32(pos4, 0)}; + frac = vgetq_lane_u32(frac4, 0); + + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] + { + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + const float32x4_t pf4{vdupq_n_f32(pf)}; + + const float32x4_t f4{vmlaq_f32(vld1q_f32(filter[pi].mCoeffs.data()), pf4, + vld1q_f32(filter[pi].mDeltas.data()))}; + float32x4_t r4{vmulq_f32(f4, vld1q_f32(&src[pos]))}; + + r4 = vaddq_f32(r4, vrev64q_f32(r4)); + const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; + + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); } } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const float32x4_t sf4{vdupq_n_f32(state->bsinc.sf)}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto sf4 = vdupq_n_f32(bsinc.sf); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); + const auto spd = scd.subspan(m); size_t td{m >> 2}; size_t j{0u}; @@ -257,41 +369,46 @@ void Resample_(const InterpState *state, const float *RESTRICT vmlaq_f32(vld1q_f32(&fil[j]), sf4, vld1q_f32(&scd[j])), pf4, vmlaq_f32(vld1q_f32(&phd[j]), sf4, vld1q_f32(&spd[j]))); /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+j])); j += 4; } while(--td); } r4 = vaddq_f32(r4, vrev64q_f32(r4)); - out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(2_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const uint pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. float32x4_t r4{vdupq_n_f32(0.0f)}; { const float32x4_t pf4{vdupq_n_f32(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); size_t td{m >> 2}; size_t j{0u}; @@ -299,64 +416,69 @@ void Resample_(const InterpState *state, const float *REST /* f = fil + pf*phd */ const float32x4_t f4 = vmlaq_f32(vld1q_f32(&fil[j]), pf4, vld1q_f32(&phd[j])); /* r += f*src */ - r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[j])); + r4 = vmlaq_f32(r4, f4, vld1q_f32(&src[pos+j])); j += 4; } while(--td); } r4 = vaddq_f32(r4, vrev64q_f32(r4)); - out_sample = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0); + const float output{vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> -void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) +void Mix_(const al::span InSamples,const al::span OutBuffer, + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, aligned_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, realign_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, - aligned_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp index 0aa5d5fba..df42823a7 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse.cpp @@ -1,15 +1,25 @@ #include "config.h" +#include #include -#include +#include +#include +#include +#include #include +#include #include "alnumeric.h" +#include "alspan.h" #include "core/bsinc_defs.h" +#include "core/bufferline.h" #include "core/cubic_defs.h" +#include "core/mixer/hrtfdefs.h" +#include "core/resampler_limits.h" #include "defs.h" #include "hrtfbase.h" +#include "opthelpers.h" struct SSETag; struct CubicTag; @@ -31,42 +41,48 @@ constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; -#define MLA4(x, y, z) _mm_add_ps(x, _mm_mul_ps(y, z)) +force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept +{ return _mm_add_ps(x, _mm_mul_ps(y, z)); } -inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const ConstHrirSpan Coeffs, - const float left, const float right) +inline void ApplyCoeffs(const al::span Values, const size_t IrSize, + const ConstHrirSpan Coeffs, const float left, const float right) { - const __m128 lrlr{_mm_setr_ps(left, right, left, right)}; - ASSUME(IrSize >= MinIrLength); + ASSUME(IrSize <= HrirLength); + const auto lrlr = _mm_setr_ps(left, right, left, right); + /* Round up the IR size to a multiple of 2 for SIMD (2 IRs for 2 channels + * is 4 floats), to avoid cutting the last sample for odd IR counts. The + * underlying HRIR is a fixed-size multiple of 2, any extra samples are + * either 0 (silence) or more IR samples that get applied for "free". + */ + const auto count4 = size_t{(IrSize+1) >> 1}; + /* This isn't technically correct to test alignment, but it's true for * systems that support SSE, which is the only one that needs to know the * alignment of Values (which alternates between 8- and 16-byte aligned). */ - if(!(reinterpret_cast(Values)&15)) + if(!(reinterpret_cast(Values.data())&15)) { - for(size_t i{0};i < IrSize;i += 2) - { - const __m128 coeffs{_mm_load_ps(Coeffs[i].data())}; - __m128 vals{_mm_load_ps(Values[i].data())}; - vals = MLA4(vals, lrlr, coeffs); - _mm_store_ps(Values[i].data(), vals); - } + const auto vals4 = al::span{reinterpret_cast<__m128*>(Values[0].data()), count4}; + const auto coeffs4 = al::span{reinterpret_cast(Coeffs[0].data()), count4}; + + std::transform(vals4.cbegin(), vals4.cend(), coeffs4.cbegin(), vals4.begin(), + [lrlr](const __m128 &val, const __m128 &coeff) -> __m128 + { return vmadd(val, coeff, lrlr); }); } else { - __m128 imp0, imp1; - __m128 coeffs{_mm_load_ps(Coeffs[0].data())}; - __m128 vals{_mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data()))}; - imp0 = _mm_mul_ps(lrlr, coeffs); + auto coeffs = _mm_load_ps(Coeffs[0].data()); + auto vals = _mm_loadl_pi(_mm_setzero_ps(), reinterpret_cast<__m64*>(Values[0].data())); + auto imp0 = _mm_mul_ps(lrlr, coeffs); vals = _mm_add_ps(imp0, vals); _mm_storel_pi(reinterpret_cast<__m64*>(Values[0].data()), vals); - size_t td{((IrSize+1)>>1) - 1}; + size_t td{count4 - 1}; size_t i{1}; do { coeffs = _mm_load_ps(Coeffs[i+1].data()); vals = _mm_load_ps(Values[i].data()); - imp1 = _mm_mul_ps(lrlr, coeffs); + const auto imp1 = _mm_mul_ps(lrlr, coeffs); imp0 = _mm_shuffle_ps(imp0, imp1, _MM_SHUFFLE(1, 0, 3, 2)); vals = _mm_add_ps(imp0, vals); _mm_store_ps(Values[i].data(), vals); @@ -80,37 +96,38 @@ inline void ApplyCoeffs(float2 *RESTRICT Values, const size_t IrSize, const Cons } } -force_inline void MixLine(const al::span InSamples, float *RESTRICT dst, - float &CurrentGain, const float TargetGain, const float delta, const size_t min_len, - const size_t aligned_len, size_t Counter) +force_inline void MixLine(const al::span InSamples, const al::span dst, + float &CurrentGain, const float TargetGain, const float delta, const size_t fade_len, + const size_t realign_len, size_t Counter) { - float gain{CurrentGain}; - const float step{(TargetGain-gain) * delta}; + const auto step = float{(TargetGain-CurrentGain) * delta}; size_t pos{0}; - if(!(std::abs(step) > std::numeric_limits::epsilon())) - gain = TargetGain; - else + if(std::abs(step) > std::numeric_limits::epsilon()) { - float step_count{0.0f}; + const auto gain = float{CurrentGain}; + auto step_count = float{0.0f}; /* Mix with applying gain steps in aligned multiples of 4. */ - if(size_t todo{min_len >> 2}) + if(const size_t todo{fade_len >> 2}) { - const __m128 four4{_mm_set1_ps(4.0f)}; - const __m128 step4{_mm_set1_ps(step)}; - const __m128 gain4{_mm_set1_ps(gain)}; - __m128 step_count4{_mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f)}; - do { - const __m128 val4{_mm_load_ps(&InSamples[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; + const auto four4 = _mm_set1_ps(4.0f); + const auto step4 = _mm_set1_ps(step); + const auto gain4 = _mm_set1_ps(gain); + auto step_count4 = _mm_setr_ps(0.0f, 1.0f, 2.0f, 3.0f); - /* dry += val * (gain + step*step_count) */ - dry4 = MLA4(dry4, val4, MLA4(gain4, step4, step_count4)); + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.first(todo); + const auto out4 = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4,step4,four4,&step_count4](const __m128 val4, __m128 dry4) -> __m128 + { + /* dry += val * (gain + step*step_count) */ + dry4 = vmadd(dry4, val4, vmadd(gain4, step4, step_count4)); + step_count4 = _mm_add_ps(step_count4, four4); + return dry4; + }); + pos += in4.size()*4; - _mm_store_ps(&dst[pos], dry4); - step_count4 = _mm_add_ps(step_count4, four4); - pos += 4; - } while(--todo); /* NOTE: step_count4 now represents the next four counts after the * last four mixed samples, so the lowest element represents the * next step count to apply. @@ -118,210 +135,252 @@ force_inline void MixLine(const al::span InSamples, float *RESTRICT step_count = _mm_cvtss_f32(step_count4); } /* Mix with applying left over gain steps that aren't aligned multiples of 4. */ - for(size_t leftover{min_len&3};leftover;++pos,--leftover) + if(const size_t leftover{fade_len&3}) { - dst[pos] += InSamples[pos] * (gain + step*step_count); - step_count += 1.0f; + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [gain,step,&step_count](const float val, float dry) noexcept -> float + { + dry += val * (gain + step*step_count); + step_count += 1.0f; + return dry; + }); + pos += leftover; + } + if(pos < Counter) + { + CurrentGain = gain + step*step_count; + return; } - if(pos == Counter) - gain = TargetGain; - else - gain += step*step_count; /* Mix until pos is aligned with 4 or the mix is done. */ - for(size_t leftover{aligned_len&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; - } - CurrentGain = gain; + if(const size_t leftover{realign_len&3}) + { + const auto in = InSamples.subspan(pos, leftover); + const auto out = dst.subspan(pos); - if(!(std::abs(gain) > GainSilenceThreshold)) + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); + pos += leftover; + } + } + CurrentGain = TargetGain; + + if(!(std::abs(TargetGain) > GainSilenceThreshold)) return; if(size_t todo{(InSamples.size()-pos) >> 2}) { - const __m128 gain4{_mm_set1_ps(gain)}; - do { - const __m128 val4{_mm_load_ps(&InSamples[pos])}; - __m128 dry4{_mm_load_ps(&dst[pos])}; - dry4 = _mm_add_ps(dry4, _mm_mul_ps(val4, gain4)); - _mm_store_ps(&dst[pos], dry4); - pos += 4; - } while(--todo); + const auto in4 = al::span{reinterpret_cast(InSamples.data()), + InSamples.size()/4}.last(todo); + const auto out = dst.subspan(pos); + const auto out4 = al::span{reinterpret_cast<__m128*>(out.data()), out.size()/4}; + + const auto gain4 = _mm_set1_ps(TargetGain); + std::transform(in4.begin(), in4.end(), out4.begin(), out4.begin(), + [gain4](const __m128 val4, const __m128 dry4) -> __m128 + { return vmadd(dry4, val4, gain4); }); + pos += in4.size()*4; + } + if(const size_t leftover{(InSamples.size()-pos)&3}) + { + const auto in = InSamples.last(leftover); + const auto out = dst.subspan(pos); + + std::transform(in.begin(), in.end(), out.begin(), out.begin(), + [TargetGain](const float val, const float dry) noexcept -> float + { return dry + val*TargetGain; }); } - for(size_t leftover{(InSamples.size()-pos)&3};leftover;++pos,--leftover) - dst[pos] += InSamples[pos] * gain; } } // namespace template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); - const CubicCoefficients *RESTRICT filter = al::assume_aligned<16>(state->cubic.filter); + const auto filter = std::get(*state).filter; - src -= 1; - for(float &out_sample : dst) + size_t pos{MaxResamplerEdge-1}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter]() -> float { - const uint pi{frac >> CubicPhaseDiffBits}; + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; const __m128 pf4{_mm_set1_ps(pf)}; /* Apply the phase interpolated filter. */ /* f = fil + pf*phd */ - const __m128 f4 = MLA4(_mm_load_ps(filter[pi].mCoeffs), pf4, - _mm_load_ps(filter[pi].mDeltas)); + const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, + _mm_load_ps(filter[pi].mDeltas.data())); /* r = f*src */ - __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(src))}; + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - out_sample = _mm_cvtss_f32(r4); + const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const __m128 sf4{_mm_set1_ps(state->bsinc.sf)}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto sf4 = _mm_set1_ps(bsinc.sf); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(4_uz*BSincPhaseCount*m); + + ASSUME(bsinc.l <= MaxResamplerEdge); + auto pos = size_t{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,sf4,m,filter]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the scale and phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; + auto r4 = _mm_setzero_ps(); { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - const float *RESTRICT scd{fil + BSincPhaseCount*2*m}; - const float *RESTRICT spd{scd + m}; - size_t td{m >> 2}; - size_t j{0u}; + const auto pf4 = _mm_set1_ps(pf); + const auto fil = filter.subspan(2_uz*pi*m); + const auto phd = fil.subspan(m); + const auto scd = fil.subspan(2_uz*BSincPhaseCount*m); + const auto spd = scd.subspan(m); + auto td = size_t{m >> 2}; + auto j = size_t{0}; do { /* f = ((fil + sf*scd) + pf*(phd + sf*spd)) */ - const __m128 f4 = MLA4( - MLA4(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])), - pf4, MLA4(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j]))); + const __m128 f4 = vmadd( + vmadd(_mm_load_ps(&fil[j]), sf4, _mm_load_ps(&scd[j])), + pf4, vmadd(_mm_load_ps(&phd[j]), sf4, _mm_load_ps(&spd[j]))); /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); + r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - out_sample = _mm_cvtss_f32(r4); + const auto output = _mm_cvtss_f32(r4); frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void Resample_(const InterpState *state, const float *RESTRICT src, uint frac, - const uint increment, const al::span dst) +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) { - const float *const filter{state->bsinc.filter}; - const size_t m{state->bsinc.m}; + const auto &bsinc = std::get(*state); + const auto m = size_t{bsinc.m}; ASSUME(m > 0); + ASSUME(m <= MaxResamplerPadding); ASSUME(frac < MixerFracOne); - src -= state->bsinc.l; - for(float &out_sample : dst) + const auto filter = bsinc.filter.first(2_uz*m*BSincPhaseCount); + + ASSUME(bsinc.l <= MaxResamplerEdge); + size_t pos{MaxResamplerEdge-bsinc.l}; + std::generate(dst.begin(), dst.end(), [&pos,&frac,src,increment,filter,m]() -> float { // Calculate the phase index and factor. - const uint pi{frac >> BSincPhaseDiffBits}; + const size_t pi{frac >> BSincPhaseDiffBits}; ASSUME(pi < BSincPhaseCount); const float pf{static_cast(frac&BSincPhaseDiffMask) * (1.0f/BSincPhaseDiffOne)}; // Apply the phase interpolated filter. - __m128 r4{_mm_setzero_ps()}; + auto r4 = _mm_setzero_ps(); { - const __m128 pf4{_mm_set1_ps(pf)}; - const float *RESTRICT fil{filter + m*pi*2}; - const float *RESTRICT phd{fil + m}; - size_t td{m >> 2}; - size_t j{0u}; + const auto pf4 = _mm_set1_ps(pf); + const auto fil = filter.subspan(2_uz*m*pi); + const auto phd = fil.subspan(m); + auto td = size_t{m >> 2}; + auto j = size_t{0}; do { /* f = fil + pf*phd */ - const __m128 f4 = MLA4(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); + const auto f4 = vmadd(_mm_load_ps(&fil[j]), pf4, _mm_load_ps(&phd[j])); /* r += f*src */ - r4 = MLA4(r4, f4, _mm_loadu_ps(&src[j])); + r4 = vmadd(r4, f4, _mm_loadu_ps(&src[pos+j])); j += 4; } while(--td); } r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); - out_sample = _mm_cvtss_f32(r4); + const auto output = _mm_cvtss_f32(r4); frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } + return output; + }); } template<> -void MixHrtf_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize) -{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, BufferSize); } +void MixHrtf_(const al::span InSamples, const al::span AccumSamples, + const uint IrSize, const MixHrtfFilter *hrtfparams, const size_t SamplesToDo) +{ MixHrtfBase(InSamples, AccumSamples, IrSize, hrtfparams, SamplesToDo); } template<> -void MixHrtfBlend_(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const HrtfFilter *oldparams, const MixHrtfFilter *newparams, const size_t BufferSize) +void MixHrtfBlend_(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo) { MixHrtfBlendBase(InSamples, AccumSamples, IrSize, oldparams, newparams, - BufferSize); + SamplesToDo); } template<> void MixDirectHrtf_(const FloatBufferSpan LeftOut, const FloatBufferSpan RightOut, - const al::span InSamples, float2 *AccumSamples, - float *TempBuf, HrtfChannelState *ChanState, const size_t IrSize, const size_t BufferSize) + const al::span InSamples, const al::span AccumSamples, + const al::span TempBuf, const al::span ChanState, + const size_t IrSize, const size_t SamplesToDo) { MixDirectHrtfBase(LeftOut, RightOut, InSamples, AccumSamples, TempBuf, ChanState, - IrSize, BufferSize); + IrSize, SamplesToDo); } template<> void Mix_(const al::span InSamples, const al::span OutBuffer, - float *CurrentGains, const float *TargetGains, const size_t Counter, const size_t OutPos) + const al::span CurrentGains, const al::span TargetGains, + const size_t Counter, const size_t OutPos) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; + auto curgains = CurrentGains.begin(); + auto targetgains = TargetGains.cbegin(); for(FloatBufferLine &output : OutBuffer) - MixLine(InSamples, al::assume_aligned<16>(output.data()+OutPos), *CurrentGains++, - *TargetGains++, delta, min_len, aligned_len, Counter); + MixLine(InSamples, al::span{output}.subspan(OutPos), *curgains++, *targetgains++, delta, + fade_len, realign_len, Counter); } template<> -void Mix_(const al::span InSamples, float *OutBuffer, float &CurrentGain, - const float TargetGain, const size_t Counter) +void Mix_(const al::span InSamples, const al::span OutBuffer, + float &CurrentGain, const float TargetGain, const size_t Counter) { const float delta{(Counter > 0) ? 1.0f / static_cast(Counter) : 0.0f}; - const auto min_len = minz(Counter, InSamples.size()); - const auto aligned_len = minz((min_len+3) & ~size_t{3}, InSamples.size()) - min_len; + const auto fade_len = std::min(Counter, InSamples.size()); + const auto realign_len = std::min((fade_len+3_uz) & ~3_uz, InSamples.size()) - fade_len; - MixLine(InSamples, al::assume_aligned<16>(OutBuffer), CurrentGain, TargetGain, delta, min_len, - aligned_len, Counter); + MixLine(InSamples, OutBuffer, CurrentGain, TargetGain, delta, fade_len, realign_len, Counter); } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp index edaaf7a1a..c79d50cab 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse2.cpp @@ -23,19 +23,42 @@ #include #include +#include +#include +#include +#include + #include "alnumeric.h" +#include "alspan.h" +#include "core/cubic_defs.h" +#include "core/resampler_limits.h" #include "defs.h" +#include "opthelpers.h" struct SSE2Tag; struct LerpTag; +struct CubicTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE2__) #pragma GCC target("sse2") #endif +using uint = unsigned int; + +namespace { + +constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; +constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; +constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; + +force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept +{ return _mm_add_ps(x, _mm_mul_ps(y, z)); } + +} // namespace + template<> -void Resample_(const InterpState*, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -44,47 +67,148 @@ void Resample_(const InterpState*, const float *RESTRICT src, u const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) uint pos_[4], frac_[4]; - InitPosArrays(frac, increment, frac_, pos_); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - auto dst_iter = dst.begin(); - for(size_t todo{dst.size()>>2};todo;--todo) + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4]() -> __m128 { - const int pos0{_mm_cvtsi128_si32(pos4)}; - const int pos1{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))}; - const int pos2{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))}; - const int pos3{_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))}; - const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; - const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; + const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); + const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); + const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); + const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; + const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* val1 + (val2-val1)*mu */ const __m128 r0{_mm_sub_ps(val2, val1)}; const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - _mm_store_ps(dst_iter, out); - dst_iter += 4; + frac4 = _mm_add_epi32(frac4, increment4); + pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); + frac4 = _mm_and_si128(frac4, fracMask4); + return out; + }); + + if(size_t todo{dst.size()&3}) + { + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + const auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment]() + { + const float smp{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; + + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return smp; + }); + } +} + +template<> +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + ASSUME(frac < MixerFracOne); + + const auto filter = std::get(*state).filter; + + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; + const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; + const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; + const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; + + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); + __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), + static_cast(frac_[2]), static_cast(frac_[3]))}; + __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), + static_cast(pos_[2]), static_cast(pos_[3]))}; + + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] + { + const auto pos0 = static_cast(_mm_cvtsi128_si32(pos4)); + const auto pos1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 4))); + const auto pos2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 8))); + const auto pos3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pos4, 12))); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val0{_mm_loadu_ps(&src[pos0])}; + const __m128 val1{_mm_loadu_ps(&src[pos1])}; + const __m128 val2{_mm_loadu_ps(&src[pos2])}; + const __m128 val3{_mm_loadu_ps(&src[pos3])}; + + const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; + const auto pi0 = static_cast(_mm_cvtsi128_si32(pi4)); + const auto pi1 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 4))); + const auto pi2 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 8))); + const auto pi3 = static_cast(_mm_cvtsi128_si32(_mm_srli_si128(pi4, 12))); + ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); + ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); + + const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), + fracDiffOne4)}; + + __m128 r0{_mm_mul_ps(val0, + vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)), + _mm_load_ps(filter[pi0].mDeltas.data())))}; + __m128 r1{_mm_mul_ps(val1, + vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)), + _mm_load_ps(filter[pi1].mDeltas.data())))}; + __m128 r2{_mm_mul_ps(val2, + vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)), + _mm_load_ps(filter[pi2].mDeltas.data())))}; + __m128 r3{_mm_mul_ps(val3, + vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)), + _mm_load_ps(filter[pi3].mDeltas.data())))}; + + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); - } + return r0; + }); - if(size_t todo{dst.size()&3}) + if(const size_t todo{dst.size()&3}) { - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - do { - *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] + { + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + const __m128 pf4{_mm_set1_ps(pf)}; + + const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, + _mm_load_ps(filter[pi].mDeltas.data())); + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; + + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + const float output{_mm_cvtss_f32(r4)}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return output; + }); } } diff --git a/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp b/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp index 8ccd9fd3a..345330454 100644 --- a/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp +++ b/Engine/lib/openal-soft/core/mixer/mixer_sse41.cpp @@ -24,19 +24,42 @@ #include #include +#include +#include +#include +#include + #include "alnumeric.h" +#include "alspan.h" +#include "core/cubic_defs.h" +#include "core/resampler_limits.h" #include "defs.h" +#include "opthelpers.h" struct SSE4Tag; struct LerpTag; +struct CubicTag; #if defined(__GNUC__) && !defined(__clang__) && !defined(__SSE4_1__) #pragma GCC target("sse4.1") #endif +using uint = unsigned int; + +namespace { + +constexpr uint CubicPhaseDiffBits{MixerFracBits - CubicPhaseBits}; +constexpr uint CubicPhaseDiffOne{1 << CubicPhaseDiffBits}; +constexpr uint CubicPhaseDiffMask{CubicPhaseDiffOne - 1u}; + +force_inline __m128 vmadd(const __m128 x, const __m128 y, const __m128 z) noexcept +{ return _mm_add_ps(x, _mm_mul_ps(y, z)); } + +} // namespace + template<> -void Resample_(const InterpState*, const float *RESTRICT src, uint frac, +void Resample_(const InterpState*, const al::span src, uint frac, const uint increment, const al::span dst) { ASSUME(frac < MixerFracOne); @@ -45,35 +68,34 @@ void Resample_(const InterpState*, const float *RESTRICT src, u const __m128 fracOne4{_mm_set1_ps(1.0f/MixerFracOne)}; const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; - alignas(16) uint pos_[4], frac_[4]; - InitPosArrays(frac, increment, frac_, pos_); + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge, frac, increment, al::span{frac_}, al::span{pos_}); __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), static_cast(frac_[2]), static_cast(frac_[3]))}; __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), static_cast(pos_[2]), static_cast(pos_[3]))}; - auto dst_iter = dst.begin(); - for(size_t todo{dst.size()>>2};todo;--todo) + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] { - const int pos0{_mm_extract_epi32(pos4, 0)}; - const int pos1{_mm_extract_epi32(pos4, 1)}; - const int pos2{_mm_extract_epi32(pos4, 2)}; - const int pos3{_mm_extract_epi32(pos4, 3)}; - const __m128 val1{_mm_setr_ps(src[pos0 ], src[pos1 ], src[pos2 ], src[pos3 ])}; - const __m128 val2{_mm_setr_ps(src[pos0+1], src[pos1+1], src[pos2+1], src[pos3+1])}; + const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); + const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); + const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); + const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val1{_mm_setr_ps(src[pos0], src[pos1], src[pos2], src[pos3])}; + const __m128 val2{_mm_setr_ps(src[pos0+1_uz], src[pos1+1_uz], src[pos2+1_uz], src[pos3+1_uz])}; /* val1 + (val2-val1)*mu */ const __m128 r0{_mm_sub_ps(val2, val1)}; const __m128 mu{_mm_mul_ps(_mm_cvtepi32_ps(frac4), fracOne4)}; const __m128 out{_mm_add_ps(val1, _mm_mul_ps(mu, r0))}; - _mm_store_ps(dst_iter, out); - dst_iter += 4; - frac4 = _mm_add_epi32(frac4, increment4); pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); frac4 = _mm_and_si128(frac4, fracMask4); - } + return out; + }); if(size_t todo{dst.size()&3}) { @@ -81,15 +103,117 @@ void Resample_(const InterpState*, const float *RESTRICT src, u * four samples, so the lowest element is the next position to * resample. */ - src += static_cast(_mm_cvtsi128_si32(pos4)); + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; frac = static_cast(_mm_cvtsi128_si32(frac4)); - do { - *(dst_iter++) = lerpf(src[0], src[1], static_cast(frac) * (1.0f/MixerFracOne)); + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment] + { + const float smp{lerpf(src[pos+0], src[pos+1], + static_cast(frac) * (1.0f/MixerFracOne))}; frac += increment; - src += frac>>MixerFracBits; + pos += frac>>MixerFracBits; frac &= MixerFracMask; - } while(--todo); + return smp; + }); + } +} + +template<> +void Resample_(const InterpState *state, const al::span src, + uint frac, const uint increment, const al::span dst) +{ + ASSUME(frac < MixerFracOne); + + const auto filter = std::get(*state).filter; + + const __m128i increment4{_mm_set1_epi32(static_cast(increment*4))}; + const __m128i fracMask4{_mm_set1_epi32(MixerFracMask)}; + const __m128 fracDiffOne4{_mm_set1_ps(1.0f/CubicPhaseDiffOne)}; + const __m128i fracDiffMask4{_mm_set1_epi32(CubicPhaseDiffMask)}; + + std::array pos_{}, frac_{}; + InitPosArrays(MaxResamplerEdge-1, frac, increment, al::span{frac_}, al::span{pos_}); + __m128i frac4{_mm_setr_epi32(static_cast(frac_[0]), static_cast(frac_[1]), + static_cast(frac_[2]), static_cast(frac_[3]))}; + __m128i pos4{_mm_setr_epi32(static_cast(pos_[0]), static_cast(pos_[1]), + static_cast(pos_[2]), static_cast(pos_[3]))}; + + auto vecout = al::span{reinterpret_cast<__m128*>(dst.data()), dst.size()/4}; + std::generate(vecout.begin(), vecout.end(), [=,&pos4,&frac4] + { + const auto pos0 = static_cast(_mm_extract_epi32(pos4, 0)); + const auto pos1 = static_cast(_mm_extract_epi32(pos4, 1)); + const auto pos2 = static_cast(_mm_extract_epi32(pos4, 2)); + const auto pos3 = static_cast(_mm_extract_epi32(pos4, 3)); + ASSUME(pos0 <= pos1); ASSUME(pos1 <= pos2); ASSUME(pos2 <= pos3); + const __m128 val0{_mm_loadu_ps(&src[pos0])}; + const __m128 val1{_mm_loadu_ps(&src[pos1])}; + const __m128 val2{_mm_loadu_ps(&src[pos2])}; + const __m128 val3{_mm_loadu_ps(&src[pos3])}; + + const __m128i pi4{_mm_srli_epi32(frac4, CubicPhaseDiffBits)}; + const auto pi0 = static_cast(_mm_extract_epi32(pi4, 0)); + const auto pi1 = static_cast(_mm_extract_epi32(pi4, 1)); + const auto pi2 = static_cast(_mm_extract_epi32(pi4, 2)); + const auto pi3 = static_cast(_mm_extract_epi32(pi4, 3)); + ASSUME(pi0 < CubicPhaseCount); ASSUME(pi1 < CubicPhaseCount); + ASSUME(pi2 < CubicPhaseCount); ASSUME(pi3 < CubicPhaseCount); + + const __m128 pf4{_mm_mul_ps(_mm_cvtepi32_ps(_mm_and_si128(frac4, fracDiffMask4)), + fracDiffOne4)}; + + __m128 r0{_mm_mul_ps(val0, + vmadd(_mm_load_ps(filter[pi0].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(0, 0, 0, 0)), + _mm_load_ps(filter[pi0].mDeltas.data())))}; + __m128 r1{_mm_mul_ps(val1, + vmadd(_mm_load_ps(filter[pi1].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(1, 1, 1, 1)), + _mm_load_ps(filter[pi1].mDeltas.data())))}; + __m128 r2{_mm_mul_ps(val2, + vmadd(_mm_load_ps(filter[pi2].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(2, 2, 2, 2)), + _mm_load_ps(filter[pi2].mDeltas.data())))}; + __m128 r3{_mm_mul_ps(val3, + vmadd(_mm_load_ps(filter[pi3].mCoeffs.data()), + _mm_shuffle_ps(pf4, pf4, _MM_SHUFFLE(3, 3, 3, 3)), + _mm_load_ps(filter[pi3].mDeltas.data())))}; + + _MM_TRANSPOSE4_PS(r0, r1, r2, r3); + r0 = _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3)); + + frac4 = _mm_add_epi32(frac4, increment4); + pos4 = _mm_add_epi32(pos4, _mm_srli_epi32(frac4, MixerFracBits)); + frac4 = _mm_and_si128(frac4, fracMask4); + return r0; + }); + + if(const size_t todo{dst.size()&3}) + { + auto pos = size_t{static_cast(_mm_cvtsi128_si32(pos4))}; + frac = static_cast(_mm_cvtsi128_si32(frac4)); + + auto out = dst.last(todo); + std::generate(out.begin(), out.end(), [&pos,&frac,src,increment,filter] + { + const uint pi{frac >> CubicPhaseDiffBits}; ASSUME(pi < CubicPhaseCount); + const float pf{static_cast(frac&CubicPhaseDiffMask) * (1.0f/CubicPhaseDiffOne)}; + const __m128 pf4{_mm_set1_ps(pf)}; + + const __m128 f4 = vmadd(_mm_load_ps(filter[pi].mCoeffs.data()), pf4, + _mm_load_ps(filter[pi].mDeltas.data())); + __m128 r4{_mm_mul_ps(f4, _mm_loadu_ps(&src[pos]))}; + + r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3))); + r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4)); + const float output{_mm_cvtss_f32(r4)}; + + frac += increment; + pos += frac>>MixerFracBits; + frac &= MixerFracMask; + return output; + }); } } diff --git a/Engine/lib/openal-soft/core/resampler_limits.h b/Engine/lib/openal-soft/core/resampler_limits.h index 9d4cefdae..a32807e8e 100644 --- a/Engine/lib/openal-soft/core/resampler_limits.h +++ b/Engine/lib/openal-soft/core/resampler_limits.h @@ -5,8 +5,8 @@ * Note that the padding is symmetric (half at the beginning and half at the * end)! */ -constexpr int MaxResamplerPadding{48}; +constexpr unsigned int MaxResamplerPadding{48}; -constexpr int MaxResamplerEdge{MaxResamplerPadding >> 1}; +constexpr unsigned int MaxResamplerEdge{MaxResamplerPadding >> 1}; #endif /* CORE_RESAMPLER_LIMITS_H */ diff --git a/Engine/lib/openal-soft/core/rtkit.cpp b/Engine/lib/openal-soft/core/rtkit.cpp index ff944ebf2..70b5e12bb 100644 --- a/Engine/lib/openal-soft/core/rtkit.cpp +++ b/Engine/lib/openal-soft/core/rtkit.cpp @@ -30,14 +30,14 @@ #include "rtkit.h" -#include +#include #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include -#include +#include #include #include #ifdef __linux__ @@ -153,29 +153,29 @@ int rtkit_get_int_property(DBusConnection *connection, const char *propname, lon } // namespace -int rtkit_get_max_realtime_priority(DBusConnection *connection) +int rtkit_get_max_realtime_priority(DBusConnection *system_bus) { long long retval{}; - int err{rtkit_get_int_property(connection, "MaxRealtimePriority", &retval)}; + int err{rtkit_get_int_property(system_bus, "MaxRealtimePriority", &retval)}; return err < 0 ? err : static_cast(retval); } -int rtkit_get_min_nice_level(DBusConnection *connection, int *min_nice_level) +int rtkit_get_min_nice_level(DBusConnection *system_bus, int *min_nice_level) { long long retval{}; - int err{rtkit_get_int_property(connection, "MinNiceLevel", &retval)}; + int err{rtkit_get_int_property(system_bus, "MinNiceLevel", &retval)}; if(err >= 0) *min_nice_level = static_cast(retval); return err; } -long long rtkit_get_rttime_usec_max(DBusConnection *connection) +long long rtkit_get_rttime_usec_max(DBusConnection *system_bus) { long long retval{}; - int err{rtkit_get_int_property(connection, "RTTimeUSecMax", &retval)}; + int err{rtkit_get_int_property(system_bus, "RTTimeUSecMax", &retval)}; return err < 0 ? err : retval; } -int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) +int rtkit_make_realtime(DBusConnection *system_bus, pid_t thread, int priority) { if(thread == 0) thread = _gettid(); @@ -195,7 +195,7 @@ int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) if(!ready) return -ENOMEM; dbus::Error error; - dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); @@ -205,7 +205,7 @@ int rtkit_make_realtime(DBusConnection *connection, pid_t thread, int priority) return 0; } -int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_level) +int rtkit_make_high_priority(DBusConnection *system_bus, pid_t thread, int nice_level) { if(thread == 0) thread = _gettid(); @@ -225,7 +225,7 @@ int rtkit_make_high_priority(DBusConnection *connection, pid_t thread, int nice_ if(!ready) return -ENOMEM; dbus::Error error; - dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(connection, m.get(), -1, + dbus::MessagePtr r{dbus_connection_send_with_reply_and_block(system_bus, m.get(), -1, &error.get())}; if(!r) return translate_error(error->name); diff --git a/Engine/lib/openal-soft/core/storage_formats.cpp b/Engine/lib/openal-soft/core/storage_formats.cpp new file mode 100644 index 000000000..51f64644c --- /dev/null +++ b/Engine/lib/openal-soft/core/storage_formats.cpp @@ -0,0 +1,85 @@ + +#include "config.h" + +#include "storage_formats.h" + +#include + + +const char *NameFromFormat(FmtType type) noexcept +{ + switch(type) + { + case FmtUByte: return "UInt8"; + case FmtShort: return "Int16"; + case FmtInt: return "Int32"; + case FmtFloat: return "Float"; + case FmtDouble: return "Double"; + case FmtMulaw: return "muLaw"; + case FmtAlaw: return "aLaw"; + case FmtIMA4: return "IMA4 ADPCM"; + case FmtMSADPCM: return "MS ADPCM"; + } + return ""; +} + +const char *NameFromFormat(FmtChannels channels) noexcept +{ + switch(channels) + { + case FmtMono: return "Mono"; + case FmtStereo: return "Stereo"; + case FmtRear: return "Rear"; + case FmtQuad: return "Quadraphonic"; + case FmtX51: return "Surround 5.1"; + case FmtX61: return "Surround 6.1"; + case FmtX71: return "Surround 7.1"; + case FmtBFormat2D: return "B-Format 2D"; + case FmtBFormat3D: return "B-Format 3D"; + case FmtUHJ2: return "UHJ2"; + case FmtUHJ3: return "UHJ3"; + case FmtUHJ4: return "UHJ4"; + case FmtSuperStereo: return "Super Stereo"; + case FmtMonoDup: return "Mono (dup)"; + } + return ""; +} + +uint BytesFromFmt(FmtType type) noexcept +{ + switch(type) + { + case FmtUByte: return sizeof(std::uint8_t); + case FmtShort: return sizeof(std::int16_t); + case FmtInt: return sizeof(std::int32_t); + case FmtFloat: return sizeof(float); + case FmtDouble: return sizeof(double); + case FmtMulaw: return sizeof(std::uint8_t); + case FmtAlaw: return sizeof(std::uint8_t); + case FmtIMA4: break; + case FmtMSADPCM: break; + } + return 0; +} + +uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept +{ + switch(chans) + { + case FmtMono: return 1; + case FmtStereo: return 2; + case FmtRear: return 2; + case FmtQuad: return 4; + case FmtX51: return 6; + case FmtX61: return 7; + case FmtX71: return 8; + case FmtBFormat2D: return (ambiorder*2) + 1; + case FmtBFormat3D: return (ambiorder+1) * (ambiorder+1); + case FmtUHJ2: return 2; + case FmtUHJ3: return 3; + case FmtUHJ4: return 4; + case FmtSuperStereo: return 2; + case FmtMonoDup: return 1; + } + return 0; +} diff --git a/Engine/lib/openal-soft/core/storage_formats.h b/Engine/lib/openal-soft/core/storage_formats.h new file mode 100644 index 000000000..acced258a --- /dev/null +++ b/Engine/lib/openal-soft/core/storage_formats.h @@ -0,0 +1,54 @@ +#ifndef CORE_STORAGE_FORMATS_H +#define CORE_STORAGE_FORMATS_H + +using uint = unsigned int; + +/* Storable formats */ +enum FmtType : unsigned char { + FmtUByte, + FmtShort, + FmtInt, + FmtFloat, + FmtDouble, + FmtMulaw, + FmtAlaw, + FmtIMA4, + FmtMSADPCM, +}; +enum FmtChannels : unsigned char { + FmtMono, + FmtStereo, + FmtRear, + FmtQuad, + FmtX51, /* (WFX order) */ + FmtX61, /* (WFX order) */ + FmtX71, /* (WFX order) */ + FmtBFormat2D, + FmtBFormat3D, + FmtUHJ2, /* 2-channel UHJ, aka "BHJ", stereo-compatible */ + FmtUHJ3, /* 3-channel UHJ, aka "THJ" */ + FmtUHJ4, /* 4-channel UHJ, aka "PHJ" */ + FmtSuperStereo, /* Stereo processed with Super Stereo. */ + FmtMonoDup, /* Mono duplicated for left/right separation */ +}; + +enum class AmbiLayout : unsigned char { + FuMa, + ACN, +}; +enum class AmbiScaling : unsigned char { + FuMa, + SN3D, + N3D, + UHJ, +}; + +const char *NameFromFormat(FmtType type) noexcept; +const char *NameFromFormat(FmtChannels channels) noexcept; + +uint BytesFromFmt(FmtType type) noexcept; +uint ChannelsFromFmt(FmtChannels chans, uint ambiorder) noexcept; +inline uint FrameSizeFromFmt(FmtChannels chans, FmtType type, uint ambiorder) noexcept +{ return ChannelsFromFmt(chans, ambiorder) * BytesFromFmt(type); } + +#endif /* CORE_STORAGE_FORMATS_H */ diff --git a/Engine/lib/openal-soft/core/uhjfilter.cpp b/Engine/lib/openal-soft/core/uhjfilter.cpp index df50956a3..a2c3cd459 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.cpp +++ b/Engine/lib/openal-soft/core/uhjfilter.cpp @@ -4,54 +4,150 @@ #include "uhjfilter.h" #include -#include +#include +#include +#include +#include #include "alcomplex.h" -#include "alnumeric.h" +#include "almalloc.h" +#include "alnumbers.h" +#include "core/bufferline.h" #include "opthelpers.h" +#include "pffft.h" #include "phase_shifter.h" - - -UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; -UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; +#include "vector.h" namespace { -const PhaseShifterT PShiftLq{}; -const PhaseShifterT PShiftHq{}; +template +constexpr auto assume_aligned_span(const al::span s) noexcept -> al::span +{ return al::span{al::assume_aligned(s.data()), s.size()}; } + +/* Convolution is implemented using a segmented overlap-add method. The filter + * response is broken up into multiple segments of 128 samples, and each + * segment has an FFT applied with a 256-sample buffer (the latter half left + * silent) to get its frequency-domain response. + * + * Input samples are similarly broken up into 128-sample segments, with a 256- + * sample FFT applied to each new incoming segment to get its frequency-domain + * response. A history of FFT'd input segments is maintained, equal to the + * number of filter response segments. + * + * To apply the convolution, each filter response segment is convolved with its + * paired input segment (using complex multiplies, far cheaper than time-domain + * FIRs), accumulating into an FFT buffer. The input history is then shifted to + * align with later filter response segments for the next input segment. + * + * An inverse FFT is then applied to the accumulated FFT buffer to get a 256- + * sample time-domain response for output, which is split in two halves. The + * first half is the 128-sample output, and the second half is a 128-sample + * (really, 127) delayed extension, which gets added to the output next time. + * Convolving two time-domain responses of length N results in a time-domain + * signal of length N*2 - 1, and this holds true regardless of the convolution + * being applied in the frequency domain, so these "overflow" samples need to + * be accounted for. + */ +template +struct SegmentedFilter { + static constexpr size_t sFftLength{256}; + static constexpr size_t sSampleLength{sFftLength / 2}; + static constexpr size_t sNumSegments{N/sSampleLength}; + static_assert(N >= sFftLength); + static_assert((N % sSampleLength) == 0); + + PFFFTSetup mFft; + alignas(16) std::array mFilterData; + + SegmentedFilter() : mFft{sFftLength, PFFFT_REAL} + { + static constexpr size_t fft_size{N}; + + /* To set up the filter, we first need to generate the desired + * response (not reversed). + */ + auto tmpBuffer = std::vector(fft_size, 0.0); + for(std::size_t i{0};i < fft_size/2;++i) + { + const int k{int{fft_size/2} - static_cast(i*2 + 1)}; + + const double w{2.0*al::numbers::pi * static_cast(i*2 + 1) + / double{fft_size}}; + const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w) + - 0.0106411*std::cos(3.0*w)}; + + const double pk{al::numbers::pi * static_cast(k)}; + tmpBuffer[i*2 + 1] = window * (1.0-std::cos(pk)) / pk; + } + + /* The segments of the filter are converted back to the frequency + * domain, each on their own (0 stuffed). + */ + using complex_d = std::complex; + auto fftBuffer = std::vector(sFftLength); + auto fftTmp = al::vector(sFftLength); + auto filter = mFilterData.begin(); + for(size_t s{0};s < sNumSegments;++s) + { + const auto tmpspan = al::span{tmpBuffer}.subspan(sSampleLength*s, sSampleLength); + auto iter = std::copy_n(tmpspan.cbegin(), tmpspan.size(), fftBuffer.begin()); + std::fill(iter, fftBuffer.end(), complex_d{}); + forward_fft(fftBuffer); + + /* Convert to zdomain data for PFFFT, scaled by the FFT length so + * the iFFT result will be normalized. + */ + for(size_t i{0};i < sSampleLength;++i) + { + fftTmp[i*2 + 0] = static_cast(fftBuffer[i].real()) / float{sFftLength}; + fftTmp[i*2 + 1] = static_cast((i == 0) ? fftBuffer[sSampleLength].real() + : fftBuffer[i].imag()) / float{sFftLength}; + } + mFft.zreorder(fftTmp.data(), al::to_address(filter), PFFFT_BACKWARD); + filter += sFftLength; + } + } +}; template -struct GetPhaseShifter; -template<> -struct GetPhaseShifter { static auto& Get() noexcept { return PShiftLq; } }; -template<> -struct GetPhaseShifter { static auto& Get() noexcept { return PShiftHq; } }; +const SegmentedFilter gSegmentedFilter; +template +const PhaseShifterT PShifter; -constexpr float square(float x) noexcept -{ return x*x; } /* Filter coefficients for the 'base' all-pass IIR, which applies a frequency- * dependent phase-shift of N degrees. The output of the filter requires a 1- * sample delay. */ constexpr std::array Filter1Coeff{{ - square(0.6923878f), square(0.9360654322959f), square(0.9882295226860f), - square(0.9987488452737f) + 0.479400865589f, 0.876218493539f, 0.976597589508f, 0.997499255936f }}; /* Filter coefficients for the offset all-pass IIR, which applies a frequency- * dependent phase-shift of N+90 degrees. */ constexpr std::array Filter2Coeff{{ - square(0.4021921162426f), square(0.8561710882420f), square(0.9722909545651f), - square(0.9952884791278f) + 0.161758498368f, 0.733028932341f, 0.945349700329f, 0.990599156684f }}; } // namespace +void UhjAllPassFilter::processOne(const al::span coeffs, float x) +{ + auto state = mState; + for(size_t i{0};i < 4;++i) + { + const float y{x*coeffs[i] + state[i].z[0]}; + state[i].z[0] = state[i].z[1]; + state[i].z[1] = y*coeffs[i] - x; + x = y; + } + mState = state; +} + void UhjAllPassFilter::process(const al::span coeffs, - const al::span src, const bool updateState, float *RESTRICT dst) + const al::span src, const bool updateState, const al::span dst) { auto state = mState; @@ -66,7 +162,7 @@ void UhjAllPassFilter::process(const al::span coeffs, } return x; }; - std::transform(src.begin(), src.end(), dst, proc_sample); + std::transform(src.begin(), src.end(), dst.begin(), proc_sample); if(updateState) LIKELY mState = state; } @@ -92,68 +188,138 @@ template void UhjEncoder::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { - const auto &PShift = GetPhaseShifter::Get(); + static constexpr auto &Filter = gSegmentedFilter; + static_assert(sFftLength == Filter.sFftLength); + static_assert(sSegmentSize == Filter.sSampleLength); + static_assert(sNumSegments == Filter.sNumSegments); ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])}; + const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; + const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; + const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; - std::copy_n(winput, SamplesToDo, mW.begin()+sFilterDelay); - std::copy_n(xinput, SamplesToDo, mX.begin()+sFilterDelay); - std::copy_n(yinput, SamplesToDo, mY.begin()+sFilterDelay); + std::copy_n(winput.begin(), SamplesToDo, mW.begin()+sFilterDelay); + std::copy_n(xinput.begin(), SamplesToDo, mX.begin()+sFilterDelay); + std::copy_n(yinput.begin(), SamplesToDo, mY.begin()+sFilterDelay); /* S = 0.9396926*W + 0.1855740*X */ - for(size_t i{0};i < SamplesToDo;++i) - mS[i] = 0.9396926f*mW[i] + 0.1855740f*mX[i]; + std::transform(mW.begin(), mW.begin()+SamplesToDo, mX.begin(), mS.begin(), + [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mD. */ - std::transform(winput, winput+SamplesToDo, xinput, mWX.begin() + sWXInOffset, - [](const float w, const float x) noexcept -> float - { return -0.3420201f*w + 0.5098604f*x; }); - PShift.process({mD.data(), SamplesToDo}, mWX.data()); + auto dstore = mD.begin(); + size_t curseg{mCurrentSegment}; + for(size_t base{0};base < SamplesToDo;) + { + const size_t todo{std::min(sSegmentSize-mFifoPos, SamplesToDo-base)}; + auto wseg = winput.subspan(base, todo); + auto xseg = xinput.subspan(base, todo); + auto wxio = al::span{mWXInOut}.subspan(mFifoPos, todo); + + /* Copy out the samples that were previously processed by the FFT. */ + dstore = std::copy_n(wxio.begin(), todo, dstore); + + /* Transform the non-delayed input and store in the front half of the + * filter input. + */ + std::transform(wseg.begin(), wseg.end(), xseg.begin(), wxio.begin(), + [](const float w, const float x) noexcept -> float + { return -0.3420201f*w + 0.5098604f*x; }); + + mFifoPos += todo; + base += todo; + + /* Check whether the input buffer is filled with new samples. */ + if(mFifoPos < sSegmentSize) break; + mFifoPos = 0; + + /* Copy the new input to the next history segment, clearing the back + * half of the segment, and convert to the frequency domain. + */ + auto input = mWXHistory.begin() + curseg*sFftLength; + std::copy_n(mWXInOut.begin(), sSegmentSize, input); + std::fill_n(input+sSegmentSize, sSegmentSize, 0.0f); + + Filter.mFft.transform(al::to_address(input), al::to_address(input), mWorkData.data(), + PFFFT_FORWARD); + + /* Convolve each input segment with its IR filter counterpart (aligned + * in time, from newest to oldest). + */ + mFftBuffer.fill(0.0f); + auto filter = Filter.mFilterData.begin(); + for(size_t s{curseg};s < sNumSegments;++s) + { + Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += sFftLength; + filter += sFftLength; + } + input = mWXHistory.begin(); + for(size_t s{0};s < curseg;++s) + { + Filter.mFft.zconvolve_accumulate(al::to_address(input), al::to_address(filter), + mFftBuffer.data()); + input += sFftLength; + filter += sFftLength; + } + + /* Convert back to samples, writing to the output and storing the extra + * for next time. + */ + Filter.mFft.transform(mFftBuffer.data(), mFftBuffer.data(), mWorkData.data(), + PFFFT_BACKWARD); + + std::transform(mFftBuffer.begin(), mFftBuffer.begin()+sSegmentSize, + mWXInOut.begin()+sSegmentSize, mWXInOut.begin(), std::plus{}); + std::copy_n(mFftBuffer.begin()+sSegmentSize, sSegmentSize, mWXInOut.begin()+sSegmentSize); + + /* Shift the input history. */ + curseg = curseg ? (curseg-1) : (sNumSegments-1); + } + mCurrentSegment = curseg; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ - for(size_t i{0};i < SamplesToDo;++i) - mD[i] = mD[i] + 0.6554516f*mY[i]; + std::transform(mD.begin(), mD.begin()+SamplesToDo, mY.begin(), mD.begin(), + [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Copy the future samples to the front for next time. */ std::copy(mW.cbegin()+SamplesToDo, mW.cbegin()+SamplesToDo+sFilterDelay, mW.begin()); std::copy(mX.cbegin()+SamplesToDo, mX.cbegin()+SamplesToDo+sFilterDelay, mX.begin()); std::copy(mY.cbegin()+SamplesToDo, mY.cbegin()+SamplesToDo+sFilterDelay, mY.begin()); - std::copy(mWX.cbegin()+SamplesToDo, mWX.cbegin()+SamplesToDo+sWXInOffset, mWX.begin()); /* Apply a delay to the existing output to align with the input delay. */ - auto *delayBuffer = mDirectDelay.data(); + auto delayBuffer = mDirectDelay.begin(); for(float *buffer : {LeftOut, RightOut}) { - float *distbuf{al::assume_aligned<16>(delayBuffer->data())}; + const auto distbuf = assume_aligned_span<16>(al::span{*delayBuffer}); ++delayBuffer; - float *inout{al::assume_aligned<16>(buffer)}; - auto inout_end = inout + SamplesToDo; - if(SamplesToDo >= sFilterDelay) LIKELY + const auto inout = al::span{al::assume_aligned<16>(buffer), SamplesToDo}; + if(SamplesToDo >= sFilterDelay) { - auto delay_end = std::rotate(inout, inout_end - sFilterDelay, inout_end); - std::swap_ranges(inout, delay_end, distbuf); + auto delay_end = std::rotate(inout.begin(), inout.end() - sFilterDelay, inout.end()); + std::swap_ranges(inout.begin(), delay_end, distbuf.begin()); } else { - auto delay_start = std::swap_ranges(inout, inout_end, distbuf); - std::rotate(distbuf, delay_start, distbuf + sFilterDelay); + auto delay_start = std::swap_ranges(inout.begin(), inout.end(), distbuf.begin()); + std::rotate(distbuf.begin(), delay_start, distbuf.begin() + sFilterDelay); } } /* Combine the direct signal with the produced output. */ /* Left = (S + D)/2.0 */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) left[i] += (mS[i] + mD[i]) * 0.5f; + /* Right = (S - D)/2.0 */ - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) right[i] += (mS[i] - mD[i]) * 0.5f; } @@ -176,47 +342,51 @@ void UhjEncoderIIR::encode(float *LeftOut, float *RightOut, const al::span InSamples, const size_t SamplesToDo) { ASSUME(SamplesToDo > 0); + ASSUME(SamplesToDo <= BufferLineSize); - const float *RESTRICT winput{al::assume_aligned<16>(InSamples[0])}; - const float *RESTRICT xinput{al::assume_aligned<16>(InSamples[1])}; - const float *RESTRICT yinput{al::assume_aligned<16>(InSamples[2])}; + const auto winput = al::span{al::assume_aligned<16>(InSamples[0]), SamplesToDo}; + const auto xinput = al::span{al::assume_aligned<16>(InSamples[1]), SamplesToDo}; + const auto yinput = al::span{al::assume_aligned<16>(InSamples[2]), SamplesToDo}; /* S = 0.9396926*W + 0.1855740*X */ - std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return 0.9396926f*w + 0.1855740f*x; }); - mFilter1WX.process(Filter1Coeff, {mTemp.data(), SamplesToDo}, true, mS.data()+1); + mFilter1WX.process(Filter1Coeff, al::span{mTemp}.first(SamplesToDo), true, + al::span{mS}.subspan(1)); mS[0] = mDelayWX; mDelayWX = mS[SamplesToDo]; /* Precompute j(-0.3420201*W + 0.5098604*X) and store in mWX. */ - std::transform(winput, winput+SamplesToDo, xinput, mTemp.begin(), + std::transform(winput.begin(), winput.end(), xinput.begin(), mTemp.begin(), [](const float w, const float x) noexcept { return -0.3420201f*w + 0.5098604f*x; }); - mFilter2WX.process(Filter2Coeff, {mTemp.data(), SamplesToDo}, true, mWX.data()); + mFilter2WX.process(Filter2Coeff, al::span{mTemp}.first(SamplesToDo), true, mWX); /* Apply filter1 to Y and store in mD. */ - mFilter1Y.process(Filter1Coeff, {yinput, SamplesToDo}, SamplesToDo, mD.data()+1); + mFilter1Y.process(Filter1Coeff, yinput, true, al::span{mD}.subspan(1)); mD[0] = mDelayY; mDelayY = mD[SamplesToDo]; /* D = j(-0.3420201*W + 0.5098604*X) + 0.6554516*Y */ - for(size_t i{0};i < SamplesToDo;++i) - mD[i] = mWX[i] + 0.6554516f*mD[i]; + std::transform(mWX.begin(), mWX.begin()+SamplesToDo, mD.begin(), mD.begin(), + [](const float jwx, const float y) noexcept { return jwx + 0.6554516f*y; }); /* Apply the base filter to the existing output to align with the processed * signal. */ - mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, mTemp.data()+1); + mFilter1Direct[0].process(Filter1Coeff, {LeftOut, SamplesToDo}, true, + al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[0]; mDirectDelay[0] = mTemp[SamplesToDo]; /* Left = (S + D)/2.0 */ - float *RESTRICT left{al::assume_aligned<16>(LeftOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto left = al::span{al::assume_aligned<16>(LeftOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) left[i] = (mS[i] + mD[i])*0.5f + mTemp[i]; - mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, mTemp.data()+1); + mFilter1Direct[1].process(Filter1Coeff, {RightOut, SamplesToDo}, true, + al::span{mTemp}.subspan(1)); mTemp[0] = mDirectDelay[1]; mDirectDelay[1] = mTemp[SamplesToDo]; /* Right = (S - D)/2.0 */ - float *RESTRICT right{al::assume_aligned<16>(RightOut)}; - for(size_t i{0};i < SamplesToDo;i++) + const auto right = al::span{al::assume_aligned<16>(RightOut), SamplesToDo}; + for(size_t i{0};i < SamplesToDo;++i) right[i] = (mS[i] - mD[i])*0.5f + mTemp[i]; } @@ -240,31 +410,29 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); - const auto &PShift = GetPhaseShifter::Get(); + constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; - const float *RESTRICT t{al::assume_aligned<16>(samples[2])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; + const auto t = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = left[i] - right[i]; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); /* T */ - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mT[i] = t[i]; + std::copy(t.begin(), t.end(), mT.begin()); } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); @@ -272,21 +440,22 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); - PShift.process({xoutput, samplesToDo}, mTemp.data()); + PShift.process(xoutput, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.981532f*mS[i] + 0.197484f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); + /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.418496f*mS[i] - xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); - PShift.process({youtput, samplesToDo}, mTemp.data()); + PShift.process(youtput, mTemp); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ for(size_t i{0};i < samplesToDo;++i) @@ -294,10 +463,10 @@ void UhjDecoder::decode(const al::span samples, const size_t samplesT if(samples.size() > 3) { - float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Z = 1.023332*Q */ - for(size_t i{0};i < samplesToDo;++i) - zoutput[i] = 1.023332f*zoutput[i]; + std::transform(zoutput.begin(), zoutput.end(), zoutput.begin(), + [](const float q) noexcept { return 1.023332f*q; }); } } @@ -307,70 +476,67 @@ void UhjDecoderIIR::decode(const al::span samples, const size_t samplesT static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; /* S = Left + Right */ - for(size_t i{0};i < samplesToDo;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* D = Left - Right */ - for(size_t i{0};i < samplesToDo;++i) - mD[i] = left[i] - right[i]; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), std::minus{}); } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo+sInputPadding}; /* Precompute j(0.828331*D + 0.767820*T) and store in xoutput. */ - std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, mTemp.begin(), + std::transform(mD.cbegin(), mD.cbegin()+sInputPadding+samplesToDo, youtput.begin(), + mTemp.begin(), [](const float d, const float t) noexcept { return 0.828331f*d + 0.767820f*t; }); - mFilter2DT.process(Filter2Coeff, {mTemp.data(), samplesToDo}, updateState, xoutput); + if(mFirstRun) mFilter2DT.processOne(Filter2Coeff, mTemp[0]); + mFilter2DT.process(Filter2Coeff, al::span{mTemp}.subspan(1,samplesToDo), updateState, xoutput); /* Apply filter1 to S and store in mTemp. */ - mTemp[0] = mDelayS; - mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayS = mTemp[samplesToDo]; + mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* W = 0.981532*S + 0.197484*j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.981532f*mTemp[i] + 0.197484f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jdt) noexcept { return 0.981532f*s + 0.197484f*jdt; }); /* X = 0.418496*S - j(0.828331*D + 0.767820*T) */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.418496f*mTemp[i] - xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jdt) noexcept { return 0.418496f*s - jdt; }); /* Apply filter1 to (0.795968*D - 0.676392*T) and store in mTemp. */ - std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput, youtput, + std::transform(mD.cbegin(), mD.cbegin()+samplesToDo, youtput.begin(), youtput.begin(), [](const float d, const float t) noexcept { return 0.795968f*d - 0.676392f*t; }); - mTemp[0] = mDelayDT; - mFilter1DT.process(Filter1Coeff, {youtput, samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayDT = mTemp[samplesToDo]; + mFilter1DT.process(Filter1Coeff, youtput.first(samplesToDo), updateState, mTemp); /* Precompute j*S and store in youtput. */ - mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput); + if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); + mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Y = 0.795968*D - 0.676392*T + j(0.186633*S) */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = mTemp[i] + 0.186633f*youtput[i]; - + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float dt, const float js) noexcept { return dt + 0.186633f*js; }); if(samples.size() > 3) { - float *RESTRICT zoutput{al::assume_aligned<16>(samples[3])}; + const auto zoutput = al::span{al::assume_aligned<16>(samples[3]), samplesToDo}; /* Apply filter1 to Q and store in mTemp. */ - mTemp[0] = mDelayQ; - mFilter1Q.process(Filter1Coeff, {zoutput, samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayQ = mTemp[samplesToDo]; + mFilter1Q.process(Filter1Coeff, zoutput, updateState, mTemp); /* Z = 1.023332*Q */ - for(size_t i{0};i < samplesToDo;++i) - zoutput[i] = 1.023332f*mTemp[i]; + std::transform(mTemp.begin(), mTemp.end(), zoutput.begin(), + [](const float q) noexcept { return 1.023332f*q; }); } + + mFirstRun = false; } @@ -392,16 +558,16 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa { static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); - const auto &PShift = GetPhaseShifter::Get(); + constexpr auto &PShift = PShifter; ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. @@ -410,53 +576,60 @@ void UhjStereoDecoder::decode(const al::span samples, const size_t sa const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wcurrent; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), + [wcurrent](const float l, const float r) noexcept { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - for(size_t i{0};i < samplesToDo;++i) - { - mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); - fi += 1.0f; - } - for(size_t i{samplesToDo};i < samplesToDo+sInputPadding;++i) - mD[i] = (left[i] - right[i]) * wtarget; + + const auto lfade = left.first(samplesToDo); + auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), + [wcurrent,wstep,&fi](const float l, const float r) noexcept + { + const float ret{(l-r) * (wcurrent + wstep*fi)}; + fi += 1.0f; + return ret; + }); + + const auto lend = left.subspan(samplesToDo); + const auto rend = right.subspan(samplesToDo); + std::transform(lend.begin(), lend.end(), rend.begin(), dstore, + [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Precompute j*D and store in xoutput. */ auto tmpiter = std::copy(mDTHistory.cbegin(), mDTHistory.cend(), mTemp.begin()); std::copy_n(mD.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mDTHistory.size(), mDTHistory.begin()); - PShift.process({xoutput, samplesToDo}, mTemp.data()); + PShift.process(xoutput, mTemp); /* W = 0.6098637*S - 0.6896511*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.6098637f*mS[i] - 0.6896511f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; }); /* X = 0.8624776*S + 0.7626955*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.8624776f*mS[i] + 0.7626955f*xoutput[i]; + std::transform(mS.begin(), mS.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ tmpiter = std::copy(mSHistory.cbegin(), mSHistory.cend(), mTemp.begin()); std::copy_n(mS.cbegin(), samplesToDo+sInputPadding, tmpiter); if(updateState) LIKELY std::copy_n(mTemp.cbegin()+samplesToDo, mSHistory.size(), mSHistory.begin()); - PShift.process({youtput, samplesToDo}, mTemp.data()); + PShift.process(youtput, mTemp); /* Y = 1.6822415*w*D - 0.2156194*j*S */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = 1.6822415f*mD[i] - 0.2156194f*youtput[i]; + std::transform(mD.begin(), mD.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; }); } void UhjStereoDecoderIIR::decode(const al::span samples, const size_t samplesToDo, @@ -465,13 +638,13 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa static_assert(sInputPadding <= sMaxPadding, "Filter padding is too large"); ASSUME(samplesToDo > 0); + ASSUME(samplesToDo <= BufferLineSize); { - const float *RESTRICT left{al::assume_aligned<16>(samples[0])}; - const float *RESTRICT right{al::assume_aligned<16>(samples[1])}; + const auto left = al::span{al::assume_aligned<16>(samples[0]), samplesToDo+sInputPadding}; + const auto right = al::span{al::assume_aligned<16>(samples[1]), samplesToDo+sInputPadding}; - for(size_t i{0};i < samplesToDo;++i) - mS[i] = left[i] + right[i]; + std::transform(left.begin(), left.end(), right.begin(), mS.begin(), std::plus{}); /* Pre-apply the width factor to the difference signal D. Smoothly * interpolate when it changes. @@ -480,53 +653,63 @@ void UhjStereoDecoderIIR::decode(const al::span samples, const size_t sa const float wcurrent{(mCurrentWidth < 0.0f) ? wtarget : mCurrentWidth}; if(wtarget == wcurrent || !updateState) { - for(size_t i{0};i < samplesToDo;++i) - mD[i] = (left[i] - right[i]) * wcurrent; + std::transform(left.begin(), left.end(), right.begin(), mD.begin(), + [wcurrent](const float l, const float r) noexcept + { return (l-r) * wcurrent; }); mCurrentWidth = wcurrent; } else { const float wstep{(wtarget - wcurrent) / static_cast(samplesToDo)}; float fi{0.0f}; - for(size_t i{0};i < samplesToDo;++i) - { - mD[i] = (left[i] - right[i]) * (wcurrent + wstep*fi); - fi += 1.0f; - } + + const auto lfade = left.first(samplesToDo); + auto dstore = std::transform(lfade.begin(), lfade.begin(), right.begin(), mD.begin(), + [wcurrent,wstep,&fi](const float l, const float r) noexcept + { + const float ret{(l-r) * (wcurrent + wstep*fi)}; + fi += 1.0f; + return ret; + }); + + const auto lend = left.subspan(samplesToDo); + const auto rend = right.subspan(samplesToDo); + std::transform(lend.begin(), lend.end(), rend.begin(), dstore, + [wtarget](const float l, const float r) noexcept { return (l-r) * wtarget; }); mCurrentWidth = wtarget; } } - float *RESTRICT woutput{al::assume_aligned<16>(samples[0])}; - float *RESTRICT xoutput{al::assume_aligned<16>(samples[1])}; - float *RESTRICT youtput{al::assume_aligned<16>(samples[2])}; + const auto woutput = al::span{al::assume_aligned<16>(samples[0]), samplesToDo}; + const auto xoutput = al::span{al::assume_aligned<16>(samples[1]), samplesToDo}; + const auto youtput = al::span{al::assume_aligned<16>(samples[2]), samplesToDo}; /* Apply filter1 to S and store in mTemp. */ - mTemp[0] = mDelayS; - mFilter1S.process(Filter1Coeff, {mS.data(), samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayS = mTemp[samplesToDo]; + mFilter1S.process(Filter1Coeff, al::span{mS}.first(samplesToDo), updateState, mTemp); /* Precompute j*D and store in xoutput. */ - mFilter2D.process(Filter2Coeff, {mD.data(), samplesToDo}, updateState, xoutput); + if(mFirstRun) mFilter2D.processOne(Filter2Coeff, mD[0]); + mFilter2D.process(Filter2Coeff, al::span{mD}.subspan(1, samplesToDo), updateState, xoutput); /* W = 0.6098637*S - 0.6896511*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - woutput[i] = 0.6098637f*mTemp[i] - 0.6896511f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), woutput.begin(), + [](const float s, const float jd) noexcept { return 0.6098637f*s - 0.6896511f*jd; }); /* X = 0.8624776*S + 0.7626955*j*w*D */ - for(size_t i{0};i < samplesToDo;++i) - xoutput[i] = 0.8624776f*mTemp[i] + 0.7626955f*xoutput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, xoutput.begin(), xoutput.begin(), + [](const float s, const float jd) noexcept { return 0.8624776f*s + 0.7626955f*jd; }); /* Precompute j*S and store in youtput. */ - mFilter2S.process(Filter2Coeff, {mS.data(), samplesToDo}, updateState, youtput); + if(mFirstRun) mFilter2S.processOne(Filter2Coeff, mS[0]); + mFilter2S.process(Filter2Coeff, al::span{mS}.subspan(1, samplesToDo), updateState, youtput); /* Apply filter1 to D and store in mTemp. */ - mTemp[0] = mDelayD; - mFilter1D.process(Filter1Coeff, {mD.data(), samplesToDo}, updateState, mTemp.data()+1); - if(updateState) LIKELY mDelayD = mTemp[samplesToDo]; + mFilter1D.process(Filter1Coeff, al::span{mD}.first(samplesToDo), updateState, mTemp); /* Y = 1.6822415*w*D - 0.2156194*j*S */ - for(size_t i{0};i < samplesToDo;++i) - youtput[i] = 1.6822415f*mTemp[i] - 0.2156194f*youtput[i]; + std::transform(mTemp.begin(), mTemp.begin()+samplesToDo, youtput.begin(), youtput.begin(), + [](const float d, const float js) noexcept { return 1.6822415f*d - 0.2156194f*js; }); + + mFirstRun = false; } diff --git a/Engine/lib/openal-soft/core/uhjfilter.h b/Engine/lib/openal-soft/core/uhjfilter.h index df3080944..55be31b22 100644 --- a/Engine/lib/openal-soft/core/uhjfilter.h +++ b/Engine/lib/openal-soft/core/uhjfilter.h @@ -2,42 +2,50 @@ #define CORE_UHJFILTER_H #include +#include +#include -#include "almalloc.h" #include "alspan.h" #include "bufferline.h" -static constexpr size_t UhjLength256{256}; -static constexpr size_t UhjLength512{512}; +inline constexpr std::size_t UhjLength256{256}; +inline constexpr std::size_t UhjLength512{512}; -enum class UhjQualityType : uint8_t { +enum class UhjQualityType : std::uint8_t { IIR = 0, FIR256, FIR512, Default = IIR }; -extern UhjQualityType UhjDecodeQuality; -extern UhjQualityType UhjEncodeQuality; +inline UhjQualityType UhjDecodeQuality{UhjQualityType::Default}; +inline UhjQualityType UhjEncodeQuality{UhjQualityType::Default}; struct UhjAllPassFilter { struct AllPassState { /* Last two delayed components for direct form II. */ - float z[2]; + std::array z{}; }; std::array mState; + void processOne(const al::span coeffs, float x); void process(const al::span coeffs, const al::span src, - const bool update, float *RESTRICT dst); + const bool update, const al::span dst); }; struct UhjEncoderBase { + UhjEncoderBase() = default; + UhjEncoderBase(const UhjEncoderBase&) = delete; + UhjEncoderBase(UhjEncoderBase&&) = delete; virtual ~UhjEncoderBase() = default; - virtual size_t getDelay() noexcept = 0; + void operator=(const UhjEncoderBase&) = delete; + void operator=(UhjEncoderBase&&) = delete; + + virtual std::size_t getDelay() noexcept = 0; /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input @@ -45,12 +53,15 @@ struct UhjEncoderBase { * with an additional +3dB boost). */ virtual void encode(float *LeftOut, float *RightOut, - const al::span InSamples, const size_t SamplesToDo) = 0; + const al::span InSamples, const std::size_t SamplesToDo) = 0; }; -template +template struct UhjEncoder final : public UhjEncoderBase { - static constexpr size_t sFilterDelay{N/2}; + static constexpr std::size_t sFftLength{256}; + static constexpr std::size_t sSegmentSize{sFftLength/2}; + static constexpr std::size_t sNumSegments{N/sSegmentSize}; + static constexpr std::size_t sFilterDelay{N/2 + sSegmentSize}; /* Delays and processing storage for the input signal. */ alignas(16) std::array mW{}; @@ -60,15 +71,16 @@ struct UhjEncoder final : public UhjEncoderBase { alignas(16) std::array mS{}; alignas(16) std::array mD{}; - /* History and temp storage for the FIR filter. New samples should be - * written to index sFilterDelay*2 - 1. - */ - static constexpr size_t sWXInOffset{sFilterDelay*2 - 1}; - alignas(16) std::array mWX{}; + /* History and temp storage for the convolution filter. */ + std::size_t mFifoPos{}, mCurrentSegment{}; + alignas(16) std::array mWXInOut{}; + alignas(16) std::array mFftBuffer{}; + alignas(16) std::array mWorkData{}; + alignas(16) std::array mWXHistory{}; alignas(16) std::array,2> mDirectDelay{}; - size_t getDelay() noexcept override { return sFilterDelay; } + std::size_t getDelay() noexcept override { return sFilterDelay; } /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input @@ -76,13 +88,11 @@ struct UhjEncoder final : public UhjEncoderBase { * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, - const size_t SamplesToDo) override; - - DEF_NEWDEL(UhjEncoder) + const std::size_t SamplesToDo) final; }; struct UhjEncoderIIR final : public UhjEncoderBase { - static constexpr size_t sFilterDelay{1}; + static constexpr std::size_t sFilterDelay{1}; /* Processing storage for the input signal. */ alignas(16) std::array mS{}; @@ -98,7 +108,7 @@ struct UhjEncoderIIR final : public UhjEncoderBase { std::array mFilter1Direct; std::array mDirectDelay{}; - size_t getDelay() noexcept override { return sFilterDelay; } + std::size_t getDelay() noexcept override { return sFilterDelay; } /** * Encodes a 2-channel UHJ (stereo-compatible) signal from a B-Format input @@ -106,22 +116,26 @@ struct UhjEncoderIIR final : public UhjEncoderBase { * with an additional +3dB boost). */ void encode(float *LeftOut, float *RightOut, const al::span InSamples, - const size_t SamplesToDo) override; - - DEF_NEWDEL(UhjEncoderIIR) + const std::size_t SamplesToDo) final; }; struct DecoderBase { - static constexpr size_t sMaxPadding{256}; + static constexpr std::size_t sMaxPadding{256}; /* For 2-channel UHJ, shelf filters should use these LF responses. */ static constexpr float sWLFScale{0.661f}; static constexpr float sXYLFScale{1.293f}; + DecoderBase() = default; + DecoderBase(const DecoderBase&) = delete; + DecoderBase(DecoderBase&&) = delete; virtual ~DecoderBase() = default; - virtual void decode(const al::span samples, const size_t samplesToDo, + void operator=(const DecoderBase&) = delete; + void operator=(DecoderBase&&) = delete; + + virtual void decode(const al::span samples, const std::size_t samplesToDo, const bool updateState) = 0; /** @@ -131,10 +145,10 @@ struct DecoderBase { float mWidthControl{0.593f}; }; -template +template struct UhjDecoder final : public DecoderBase { /* The number of extra sample frames needed for input. */ - static constexpr size_t sInputPadding{N/2}; + static constexpr std::size_t sInputPadding{N/2}; alignas(16) std::array mS{}; alignas(16) std::array mD{}; @@ -153,24 +167,23 @@ struct UhjDecoder final : public DecoderBase { * reconstructed from 2-channel UHJ should not be run through a normal * B-Format decoder, as it needs different shelf filters. */ - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjDecoder) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; struct UhjDecoderIIR final : public DecoderBase { - /* FIXME: These IIR decoder filters actually have a 1-sample delay on the - * non-filtered components, which is not reflected in the source latency - * value. sInputPadding is 0, however, because it doesn't need any extra - * input samples. + /* These IIR decoder filters normally have a 1-sample delay on the non- + * filtered components. However, the filtered components are made to skip + * the first output sample and take one future sample, which puts it ahead + * by one sample. The first filtered output sample is cut to align it with + * the first non-filtered sample, similar to the FIR filters. */ - static constexpr size_t sInputPadding{0}; + static constexpr std::size_t sInputPadding{1}; - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; - alignas(16) std::array mTemp{}; - float mDelayS{}, mDelayDT{}, mDelayQ{}; + bool mFirstRun{true}; + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2DT; @@ -178,15 +191,13 @@ struct UhjDecoderIIR final : public DecoderBase { UhjAllPassFilter mFilter2S; UhjAllPassFilter mFilter1Q; - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjDecoderIIR) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; -template +template struct UhjStereoDecoder final : public DecoderBase { - static constexpr size_t sInputPadding{N/2}; + static constexpr std::size_t sInputPadding{N/2}; float mCurrentWidth{-1.0f}; @@ -204,31 +215,27 @@ struct UhjStereoDecoder final : public DecoderBase { * should contain 3 channels, the first two being the left and right stereo * channels, and the third left empty. */ - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjStereoDecoder) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; struct UhjStereoDecoderIIR final : public DecoderBase { - static constexpr size_t sInputPadding{0}; + static constexpr std::size_t sInputPadding{1}; + bool mFirstRun{true}; float mCurrentWidth{-1.0f}; - alignas(16) std::array mS{}; - alignas(16) std::array mD{}; - alignas(16) std::array mTemp{}; - float mDelayS{}, mDelayD{}; + alignas(16) std::array mS{}; + alignas(16) std::array mD{}; + alignas(16) std::array mTemp{}; UhjAllPassFilter mFilter1S; UhjAllPassFilter mFilter2D; UhjAllPassFilter mFilter1D; UhjAllPassFilter mFilter2S; - void decode(const al::span samples, const size_t samplesToDo, - const bool updateState) override; - - DEF_NEWDEL(UhjStereoDecoderIIR) + void decode(const al::span samples, const std::size_t samplesToDo, + const bool updateState) final; }; #endif /* CORE_UHJFILTER_H */ diff --git a/Engine/lib/openal-soft/core/uiddefs.cpp b/Engine/lib/openal-soft/core/uiddefs.cpp index 244c01a5a..e93376763 100644 --- a/Engine/lib/openal-soft/core/uiddefs.cpp +++ b/Engine/lib/openal-soft/core/uiddefs.cpp @@ -4,14 +4,10 @@ #ifndef AL_NO_UID_DEFS -#if defined(HAVE_GUIDDEF_H) || defined(HAVE_INITGUID_H) +#if defined(HAVE_GUIDDEF_H) #define INITGUID #include -#ifdef HAVE_GUIDDEF_H #include -#else -#include -#endif DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x00, 0x00,0xaa,0x00,0x38,0x9b,0x71); @@ -19,12 +15,8 @@ DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80,0x DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf,0x08, 0x00,0xa0,0xc9,0x25,0xcd,0x16); DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xbcde0395, 0xe52f, 0x467c, 0x8e,0x3d, 0xc4,0x57,0x92,0x91,0x69,0x2e); -DEFINE_GUID(IID_IMMDeviceEnumerator, 0xa95664d2, 0x9614, 0x4f35, 0xa7,0x46, 0xde,0x8d,0xb6,0x36,0x17,0xe6); -DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1,0x78, 0xc2,0xf5,0x68,0xa7,0x03,0xb2); -DEFINE_GUID(IID_IAudioRenderClient, 0xf294acfc, 0x3146, 0x4483, 0xa7,0xbf, 0xad,0xdc,0xa7,0xc2,0x60,0xe2); -DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4,0xde, 0x18,0x5c,0x39,0x5c,0xd3,0x17); -#ifdef HAVE_WASAPI +#if defined(HAVE_WASAPI) && !defined(ALSOFT_UWP) #include #include #include diff --git a/Engine/lib/openal-soft/core/voice.cpp b/Engine/lib/openal-soft/core/voice.cpp index e8fbcccd3..9d2b34a07 100644 --- a/Engine/lib/openal-soft/core/voice.cpp +++ b/Engine/lib/openal-soft/core/voice.cpp @@ -9,16 +9,15 @@ #include #include #include +#include #include #include #include -#include +#include #include #include -#include "albyte.h" #include "alnumeric.h" -#include "aloptional.h" #include "alspan.h" #include "alstring.h" #include "ambidefs.h" @@ -51,26 +50,25 @@ struct NEONTag; #endif -static_assert(!(sizeof(DeviceBase::MixerBufferLine)&15), - "DeviceBase::MixerBufferLine must be a multiple of 16 bytes"); +static_assert(!(DeviceBase::MixerLineSize&3), "MixerLineSize must be a multiple of 4"); static_assert(!(MaxResamplerEdge&3), "MaxResamplerEdge is not a multiple of 4"); static_assert((BufferLineSize-1)/MaxPitch > 0, "MaxPitch is too large for BufferLineSize!"); static_assert((INT_MAX>>MixerFracBits)/MaxPitch > BufferLineSize, "MaxPitch and/or BufferLineSize are too large for MixerFracBits!"); -Resampler ResamplerDefault{Resampler::Cubic}; - namespace { using uint = unsigned int; using namespace std::chrono; +using namespace std::string_view_literals; -using HrtfMixerFunc = void(*)(const float *InSamples, float2 *AccumSamples, const uint IrSize, - const MixHrtfFilter *hrtfparams, const size_t BufferSize); -using HrtfMixerBlendFunc = void(*)(const float *InSamples, float2 *AccumSamples, - const uint IrSize, const HrtfFilter *oldparams, const MixHrtfFilter *newparams, - const size_t BufferSize); +using HrtfMixerFunc = void(*)(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const MixHrtfFilter *hrtfparams, + const size_t SamplesToDo); +using HrtfMixerBlendFunc = void(*)(const al::span InSamples, + const al::span AccumSamples, const uint IrSize, const HrtfFilter *oldparams, + const MixHrtfFilter *newparams, const size_t SamplesToDo); HrtfMixerFunc MixHrtfSamples{MixHrtf_}; HrtfMixerBlendFunc MixHrtfBlendSamples{MixHrtfBlend_}; @@ -129,42 +127,45 @@ inline HrtfMixerBlendFunc SelectHrtfBlendMixer() } // namespace -void Voice::InitMixer(al::optional resampler) +void Voice::InitMixer(std::optional resopt) { - if(resampler) + if(resopt) { struct ResamplerEntry { - const char name[16]; + const std::string_view name; const Resampler resampler; }; - constexpr ResamplerEntry ResamplerList[]{ - { "none", Resampler::Point }, - { "point", Resampler::Point }, - { "linear", Resampler::Linear }, - { "cubic", Resampler::Cubic }, - { "bsinc12", Resampler::BSinc12 }, - { "fast_bsinc12", Resampler::FastBSinc12 }, - { "bsinc24", Resampler::BSinc24 }, - { "fast_bsinc24", Resampler::FastBSinc24 }, + constexpr std::array ResamplerList{ + ResamplerEntry{"none"sv, Resampler::Point}, + ResamplerEntry{"point"sv, Resampler::Point}, + ResamplerEntry{"linear"sv, Resampler::Linear}, + ResamplerEntry{"spline"sv, Resampler::Spline}, + ResamplerEntry{"gaussian"sv, Resampler::Gaussian}, + ResamplerEntry{"bsinc12"sv, Resampler::BSinc12}, + ResamplerEntry{"fast_bsinc12"sv, Resampler::FastBSinc12}, + ResamplerEntry{"bsinc24"sv, Resampler::BSinc24}, + ResamplerEntry{"fast_bsinc24"sv, Resampler::FastBSinc24}, }; - const char *str{resampler->c_str()}; - if(al::strcasecmp(str, "bsinc") == 0) + std::string_view resampler{*resopt}; + if(al::case_compare(resampler, "cubic"sv) == 0 + || al::case_compare(resampler, "sinc4"sv) == 0 + || al::case_compare(resampler, "sinc8"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", str); - str = "bsinc12"; + WARN("Resampler option \"%s\" is deprecated, using gaussian\n", resopt->c_str()); + resampler = "gaussian"sv; } - else if(al::strcasecmp(str, "sinc4") == 0 || al::strcasecmp(str, "sinc8") == 0) + else if(al::case_compare(resampler, "bsinc"sv) == 0) { - WARN("Resampler option \"%s\" is deprecated, using cubic\n", str); - str = "cubic"; + WARN("Resampler option \"%s\" is deprecated, using bsinc12\n", resopt->c_str()); + resampler = "bsinc12"sv; } - auto iter = std::find_if(std::begin(ResamplerList), std::end(ResamplerList), - [str](const ResamplerEntry &entry) -> bool - { return al::strcasecmp(str, entry.name) == 0; }); - if(iter == std::end(ResamplerList)) - ERR("Invalid resampler: %s\n", str); + auto iter = std::find_if(ResamplerList.begin(), ResamplerList.end(), + [resampler](const ResamplerEntry &entry) -> bool + { return al::case_compare(resampler, entry.name) == 0; }); + if(iter == ResamplerList.end()) + ERR("Invalid resampler: %s\n", resopt->c_str()); else ResamplerDefault = iter->resampler; } @@ -179,7 +180,7 @@ void Voice::InitMixer(al::optional resampler) namespace { /* IMA ADPCM Stepsize table */ -constexpr int IMAStep_size[89] = { +constexpr std::array IMAStep_size{{ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, @@ -189,35 +190,35 @@ constexpr int IMAStep_size[89] = { 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,10442, 11487,12635,13899,15289,16818,18500,20350,22358,24633,27086,29794, 32767 -}; +}}; /* IMA4 ADPCM Codeword decode table */ -constexpr int IMA4Codeword[16] = { +constexpr std::array IMA4Codeword{{ 1, 3, 5, 7, 9, 11, 13, 15, -1,-3,-5,-7,-9,-11,-13,-15, -}; +}}; /* IMA4 ADPCM Step index adjust decode table */ -constexpr int IMA4Index_adjust[16] = { +constexpr std::arrayIMA4Index_adjust{{ -1,-1,-1,-1, 2, 4, 6, 8, -1,-1,-1,-1, 2, 4, 6, 8 -}; +}}; /* MSADPCM Adaption table */ -constexpr int MSADPCMAdaption[16] = { +constexpr std::array MSADPCMAdaption{{ 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 -}; +}}; /* MSADPCM Adaption Coefficient tables */ -constexpr int MSADPCMAdaptionCoeff[7][2] = { - { 256, 0 }, - { 512, -256 }, - { 0, 0 }, - { 192, 64 }, - { 240, 0 }, - { 460, -208 }, - { 392, -232 } +constexpr std::array MSADPCMAdaptionCoeff{ + std::array{256, 0}, + std::array{512, -256}, + std::array{ 0, 0}, + std::array{192, 64}, + std::array{240, 0}, + std::array{460, -208}, + std::array{392, -232} }; @@ -227,17 +228,16 @@ void SendSourceStoppedEvent(ContextBase *context, uint id) auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len < 1) return; - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::SourceStateChange)}; - evt->u.srcstate.id = id; - evt->u.srcstate.state = AsyncEvent::SrcState::Stop; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mId = id; + evt.mState = AsyncSrcState::Stop; ring->writeAdvance(1); } -const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *dst, - const al::span src, int type) +al::span DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, + const al::span dst, const al::span src, int type) { switch(type) { @@ -249,70 +249,91 @@ const float *DoFilters(BiquadFilter &lpfilter, BiquadFilter &hpfilter, float *ds case AF_LowPass: lpfilter.process(src, dst); hpfilter.clear(); - return dst; + return dst.first(src.size()); case AF_HighPass: lpfilter.clear(); hpfilter.process(src, dst); - return dst; + return dst.first(src.size()); case AF_BandPass: DualBiquad{lpfilter, hpfilter}.process(src, dst); - return dst; + return dst.first(src.size()); } - return src.data(); + return src; } template -inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src, const size_t srcChan, - const size_t srcOffset, const size_t srcStep, const size_t /*samplesPerBlock*/, - const size_t samplesToLoad) noexcept +inline void LoadSamples(const al::span dstSamples, const al::span srcData, + const size_t srcChan, const size_t srcOffset, const size_t srcStep, + const size_t samplesPerBlock [[maybe_unused]]) noexcept { - constexpr size_t sampleSize{sizeof(typename al::FmtTypeTraits::Type)}; - auto s = src + (srcOffset*srcStep + srcChan)*sampleSize; + using TypeTraits = al::FmtTypeTraits; + using SampleType = typename TypeTraits::Type; + static constexpr size_t sampleSize{sizeof(SampleType)}; + assert(srcChan < srcStep); + auto converter = TypeTraits{}; - al::LoadSampleArray(dstSamples, s, srcStep, samplesToLoad); + al::span src{reinterpret_cast(srcData.data()), + srcData.size()/sampleSize}; + auto ssrc = src.cbegin() + ptrdiff_t(srcOffset*srcStep); + std::generate(dstSamples.begin(), dstSamples.end(), [&ssrc,srcChan,srcStep,converter] + { + auto ret = converter(ssrc[srcChan]); + ssrc += ptrdiff_t(srcStep); + return ret; + }); } template<> -inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src, +inline void LoadSamples(al::span dstSamples, al::span src, const size_t srcChan, const size_t srcOffset, const size_t srcStep, - const size_t samplesPerBlock, const size_t samplesToLoad) noexcept + const size_t samplesPerBlock) noexcept { + static constexpr int MaxStepIndex{static_cast(IMAStep_size.size()) - 1}; + + assert(srcStep > 0 || srcStep <= 2); + assert(srcChan < srcStep); + assert(samplesPerBlock > 1); const size_t blockBytes{((samplesPerBlock-1)/2 + 4)*srcStep}; /* Skip to the ADPCM block containing the srcOffset sample. */ - src += srcOffset/samplesPerBlock*blockBytes; + src = src.subspan(srcOffset/samplesPerBlock*blockBytes); /* Calculate how many samples need to be skipped in the block. */ size_t skip{srcOffset % samplesPerBlock}; /* NOTE: This could probably be optimized better. */ - size_t wrote{0}; - do { + while(!dstSamples.empty()) + { + auto nibbleData = src.cbegin(); + src = src.subspan(blockBytes); + /* Each IMA4 block starts with a signed 16-bit sample, and a signed * 16-bit table index. The table index needs to be clamped. */ - int sample{src[srcChan*4] | (src[srcChan*4 + 1] << 8)}; - int index{src[srcChan*4 + 2] | (src[srcChan*4 + 3] << 8)}; + int sample{int(nibbleData[srcChan*4]) | (int(nibbleData[srcChan*4 + 1]) << 8)}; + int index{int(nibbleData[srcChan*4 + 2]) | (int(nibbleData[srcChan*4 + 3]) << 8)}; + nibbleData += ptrdiff_t((srcStep+srcChan)*4); sample = (sample^0x8000) - 32768; - index = clampi((index^0x8000) - 32768, 0, al::size(IMAStep_size)-1); + index = std::clamp((index^0x8000) - 32768, 0, MaxStepIndex); if(skip == 0) { - dstSamples[wrote++] = static_cast(sample) / 32768.0f; - if(wrote == samplesToLoad) return; + dstSamples[0] = static_cast(sample) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; } else --skip; auto decode_sample = [&sample,&index](const uint nibble) { - sample += IMA4Codeword[nibble] * IMAStep_size[index] / 8; - sample = clampi(sample, -32768, 32767); + sample += IMA4Codeword[nibble] * IMAStep_size[static_cast(index)] / 8; + sample = std::clamp(sample, -32768, 32767); index += IMA4Index_adjust[nibble]; - index = clampi(index, 0, al::size(IMAStep_size)-1); + index = std::clamp(index, 0, MaxStepIndex); return sample; }; @@ -325,71 +346,72 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src * always be less than the block size). They need to be decoded despite * being ignored for proper state on the remaining samples. */ - const al::byte *nibbleData{src + (srcStep+srcChan)*4}; size_t nibbleOffset{0}; const size_t startOffset{skip + 1}; for(;skip;--skip) { const size_t byteShift{(nibbleOffset&1) * 4}; - const size_t wordOffset{(nibbleOffset>>1) & ~size_t{3}}; + const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; ++nibbleOffset; - std::ignore = decode_sample((nibbleData[byteOffset]>>byteShift) & 15u); + std::ignore = decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u); } /* Second, decode the rest of the block and write to the output, until * the end of the block or the end of output. */ - const size_t todo{minz(samplesPerBlock-startOffset, samplesToLoad-wrote)}; - for(size_t i{0};i < todo;++i) + const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; + std::generate_n(dstSamples.begin(), todo, [&] { const size_t byteShift{(nibbleOffset&1) * 4}; - const size_t wordOffset{(nibbleOffset>>1) & ~size_t{3}}; + const size_t wordOffset{(nibbleOffset>>1) & ~3_uz}; const size_t byteOffset{wordOffset*srcStep + ((nibbleOffset>>1)&3u)}; ++nibbleOffset; - const int result{decode_sample((nibbleData[byteOffset]>>byteShift) & 15u)}; - dstSamples[wrote++] = static_cast(result) / 32768.0f; - } - if(wrote == samplesToLoad) - return; - - src += blockBytes; - } while(true); + const int result{decode_sample(uint(nibbleData[byteOffset]>>byteShift) & 15u)}; + return static_cast(result) / 32768.0f; + }); + dstSamples = dstSamples.subspan(todo); + } } template<> -inline void LoadSamples(float *RESTRICT dstSamples, const al::byte *src, +inline void LoadSamples(al::span dstSamples, al::span src, const size_t srcChan, const size_t srcOffset, const size_t srcStep, - const size_t samplesPerBlock, const size_t samplesToLoad) noexcept + const size_t samplesPerBlock) noexcept { + assert(srcStep > 0 || srcStep <= 2); + assert(srcChan < srcStep); + assert(samplesPerBlock > 2); const size_t blockBytes{((samplesPerBlock-2)/2 + 7)*srcStep}; - src += srcOffset/samplesPerBlock*blockBytes; + src = src.subspan(srcOffset/samplesPerBlock*blockBytes); size_t skip{srcOffset % samplesPerBlock}; - size_t wrote{0}; - do { + while(!dstSamples.empty()) + { + auto input = src.cbegin(); + src = src.subspan(blockBytes); + /* Each MS ADPCM block starts with an 8-bit block predictor, used to * dictate how the two sample history values are mixed with the decoded * sample, and an initial signed 16-bit delta value which scales the * nibble sample value. This is followed by the two initial 16-bit * sample history values. */ - const al::byte *input{src}; - const uint8_t blockpred{std::min(input[srcChan], uint8_t{6})}; - input += srcStep; - int delta{input[2*srcChan + 0] | (input[2*srcChan + 1] << 8)}; - input += srcStep*2; + const uint8_t blockpred{std::min(uint8_t(input[srcChan]), uint8_t{6})}; + input += ptrdiff_t(srcStep); + int delta{int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1]) << 8)}; + input += ptrdiff_t(srcStep*2); - int sampleHistory[2]{}; - sampleHistory[0] = input[2*srcChan + 0] | (input[2*srcChan + 1]<<8); - input += srcStep*2; - sampleHistory[1] = input[2*srcChan + 0] | (input[2*srcChan + 1]<<8); - input += srcStep*2; + std::array sampleHistory{}; + sampleHistory[0] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); + input += ptrdiff_t(srcStep*2); + sampleHistory[1] = int(input[2*srcChan + 0]) | (int(input[2*srcChan + 1])<<8); + input += ptrdiff_t(srcStep*2); - const auto coeffs = al::as_span(MSADPCMAdaptionCoeff[blockpred]); + const al::span coeffs{MSADPCMAdaptionCoeff[blockpred]}; delta = (delta^0x8000) - 32768; sampleHistory[0] = (sampleHistory[0]^0x8000) - 32768; sampleHistory[1] = (sampleHistory[1]^0x8000) - 32768; @@ -399,16 +421,19 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte * */ if(skip == 0) { - dstSamples[wrote++] = static_cast(sampleHistory[1]) / 32768.0f; - if(wrote == samplesToLoad) return; - dstSamples[wrote++] = static_cast(sampleHistory[0]) / 32768.0f; - if(wrote == samplesToLoad) return; + dstSamples[0] = static_cast(sampleHistory[1]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; + dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; } else if(skip == 1) { --skip; - dstSamples[wrote++] = static_cast(sampleHistory[0]) / 32768.0f; - if(wrote == samplesToLoad) return; + dstSamples[0] = static_cast(sampleHistory[0]) / 32768.0f; + dstSamples = dstSamples.subspan<1>(); + if(dstSamples.empty()) return; } else skip -= 2; @@ -417,13 +442,13 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte * { int pred{(sampleHistory[0]*coeffs[0] + sampleHistory[1]*coeffs[1]) / 256}; pred += ((nibble^0x08) - 0x08) * delta; - pred = clampi(pred, -32768, 32767); + pred = std::clamp(pred, -32768, 32767); sampleHistory[1] = sampleHistory[0]; sampleHistory[0] = pred; - delta = (MSADPCMAdaption[nibble] * delta) / 256; - delta = maxi(16, delta); + delta = (MSADPCMAdaption[static_cast(nibble)] * delta) / 256; + delta = std::max(16, delta); return pred; }; @@ -439,42 +464,40 @@ inline void LoadSamples(float *RESTRICT dstSamples, const al::byte * const size_t byteShift{((nibbleOffset&1)^1) * 4}; nibbleOffset += srcStep; - std::ignore = decode_sample((input[byteOffset]>>byteShift) & 15); + std::ignore = decode_sample(int(input[byteOffset]>>byteShift) & 15); } /* Now decode the rest of the block, until the end of the block or the * dst buffer is filled. */ - const size_t todo{minz(samplesPerBlock-startOffset, samplesToLoad-wrote)}; - for(size_t j{0};j < todo;++j) + const size_t todo{std::min(samplesPerBlock-startOffset, dstSamples.size())}; + std::generate_n(dstSamples.begin(), todo, [&] { const size_t byteOffset{nibbleOffset>>1}; const size_t byteShift{((nibbleOffset&1)^1) * 4}; nibbleOffset += srcStep; - const int sample{decode_sample((input[byteOffset]>>byteShift) & 15)}; - dstSamples[wrote++] = static_cast(sample) / 32768.0f; - } - if(wrote == samplesToLoad) - return; - - src += blockBytes; - } while(true); + const int sample{decode_sample(int(input[byteOffset]>>byteShift) & 15)}; + return static_cast(sample) / 32768.0f; + }); + dstSamples = dstSamples.subspan(todo); + } } -void LoadSamples(float *dstSamples, const al::byte *src, const size_t srcChan, - const size_t srcOffset, const FmtType srcType, const size_t srcStep, - const size_t samplesPerBlock, const size_t samplesToLoad) noexcept +void LoadSamples(const al::span dstSamples, const al::span src, + const size_t srcChan, const size_t srcOffset, const FmtType srcType, const size_t srcStep, + const size_t samplesPerBlock) noexcept { #define HANDLE_FMT(T) case T: \ LoadSamples(dstSamples, src, srcChan, srcOffset, srcStep, \ - samplesPerBlock, samplesToLoad); \ + samplesPerBlock); \ break switch(srcType) { HANDLE_FMT(FmtUByte); HANDLE_FMT(FmtShort); + HANDLE_FMT(FmtInt); HANDLE_FMT(FmtFloat); HANDLE_FMT(FmtDouble); HANDLE_FMT(FmtMulaw); @@ -487,26 +510,24 @@ void LoadSamples(float *dstSamples, const al::byte *src, const size_t srcChan, void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, const size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, - const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, - float *voiceSamples) + const size_t srcStep, al::span voiceSamples) { if(!bufferLoopItem) { + float lastSample{0.0f}; /* Load what's left to play from the buffer */ if(buffer->mSampleLen > dataPosInt) LIKELY { const size_t buffer_remaining{buffer->mSampleLen - dataPosInt}; - const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer_remaining)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt, - sampleType, srcStep, buffer->mBlockAlign, remaining); - samplesLoaded += remaining; + const size_t remaining{std::min(voiceSamples.size(), buffer_remaining)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, + sampleType, srcStep, buffer->mBlockAlign); + lastSample = voiceSamples[remaining-1]; + voiceSamples = voiceSamples.subspan(remaining); } - if(const size_t toFill{samplesToLoad - samplesLoaded}) - { - auto srcsamples = voiceSamples + samplesLoaded; - std::fill_n(srcsamples, toFill, *(srcsamples-1)); - } + if(const size_t toFill{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } else { @@ -518,49 +539,47 @@ void LoadBufferStatic(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, : (((dataPosInt-loopStart)%(loopEnd-loopStart)) + loopStart)}; /* Load what's left of this loop iteration */ - const size_t remaining{minz(samplesToLoad-samplesLoaded, loopEnd-dataPosInt)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, intPos, sampleType, - srcStep, buffer->mBlockAlign, remaining); - samplesLoaded += remaining; + const size_t remaining{std::min(voiceSamples.size(), loopEnd-dataPosInt)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, intPos, + sampleType, srcStep, buffer->mBlockAlign); + voiceSamples = voiceSamples.subspan(remaining); /* Load repeats of the loop to fill the buffer. */ const size_t loopSize{loopEnd - loopStart}; - while(const size_t toFill{minz(samplesToLoad - samplesLoaded, loopSize)}) + while(const size_t toFill{std::min(voiceSamples.size(), loopSize)}) { - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, loopStart, - sampleType, srcStep, buffer->mBlockAlign, toFill); - samplesLoaded += toFill; + LoadSamples(voiceSamples.first(toFill), buffer->mSamples, srcChannel, loopStart, + sampleType, srcStep, buffer->mBlockAlign); + voiceSamples = voiceSamples.subspan(toFill); } } } void LoadBufferCallback(VoiceBufferItem *buffer, const size_t dataPosInt, const size_t numCallbackSamples, const FmtType sampleType, const size_t srcChannel, - const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, float *voiceSamples) + const size_t srcStep, al::span voiceSamples) { - /* Load what's left to play from the buffer */ + float lastSample{0.0f}; if(numCallbackSamples > dataPosInt) LIKELY { - const size_t remaining{minz(samplesToLoad-samplesLoaded, numCallbackSamples-dataPosInt)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt, - sampleType, srcStep, buffer->mBlockAlign, remaining); - samplesLoaded += remaining; + const size_t remaining{std::min(voiceSamples.size(), numCallbackSamples-dataPosInt)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, + sampleType, srcStep, buffer->mBlockAlign); + lastSample = voiceSamples[remaining-1]; + voiceSamples = voiceSamples.subspan(remaining); } - if(const size_t toFill{samplesToLoad - samplesLoaded}) - { - auto srcsamples = voiceSamples + samplesLoaded; - std::fill_n(srcsamples, toFill, *(srcsamples-1)); - } + if(const size_t toFill{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, size_t dataPosInt, const FmtType sampleType, const size_t srcChannel, - const size_t srcStep, size_t samplesLoaded, const size_t samplesToLoad, - float *voiceSamples) + const size_t srcStep, al::span voiceSamples) { + float lastSample{0.0f}; /* Crawl the buffer queue to fill in the temp buffer */ - while(buffer && samplesLoaded != samplesToLoad) + while(buffer && !voiceSamples.empty()) { if(dataPosInt >= buffer->mSampleLen) { @@ -570,48 +589,47 @@ void LoadBufferQueue(VoiceBufferItem *buffer, VoiceBufferItem *bufferLoopItem, continue; } - const size_t remaining{minz(samplesToLoad-samplesLoaded, buffer->mSampleLen-dataPosInt)}; - LoadSamples(voiceSamples+samplesLoaded, buffer->mSamples, srcChannel, dataPosInt, - sampleType, srcStep, buffer->mBlockAlign, remaining); + const size_t remaining{std::min(voiceSamples.size(), buffer->mSampleLen-dataPosInt)}; + LoadSamples(voiceSamples.first(remaining), buffer->mSamples, srcChannel, dataPosInt, + sampleType, srcStep, buffer->mBlockAlign); - samplesLoaded += remaining; - if(samplesLoaded == samplesToLoad) + lastSample = voiceSamples[remaining-1]; + voiceSamples = voiceSamples.subspan(remaining); + if(voiceSamples.empty()) break; dataPosInt = 0; buffer = buffer->mNext.load(std::memory_order_acquire); if(!buffer) buffer = bufferLoopItem; } - if(const size_t toFill{samplesToLoad - samplesLoaded}) - { - auto srcsamples = voiceSamples + samplesLoaded; - std::fill_n(srcsamples, toFill, *(srcsamples-1)); - } + if(const size_t toFill{voiceSamples.size()}) + std::fill_n(voiceSamples.begin(), toFill, lastSample); } -void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &parms, - const float TargetGain, const uint Counter, uint OutPos, const bool IsPlaying, - DeviceBase *Device) +void DoHrtfMix(const al::span samples, DirectParams &parms, const float TargetGain, + const size_t Counter, size_t OutPos, const bool IsPlaying, DeviceBase *Device) { const uint IrSize{Device->mIrSize}; - auto &HrtfSamples = Device->HrtfSourceData; - auto &AccumSamples = Device->HrtfAccumData; + const auto HrtfSamples = al::span{Device->ExtraSampleData}; + const auto AccumSamples = al::span{Device->HrtfAccumData}; /* Copy the HRTF history and new input samples into a temp buffer. */ auto src_iter = std::copy(parms.Hrtf.History.begin(), parms.Hrtf.History.end(), - std::begin(HrtfSamples)); - std::copy_n(samples, DstBufferSize, src_iter); + HrtfSamples.begin()); + std::copy_n(samples.begin(), samples.size(), src_iter); /* Copy the last used samples back into the history buffer for later. */ if(IsPlaying) LIKELY - std::copy_n(std::begin(HrtfSamples) + DstBufferSize, parms.Hrtf.History.size(), - parms.Hrtf.History.begin()); + { + const auto endsamples = HrtfSamples.subspan(samples.size(), parms.Hrtf.History.size()); + std::copy_n(endsamples.cbegin(), endsamples.size(), parms.Hrtf.History.begin()); + } /* If fading and this is the first mixing pass, fade between the IRs. */ - uint fademix{0u}; + size_t fademix{0}; if(Counter && OutPos == 0) { - fademix = minu(DstBufferSize, Counter); + fademix = std::min(samples.size(), Counter); float gain{TargetGain}; @@ -630,8 +648,8 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par parms.Hrtf.Target.Coeffs, parms.Hrtf.Target.Delay, 0.0f, gain / static_cast(fademix)}; - MixHrtfBlendSamples(HrtfSamples, AccumSamples+OutPos, IrSize, &parms.Hrtf.Old, &hrtfparams, - fademix); + MixHrtfBlendSamples(HrtfSamples, AccumSamples.subspan(OutPos), IrSize, &parms.Hrtf.Old, + &hrtfparams, fademix); /* Update the old parameters with the result. */ parms.Hrtf.Old = parms.Hrtf.Target; @@ -639,15 +657,15 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par OutPos += fademix; } - if(fademix < DstBufferSize) + if(fademix < samples.size()) { - const uint todo{DstBufferSize - fademix}; + const size_t todo{samples.size() - fademix}; float gain{TargetGain}; /* Interpolate the target gain if the gain fading lasts longer than * this mix. */ - if(Counter > DstBufferSize) + if(Counter > samples.size()) { const float a{static_cast(todo) / static_cast(Counter-fademix)}; gain = lerpf(parms.Hrtf.Old.Gain, TargetGain, a); @@ -658,35 +676,39 @@ void DoHrtfMix(const float *samples, const uint DstBufferSize, DirectParams &par parms.Hrtf.Target.Delay, parms.Hrtf.Old.Gain, (gain - parms.Hrtf.Old.Gain) / static_cast(todo)}; - MixHrtfSamples(HrtfSamples+fademix, AccumSamples+OutPos, IrSize, &hrtfparams, todo); + MixHrtfSamples(HrtfSamples.subspan(fademix), AccumSamples.subspan(OutPos), IrSize, + &hrtfparams, todo); /* Store the now-current gain for next time. */ parms.Hrtf.Old.Gain = gain; } } -void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, DirectParams &parms, - const float *TargetGains, const uint Counter, const uint OutPos, DeviceBase *Device) +void DoNfcMix(const al::span samples, al::span OutBuffer, + DirectParams &parms, const al::span OutGains, + const uint Counter, const uint OutPos, DeviceBase *Device) { - using FilterProc = void (NfcFilter::*)(const al::span, float*); - static constexpr FilterProc NfcProcess[MaxAmbiOrder+1]{ - nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}; + using FilterProc = void (NfcFilter::*)(const al::span, const al::span); + static constexpr std::array NfcProcess{{ + nullptr, &NfcFilter::process1, &NfcFilter::process2, &NfcFilter::process3}}; - float *CurrentGains{parms.Gains.Current.data()}; - MixSamples(samples, {OutBuffer, 1u}, CurrentGains, TargetGains, Counter, OutPos); - ++OutBuffer; - ++CurrentGains; - ++TargetGains; + auto CurrentGains = al::span{parms.Gains.Current}.subspan(0); + auto TargetGains = OutGains.subspan(0); + MixSamples(samples, OutBuffer.first(1), CurrentGains, TargetGains, Counter, OutPos); + OutBuffer = OutBuffer.subspan(1); + CurrentGains = CurrentGains.subspan(1); + TargetGains = TargetGains.subspan(1); - const al::span nfcsamples{Device->NfcSampleData, samples.size()}; + const auto nfcsamples = al::span{Device->ExtraSampleData}.subspan(samples.size()); size_t order{1}; while(const size_t chancount{Device->NumChannelsPerOrder[order]}) { - (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples.data()); - MixSamples(nfcsamples, {OutBuffer, chancount}, CurrentGains, TargetGains, Counter, OutPos); - OutBuffer += chancount; - CurrentGains += chancount; - TargetGains += chancount; + (parms.NFCtrlFilter.*NfcProcess[order])(samples, nfcsamples); + MixSamples(nfcsamples, OutBuffer.first(chancount), CurrentGains, TargetGains, Counter, + OutPos); + OutBuffer = OutBuffer.subspan(chancount); + CurrentGains = CurrentGains.subspan(chancount); + TargetGains = TargetGains.subspan(chancount); if(++order == MaxAmbiOrder+1) break; } @@ -697,7 +719,7 @@ void DoNfcMix(const al::span samples, FloatBufferLine *OutBuffer, D void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds deviceTime, const uint SamplesToDo) { - static constexpr std::array SilentTarget{}; + static constexpr std::array SilentTarget{}; ASSUME(SamplesToDo > 0); @@ -773,34 +795,34 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Get a span of pointers to hold the floating point, deinterlaced, * resampled buffer data to be mixed. */ - std::array SamplePointers; - const al::span MixingSamples{SamplePointers.data(), mChans.size()}; - auto get_bufferline = [](DeviceBase::MixerBufferLine &bufline) noexcept -> float* - { return bufline.data(); }; - std::transform(Device->mSampleData.end() - mChans.size(), Device->mSampleData.end(), - MixingSamples.begin(), get_bufferline); - - /* If there's a matching sample step and no phase offset, use a simple copy - * for resampling. - */ - const ResamplerFunc Resample{(increment == MixerFracOne && DataPosFrac == 0) - ? ResamplerFunc{[](const InterpState*, const float *RESTRICT src, uint, const uint, - const al::span dst) { std::copy_n(src, dst.size(), dst.begin()); }} - : mResampler}; + auto SamplePointers = std::array{}; + const auto MixingSamples = al::span{SamplePointers}.first(mChans.size()); + { + const uint channelStep{(samplesToLoad+3u)&~3u}; + auto base = Device->mSampleData.end() - MixingSamples.size()*channelStep; + std::generate(MixingSamples.begin(), MixingSamples.end(), [&base,channelStep] + { + const auto ret = base; + base += channelStep; + return al::to_address(ret); + }); + } /* UHJ2 and SuperStereo only have 2 buffer channels, but 3 mixing channels - * (3rd channel is generated from decoding). + * (3rd channel is generated from decoding). MonoDup only has 1 buffer + * channel, but 2 mixing channels (2nd channel is just duplicated). */ - const size_t realChannels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u + const size_t realChannels{(mFmtChannels == FmtMonoDup) ? 1u + : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 2u : MixingSamples.size()}; for(size_t chan{0};chan < realChannels;++chan) { - using ResBufType = decltype(DeviceBase::mResampleData); - static constexpr uint srcSizeMax{static_cast(ResBufType{}.size()-MaxResamplerEdge)}; + static constexpr uint ResBufSize{std::tuple_size_v}; + static constexpr uint srcSizeMax{ResBufSize - MaxResamplerEdge}; - const auto prevSamples = al::as_span(mPrevSamples[chan]); - const auto resampleBuffer = std::copy(prevSamples.cbegin(), prevSamples.cend(), - Device->mResampleData.begin()) - MaxResamplerEdge; + const al::span prevSamples{mPrevSamples[chan]}; + std::copy(prevSamples.cbegin(), prevSamples.cend(), Device->mResampleData.begin()); + const auto resampleBuffer = al::span{Device->mResampleData}.subspan(); int intPos{DataPosInt}; uint fracPos{DataPosFrac}; @@ -849,86 +871,101 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi } return std::make_pair(dstBufferSize, srcSizeMax); }; - const auto bufferSizes = calc_buffer_sizes(samplesToLoad - samplesLoaded); - const auto dstBufferSize = bufferSizes.first; - const auto srcBufferSize = bufferSizes.second; + const auto [dstBufferSize, srcBufferSize] = calc_buffer_sizes( + samplesToLoad - samplesLoaded); + + size_t srcSampleDelay{0}; + if(intPos < 0) UNLIKELY + { + /* If the current position is negative, there's that many + * silent samples to load before using the buffer. + */ + srcSampleDelay = static_cast(-intPos); + if(srcSampleDelay >= srcBufferSize) + { + /* If the number of silent source samples exceeds the + * number to load, the output will be silent. + */ + std::fill_n(MixingSamples[chan]+samplesLoaded, dstBufferSize, 0.0f); + std::fill_n(resampleBuffer.begin(), srcBufferSize, 0.0f); + goto skip_resample; + } + + std::fill_n(resampleBuffer.begin(), srcSampleDelay, 0.0f); + } /* Load the necessary samples from the given buffer(s). */ - if(!BufferListItem) + if(!BufferListItem) UNLIKELY { - const uint avail{minu(srcBufferSize, MaxResamplerEdge)}; - const uint tofill{maxu(srcBufferSize, MaxResamplerEdge)}; + const uint avail{std::min(srcBufferSize, MaxResamplerEdge)}; + const uint tofill{std::max(srcBufferSize, MaxResamplerEdge)}; + const auto srcbuf = resampleBuffer.first(tofill); /* When loading from a voice that ended prematurely, only take * the samples that get closest to 0 amplitude. This helps * certain sounds fade out better. */ - auto abs_lt = [](const float lhs, const float rhs) noexcept -> bool - { return std::abs(lhs) < std::abs(rhs); }; - auto srciter = std::min_element(resampleBuffer, resampleBuffer+avail, abs_lt); + auto srciter = std::min_element(srcbuf.begin(), srcbuf.begin()+ptrdiff_t(avail), + [](const float l, const float r) { return std::abs(l) < std::abs(r); }); - std::fill(srciter+1, resampleBuffer+tofill, *srciter); + std::fill(srciter+1, srcbuf.end(), *srciter); + } + else if(mFlags.test(VoiceIsStatic)) + { + const auto uintPos = static_cast(std::max(intPos, 0)); + const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, + srcBufferSize-srcSampleDelay); + LoadBufferStatic(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, + mFrameStep, bufferSamples); + } + else if(mFlags.test(VoiceIsCallback)) + { + const auto uintPos = static_cast(std::max(intPos, 0)); + const uint callbackBase{mCallbackBlockBase * mSamplesPerBlock}; + const size_t bufferOffset{uintPos - callbackBase}; + const size_t needSamples{bufferOffset + srcBufferSize - srcSampleDelay}; + const size_t needBlocks{(needSamples + mSamplesPerBlock-1) / mSamplesPerBlock}; + if(!mFlags.test(VoiceCallbackStopped) && needBlocks > mNumCallbackBlocks) + { + const size_t byteOffset{mNumCallbackBlocks*size_t{mBytesPerBlock}}; + const size_t needBytes{(needBlocks-mNumCallbackBlocks)*size_t{mBytesPerBlock}}; + + const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, + &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; + if(gotBytes < 0) + mFlags.set(VoiceCallbackStopped); + else if(static_cast(gotBytes) < needBytes) + { + mFlags.set(VoiceCallbackStopped); + mNumCallbackBlocks += static_cast(gotBytes) / mBytesPerBlock; + } + else + mNumCallbackBlocks = static_cast(needBlocks); + } + const size_t numSamples{size_t{mNumCallbackBlocks} * mSamplesPerBlock}; + const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, + srcBufferSize-srcSampleDelay); + LoadBufferCallback(BufferListItem, bufferOffset, numSamples, mFmtType, chan, + mFrameStep, bufferSamples); } else { - size_t srcSampleDelay{0}; - if(intPos < 0) UNLIKELY - { - /* If the current position is negative, there's that many - * silent samples to load before using the buffer. - */ - srcSampleDelay = static_cast(-intPos); - if(srcSampleDelay >= srcBufferSize) - { - /* If the number of silent source samples exceeds the - * number to load, the output will be silent. - */ - std::fill_n(MixingSamples[chan]+samplesLoaded, dstBufferSize, 0.0f); - std::fill_n(resampleBuffer, srcBufferSize, 0.0f); - goto skip_resample; - } - - std::fill_n(resampleBuffer, srcSampleDelay, 0.0f); - } - const uint uintPos{static_cast(maxi(intPos, 0))}; - - if(mFlags.test(VoiceIsStatic)) - LoadBufferStatic(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, - mFrameStep, srcSampleDelay, srcBufferSize, al::to_address(resampleBuffer)); - else if(mFlags.test(VoiceIsCallback)) - { - const uint callbackBase{mCallbackBlockBase * mSamplesPerBlock}; - const size_t bufferOffset{uintPos - callbackBase}; - const size_t needSamples{bufferOffset + srcBufferSize - srcSampleDelay}; - const size_t needBlocks{(needSamples + mSamplesPerBlock-1) / mSamplesPerBlock}; - if(!mFlags.test(VoiceCallbackStopped) && needBlocks > mNumCallbackBlocks) - { - const size_t byteOffset{mNumCallbackBlocks*mBytesPerBlock}; - const size_t needBytes{(needBlocks-mNumCallbackBlocks)*mBytesPerBlock}; - - const int gotBytes{BufferListItem->mCallback(BufferListItem->mUserData, - &BufferListItem->mSamples[byteOffset], static_cast(needBytes))}; - if(gotBytes < 0) - mFlags.set(VoiceCallbackStopped); - else if(static_cast(gotBytes) < needBytes) - { - mFlags.set(VoiceCallbackStopped); - mNumCallbackBlocks += static_cast(gotBytes) / mBytesPerBlock; - } - else - mNumCallbackBlocks = static_cast(needBlocks); - } - const size_t numSamples{uint{mNumCallbackBlocks} * mSamplesPerBlock}; - LoadBufferCallback(BufferListItem, bufferOffset, numSamples, mFmtType, chan, - mFrameStep, srcSampleDelay, srcBufferSize, al::to_address(resampleBuffer)); - } - else - LoadBufferQueue(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, - mFrameStep, srcSampleDelay, srcBufferSize, al::to_address(resampleBuffer)); + const auto uintPos = static_cast(std::max(intPos, 0)); + const auto bufferSamples = resampleBuffer.subspan(srcSampleDelay, + srcBufferSize-srcSampleDelay); + LoadBufferQueue(BufferListItem, BufferLoopItem, uintPos, mFmtType, chan, + mFrameStep, bufferSamples); } - Resample(&mResampleState, al::to_address(resampleBuffer), fracPos, increment, - {MixingSamples[chan]+samplesLoaded, dstBufferSize}); + /* If there's a matching sample step and no phase offset, use a + * simple copy for resampling. + */ + if(increment == MixerFracOne && fracPos == 0) + std::copy_n(resampleBuffer.cbegin(), dstBufferSize, + MixingSamples[chan]+samplesLoaded); + else + mResampler(&mResampleState, Device->mResampleData, fracPos, increment, + {MixingSamples[chan]+samplesLoaded, dstBufferSize}); /* Store the last source samples used for next time. */ if(vstate == Playing) LIKELY @@ -941,7 +978,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi { const size_t dstOffset{samplesToMix - samplesLoaded}; const size_t srcOffset{(dstOffset*increment + fracPos) >> MixerFracBits}; - std::copy_n(resampleBuffer-MaxResamplerEdge+srcOffset, prevSamples.size(), + std::copy_n(Device->mResampleData.cbegin()+srcOffset, prevSamples.size(), prevSamples.begin()); } } @@ -953,18 +990,26 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi fracPos += dstBufferSize*increment; const uint srcOffset{fracPos >> MixerFracBits}; fracPos &= MixerFracMask; - intPos += srcOffset; + intPos += static_cast(srcOffset); /* If more samples need to be loaded, copy the back of the * resampleBuffer to the front to reuse it. prevSamples isn't * reliable since it's only updated for the end of the mix. */ - std::copy(resampleBuffer-MaxResamplerEdge+srcOffset, - resampleBuffer+MaxResamplerEdge+srcOffset, resampleBuffer-MaxResamplerEdge); + std::copy_n(Device->mResampleData.cbegin()+srcOffset, MaxResamplerPadding, + Device->mResampleData.begin()); } } } - for(auto &samples : MixingSamples.subspan(realChannels)) + if(mFmtChannels == FmtMonoDup) + { + /* NOTE: a mono source shouldn't have a decoder or the VoiceIsAmbisonic + * flag, so aliasing instead of copying to the second channel shouldn't + * be a problem. + */ + MixingSamples[1] = MixingSamples[0]; + } + else for(auto &samples : MixingSamples.subspan(realChannels)) std::fill_n(samples, samplesToLoad, 0.0f); if(mDecoder) @@ -981,7 +1026,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi } } - const uint Counter{mFlags.test(VoiceIsFading) ? minu(samplesToMix, 64u) : 0u}; + const uint Counter{mFlags.test(VoiceIsFading) ? std::min(samplesToMix, 64u) : 0u}; if(!Counter) { /* No fading, just overwrite the old/current params. */ @@ -1012,25 +1057,24 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi const al::span FilterBuf{Device->FilteredData}; { DirectParams &parms = chandata.mDryParams; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), - {*voiceSamples, samplesToMix}, mDirect.FilterType)}; + const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, + {*voiceSamples, samplesToMix}, mDirect.FilterType); if(mFlags.test(VoiceHasHrtf)) { - const float TargetGain{parms.Hrtf.Target.Gain * (vstate == Playing)}; - DoHrtfMix(samples, samplesToMix, parms, TargetGain, Counter, OutPos, - (vstate == Playing), Device); + const float TargetGain{parms.Hrtf.Target.Gain * float(vstate == Playing)}; + DoHrtfMix(samples, parms, TargetGain, Counter, OutPos, (vstate == Playing), + Device); } else { - const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; + const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} + : al::span{SilentTarget}; if(mFlags.test(VoiceHasNfc)) - DoNfcMix({samples, samplesToMix}, mDirect.Buffer.data(), parms, - TargetGains, Counter, OutPos, Device); + DoNfcMix(samples, mDirect.Buffer, parms, TargetGains, Counter, OutPos, Device); else - MixSamples({samples, samplesToMix}, mDirect.Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); + MixSamples(samples, mDirect.Buffer, parms.Gains.Current, TargetGains, Counter, + OutPos); } } @@ -1040,13 +1084,13 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi continue; SendParams &parms = chandata.mWetParams[send]; - const float *samples{DoFilters(parms.LowPass, parms.HighPass, FilterBuf.data(), - {*voiceSamples, samplesToMix}, mSend[send].FilterType)}; + const auto samples = DoFilters(parms.LowPass, parms.HighPass, FilterBuf, + {*voiceSamples, samplesToMix}, mSend[send].FilterType); - const float *TargetGains{(vstate == Playing) ? parms.Gains.Target.data() - : SilentTarget.data()}; - MixSamples({samples, samplesToMix}, mSend[send].Buffer, - parms.Gains.Current.data(), TargetGains, Counter, OutPos); + const auto TargetGains = (vstate == Playing) ? al::span{parms.Gains.Target} + : al::span{SilentTarget}; + MixSamples(samples, mSend[send].Buffer, parms.Gains.Current, TargetGains, Counter, + OutPos); } ++voiceSamples; @@ -1063,12 +1107,11 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Update voice positions and buffers as needed. */ DataPosFrac += increment*samplesToMix; - const uint SrcSamplesDone{DataPosFrac>>MixerFracBits}; - DataPosInt += SrcSamplesDone; + DataPosInt += static_cast(DataPosFrac>>MixerFracBits); DataPosFrac &= MixerFracMask; uint buffers_done{0u}; - if(BufferListItem && DataPosInt >= 0) LIKELY + if(BufferListItem && DataPosInt > 0) LIKELY { if(mFlags.test(VoiceIsStatic)) { @@ -1099,10 +1142,11 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi const uint blocksDone{currentBlock - mCallbackBlockBase}; if(blocksDone < mNumCallbackBlocks) { - const size_t byteOffset{blocksDone*mBytesPerBlock}; - const size_t byteEnd{mNumCallbackBlocks*mBytesPerBlock}; - al::byte *data{BufferListItem->mSamples}; - std::copy(data+byteOffset, data+byteEnd, data); + const size_t byteOffset{blocksDone*size_t{mBytesPerBlock}}; + const size_t byteEnd{mNumCallbackBlocks*size_t{mBytesPerBlock}}; + const al::span data{BufferListItem->mSamples}; + std::copy(data.cbegin()+ptrdiff_t(byteOffset), data.cbegin()+ptrdiff_t(byteEnd), + data.begin()); mNumCallbackBlocks -= blocksDone; mCallbackBlockBase += blocksDone; } @@ -1120,7 +1164,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi if(BufferListItem->mSampleLen > static_cast(DataPosInt)) break; - DataPosInt -= BufferListItem->mSampleLen; + DataPosInt -= static_cast(BufferListItem->mSampleLen); ++buffers_done; BufferListItem = BufferListItem->mNext.load(std::memory_order_relaxed); @@ -1145,16 +1189,15 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi /* Send any events now, after the position/buffer info was updated. */ const auto enabledevt = Context->mEnabledEvts.load(std::memory_order_acquire); - if(buffers_done > 0 && enabledevt.test(AsyncEvent::BufferCompleted)) + if(buffers_done > 0 && enabledevt.test(al::to_underlying(AsyncEnableBits::BufferCompleted))) { RingBuffer *ring{Context->mAsyncEvents.get()}; auto evt_vec = ring->getWriteVector(); if(evt_vec.first.len > 0) { - AsyncEvent *evt{al::construct_at(reinterpret_cast(evt_vec.first.buf), - AsyncEvent::BufferCompleted)}; - evt->u.bufcomp.id = SourceID; - evt->u.bufcomp.count = buffers_done; + auto &evt = InitAsyncEvent(evt_vec.first.buf); + evt.mId = SourceID; + evt.mCount = buffers_done; ring->writeAdvance(1); } } @@ -1165,7 +1208,7 @@ void Voice::mix(const State vstate, ContextBase *Context, const nanoseconds devi * ensures any residual noise fades to 0 amplitude. */ mPlayState.store(Stopping, std::memory_order_release); - if(enabledevt.test(AsyncEvent::SourceStateChange)) + if(enabledevt.test(al::to_underlying(AsyncEnableBits::SourceState))) SendSourceStoppedEvent(Context, SourceID); } } @@ -1175,22 +1218,23 @@ void Voice::prepare(DeviceBase *device) /* Even if storing really high order ambisonics, we only mix channels for * orders up to the device order. The rest are simply dropped. */ - uint num_channels{(mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 : - ChannelsFromFmt(mFmtChannels, minu(mAmbiOrder, device->mAmbiOrder))}; - if(num_channels > device->mSampleData.size()) UNLIKELY + uint num_channels{(mFmtChannels == FmtMonoDup) ? 2 + : (mFmtChannels == FmtUHJ2 || mFmtChannels == FmtSuperStereo) ? 3 + : ChannelsFromFmt(mFmtChannels, std::min(mAmbiOrder, device->mAmbiOrder))}; + if(num_channels > device->MixerChannelsMax) UNLIKELY { - ERR("Unexpected channel count: %u (limit: %zu, %d:%d)\n", num_channels, - device->mSampleData.size(), mFmtChannels, mAmbiOrder); - num_channels = static_cast(device->mSampleData.size()); + ERR("Unexpected channel count: %u (limit: %zu, %s : %d)\n", num_channels, + device->MixerChannelsMax, NameFromFormat(mFmtChannels), mAmbiOrder); + num_channels = device->MixerChannelsMax; } if(mChans.capacity() > 2 && num_channels < mChans.capacity()) { decltype(mChans){}.swap(mChans); decltype(mPrevSamples){}.swap(mPrevSamples); } - mChans.reserve(maxu(2, num_channels)); + mChans.reserve(std::max(2u, num_channels)); mChans.resize(num_channels); - mPrevSamples.reserve(maxu(2, num_channels)); + mPrevSamples.reserve(std::max(2u, num_channels)); mPrevSamples.resize(num_channels); mDecoder = nullptr; @@ -1274,8 +1318,10 @@ void Voice::prepare(DeviceBase *device) */ else if(mAmbiOrder && device->mAmbiOrder > mAmbiOrder) { - const uint8_t *OrderFromChan{Is2DAmbisonic(mFmtChannels) ? - AmbiIndex::OrderFrom2DChannel().data() : AmbiIndex::OrderFromChannel().data()}; + auto OrdersSpan = Is2DAmbisonic(mFmtChannels) + ? al::span{AmbiIndex::OrderFrom2DChannel} + : al::span{AmbiIndex::OrderFromChannel}; + auto OrderFromChan = OrdersSpan.cbegin(); const auto scales = AmbiScale::GetHFOrderScales(mAmbiOrder, device->mAmbiOrder, device->m2DMixing); diff --git a/Engine/lib/openal-soft/core/voice.h b/Engine/lib/openal-soft/core/voice.h index 57ee7b019..c983c3e9f 100644 --- a/Engine/lib/openal-soft/core/voice.h +++ b/Engine/lib/openal-soft/core/voice.h @@ -5,13 +5,12 @@ #include #include #include +#include #include -#include +#include #include -#include "albyte.h" #include "almalloc.h" -#include "aloptional.h" #include "alspan.h" #include "bufferline.h" #include "buffer_storage.h" @@ -33,7 +32,7 @@ enum class DistanceModel : unsigned char; using uint = unsigned int; -#define MAX_SENDS 6 +inline constexpr size_t MaxSendCount{6}; enum class SpatializeMode : unsigned char { @@ -49,7 +48,7 @@ enum class DirectMode : unsigned char { }; -constexpr uint MaxPitch{10}; +inline constexpr uint MaxPitch{10}; enum { @@ -66,26 +65,29 @@ struct DirectParams { NfcFilter NFCtrlFilter; - struct { - HrtfFilter Old; - HrtfFilter Target; - alignas(16) std::array History; - } Hrtf; + struct HrtfParams { + HrtfFilter Old{}; + HrtfFilter Target{}; + alignas(16) std::array History{}; + }; + HrtfParams Hrtf; - struct { - std::array Current; - std::array Target; - } Gains; + struct GainParams { + std::array Current{}; + std::array Target{}; + }; + GainParams Gains; }; struct SendParams { BiquadFilter LowPass; BiquadFilter HighPass; - struct { - std::array Current; - std::array Target; - } Gains; + struct GainParams { + std::array Current{}; + std::array Target{}; + }; + GainParams Gains; }; @@ -100,7 +102,7 @@ struct VoiceBufferItem { uint mLoopStart{0u}; uint mLoopEnd{0u}; - al::byte *mSamples{nullptr}; + al::span mSamples{}; }; @@ -139,15 +141,18 @@ struct VoiceProps { float Radius; float EnhWidth; + float Panning; /** Direct filter and auxiliary send info. */ - struct { + struct DirectData { float Gain; float GainHF; float HFReference; float GainLF; float LFReference; - } Direct; + }; + DirectData Direct; + struct SendData { EffectSlot *Slot; float Gain; @@ -155,13 +160,12 @@ struct VoiceProps { float HFReference; float GainLF; float LFReference; - } Send[MAX_SENDS]; + }; + std::array Send; }; struct VoicePropsItem : public VoiceProps { std::atomic next{nullptr}; - - DEF_NEWDEL(VoicePropsItem) }; enum : uint { @@ -186,7 +190,7 @@ struct Voice { std::atomic mUpdate{nullptr}; - VoiceProps mProps; + VoiceProps mProps{}; std::atomic mSourceID{0u}; std::atomic mPlayState{Stopped}; @@ -196,30 +200,30 @@ struct Voice { * Source offset in samples, relative to the currently playing buffer, NOT * the whole queue. */ - std::atomic mPosition; + std::atomic mPosition{}; /** Fractional (fixed-point) offset to the next sample. */ - std::atomic mPositionFrac; + std::atomic mPositionFrac{}; /* Current buffer queue item being played. */ - std::atomic mCurrentBuffer; + std::atomic mCurrentBuffer{}; /* Buffer queue item to loop to at end of queue (will be NULL for non- * looping voices). */ - std::atomic mLoopBuffer; + std::atomic mLoopBuffer{}; std::chrono::nanoseconds mStartTime{}; /* Properties for the attached buffer(s). */ - FmtChannels mFmtChannels; - FmtType mFmtType; - uint mFrequency; - uint mFrameStep; /**< In steps of the sample type size. */ - uint mBytesPerBlock; /**< Or for PCM formats, BytesPerFrame. */ - uint mSamplesPerBlock; /**< Always 1 for PCM formats. */ - AmbiLayout mAmbiLayout; - AmbiScaling mAmbiScaling; - uint mAmbiOrder; + FmtChannels mFmtChannels{}; + FmtType mFmtType{}; + uint mFrequency{}; + uint mFrameStep{}; /**< In steps of the sample type size. */ + uint mBytesPerBlock{}; /**< Or for PCM formats, BytesPerFrame. */ + uint mSamplesPerBlock{}; /**< Always 1 for PCM formats. */ + AmbiLayout mAmbiLayout{}; + AmbiScaling mAmbiScaling{}; + uint mAmbiOrder{}; std::unique_ptr mDecoder; uint mDecoderPadding{}; @@ -227,20 +231,20 @@ struct Voice { /** Current target parameters used for mixing. */ uint mStep{0}; - ResamplerFunc mResampler; + ResamplerFunc mResampler{}; - InterpState mResampleState; + InterpState mResampleState{}; std::bitset mFlags{}; uint mNumCallbackBlocks{0}; uint mCallbackBlockBase{0}; struct TargetData { - int FilterType; + int FilterType{}; al::span Buffer; }; TargetData mDirect; - std::array mSend; + std::array mSend; /* The first MaxResamplerPadding/2 elements are the sample history from the * previous mix, with an additional MaxResamplerPadding/2 elements that are @@ -251,11 +255,11 @@ struct Voice { al::vector mPrevSamples{2}; struct ChannelData { - float mAmbiHFScale, mAmbiLFScale; + float mAmbiHFScale{}, mAmbiLFScale{}; BandSplitter mAmbiSplitter; DirectParams mDryParams; - std::array mWetParams; + std::array mWetParams; }; al::vector mChans{2}; @@ -270,11 +274,9 @@ struct Voice { void prepare(DeviceBase *device); - static void InitMixer(al::optional resampler); - - DEF_NEWDEL(Voice) + static void InitMixer(std::optional resopt); }; -extern Resampler ResamplerDefault; +inline Resampler ResamplerDefault{Resampler::Gaussian}; #endif /* CORE_VOICE_H */ diff --git a/Engine/lib/openal-soft/core/voice_change.h b/Engine/lib/openal-soft/core/voice_change.h index ddc6186f5..e97c48f33 100644 --- a/Engine/lib/openal-soft/core/voice_change.h +++ b/Engine/lib/openal-soft/core/voice_change.h @@ -3,8 +3,6 @@ #include -#include "almalloc.h" - struct Voice; using uint = unsigned int; @@ -24,8 +22,6 @@ struct VoiceChange { VChangeState mState{}; std::atomic mNext{nullptr}; - - DEF_NEWDEL(VoiceChange) }; #endif /* VOICE_CHANGE_H */ diff --git a/Engine/lib/openal-soft/docs/ambisonics.txt b/Engine/lib/openal-soft/docs/ambisonics.txt index b1b111d6d..7798c8f90 100644 --- a/Engine/lib/openal-soft/docs/ambisonics.txt +++ b/Engine/lib/openal-soft/docs/ambisonics.txt @@ -12,7 +12,7 @@ What Is It? Originally developed in the 1970s by Michael Gerzon and a team others, Ambisonics was created as a means of recording and playing back 3D sound. -Taking advantage of the way sound waves propogate, it is possible to record a +Taking advantage of the way sound waves propagate, it is possible to record a fully 3D soundfield using as few as 4 channels (or even just 3, if you don't mind dropping down to 2 dimensions like many surround sound systems are). This representation is called B-Format. It was designed to handle audio independent @@ -63,7 +63,7 @@ remain correct over a larger area around the center of the speakers. In addition, Ambisonics can encode the near-field effect of sounds, effectively capturing the sound distance. The near-field effect is a subtle low-frequency boost as a result of wave-front curvature, and properly compensating for this -occuring with the output speakers (as well as emulating it with a synthesized +occurring with the output speakers (as well as emulating it with a synthesized soundfield) can create an improved sense of distance for sounds that move near or far. diff --git a/Engine/lib/openal-soft/docs/env-vars.txt b/Engine/lib/openal-soft/docs/env-vars.txt index 815a30980..0c15cbe99 100644 --- a/Engine/lib/openal-soft/docs/env-vars.txt +++ b/Engine/lib/openal-soft/docs/env-vars.txt @@ -78,6 +78,15 @@ Same as for __ALSOFT_REVERSE_Z, but for Y (up/down) panning. __ALSOFT_REVERSE_X Same as for __ALSOFT_REVERSE_Z, but for X (left/right) panning. +__ALSOFT_DEFAULT_ERROR +Applications that erroneously call alGetError prior to setting a context as +current may not like that OpenAL Soft returns 0xA004 (AL_INVALID_OPERATION), +indicating that the call could not be executed as there's no context to get the +error value from. This can be set to 0 (AL_NO_ERROR) to let such apps pass the +check despite the problem. Other applications, however, may see AL_NO_ERROR +returned and assume any previous AL calls succeeded when they actually failed, +so this should only be set when necessary. + __ALSOFT_SUSPEND_CONTEXT Due to the OpenAL spec not being very clear about them, behavior of the alcSuspendContext and alcProcessContext methods has varied, and because of diff --git a/Engine/lib/openal-soft/examples/alconvolve.c b/Engine/lib/openal-soft/examples/alconvolve.c index 93fd2eb4a..597d6ea2c 100644 --- a/Engine/lib/openal-soft/examples/alconvolve.c +++ b/Engine/lib/openal-soft/examples/alconvolve.c @@ -22,7 +22,7 @@ * THE SOFTWARE. */ -/* This file contains an example for applying convolution reverb to a source. */ +/* This file contains an example for applying convolution to a source. */ #include #include @@ -38,10 +38,12 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" -#ifndef AL_SOFT_convolution_reverb -#define AL_SOFT_convolution_reverb -#define AL_EFFECT_CONVOLUTION_REVERB_SOFT 0xA000 + +#ifndef AL_SOFT_convolution_effect +#define AL_SOFT_convolution_effect +#define AL_EFFECT_CONVOLUTION_SOFT 0xA000 #endif @@ -88,11 +90,11 @@ static LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv; /* This stuff defines a simple streaming player object, the same as alstream.c. * Comments are removed for brevity, see alstream.c for more details. */ -#define NUM_BUFFERS 4 -#define BUFFER_SAMPLES 8192 +enum { NumBuffers = 4 }; +enum { BufferSamples = 8192 }; typedef struct StreamPlayer { - ALuint buffers[NUM_BUFFERS]; + ALuint buffers[NumBuffers]; ALuint source; SNDFILE *sndfile; @@ -109,7 +111,7 @@ static StreamPlayer *NewPlayer(void) player = calloc(1, sizeof(*player)); assert(player != NULL); - alGenBuffers(NUM_BUFFERS, player->buffers); + alGenBuffers(NumBuffers, player->buffers); assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); alGenSources(1, &player->source); @@ -138,11 +140,11 @@ static void DeletePlayer(StreamPlayer *player) ClosePlayerFile(player); alDeleteSources(1, &player->source); - alDeleteBuffers(NUM_BUFFERS, player->buffers); + alDeleteBuffers(NumBuffers, player->buffers); if(alGetError() != AL_NO_ERROR) fprintf(stderr, "Failed to delete object IDs\n"); - memset(player, 0, sizeof(*player)); + memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */ free(player); } @@ -184,7 +186,7 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename) return 0; } - frame_size = (size_t)(BUFFER_SAMPLES * player->sfinfo.channels) * sizeof(float); + frame_size = (size_t)(BufferSamples * player->sfinfo.channels) * sizeof(float); player->membuf = malloc(frame_size); return 1; @@ -197,9 +199,9 @@ static int StartPlayer(StreamPlayer *player) alSourceRewind(player->source); alSourcei(player->source, AL_BUFFER, 0); - for(i = 0;i < NUM_BUFFERS;i++) + for(i = 0;i < NumBuffers;i++) { - sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + sf_count_t slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples); if(slen < 1) break; slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); @@ -243,7 +245,7 @@ static int UpdatePlayer(StreamPlayer *player) alSourceUnqueueBuffers(player->source, 1, &bufid); processed--; - slen = sf_readf_float(player->sndfile, player->membuf, BUFFER_SAMPLES); + slen = sf_readf_float(player->sndfile, player->membuf, BufferSamples); if(slen > 0) { slen *= player->sfinfo.channels * (sf_count_t)sizeof(float); @@ -278,21 +280,21 @@ static int UpdatePlayer(StreamPlayer *player) } -/* CreateEffect creates a new OpenAL effect object with a convolution reverb - * type, and returns the new effect ID. +/* CreateEffect creates a new OpenAL effect object with a convolution type, and + * returns the new effect ID. */ static ALuint CreateEffect(void) { ALuint effect = 0; ALenum err; - printf("Using Convolution Reverb\n"); + printf("Using Convolution\n"); - /* Create the effect object and set the convolution reverb effect type. */ + /* Create the effect object and set the convolution effect type. */ alGenEffects(1, &effect); - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_REVERB_SOFT); + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_CONVOLUTION_SOFT); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -359,10 +361,10 @@ static ALuint LoadSound(const char *filename) } namepart = strrchr(filename, '/'); - if(namepart || (namepart=strrchr(filename, '\\'))) - namepart++; - else - namepart = filename; + if(!namepart) namepart = strrchr(filename, '\\'); + if(!namepart) namepart = filename; + else namepart++; + printf("Loading: %s (%s, %dhz, %" PRId64 " samples / %.2f seconds)\n", namepart, FormatName(format), sfinfo.samplerate, sfinfo.frames, (double)sfinfo.frames / sfinfo.samplerate); @@ -391,7 +393,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -423,10 +425,10 @@ int main(int argc, char **argv) if(InitAL(&argv, &argc) != 0) return 1; - if(!alIsExtensionPresent("AL_SOFTX_convolution_reverb")) + if(!alIsExtensionPresent("AL_SOFTX_convolution_effect")) { CloseAL(); - fprintf(stderr, "Error: Convolution revern not supported\n"); + fprintf(stderr, "Error: Convolution effect not supported\n"); return 1; } @@ -500,11 +502,11 @@ int main(int argc, char **argv) alGenAuxiliaryEffectSlots(1, &slot); /* Set the impulse response sound buffer on the effect slot. This allows - * effects to access it as needed. In this case, convolution reverb uses it - * as the filter source. NOTE: Unlike the effect object, the buffer *is* - * kept referenced and may not be changed or deleted as long as it's set, - * just like with a source. When another buffer is set, or the effect slot - * is deleted, the buffer reference is released. + * effects to access it as needed. In this case, convolution uses it as the + * filter source. NOTE: Unlike the effect object, the buffer *is* kept + * referenced and may not be changed or deleted as long as it's set, just + * like with a source. When another buffer is set, or the effect slot is + * deleted, the buffer reference is released. * * The effect slot's gain is reduced because the impulse responses I've * tested with result in excessively loud reverb. Is that normal? Even with @@ -555,10 +557,9 @@ int main(int argc, char **argv) continue; namepart = strrchr(argv[i], '/'); - if(namepart || (namepart=strrchr(argv[i], '\\'))) - namepart++; - else - namepart = argv[i]; + if(!namepart) namepart = strrchr(argv[i], '\\'); + if(!namepart) namepart = argv[i]; + else namepart++; printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), player->sfinfo.samplerate); diff --git a/Engine/lib/openal-soft/examples/aldirect.cpp b/Engine/lib/openal-soft/examples/aldirect.cpp new file mode 100644 index 000000000..d7964adda --- /dev/null +++ b/Engine/lib/openal-soft/examples/aldirect.cpp @@ -0,0 +1,472 @@ +/* + * OpenAL Direct Context Example + * + * Copyright (c) 2024 by Chris Robinson + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* This file contains an example for playing a sound buffer with the Direct API + * extension. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sndfile.h" + +#include "AL/al.h" +#include "AL/alc.h" +#include "AL/alext.h" + +#include "alspan.h" +#include "common/alhelpers.h" + +#include "win_main_utf8.h" + +namespace { + +/* On Windows when using Creative's router, we need to override the ALC + * functions and access the driver functions directly. This isn't needed when + * not using the router, or on other OSs. + */ +LPALCOPENDEVICE p_alcOpenDevice{alcOpenDevice}; +LPALCCLOSEDEVICE p_alcCloseDevice{alcCloseDevice}; +LPALCISEXTENSIONPRESENT p_alcIsExtensionPresent{alcIsExtensionPresent}; +LPALCCREATECONTEXT p_alcCreateContext{alcCreateContext}; +LPALCDESTROYCONTEXT p_alcDestroyContext{alcDestroyContext}; +LPALCGETPROCADDRESS p_alcGetProcAddress{alcGetProcAddress}; + + +LPALGETSTRINGDIRECT alGetStringDirect{}; +LPALGETERRORDIRECT alGetErrorDirect{}; +LPALISEXTENSIONPRESENTDIRECT alIsExtensionPresentDirect{}; + +LPALGENBUFFERSDIRECT alGenBuffersDirect{}; +LPALDELETEBUFFERSDIRECT alDeleteBuffersDirect{}; +LPALISBUFFERDIRECT alIsBufferDirect{}; +LPALBUFFERIDIRECT alBufferiDirect{}; +LPALBUFFERDATADIRECT alBufferDataDirect{}; + +LPALGENSOURCESDIRECT alGenSourcesDirect{}; +LPALDELETESOURCESDIRECT alDeleteSourcesDirect{}; +LPALSOURCEIDIRECT alSourceiDirect{}; +LPALGETSOURCEIDIRECT alGetSourceiDirect{}; +LPALGETSOURCEFDIRECT alGetSourcefDirect{}; +LPALSOURCEPLAYDIRECT alSourcePlayDirect{}; + + +struct SndFileDeleter { + void operator()(SNDFILE *sndfile) { sf_close(sndfile); } +}; +using SndFilePtr = std::unique_ptr; + +enum class FormatType { + Int16, + Float, + IMA4, + MSADPCM +}; + +/* LoadBuffer loads the named audio file into an OpenAL buffer object, and + * returns the new buffer ID. + */ +ALuint LoadSound(ALCcontext *context, const std::string_view filename) +{ + /* Open the audio file and check that it's usable. */ + SF_INFO sfinfo{}; + SndFilePtr sndfile{sf_open(std::string{filename}.c_str(), SFM_READ, &sfinfo)}; + if(!sndfile) + { + std::cerr<< "Could not open audio in "<(inf.datalen, ALubyte{0}); + inf.data = fmtbuf.data(); + if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) + sample_format = FormatType::Int16; + else + { + /* Read the nBlockAlign field, and convert from bytes- to + * samples-per-block (verifying it's valid by converting back + * and comparing to the original value). + */ + byteblockalign = fmtbuf[12] | (fmtbuf[13]<<8); + if(sample_format == FormatType::IMA4) + { + splblockalign = (byteblockalign/sfinfo.channels - 4)/4*8 + 1; + if(splblockalign < 1 + || ((splblockalign-1)/2 + 4)*sfinfo.channels != byteblockalign) + sample_format = FormatType::Int16; + } + else if(sample_format == FormatType::MSADPCM) + { + splblockalign = (byteblockalign/sfinfo.channels - 7)*2 + 2; + if(splblockalign < 2 + || ((splblockalign-2)/2 + 7)*sfinfo.channels != byteblockalign) + sample_format = FormatType::Int16; + } + else + sample_format = FormatType::Int16; + } + } + } + + if(sample_format == FormatType::Int16) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 2; + } + else if(sample_format == FormatType::Float) + { + splblockalign = 1; + byteblockalign = sfinfo.channels * 4; + } + + /* Figure out the OpenAL format from the file and desired sample type. */ + ALenum format{AL_NONE}; + if(sfinfo.channels == 1) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_MONO16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_MONO_FLOAT32; + else if(sample_format == FormatType::IMA4) + format = AL_FORMAT_MONO_IMA4; + else if(sample_format == FormatType::MSADPCM) + format = AL_FORMAT_MONO_MSADPCM_SOFT; + } + else if(sfinfo.channels == 2) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_STEREO16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_STEREO_FLOAT32; + else if(sample_format == FormatType::IMA4) + format = AL_FORMAT_STEREO_IMA4; + else if(sample_format == FormatType::MSADPCM) + format = AL_FORMAT_STEREO_MSADPCM_SOFT; + } + else if(sfinfo.channels == 3) + { + if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_BFORMAT2D_16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_BFORMAT2D_FLOAT32; + } + } + else if(sfinfo.channels == 4) + { + if(sf_command(sndfile.get(), SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) + { + if(sample_format == FormatType::Int16) + format = AL_FORMAT_BFORMAT3D_16; + else if(sample_format == FormatType::Float) + format = AL_FORMAT_BFORMAT3D_FLOAT32; + } + } + if(!format) + { + std::cerr<< "Unsupported channel count: "< sf_count_t{std::numeric_limits::max()}/byteblockalign) + { + std::cerr<< "Too many sample frames in "<(static_cast(sfinfo.frames / splblockalign + * byteblockalign)); + + sf_count_t num_frames{}; + if(sample_format == FormatType::Int16) + num_frames = sf_readf_short(sndfile.get(), reinterpret_cast(membuf.data()), + sfinfo.frames); + else if(sample_format == FormatType::Float) + num_frames = sf_readf_float(sndfile.get(), reinterpret_cast(membuf.data()), + sfinfo.frames); + else + { + const sf_count_t count{sfinfo.frames / splblockalign * byteblockalign}; + num_frames = sf_read_raw(sndfile.get(), membuf.data(), count); + if(num_frames > 0) + num_frames = num_frames / byteblockalign * splblockalign; + } + if(num_frames < 1) + { + std::cerr<< "Failed to read samples in "<(num_frames / splblockalign * byteblockalign); + + std::cout<< "Loading: "< 1) + alBufferiDirect(context, buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, splblockalign); + alBufferDataDirect(context, buffer, format, membuf.data(), num_bytes, sfinfo.samplerate); + + /* Check if an error occurred, and clean up if so. */ + if(ALenum err{alGetErrorDirect(context)}; err != AL_NO_ERROR) + { + std::cerr<< "OpenAL Error: "< args) +{ + /* Print out usage if no arguments were specified */ + if(args.size() < 2) + { + std::cerr<< "Usage: "<] \n"; + return 1; + } + + /* Initialize OpenAL. */ + args = args.subspan(1); + + ALCdevice *device{}; + if(args.size() > 1 && args[0] == "-device") + { + device = p_alcOpenDevice(std::string{args[1]}.c_str()); + if(!device) + std::cerr<< "Failed to open \""<( + p_alcGetProcAddress(device, "alcGetProcAddress2")); + p_alcCloseDevice(device); + + /* Load the driver-specific ALC functions we'll be using. */ +#define LOAD_PROC(N) p_##N = reinterpret_cast(p_alcGetProcAddress2(nullptr, #N)) + LOAD_PROC(alcOpenDevice); + LOAD_PROC(alcCloseDevice); + LOAD_PROC(alcIsExtensionPresent); + LOAD_PROC(alcGetProcAddress); + LOAD_PROC(alcCreateContext); + LOAD_PROC(alcDestroyContext); + LOAD_PROC(alcGetProcAddress); +#undef LOAD_PROC + device = p_alcOpenDevice(devname.c_str()); + assert(device != nullptr); + } + + /* Load the Direct API functions we're using. */ +#define LOAD_PROC(N) N = reinterpret_cast(p_alcGetProcAddress(device, #N)) + LOAD_PROC(alGetStringDirect); + LOAD_PROC(alGetErrorDirect); + LOAD_PROC(alIsExtensionPresentDirect); + + LOAD_PROC(alGenBuffersDirect); + LOAD_PROC(alDeleteBuffersDirect); + LOAD_PROC(alIsBufferDirect); + LOAD_PROC(alBufferiDirect); + LOAD_PROC(alBufferDataDirect); + + LOAD_PROC(alGenSourcesDirect); + LOAD_PROC(alDeleteSourcesDirect); + LOAD_PROC(alSourceiDirect); + LOAD_PROC(alGetSourceiDirect); + LOAD_PROC(alGetSourcefDirect); + LOAD_PROC(alSourcePlayDirect); +#undef LOAD_PROC + + /* Create the context. It doesn't need to be set as current to use with the + * Direct API functions. + */ + ALCcontext *context{p_alcCreateContext(device, nullptr)}; + if(!context) + { + p_alcCloseDevice(device); + std::cerr<< "Could not create a context!\n"; + return 1; + } + + /* Load the sound into a buffer. */ + const ALuint buffer{LoadSound(context, args[0])}; + if(!buffer) + { + p_alcDestroyContext(context); + p_alcCloseDevice(device); + return 1; + } + + /* Create the source to play the sound with. */ + ALuint source{0}; + alGenSourcesDirect(context, 1, &source); + alSourceiDirect(context, source, AL_BUFFER, static_cast(buffer)); + assert(alGetErrorDirect(context)==AL_NO_ERROR && "Failed to setup sound source"); + + /* Play the sound until it finishes. */ + alSourcePlayDirect(context, source); + ALenum state{}; + do { + al_nssleep(10000000); + alGetSourceiDirect(context, source, AL_SOURCE_STATE, &state); + + /* Get the source offset. */ + ALfloat offset{}; + alGetSourcefDirect(context, source, AL_SEC_OFFSET, &offset); + printf("\rOffset: %f ", offset); + fflush(stdout); + } while(alGetErrorDirect(context) == AL_NO_ERROR && state == AL_PLAYING); + printf("\n"); + + /* All done. Delete resources, and close down OpenAL. */ + alDeleteSourcesDirect(context, 1, &source); + alDeleteBuffersDirect(context, 1, &buffer); + + p_alcDestroyContext(context); + p_alcCloseDevice(device); + + return 0; +} + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/alffplay.cpp b/Engine/lib/openal-soft/examples/alffplay.cpp index ae40a51aa..e51515e7e 100644 --- a/Engine/lib/openal-soft/examples/alffplay.cpp +++ b/Engine/lib/openal-soft/examples/alffplay.cpp @@ -4,29 +4,31 @@ * Requires C++14. */ -#include -#include #include -#include -#include -#include -#include -#include -#include +#include #include +#include #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include #include #include +#include +#include +#include +#include +#include #ifdef __GNUC__ _Pragma("GCC diagnostic push") @@ -67,17 +69,14 @@ _Pragma("GCC diagnostic pop") #include "AL/al.h" #include "AL/alext.h" +#include "alnumbers.h" +#include "alnumeric.h" +#include "alspan.h" #include "common/alhelpers.h" namespace { -inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast(n); } - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - using fixed32 = std::chrono::duration>; using nanoseconds = std::chrono::nanoseconds; using microseconds = std::chrono::microseconds; @@ -170,12 +169,17 @@ using SwsContextPtr = std::unique_ptr; struct ChannelLayout : public AVChannelLayout { ChannelLayout() : AVChannelLayout{} { } + ChannelLayout(const ChannelLayout &rhs) : AVChannelLayout{} + { av_channel_layout_copy(this, &rhs); } ~ChannelLayout() { av_channel_layout_uninit(this); } + + auto operator=(const ChannelLayout &rhs) -> ChannelLayout& + { av_channel_layout_copy(this, &rhs); return *this; } }; -template class DataQueue { + const size_t mSizeLimit; std::mutex mPacketMutex, mFrameMutex; std::condition_variable mPacketCond; std::condition_variable mInFrameCond, mOutFrameCond; @@ -199,6 +203,8 @@ class DataQueue { } public: + DataQueue(size_t size_limit) : mSizeLimit{size_limit} { } + int sendPacket(AVCodecContext *codecctx) { AVPacketPtr packet{getPacket()}; @@ -237,7 +243,7 @@ public: void setFinished() { { - std::lock_guard _{mPacketMutex}; + std::lock_guard packetlock{mPacketMutex}; mFinished = true; } mPacketCond.notify_one(); @@ -246,7 +252,7 @@ public: void flush() { { - std::lock_guard _{mPacketMutex}; + std::lock_guard packetlock{mPacketMutex}; mFinished = true; mPackets.clear(); @@ -258,8 +264,8 @@ public: bool put(const AVPacket *pkt) { { - std::unique_lock lock{mPacketMutex}; - if(mTotalSize >= SizeLimit || mFinished) + std::lock_guard packet_lock{mPacketMutex}; + if(mTotalSize >= mSizeLimit || mFinished) return false; mPackets.push_back(AVPacketPtr{av_packet_alloc()}); @@ -285,7 +291,7 @@ struct AudioState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue<2*1024*1024> mQueue; + DataQueue mQueue{2_uz*1024_uz*1024_uz}; /* Used for clock difference average computation */ seconds_d64 mClockDiffAvg{0}; @@ -305,13 +311,13 @@ struct AudioState { AVSampleFormat mDstSampleFmt{AV_SAMPLE_FMT_NONE}; /* Storage of converted samples */ - uint8_t *mSamples{nullptr}; + std::array mSamples{}; + al::span mSamplesSpan{}; int mSamplesLen{0}; /* In samples */ int mSamplesPos{0}; int mSamplesMax{0}; - std::unique_ptr mBufferData; - size_t mBufferDataSize{0}; + std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; @@ -321,7 +327,7 @@ struct AudioState { std::mutex mSrcMutex; std::condition_variable mSrcCond; - std::atomic_flag mConnected; + std::atomic_flag mConnected{}; ALuint mSource{0}; std::array mBuffers{}; ALuint mBufferIdx{0}; @@ -335,18 +341,18 @@ struct AudioState { if(mBuffers[0]) alDeleteBuffers(static_cast(mBuffers.size()), mBuffers.data()); - av_freep(&mSamples); + av_freep(mSamples.data()); } static void AL_APIENTRY eventCallbackC(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, void *userParam) + ALsizei length, const ALchar *message, void *userParam) noexcept { static_cast(userParam)->eventCallback(eventType, object, param, length, message); } void eventCallback(ALenum eventType, ALuint object, ALuint param, ALsizei length, - const ALchar *message); + const ALchar *message) noexcept; - static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) noexcept { return static_cast(userptr)->bufferCallback(data, size); } - ALsizei bufferCallback(void *data, ALsizei size); + ALsizei bufferCallback(void *data, ALsizei size) noexcept; nanoseconds getClockNoLock(); nanoseconds getClock() @@ -359,7 +365,7 @@ struct AudioState { int getSync(); int decodeFrame(); - bool readAudio(uint8_t *samples, unsigned int length, int &sample_skip); + bool readAudio(al::span samples, unsigned int length, int &sample_skip); bool readAudio(int sample_skip); int handler(); @@ -371,7 +377,7 @@ struct VideoState { AVStream *mStream{nullptr}; AVCodecCtxPtr mCodecCtx; - DataQueue<14*1024*1024> mQueue; + DataQueue mQueue{14_uz*1024_uz*1024_uz}; /* The pts of the currently displayed frame, and the time (av_gettime) it * was last updated - used to have running video pts @@ -409,7 +415,7 @@ struct VideoState { nanoseconds getClock(); - void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame); + void display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) const; void updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool redraw); int handler(); }; @@ -437,8 +443,7 @@ struct MovieState { std::string mFilename; - MovieState(std::string fname) - : mAudio(*this), mVideo(*this), mFilename(std::move(fname)) + MovieState(std::string_view fname) : mAudio{*this}, mVideo{*this}, mFilename{fname} { } ~MovieState() { @@ -449,16 +454,14 @@ struct MovieState { static int decode_interrupt_cb(void *ctx); bool prepare(); - void setTitle(SDL_Window *window); + void setTitle(SDL_Window *window) const; void stop(); - nanoseconds getClock(); + [[nodiscard]] nanoseconds getClock() const; + [[nodiscard]] nanoseconds getMasterClock(); + [[nodiscard]] nanoseconds getDuration() const; - nanoseconds getMasterClock(); - - nanoseconds getDuration(); - - int streamComponentOpen(unsigned int stream_index); + bool streamComponentOpen(AVStream *stream); int parse_handler(); }; @@ -474,8 +477,8 @@ nanoseconds AudioState::getClockNoLock() // Get the current device clock time and latency. auto device = alcGetContextsDevice(alcGetCurrentContext()); - ALCint64SOFT devtimes[2]{0,0}; - alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes); + std::array devtimes{}; + alcGetInteger64vSOFT(device, ALC_DEVICE_CLOCK_LATENCY_SOFT, 2, devtimes.data()); auto latency = nanoseconds{devtimes[1]}; auto device_time = nanoseconds{devtimes[0]}; @@ -485,7 +488,7 @@ nanoseconds AudioState::getClockNoLock() return device_time - mDeviceStartTime - latency; } - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { if(mDeviceStartTime == nanoseconds::min()) return nanoseconds::zero(); @@ -494,15 +497,14 @@ nanoseconds AudioState::getClockNoLock() * actually the timestamp of the first sample frame played. The audio * clock, then, is that plus the current source offset. */ - ALint64SOFT offset[2]; + std::array offset{}; if(alGetSourcei64vSOFT) - alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset); + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { ALint ioffset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; - offset[1] = 0; } /* NOTE: The source state must be checked last, in case an underrun * occurs and the source stops between getting the state and retrieving @@ -523,7 +525,7 @@ nanoseconds AudioState::getClockNoLock() */ const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset>=roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; pts = mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate; @@ -550,15 +552,14 @@ nanoseconds AudioState::getClockNoLock() nanoseconds pts{mCurrentPts}; if(mSource) { - ALint64SOFT offset[2]; + std::array offset{}; if(alGetSourcei64vSOFT) - alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset); + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_LATENCY_SOFT, offset.data()); else { ALint ioffset; alGetSourcei(mSource, AL_SAMPLE_OFFSET, &ioffset); offset[0] = ALint64SOFT{ioffset} << 32; - offset[1] = 0; } ALint queued, status; alGetSourcei(mSource, AL_BUFFERS_QUEUED, &queued); @@ -586,10 +587,10 @@ bool AudioState::startPlayback() { const size_t woffset{mWritePos.load(std::memory_order_acquire)}; const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { if(readable == 0) return false; @@ -610,8 +611,8 @@ bool AudioState::startPlayback() /* Subtract the total buffer queue time from the current pts to get the * pts of the start of the queue. */ - int64_t srctimes[2]{0,0}; - alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes); + std::array srctimes{}; + alGetSourcei64vSOFT(mSource, AL_SAMPLE_OFFSET_CLOCK_SOFT, srctimes.data()); auto device_time = nanoseconds{srctimes[1]}; auto src_offset = duration_cast(fixed32{srctimes[0]}) / mCodecCtx->sample_rate; @@ -622,7 +623,7 @@ bool AudioState::startPlayback() * the device time the stream would have started at to reach where it * is now. */ - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { nanoseconds startpts{mCurrentPts - nanoseconds{seconds{readable/mFrameSize}}/mCodecCtx->sample_rate}; @@ -680,14 +681,19 @@ int AudioState::decodeFrame() if(mDecodedFrame->nb_samples > mSamplesMax) { - av_freep(&mSamples); - av_samples_alloc(&mSamples, nullptr, mCodecCtx->ch_layout.nb_channels, + av_freep(mSamples.data()); + av_samples_alloc(mSamples.data(), nullptr, mCodecCtx->ch_layout.nb_channels, mDecodedFrame->nb_samples, mDstSampleFmt, 0); mSamplesMax = mDecodedFrame->nb_samples; + mSamplesSpan = {mSamples[0], static_cast(mSamplesMax)*mFrameSize}; } + /* Copy to a local to mark const. Don't know why this can't be implicit. */ + using data_t = decltype(decltype(mDecodedFrame)::element_type::data); + std::array> cdata{}; + std::copy(std::begin(mDecodedFrame->data), std::end(mDecodedFrame->data), cdata.begin()); /* Return the amount of sample frames converted */ - int data_size{swr_convert(mSwresCtx.get(), &mSamples, mDecodedFrame->nb_samples, - const_cast(mDecodedFrame->data), mDecodedFrame->nb_samples)}; + const int data_size{swr_convert(mSwresCtx.get(), mSamples.data(), mDecodedFrame->nb_samples, + cdata.data(), mDecodedFrame->nb_samples)}; av_frame_unref(mDecodedFrame.get()); return data_size; @@ -697,15 +703,15 @@ int AudioState::decodeFrame() * multiple of the template type size. */ template -static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { - auto *sample = reinterpret_cast(in); - auto *dst = reinterpret_cast(out); + auto sample = al::span{reinterpret_cast(in.data()), in.size()/sizeof(T)}; + auto dst = al::span{reinterpret_cast(out.data()), out.size()/sizeof(T)}; /* NOTE: frame_size is a multiple of sizeof(T). */ - size_t type_mult{frame_size / sizeof(T)}; + const size_t type_mult{frame_size / sizeof(T)}; if(type_mult == 1) - std::fill_n(dst, count, *sample); + std::fill_n(dst.begin(), count, sample.front()); else for(size_t i{0};i < count;++i) { for(size_t j{0};j < type_mult;++j) @@ -713,7 +719,7 @@ static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t fra } } -static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t frame_size) +void sample_dup(al::span out, al::span in, size_t count, size_t frame_size) { if((frame_size&7) == 0) sample_dup(out, in, count, frame_size); @@ -725,7 +731,7 @@ static void sample_dup(uint8_t *out, const uint8_t *in, size_t count, size_t fra sample_dup(out, in, count, frame_size); } -bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_skip) +bool AudioState::readAudio(al::span samples, unsigned int length, int &sample_skip) { unsigned int audio_size{0}; @@ -739,20 +745,21 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk { const auto len = static_cast(mSamplesLen - mSamplesPos); if(rem > len) rem = len; - std::copy_n(mSamples + static_cast(mSamplesPos)*mFrameSize, - rem*mFrameSize, samples); + const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; + std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), rem*size_t{mFrameSize}, + samples.begin()); } else { rem = std::min(rem, static_cast(-mSamplesPos)); /* Add samples by copying the first sample */ - sample_dup(samples, mSamples, rem, mFrameSize); + sample_dup(samples, mSamplesSpan, rem, mFrameSize); } - mSamplesPos += rem; + mSamplesPos += static_cast(rem); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; - samples += rem*mFrameSize; + samples = samples.subspan(rem*size_t{mFrameSize}); audio_size += rem; while(mSamplesPos >= mSamplesLen) @@ -777,7 +784,7 @@ bool AudioState::readAudio(uint8_t *samples, unsigned int length, int &sample_sk if(audio_size < length) { const unsigned int rem{length - audio_size}; - std::fill_n(samples, rem*mFrameSize, + std::fill_n(samples.begin(), rem*mFrameSize, (mDstSampleFmt == AV_SAMPLE_FMT_U8) ? 0x80 : 0x00); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; } @@ -791,17 +798,17 @@ bool AudioState::readAudio(int sample_skip) while(mSamplesLen > 0) { const size_t nsamples{((roffset > woffset) ? roffset-woffset-1 - : (roffset == 0) ? (mBufferDataSize-woffset-1) - : (mBufferDataSize-woffset)) / mFrameSize}; + : (roffset == 0) ? (mBufferData.size()-woffset-1) + : (mBufferData.size()-woffset)) / mFrameSize}; if(!nsamples) break; if(mSamplesPos < 0) { const size_t rem{std::min(nsamples, static_cast(-mSamplesPos))}; - sample_dup(&mBufferData[woffset], mSamples, rem, mFrameSize); + sample_dup(al::span{mBufferData}.subspan(woffset), mSamplesSpan, rem, mFrameSize); woffset += rem * mFrameSize; - if(woffset == mBufferDataSize) woffset = 0; + if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; @@ -813,9 +820,10 @@ bool AudioState::readAudio(int sample_skip) const size_t boffset{static_cast(mSamplesPos) * size_t{mFrameSize}}; const size_t nbytes{rem * mFrameSize}; - memcpy(&mBufferData[woffset], mSamples + boffset, nbytes); + std::copy_n(mSamplesSpan.cbegin()+ptrdiff_t(boffset), nbytes, + mBufferData.begin()+ptrdiff_t(woffset)); woffset += nbytes; - if(woffset == mBufferDataSize) woffset = 0; + if(woffset == mBufferData.size()) woffset = 0; mWritePos.store(woffset, std::memory_order_release); mCurrentPts += nanoseconds{seconds{rem}} / mCodecCtx->sample_rate; @@ -840,7 +848,7 @@ bool AudioState::readAudio(int sample_skip) void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message) + ALsizei length, const ALchar *message) noexcept { if(eventType == AL_EVENT_TYPE_BUFFER_COMPLETED_SOFT) { @@ -878,25 +886,26 @@ void AL_APIENTRY AudioState::eventCallback(ALenum eventType, ALuint object, ALui } } -ALsizei AudioState::bufferCallback(void *data, ALsizei size) +ALsizei AudioState::bufferCallback(void *data, ALsizei size) noexcept { + auto dst = al::span{static_cast(data), static_cast(size)}; ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) + while(!dst.empty()) { const size_t woffset{mWritePos.load(std::memory_order_relaxed)}; if(woffset == roffset) break; - size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; - todo = std::min(todo, static_cast(size-got)); + size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; + todo = std::min(todo, dst.size()); - memcpy(data, &mBufferData[roffset], todo); - data = static_cast(data) + todo; + std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); + dst = dst.subspan(todo); got += static_cast(todo); roffset += todo; - if(roffset == mBufferDataSize) + if(roffset == mBufferData.size()) roffset = 0; } mReadPos.store(roffset, std::memory_order_release); @@ -936,10 +945,11 @@ int AudioState::handler() }; EventControlManager event_controller{sleep_time}; - std::unique_ptr samples; + std::vector samples; ALsizei buffer_len{0}; /* Find a suitable format for OpenAL. */ + const auto layoutmask = mCodecCtx->ch_layout.u.mask; /* NOLINT(*-union-access) */ mDstChanLayout = 0; mFormat = AL_NONE; if((mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLT || mCodecCtx->sample_fmt == AV_SAMPLE_FMT_FLTP @@ -957,29 +967,28 @@ int AudioState::handler() { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_7POINT1) + if(layoutmask == AV_CH_LAYOUT_7POINT1) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN32"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN32"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_QUAD) + if(layoutmask == AV_CH_LAYOUT_QUAD) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_QUAD32"); } } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_MONO) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO_FLOAT32; } @@ -1019,29 +1028,28 @@ int AudioState::handler() { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_7POINT1) + if(layoutmask == AV_CH_LAYOUT_7POINT1) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN8"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN8"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_QUAD) + if(layoutmask == AV_CH_LAYOUT_QUAD) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_QUAD8"); } } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_MONO) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO8; } @@ -1073,29 +1081,28 @@ int AudioState::handler() { if(alIsExtensionPresent("AL_EXT_MCFORMATS")) { - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_7POINT1) + if(layoutmask == AV_CH_LAYOUT_7POINT1) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 8; mFormat = alGetEnumValue("AL_FORMAT_71CHN16"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1 - || mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_5POINT1_BACK) + if(layoutmask == AV_CH_LAYOUT_5POINT1 || layoutmask == AV_CH_LAYOUT_5POINT1_BACK) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 6; mFormat = alGetEnumValue("AL_FORMAT_51CHN16"); } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_QUAD) + if(layoutmask == AV_CH_LAYOUT_QUAD) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 4; mFormat = alGetEnumValue("AL_FORMAT_QUAD16"); } } - if(mCodecCtx->ch_layout.u.mask == AV_CH_LAYOUT_MONO) + if(layoutmask == AV_CH_LAYOUT_MONO) { - mDstChanLayout = mCodecCtx->ch_layout.u.mask; + mDstChanLayout = layoutmask; mFrameSize *= 1; mFormat = AL_FORMAT_MONO16; } @@ -1120,7 +1127,8 @@ int AudioState::handler() } } - mSamples = nullptr; + mSamples.fill(nullptr); + mSamplesSpan = {}; mSamplesMax = 0; mSamplesPos = 0; mSamplesLen = 0; @@ -1151,9 +1159,9 @@ int AudioState::handler() mSwresCtx.reset(ps); if(err != 0) { - char errstr[AV_ERROR_MAX_STRING_SIZE]{}; + std::array errstr{}; std::cerr<< "Failed to allocate SwrContext: " - < mtx(64*64, 0.0); + std::vector mtx(64_uz*64_uz, 0.0); mtx[0 + 0*64] = std::sqrt(0.5); mtx[3 + 1*64] = 1.0; mtx[1 + 2*64] = 1.0; @@ -1185,17 +1193,17 @@ int AudioState::handler() mSwresCtx.reset(ps); if(err != 0) { - char errstr[AV_ERROR_MAX_STRING_SIZE]{}; + std::array errstr{}; std::cerr<< "Failed to allocate SwrContext: " - < errstr{}; std::cerr<< "Failed to initialize audio converter: " - <(M_PI / 3.0), static_cast(-M_PI / 3.0)}; - alSourcefv(mSource, AL_STEREO_ANGLES, angles); + static constexpr std::array angles{static_cast(al::numbers::pi / 3.0), + static_cast(-al::numbers::pi / 3.0)}; + alSourcefv(mSource, AL_STEREO_ANGLES, angles.data()); } if(has_bfmt_ex) { @@ -1237,13 +1246,12 @@ int AudioState::handler() } else { - mBufferDataSize = static_cast(duration_cast(mCodecCtx->sample_rate * - AudioBufferTotalTime).count()) * mFrameSize; - mBufferData = std::make_unique(mBufferDataSize); - std::fill_n(mBufferData.get(), mBufferDataSize, uint8_t{}); + mBufferData.resize(static_cast(duration_cast(mCodecCtx->sample_rate * + AudioBufferTotalTime).count()) * mFrameSize); + std::fill(mBufferData.begin(), mBufferData.end(), uint8_t{}); mReadPos.store(0, std::memory_order_relaxed); - mWritePos.store(mBufferDataSize/mFrameSize/2*mFrameSize, std::memory_order_relaxed); + mWritePos.store(mBufferData.size()/mFrameSize/2*mFrameSize, std::memory_order_relaxed); ALCint refresh{}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); @@ -1255,12 +1263,12 @@ int AudioState::handler() buffer_len = static_cast(duration_cast(mCodecCtx->sample_rate * AudioBufferTime).count() * mFrameSize); if(buffer_len > 0) - samples = std::make_unique(static_cast(buffer_len)); + samples.resize(static_cast(buffer_len)); /* Prefill the codec buffer. */ auto packet_sender = [this]() { - while(1) + while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; @@ -1287,7 +1295,7 @@ int AudioState::handler() mCurrentPts += skip; } - while(1) + while(true) { if(mMovie.mQuit.load(std::memory_order_relaxed)) { @@ -1299,11 +1307,11 @@ int AudioState::handler() mSamplesLen = decodeFrame(); mSamplesPos = mSamplesLen; } while(mSamplesLen > 0); - goto finish; + break; } ALenum state; - if(mBufferDataSize > 0) + if(!mBufferData.empty()) { alGetSourcei(mSource, AL_SOURCE_STATE, &state); @@ -1333,13 +1341,13 @@ int AudioState::handler() /* Read the next chunk of data, filling the buffer, and queue * it on the source. */ - if(!readAudio(samples.get(), static_cast(buffer_len), sync_skip)) + if(!readAudio(samples, static_cast(buffer_len), sync_skip)) break; const ALuint bufid{mBuffers[mBufferIdx]}; mBufferIdx = static_cast((mBufferIdx+1) % mBuffers.size()); - alBufferData(bufid, mFormat, samples.get(), buffer_len, mCodecCtx->sample_rate); + alBufferData(bufid, mFormat, samples.data(), buffer_len, mCodecCtx->sample_rate); alSourceQueueBuffers(mSource, 1, &bufid); ++queued; } @@ -1380,7 +1388,6 @@ int AudioState::handler() mSrcCond.wait_for(srclock, sleep_time); } -finish: alSourceRewind(mSource); alSourcei(mSource, AL_BUFFER, 0); @@ -1393,7 +1400,7 @@ finish: nanoseconds VideoState::getClock() { /* NOTE: This returns incorrect times while not playing. */ - std::lock_guard _{mDispPtsMutex}; + std::lock_guard displock{mDispPtsMutex}; if(mDisplayPtsTime == microseconds::min()) return nanoseconds::zero(); auto delta = get_avtime() - mDisplayPtsTime; @@ -1401,7 +1408,7 @@ nanoseconds VideoState::getClock() } /* Called by VideoState::updateVideo to display the next video frame. */ -void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) +void VideoState::display(SDL_Window *screen, SDL_Renderer *renderer, AVFrame *frame) const { if(!mImage) return; @@ -1451,7 +1458,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re auto clocktime = mMovie.getMasterClock(); bool updated{false}; - while(1) + while(true) { size_t next_idx{(read_idx+1)%mPictQ.size()}; if(next_idx == mPictQWrite.load(std::memory_order_acquire)) @@ -1511,9 +1518,9 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re { double aspect_ratio = av_q2d(frame->sample_aspect_ratio); if(aspect_ratio >= 1.0) - frame_width = static_cast(frame_width*aspect_ratio + 0.5); + frame_width = static_cast(std::lround(frame_width * aspect_ratio)); else if(aspect_ratio > 0.0) - frame_height = static_cast(frame_height/aspect_ratio + 0.5); + frame_height = static_cast(std::lround(frame_height / aspect_ratio)); } SDL_SetWindowSize(screen, frame_width, frame_height); } @@ -1546,18 +1553,17 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re } /* point pict at the queue */ - uint8_t *pict_data[3]; - pict_data[0] = static_cast(pixels); - pict_data[1] = pict_data[0] + w*h; - pict_data[2] = pict_data[1] + w*h/4; + const auto framesize = static_cast(w)*static_cast(h); + const auto pixelspan = al::span{static_cast(pixels), framesize*3/2}; + const std::array pict_data{ + al::to_address(pixelspan.begin()), + al::to_address(pixelspan.begin() + ptrdiff_t{w}*h), + al::to_address(pixelspan.begin() + ptrdiff_t{w}*h + ptrdiff_t{w}*h/4) + }; + const std::array pict_linesize{pitch, pitch/2, pitch/2}; - int pict_linesize[3]; - pict_linesize[0] = pitch; - pict_linesize[1] = pitch / 2; - pict_linesize[2] = pitch / 2; - - sws_scale(mSwscaleCtx.get(), reinterpret_cast(frame->data), frame->linesize, - 0, h, pict_data, pict_linesize); + sws_scale(mSwscaleCtx.get(), std::data(frame->data), std::data(frame->linesize), + 0, h, pict_data.data(), pict_linesize.data()); SDL_UnlockTexture(mImage); } @@ -1575,7 +1581,7 @@ void VideoState::updateVideo(SDL_Window *screen, SDL_Renderer *renderer, bool re { auto disp_time = get_avtime(); - std::lock_guard _{mDispPtsMutex}; + std::lock_guard displock{mDispPtsMutex}; mDisplayPts = vp->mPts; mDisplayPtsTime = disp_time; } @@ -1599,7 +1605,7 @@ int VideoState::handler() /* Prefill the codec buffer. */ auto packet_sender = [this]() { - while(1) + while(true) { const int ret{mQueue.sendPacket(mCodecCtx.get())}; if(ret == AVErrorEOF) break; @@ -1608,12 +1614,12 @@ int VideoState::handler() auto sender = std::async(std::launch::async, packet_sender); { - std::lock_guard _{mDispPtsMutex}; + std::lock_guard displock{mDispPtsMutex}; mDisplayPtsTime = get_avtime(); } auto current_pts = nanoseconds::zero(); - while(1) + while(true) { size_t write_idx{mPictQWrite.load(std::memory_order_relaxed)}; Picture *vp{&mPictQ[write_idx]}; @@ -1707,7 +1713,7 @@ bool MovieState::prepare() return true; } -void MovieState::setTitle(SDL_Window *window) +void MovieState::setTitle(SDL_Window *window) const { auto pos1 = mFilename.rfind('/'); auto pos2 = mFilename.rfind('\\'); @@ -1717,7 +1723,7 @@ void MovieState::setTitle(SDL_Window *window) SDL_SetWindowTitle(window, (mFilename.substr(fpos)+" - "+AppName).c_str()); } -nanoseconds MovieState::getClock() +nanoseconds MovieState::getClock() const { if(mClockBase == microseconds::min()) return nanoseconds::zero(); @@ -1733,49 +1739,46 @@ nanoseconds MovieState::getMasterClock() return getClock(); } -nanoseconds MovieState::getDuration() +nanoseconds MovieState::getDuration() const { return std::chrono::duration>(mFormatCtx->duration); } -int MovieState::streamComponentOpen(unsigned int stream_index) +bool MovieState::streamComponentOpen(AVStream *stream) { - if(stream_index >= mFormatCtx->nb_streams) - return -1; - /* Get a pointer to the codec context for the stream, and open the * associated codec. */ AVCodecCtxPtr avctx{avcodec_alloc_context3(nullptr)}; - if(!avctx) return -1; + if(!avctx) return false; - if(avcodec_parameters_to_context(avctx.get(), mFormatCtx->streams[stream_index]->codecpar)) - return -1; + if(avcodec_parameters_to_context(avctx.get(), stream->codecpar)) + return false; const AVCodec *codec{avcodec_find_decoder(avctx->codec_id)}; if(!codec || avcodec_open2(avctx.get(), codec, nullptr) < 0) { std::cerr<< "Unsupported codec: "<codec_id) << " (0x"<codec_id<codec_type) { case AVMEDIA_TYPE_AUDIO: - mAudio.mStream = mFormatCtx->streams[stream_index]; + mAudio.mStream = stream; mAudio.mCodecCtx = std::move(avctx); - break; + return true; case AVMEDIA_TYPE_VIDEO: - mVideo.mStream = mFormatCtx->streams[stream_index]; + mVideo.mStream = stream; mVideo.mCodecCtx = std::move(avctx); - break; + return true; default: - return -1; + break; } - return static_cast(stream_index); + return false; } int MovieState::parse_handler() @@ -1787,13 +1790,16 @@ int MovieState::parse_handler() int audio_index{-1}; /* Find the first video and audio streams */ - for(unsigned int i{0u};i < mFormatCtx->nb_streams;i++) + const auto ctxstreams = al::span{mFormatCtx->streams, mFormatCtx->nb_streams}; + for(size_t i{0};i < ctxstreams.size();++i) { - auto codecpar = mFormatCtx->streams[i]->codecpar; - if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0) - video_index = streamComponentOpen(i); - else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0) - audio_index = streamComponentOpen(i); + auto codecpar = ctxstreams[i]->codecpar; + if(codecpar->codec_type == AVMEDIA_TYPE_VIDEO && !DisableVideo && video_index < 0 + && streamComponentOpen(ctxstreams[i])) + video_index = static_cast(i); + else if(codecpar->codec_type == AVMEDIA_TYPE_AUDIO && audio_index < 0 + && streamComponentOpen(ctxstreams[i])) + audio_index = static_cast(i); } { @@ -1895,18 +1901,16 @@ std::ostream &operator<<(std::ostream &os, const PrettyTime &rhs) return os; } -} // namespace - -int main(int argc, char *argv[]) +int main(al::span args) { SDL_SetMainReady(); std::unique_ptr movState; - if(argc < 2) + if(args.size() < 2) { - std::cerr<< "Usage: "<] [-direct] " <] [-direct] " <( - alcGetProcAddress(device, "alcGetInteger64vSOFT") - ); + alcGetProcAddress(device, "alcGetInteger64vSOFT")); } } @@ -1988,8 +1988,7 @@ int main(int argc, char *argv[]) { std::cout<< "Found AL_SOFT_source_latency" <( - alGetProcAddress("alGetSourcei64vSOFT") - ); + alGetProcAddress("alGetSourcei64vSOFT")); } if(alIsExtensionPresent("AL_SOFT_events")) { @@ -2006,10 +2005,10 @@ int main(int argc, char *argv[]) alGetProcAddress("alBufferCallbackSOFT")); } - int fileidx{0}; - for(;fileidx < argc;++fileidx) + size_t fileidx{0}; + for(;fileidx < args.size();++fileidx) { - if(strcmp(argv[fileidx], "-direct") == 0) + if(args[fileidx] == "-direct") { if(alIsExtensionPresent("AL_SOFT_direct_channels_remix")) { @@ -2024,7 +2023,7 @@ int main(int argc, char *argv[]) else std::cerr<< "AL_SOFT_direct_channels not supported for direct output" <{new MovieState{argv[fileidx++]}}; + movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(!movState) @@ -2077,7 +2076,7 @@ int main(int argc, char *argv[]) Next, Quit } eom_action{EomAction::Next}; seconds last_time{seconds::min()}; - while(1) + while(true) { /* SDL_WaitEventTimeout is broken, just force a 10ms sleep. */ std::this_thread::sleep_for(milliseconds{10}); @@ -2143,9 +2142,9 @@ int main(int argc, char *argv[]) if(eom_action != EomAction::Quit) { movState = nullptr; - while(fileidx < argc && !movState) + while(fileidx < args.size() && !movState) { - movState = std::unique_ptr{new MovieState{argv[fileidx++]}}; + movState = std::make_unique(args[fileidx++]); if(!movState->prepare()) movState = nullptr; } if(movState) @@ -2179,3 +2178,13 @@ int main(int argc, char *argv[]) std::cerr<< "SDL_WaitEvent error - "<= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/alhrtf.c b/Engine/lib/openal-soft/examples/alhrtf.c index d878870e1..a2f1824a1 100644 --- a/Engine/lib/openal-soft/examples/alhrtf.c +++ b/Engine/lib/openal-soft/examples/alhrtf.c @@ -40,6 +40,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + #ifndef M_PI #define M_PI (3.14159265358979323846) @@ -121,7 +123,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/allatency.c b/Engine/lib/openal-soft/examples/allatency.c index ab4a4ebc1..9a5354423 100644 --- a/Engine/lib/openal-soft/examples/allatency.c +++ b/Engine/lib/openal-soft/examples/allatency.c @@ -37,6 +37,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + static LPALSOURCEDSOFT alSourcedSOFT; static LPALSOURCE3DSOFT alSource3dSOFT; @@ -124,7 +126,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/alloopback.c b/Engine/lib/openal-soft/examples/alloopback.c index 56cd420f3..106d0d461 100644 --- a/Engine/lib/openal-soft/examples/alloopback.c +++ b/Engine/lib/openal-soft/examples/alloopback.c @@ -29,6 +29,7 @@ #include #include #include +#include #define SDL_MAIN_HANDLED #include "SDL.h" @@ -118,7 +119,7 @@ static ALuint CreateSineWave(void) alGenBuffers(1, &buffer); alBufferData(buffer, AL_FORMAT_MONO16, data, sizeof(data), 44100); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/almultireverb.c b/Engine/lib/openal-soft/examples/almultireverb.c index a77cc59e5..f9a46c4f0 100644 --- a/Engine/lib/openal-soft/examples/almultireverb.c +++ b/Engine/lib/openal-soft/examples/almultireverb.c @@ -47,6 +47,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -106,7 +108,8 @@ static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb) * the needed panning vectors). */ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - if((err=alGetError()) != AL_NO_ERROR) + err = alGetError(); + if(err != AL_NO_ERROR) { fprintf(stderr, "Failed to set EAX Reverb: %s (0x%04x)\n", alGetString(err), err); return 0; @@ -137,8 +140,9 @@ static int LoadEffect(ALuint effect, const EFXEAXREVERBPROPERTIES *reverb) alEffectf(effect, AL_EAXREVERB_ROOM_ROLLOFF_FACTOR, reverb->flRoomRolloffFactor); alEffecti(effect, AL_EAXREVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); - /* Check if an error occured, and return failure if so. */ - if((err=alGetError()) != AL_NO_ERROR) + /* Check if an error occurred, and return failure if so. */ + err = alGetError(); + if(err != AL_NO_ERROR) { fprintf(stderr, "Error setting up reverb: %s\n", alGetString(err)); return 0; @@ -210,7 +214,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -493,7 +497,7 @@ int main(int argc, char **argv) } if(argc < 1) { - fprintf(stderr, "No filename spacified.\n"); + fprintf(stderr, "No filename specified.\n"); CloseAL(); return 1; } diff --git a/Engine/lib/openal-soft/examples/alplay.c b/Engine/lib/openal-soft/examples/alplay.c index 4291cb476..9af7ca40e 100644 --- a/Engine/lib/openal-soft/examples/alplay.c +++ b/Engine/lib/openal-soft/examples/alplay.c @@ -37,6 +37,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + enum FormatType { Int16, @@ -266,7 +268,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/alrecord.c b/Engine/lib/openal-soft/examples/alrecord.c index 03894493b..2a1fbd2f4 100644 --- a/Engine/lib/openal-soft/examples/alrecord.c +++ b/Engine/lib/openal-soft/examples/alrecord.c @@ -139,7 +139,7 @@ int main(int argc, char **argv) char *end; if(strcmp(argv[0], "--") == 0) break; - else if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0) + if(strcmp(argv[0], "--channels") == 0 || strcmp(argv[0], "-c") == 0) { if(argc < 2) { diff --git a/Engine/lib/openal-soft/examples/alreverb.c b/Engine/lib/openal-soft/examples/alreverb.c index 11a3ac6b2..5f68fe3a1 100644 --- a/Engine/lib/openal-soft/examples/alreverb.c +++ b/Engine/lib/openal-soft/examples/alreverb.c @@ -40,6 +40,8 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + /* Effect object functions */ static LPALGENEFFECTS alGenEffects; @@ -75,16 +77,17 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) ALuint effect = 0; ALenum err; + /* Clear error state. */ + alGetError(); + /* Create the effect object and check if we can do EAX reverb. */ alGenEffects(1, &effect); - if(alGetEnumValue("AL_EFFECT_EAXREVERB") != 0) + alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); + err = alGetError(); + if(err == AL_NO_ERROR) { printf("Using EAX Reverb\n"); - /* EAX Reverb is available. Set the EAX effect type then load the - * reverb properties. */ - alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB); - alEffectf(effect, AL_EAXREVERB_DENSITY, reverb->flDensity); alEffectf(effect, AL_EAXREVERB_DIFFUSION, reverb->flDiffusion); alEffectf(effect, AL_EAXREVERB_GAIN, reverb->flGain); @@ -114,7 +117,8 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) printf("Using Standard Reverb\n"); /* No EAX Reverb. Set the standard reverb effect type then load the - * available reverb properties. */ + * available reverb properties. + */ alEffecti(effect, AL_EFFECT_TYPE, AL_EFFECT_REVERB); alEffectf(effect, AL_REVERB_DENSITY, reverb->flDensity); @@ -132,7 +136,7 @@ static ALuint LoadEffect(const EFXEAXREVERBPROPERTIES *reverb) alEffecti(effect, AL_REVERB_DECAY_HFLIMIT, reverb->iDecayHFLimit); } - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -219,7 +223,7 @@ static ALuint LoadSound(const char *filename) free(membuf); sf_close(sndfile); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { diff --git a/Engine/lib/openal-soft/examples/alstream.c b/Engine/lib/openal-soft/examples/alstream.c index a61680d25..028290f55 100644 --- a/Engine/lib/openal-soft/examples/alstream.c +++ b/Engine/lib/openal-soft/examples/alstream.c @@ -37,13 +37,15 @@ #include "common/alhelpers.h" +#include "win_main_utf8.h" + /* Define the number of buffers and buffer size (in milliseconds) to use. 4 * buffers at 200ms each gives a nice per-chunk size, and lets the queue last * for almost one second. */ -#define NUM_BUFFERS 4 -#define BUFFER_MILLISEC 200 +enum { NumBuffers = 4 }; +enum { BufferMillisec = 200 }; typedef enum SampleType { Int16, Float, IMA4, MSADPCM @@ -51,7 +53,7 @@ typedef enum SampleType { typedef struct StreamPlayer { /* These are the buffers and source to play out through OpenAL with. */ - ALuint buffers[NUM_BUFFERS]; + ALuint buffers[NumBuffers]; ALuint source; /* Handle for the audio file */ @@ -88,7 +90,7 @@ static StreamPlayer *NewPlayer(void) assert(player != NULL); /* Generate the buffers and source */ - alGenBuffers(NUM_BUFFERS, player->buffers); + alGenBuffers(NumBuffers, player->buffers); assert(alGetError() == AL_NO_ERROR && "Could not create buffers"); alGenSources(1, &player->source); @@ -111,11 +113,11 @@ static void DeletePlayer(StreamPlayer *player) ClosePlayerFile(player); alDeleteSources(1, &player->source); - alDeleteBuffers(NUM_BUFFERS, player->buffers); + alDeleteBuffers(NumBuffers, player->buffers); if(alGetError() != AL_NO_ERROR) fprintf(stderr, "Failed to delete object IDs\n"); - memset(player, 0, sizeof(*player)); + memset(player, 0, sizeof(*player)); /* NOLINT(clang-analyzer-security.insecureAPI.*) */ free(player); } @@ -291,8 +293,8 @@ static int OpenPlayerFile(StreamPlayer *player, const char *filename) } player->block_count = player->sfinfo.samplerate / player->sampleblockalign; - player->block_count = player->block_count * BUFFER_MILLISEC / 1000; - player->membuf = malloc((size_t)(player->block_count * player->byteblockalign)); + player->block_count = player->block_count * BufferMillisec / 1000; + player->membuf = malloc((size_t)player->block_count * (size_t)player->byteblockalign); return 1; } @@ -310,7 +312,7 @@ static void ClosePlayerFile(StreamPlayer *player) if(player->sampleblockalign > 1) { ALsizei i; - for(i = 0;i < NUM_BUFFERS;i++) + for(i = 0;i < NumBuffers;i++) alBufferi(player->buffers[i], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, 0); player->sampleblockalign = 0; player->byteblockalign = 0; @@ -328,7 +330,7 @@ static int StartPlayer(StreamPlayer *player) alSourcei(player->source, AL_BUFFER, 0); /* Fill the buffer queue */ - for(i = 0;i < NUM_BUFFERS;i++) + for(i = 0;i < NumBuffers;i++) { sf_count_t slen; @@ -336,21 +338,21 @@ static int StartPlayer(StreamPlayer *player) if(player->sample_type == Int16) { slen = sf_readf_short(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen < 1) break; slen *= player->byteblockalign; } else if(player->sample_type == Float) { slen = sf_readf_float(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen < 1) break; slen *= player->byteblockalign; } else { slen = sf_read_raw(player->sndfile, player->membuf, - player->block_count * player->byteblockalign); + (sf_count_t)player->block_count * player->byteblockalign); if(slen > 0) slen -= slen%player->byteblockalign; if(slen < 1) break; } @@ -407,19 +409,19 @@ static int UpdatePlayer(StreamPlayer *player) if(player->sample_type == Int16) { slen = sf_readf_short(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen > 0) slen *= player->byteblockalign; } else if(player->sample_type == Float) { slen = sf_readf_float(player->sndfile, player->membuf, - player->block_count * player->sampleblockalign); + (sf_count_t)player->block_count * player->sampleblockalign); if(slen > 0) slen *= player->byteblockalign; } else { slen = sf_read_raw(player->sndfile, player->membuf, - player->block_count * player->byteblockalign); + (sf_count_t)player->block_count * player->byteblockalign); if(slen > 0) slen -= slen%player->byteblockalign; } @@ -486,10 +488,9 @@ int main(int argc, char **argv) /* Get the name portion, without the path, for display. */ namepart = strrchr(argv[i], '/'); - if(namepart || (namepart=strrchr(argv[i], '\\'))) - namepart++; - else - namepart = argv[i]; + if(!namepart) namepart = strrchr(argv[i], '\\'); + if(!namepart) namepart = argv[i]; + else namepart++; printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->format), player->sfinfo.samplerate); diff --git a/Engine/lib/openal-soft/examples/alstreamcb.cpp b/Engine/lib/openal-soft/examples/alstreamcb.cpp index a2e7b6592..dae340482 100644 --- a/Engine/lib/openal-soft/examples/alstreamcb.cpp +++ b/Engine/lib/openal-soft/examples/alstreamcb.cpp @@ -24,15 +24,19 @@ /* This file contains a streaming audio player using a callback buffer. */ -#include -#include -#include +#include #include +#include #include +#include +#include +#include +#include #include #include #include +#include #include #include @@ -42,8 +46,12 @@ #include "AL/alc.h" #include "AL/alext.h" +#include "alspan.h" +#include "alstring.h" #include "common/alhelpers.h" +#include "win_main_utf8.h" + namespace { @@ -56,8 +64,7 @@ struct StreamPlayer { /* A lockless ring-buffer (supports single-provider, single-consumer * operation). */ - std::unique_ptr mBufferData; - size_t mBufferDataSize{0}; + std::vector mBufferData; std::atomic mReadPos{0}; std::atomic mWritePos{0}; size_t mSamplesPerBlock{1}; @@ -78,7 +85,7 @@ struct StreamPlayer { size_t mDecoderOffset{0}; /* The format of the callback samples. */ - ALenum mFormat; + ALenum mFormat{}; StreamPlayer() { @@ -114,15 +121,16 @@ struct StreamPlayer { } } - bool open(const char *filename) + bool open(const std::string &filename) { close(); /* Open the file and figure out the OpenAL format. */ - mSndfile = sf_open(filename, SFM_READ, &mSfInfo); + mSndfile = sf_open(filename.c_str(), SFM_READ, &mSfInfo); if(!mSndfile) { - fprintf(stderr, "Could not open audio in %s: %s\n", filename, sf_strerror(mSndfile)); + fprintf(stderr, "Could not open audio in %s: %s\n", filename.c_str(), + sf_strerror(mSndfile)); return false; } @@ -166,8 +174,8 @@ struct StreamPlayer { mSampleFormat = SampleType::Int16; else { - auto fmtbuf = std::make_unique(inf.datalen); - inf.data = fmtbuf.get(); + auto fmtbuf = std::vector(inf.datalen); + inf.data = fmtbuf.data(); if(sf_get_chunk_data(iter, &inf) != SF_ERR_NO_ERROR) mSampleFormat = SampleType::Int16; else @@ -194,12 +202,12 @@ struct StreamPlayer { if(mSampleFormat == SampleType::Int16) { mSamplesPerBlock = 1; - mBytesPerBlock = static_cast(mSfInfo.channels * 2); + mBytesPerBlock = static_cast(mSfInfo.channels) * 2; } else if(mSampleFormat == SampleType::Float) { mSamplesPerBlock = 1; - mBytesPerBlock = static_cast(mSfInfo.channels * 4); + mBytesPerBlock = static_cast(mSfInfo.channels) * 4; } else { @@ -232,7 +240,7 @@ struct StreamPlayer { } else if(mSfInfo.channels == 3) { - if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) { if(mSampleFormat == SampleType::Int16) mFormat = AL_FORMAT_BFORMAT2D_16; @@ -242,7 +250,7 @@ struct StreamPlayer { } else if(mSfInfo.channels == 4) { - if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, NULL, 0) == SF_AMBISONIC_B_FORMAT) + if(sf_command(mSndfile, SFC_WAVEX_GET_AMBISONIC, nullptr, 0) == SF_AMBISONIC_B_FORMAT) { if(mSampleFormat == SampleType::Int16) mFormat = AL_FORMAT_BFORMAT3D_16; @@ -262,8 +270,7 @@ struct StreamPlayer { /* Set a 1s ring buffer size. */ size_t numblocks{(static_cast(mSfInfo.samplerate) + mSamplesPerBlock-1) / mSamplesPerBlock}; - mBufferDataSize = static_cast(numblocks * mBytesPerBlock); - mBufferData.reset(new ALbyte[mBufferDataSize]); + mBufferData.resize(static_cast(numblocks * mBytesPerBlock)); mReadPos.store(0, std::memory_order_relaxed); mWritePos.store(0, std::memory_order_relaxed); mDecoderOffset = 0; @@ -276,10 +283,11 @@ struct StreamPlayer { * but it allows the callback implementation to have a nice 'this' pointer * with normal member access. */ - static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) + static ALsizei AL_APIENTRY bufferCallbackC(void *userptr, void *data, ALsizei size) noexcept { return static_cast(userptr)->bufferCallback(data, size); } - ALsizei bufferCallback(void *data, ALsizei size) + ALsizei bufferCallback(void *data, ALsizei size) noexcept { + auto dst = al::span{static_cast(data), static_cast(size)}; /* NOTE: The callback *MUST* be real-time safe! That means no blocking, * no allocations or deallocations, no I/O, no page faults, or calls to * functions that could do these things (this includes calling to @@ -290,7 +298,7 @@ struct StreamPlayer { ALsizei got{0}; size_t roffset{mReadPos.load(std::memory_order_acquire)}; - while(got < size) + while(!dst.empty()) { /* If the write offset == read offset, there's nothing left in the * ring-buffer. Break from the loop and give what has been written. @@ -303,19 +311,19 @@ struct StreamPlayer { * that case, otherwise read up to the write offset. Also limit the * amount to copy given how much is remaining to write. */ - size_t todo{((woffset < roffset) ? mBufferDataSize : woffset) - roffset}; - todo = std::min(todo, static_cast(size-got)); + size_t todo{((woffset < roffset) ? mBufferData.size() : woffset) - roffset}; + todo = std::min(todo, dst.size()); /* Copy from the ring buffer to the provided output buffer. Wrap * the resulting read offset if it reached the end of the ring- * buffer. */ - memcpy(data, &mBufferData[roffset], todo); - data = static_cast(data) + todo; + std::copy_n(mBufferData.cbegin()+ptrdiff_t(roffset), todo, dst.begin()); + dst = dst.subspan(todo); got += static_cast(todo); roffset += todo; - if(roffset == mBufferDataSize) + if(roffset == mBufferData.size()) roffset = 0; } /* Finally, store the updated read offset, and return how many bytes @@ -351,7 +359,7 @@ struct StreamPlayer { if(state != AL_INITIAL) { const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; /* For a stopped (underrun) source, the current playback offset is * the current decoder offset excluding the readable buffered data. @@ -362,7 +370,7 @@ struct StreamPlayer { ? (mDecoderOffset-readable) / mBytesPerBlock * mSamplesPerBlock : (static_cast(pos) + mStartOffset/mBytesPerBlock*mSamplesPerBlock)) / static_cast(mSfInfo.samplerate)}; - printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferDataSize); + printf("\r%3zus (%3zu%% full)", curtime, readable * 100 / mBufferData.size()); } else fputs("Starting...", stdout); @@ -415,8 +423,8 @@ struct StreamPlayer { * data can fit, and calculate how much can go in front before * wrapping. */ - const size_t writable{(!roffset ? mBufferDataSize-woffset-1 : - (mBufferDataSize-woffset)) / mBytesPerBlock}; + const size_t writable{(!roffset ? mBufferData.size()-woffset-1 : + (mBufferData.size()-woffset)) / mBytesPerBlock}; if(!writable) break; if(mSampleFormat == SampleType::Int16) @@ -444,7 +452,7 @@ struct StreamPlayer { } woffset += read_bytes; - if(woffset == mBufferDataSize) + if(woffset == mBufferData.size()) woffset = 0; } mWritePos.store(woffset, std::memory_order_release); @@ -459,7 +467,7 @@ struct StreamPlayer { * what's available. */ const size_t roffset{mReadPos.load(std::memory_order_relaxed)}; - const size_t readable{((woffset >= roffset) ? woffset : (mBufferDataSize+woffset)) - + const size_t readable{((woffset >= roffset) ? woffset : (mBufferData.size()+woffset)) - roffset}; if(readable == 0) return false; @@ -476,29 +484,28 @@ struct StreamPlayer { } }; -} // namespace - -int main(int argc, char **argv) +int main(al::span args) { /* A simple RAII container for OpenAL startup and shutdown. */ struct AudioManager { - AudioManager(char ***argv_, int *argc_) + AudioManager(al::span &args_) { - if(InitAL(argv_, argc_) != 0) + if(InitAL(args_) != 0) throw std::runtime_error{"Failed to initialize OpenAL"}; } ~AudioManager() { CloseAL(); } }; /* Print out usage if no arguments were specified */ - if(argc < 2) + if(args.size() < 2) { - fprintf(stderr, "Usage: %s [-device ] \n", argv[0]); + fprintf(stderr, "Usage: %.*s [-device ] \n", al::sizei(args[0]), + args[0].data()); return 1; } - argv++; argc--; - AudioManager almgr{&argv, &argc}; + args = args.subspan(1); + AudioManager almgr{args}; if(!alIsExtensionPresent("AL_SOFT_callback_buffer")) { @@ -512,23 +519,23 @@ int main(int argc, char **argv) ALCint refresh{25}; alcGetIntegerv(alcGetContextsDevice(alcGetCurrentContext()), ALC_REFRESH, 1, &refresh); - std::unique_ptr player{new StreamPlayer{}}; + auto player = std::make_unique(); /* Play each file listed on the command line */ - for(int i{0};i < argc;++i) + for(size_t i{0};i < args.size();++i) { - if(!player->open(argv[i])) + if(!player->open(std::string{args[i]})) continue; /* Get the name portion, without the path, for display. */ - const char *namepart{strrchr(argv[i], '/')}; - if(namepart || (namepart=strrchr(argv[i], '\\'))) - ++namepart; - else - namepart = argv[i]; + auto namepart = args[i]; + if(auto sep = namepart.rfind('/'); sep < namepart.size()) + namepart = namepart.substr(sep+1); + else if(sep = namepart.rfind('\\'); sep < namepart.size()) + namepart = namepart.substr(sep+1); - printf("Playing: %s (%s, %dhz)\n", namepart, FormatName(player->mFormat), - player->mSfInfo.samplerate); + printf("Playing: %.*s (%s, %dhz)\n", al::sizei(namepart), namepart.data(), + FormatName(player->mFormat), player->mSfInfo.samplerate); fflush(stdout); if(!player->prepare()) @@ -549,3 +556,13 @@ int main(int argc, char **argv) return 0; } + +} // namespace + +int main(int argc, char **argv) +{ + assert(argc >= 0); + auto args = std::vector(static_cast(argc)); + std::copy_n(argv, args.size(), args.begin()); + return main(al::span{args}); +} diff --git a/Engine/lib/openal-soft/examples/altonegen.c b/Engine/lib/openal-soft/examples/altonegen.c index 75db2d6be..52a57875a 100644 --- a/Engine/lib/openal-soft/examples/altonegen.c +++ b/Engine/lib/openal-soft/examples/altonegen.c @@ -79,63 +79,72 @@ static inline ALuint dither_rng(ALuint *seed) return *seed; } -static void ApplySin(ALfloat *data, ALdouble g, ALuint srate, ALuint freq) +static void ApplySin(ALfloat *data, ALuint length, ALdouble g, ALuint srate, ALuint freq) { - ALdouble smps_per_cycle = (ALdouble)srate / freq; + ALdouble cycles_per_sample = (ALdouble)freq / srate; ALuint i; - for(i = 0;i < srate;i++) + for(i = 0;i < length;i++) { ALdouble ival; - data[i] += (ALfloat)(sin(modf(i/smps_per_cycle, &ival) * 2.0*M_PI) * g); + data[i] += (ALfloat)(sin(modf(i*cycles_per_sample, &ival) * 2.0*M_PI) * g); } } /* Generates waveforms using additive synthesis. Each waveform is constructed * by summing one or more sine waves, up to (and excluding) nyquist. */ -static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat gain) +static ALuint CreateWave(enum WaveType type, ALuint seconds, ALuint freq, ALuint srate, + ALfloat gain) { ALuint seed = 22222; + ALuint num_samples; ALuint data_size; ALfloat *data; ALuint buffer; ALenum err; ALuint i; - data_size = (ALuint)(srate * sizeof(ALfloat)); + if(seconds > INT_MAX / srate / sizeof(ALfloat)) + { + fprintf(stderr, "Too many seconds: %u * %u * %zu > %d\n", seconds, srate, sizeof(ALfloat), + INT_MAX); + return 0; + } + + num_samples = seconds * srate; + + data_size = (ALuint)(num_samples * sizeof(ALfloat)); data = calloc(1, data_size); switch(type) { case WT_Sine: - ApplySin(data, 1.0, srate, freq); + ApplySin(data, num_samples, 1.0, srate, freq); break; case WT_Square: for(i = 1;freq*i < srate/2;i+=2) - ApplySin(data, 4.0/M_PI * 1.0/i, srate, freq*i); + ApplySin(data, num_samples, 4.0/M_PI * 1.0/i, srate, freq*i); break; case WT_Sawtooth: for(i = 1;freq*i < srate/2;i++) - ApplySin(data, 2.0/M_PI * ((i&1)*2 - 1.0) / i, srate, freq*i); + ApplySin(data, num_samples, 2.0/M_PI * ((i&1)*2 - 1.0) / i, srate, freq*i); break; case WT_Triangle: for(i = 1;freq*i < srate/2;i+=2) - ApplySin(data, 8.0/(M_PI*M_PI) * (1.0 - (i&2)) / (i*i), srate, freq*i); + ApplySin(data, num_samples, 8.0/(M_PI*M_PI) * (1.0 - (i&2)) / (i*i), srate, freq*i); break; case WT_Impulse: /* NOTE: Impulse isn't handled using additive synthesis, and is - * instead just a non-0 sample at a given rate. This can still be - * useful to test (other than resampling, the ALSOFT_DEFAULT_REVERB - * environment variable can prove useful here to test the reverb - * response). + * instead just a non-0 sample. This can be useful to test (other + * than resampling, the ALSOFT_DEFAULT_REVERB environment variable + * can test the reverb response). */ - for(i = 0;i < srate;i++) - data[i] = (i%(srate/freq)) ? 0.0f : 1.0f; + data[0] = 1.0f; break; case WT_WhiteNoise: /* NOTE: WhiteNoise is just uniform set of uncorrelated values, and * is not influenced by the waveform frequency. */ - for(i = 0;i < srate;i++) + for(i = 0;i < num_samples;i++) { ALuint rng0 = dither_rng(&seed); ALuint rng1 = dither_rng(&seed); @@ -146,7 +155,7 @@ static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat if(gain != 1.0f) { - for(i = 0;i < srate;i++) + for(i = 0;i < num_samples;i++) data[i] *= gain; } @@ -156,7 +165,7 @@ static ALuint CreateWave(enum WaveType type, ALuint freq, ALuint srate, ALfloat alBufferData(buffer, AL_FORMAT_MONO_FLOAT32, data, (ALsizei)data_size, (ALsizei)srate); free(data); - /* Check if an error occured, and clean up if so. */ + /* Check if an error occurred, and clean up if so. */ err = alGetError(); if(err != AL_NO_ERROR) { @@ -175,8 +184,8 @@ int main(int argc, char *argv[]) enum WaveType wavetype = WT_Sine; const char *appname = argv[0]; ALuint source, buffer; - ALint last_pos, num_loops; - ALint max_loops = 4; + ALint last_pos; + ALint seconds = 4; ALint srate = -1; ALint tone_freq = 1000; ALCint dev_rate; @@ -217,10 +226,13 @@ int main(int argc, char *argv[]) CloseAL(); return 1; } - else if(i+1 < argc && strcmp(argv[i], "-t") == 0) + + if(i+1 < argc && strcmp(argv[i], "-t") == 0) { i++; - max_loops = atoi(argv[i]) - 1; + seconds = atoi(argv[i]); + if(seconds <= 0) + seconds = 4; } else if(i+1 < argc && (strcmp(argv[i], "--waveform") == 0 || strcmp(argv[i], "-w") == 0)) { @@ -281,7 +293,7 @@ int main(int argc, char *argv[]) srate = dev_rate; /* Load the sound into a buffer. */ - buffer = CreateWave(wavetype, (ALuint)tone_freq, (ALuint)srate, gain); + buffer = CreateWave(wavetype, (ALuint)seconds, (ALuint)tone_freq, (ALuint)srate, gain); if(!buffer) { CloseAL(); @@ -289,7 +301,7 @@ int main(int argc, char *argv[]) } printf("Playing %dhz %s-wave tone with %dhz sample rate and %dhz output, for %d second%s...\n", - tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, max_loops+1, max_loops?"s":""); + tone_freq, GetWaveTypeName(wavetype), srate, dev_rate, seconds, (seconds!=1)?"s":""); fflush(stdout); /* Create the source to play the sound with. */ @@ -299,21 +311,18 @@ int main(int argc, char *argv[]) assert(alGetError()==AL_NO_ERROR && "Failed to setup sound source"); /* Play the sound for a while. */ - num_loops = 0; - last_pos = 0; - alSourcei(source, AL_LOOPING, (max_loops > 0) ? AL_TRUE : AL_FALSE); + last_pos = -1; alSourcePlay(source); do { ALint pos; al_nssleep(10000000); - alGetSourcei(source, AL_SAMPLE_OFFSET, &pos); alGetSourcei(source, AL_SOURCE_STATE, &state); - if(pos < last_pos && state == AL_PLAYING) + alGetSourcei(source, AL_SAMPLE_OFFSET, &pos); + pos /= srate; + + if(pos > last_pos) { - ++num_loops; - if(num_loops >= max_loops) - alSourcei(source, AL_LOOPING, AL_FALSE); - printf("%d...\n", max_loops - num_loops + 1); + printf("%d...\n", seconds - pos); fflush(stdout); } last_pos = pos; diff --git a/Engine/lib/openal-soft/examples/common/alhelpers.h b/Engine/lib/openal-soft/examples/common/alhelpers.h index 34f738647..97f553657 100644 --- a/Engine/lib/openal-soft/examples/common/alhelpers.h +++ b/Engine/lib/openal-soft/examples/common/alhelpers.h @@ -2,13 +2,14 @@ #define ALHELPERS_H #include "AL/al.h" +#include "AL/alc.h" #ifdef __cplusplus extern "C" { #endif /* Some helper functions to get the name from the format enums. */ -const char *FormatName(ALenum type); +const char *FormatName(ALenum format); /* Easy device init/deinit functions. InitAL returns 0 on success. */ int InitAL(char ***argv, int *argc); @@ -33,6 +34,54 @@ void al_nssleep(unsigned long nsec); #ifdef __cplusplus } // extern "C" + +#include +#include +#include +#include + +#include "alspan.h" + +int InitAL(al::span &args) +{ + ALCdevice *device{}; + + /* Open and initialize a device */ + if(args.size() > 1 && args[0] == "-device") + { + device = alcOpenDevice(std::string{args[1]}.c_str()); + if(!device) + std::cerr<< "Failed to open \""<= 201703L +#define AL_API_NOEXCEPT17 noexcept +#else +#define AL_API_NOEXCEPT17 +#endif + +#else /* AL_DISABLE_NOEXCEPT */ + +#define AL_API_NOEXCEPT +#define AL_API_NOEXCEPT17 +#endif + +#else /* __cplusplus */ + +#define AL_API_NOEXCEPT +#define AL_API_NOEXCEPT17 #endif #ifndef AL_API @@ -455,220 +475,221 @@ typedef void ALvoid; #ifndef AL_NO_PROTOTYPES /* Renderer State management. */ -AL_API void AL_APIENTRY alEnable(ALenum capability); -AL_API void AL_APIENTRY alDisable(ALenum capability); -AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability); +AL_API void AL_APIENTRY alEnable(ALenum capability) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDisable(ALenum capability) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsEnabled(ALenum capability) AL_API_NOEXCEPT; /* Context state setting. */ -AL_API void AL_APIENTRY alDopplerFactor(ALfloat value); -AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value); -AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value); -AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel); +AL_API void AL_APIENTRY alDopplerFactor(ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDopplerVelocity(ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSpeedOfSound(ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDistanceModel(ALenum distanceModel) AL_API_NOEXCEPT; /* Context state retrieval. */ -AL_API const ALchar* AL_APIENTRY alGetString(ALenum param); -AL_API void AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values); -AL_API void AL_APIENTRY alGetIntegerv(ALenum param, ALint *values); -AL_API void AL_APIENTRY alGetFloatv(ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetDoublev(ALenum param, ALdouble *values); -AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum param); -AL_API ALint AL_APIENTRY alGetInteger(ALenum param); -AL_API ALfloat AL_APIENTRY alGetFloat(ALenum param); -AL_API ALdouble AL_APIENTRY alGetDouble(ALenum param); +AL_API const ALchar* AL_APIENTRY alGetString(ALenum param) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBooleanv(ALenum param, ALboolean *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetIntegerv(ALenum param, ALint *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFloatv(ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetDoublev(ALenum param, ALdouble *values) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alGetBoolean(ALenum param) AL_API_NOEXCEPT; +AL_API ALint AL_APIENTRY alGetInteger(ALenum param) AL_API_NOEXCEPT; +AL_API ALfloat AL_APIENTRY alGetFloat(ALenum param) AL_API_NOEXCEPT; +AL_API ALdouble AL_APIENTRY alGetDouble(ALenum param) AL_API_NOEXCEPT; /** * Obtain the first error generated in the AL context since the last call to * this function. */ -AL_API ALenum AL_APIENTRY alGetError(void); +AL_API ALenum AL_APIENTRY alGetError(void) AL_API_NOEXCEPT; /** Query for the presence of an extension on the AL context. */ -AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname); +AL_API ALboolean AL_APIENTRY alIsExtensionPresent(const ALchar *extname) AL_API_NOEXCEPT; /** * Retrieve the address of a function. The returned function may be context- * specific. */ -AL_API void* AL_APIENTRY alGetProcAddress(const ALchar *fname); +AL_API void* AL_APIENTRY alGetProcAddress(const ALchar *fname) AL_API_NOEXCEPT; /** * Retrieve the value of an enum. The returned value may be context-specific. */ -AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *ename); +AL_API ALenum AL_APIENTRY alGetEnumValue(const ALchar *ename) AL_API_NOEXCEPT; /* Set listener parameters. */ -AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value); -AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values); -AL_API void AL_APIENTRY alListeneri(ALenum param, ALint value); -AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3); -AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values); +AL_API void AL_APIENTRY alListenerf(ALenum param, ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListener3f(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListenerfv(ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListeneri(ALenum param, ALint value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListener3i(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alListeneriv(ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get listener parameters. */ -AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value); -AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value); -AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3); -AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint *values); +AL_API void AL_APIENTRY alGetListenerf(ALenum param, ALfloat *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListener3f(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListenerfv(ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListeneri(ALenum param, ALint *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListener3i(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetListeneriv(ALenum param, ALint *values) AL_API_NOEXCEPT; /** Create source objects. */ -AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources); +AL_API void AL_APIENTRY alGenSources(ALsizei n, ALuint *sources) AL_API_NOEXCEPT; /** Delete source objects. */ -AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alDeleteSources(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Verify an ID is for a valid source. */ -AL_API ALboolean AL_APIENTRY alIsSource(ALuint source); +AL_API ALboolean AL_APIENTRY alIsSource(ALuint source) AL_API_NOEXCEPT; /* Set source parameters. */ -AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value); -AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values); -AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value); -AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); -AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values); +AL_API void AL_APIENTRY alSourcef(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3f(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcefv(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcei(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3i(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourceiv(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get source parameters. */ -AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value); -AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value); -AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3); -AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values); +AL_API void AL_APIENTRY alGetSourcef(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3f(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcefv(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcei(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3i(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourceiv(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT; /** Play, restart, or resume a source, setting its state to AL_PLAYING. */ -AL_API void AL_APIENTRY alSourcePlay(ALuint source); +AL_API void AL_APIENTRY alSourcePlay(ALuint source) AL_API_NOEXCEPT; /** Stop a source, setting its state to AL_STOPPED if playing or paused. */ -AL_API void AL_APIENTRY alSourceStop(ALuint source); +AL_API void AL_APIENTRY alSourceStop(ALuint source) AL_API_NOEXCEPT; /** Rewind a source, setting its state to AL_INITIAL. */ -AL_API void AL_APIENTRY alSourceRewind(ALuint source); +AL_API void AL_APIENTRY alSourceRewind(ALuint source) AL_API_NOEXCEPT; /** Pause a source, setting its state to AL_PAUSED if playing. */ -AL_API void AL_APIENTRY alSourcePause(ALuint source); +AL_API void AL_APIENTRY alSourcePause(ALuint source) AL_API_NOEXCEPT; /** Play, restart, or resume a list of sources atomically. */ -AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourcePlayv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Stop a list of sources atomically. */ -AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourceStopv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Rewind a list of sources atomically. */ -AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourceRewindv(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Pause a list of sources atomically. */ -AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources); +AL_API void AL_APIENTRY alSourcePausev(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; /** Queue buffers onto a source */ -AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers); +AL_API void AL_APIENTRY alSourceQueueBuffers(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT; /** Unqueue processed buffers from a source */ -AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers); +AL_API void AL_APIENTRY alSourceUnqueueBuffers(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT; /** Create buffer objects */ -AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers); +AL_API void AL_APIENTRY alGenBuffers(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT; /** Delete buffer objects */ -AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers); +AL_API void AL_APIENTRY alDeleteBuffers(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT; /** Verify an ID is a valid buffer (including the NULL buffer) */ -AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer); +AL_API ALboolean AL_APIENTRY alIsBuffer(ALuint buffer) AL_API_NOEXCEPT; /** * Copies data into the buffer, interpreting it using the specified format and * samplerate. */ -AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate); +AL_API void AL_APIENTRY alBufferData(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT; /* Set buffer parameters. */ -AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value); -AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values); -AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value); -AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); -AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values); +AL_API void AL_APIENTRY alBufferf(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBuffer3f(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferfv(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferi(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBuffer3i(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferiv(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT; /* Get buffer parameters. */ -AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value); -AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values); -AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value); -AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); -AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values); +AL_API void AL_APIENTRY alGetBufferf(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBuffer3f(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferfv(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferi(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBuffer3i(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferiv(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT; #endif /* AL_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded AL entry * points. */ -typedef void (AL_APIENTRY *LPALENABLE)(ALenum capability); -typedef void (AL_APIENTRY *LPALDISABLE)(ALenum capability); -typedef ALboolean (AL_APIENTRY *LPALISENABLED)(ALenum capability); -typedef const ALchar* (AL_APIENTRY *LPALGETSTRING)(ALenum param); -typedef void (AL_APIENTRY *LPALGETBOOLEANV)(ALenum param, ALboolean *values); -typedef void (AL_APIENTRY *LPALGETINTEGERV)(ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALGETFLOATV)(ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETDOUBLEV)(ALenum param, ALdouble *values); -typedef ALboolean (AL_APIENTRY *LPALGETBOOLEAN)(ALenum param); -typedef ALint (AL_APIENTRY *LPALGETINTEGER)(ALenum param); -typedef ALfloat (AL_APIENTRY *LPALGETFLOAT)(ALenum param); -typedef ALdouble (AL_APIENTRY *LPALGETDOUBLE)(ALenum param); -typedef ALenum (AL_APIENTRY *LPALGETERROR)(void); -typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENT)(const ALchar *extname); -typedef void* (AL_APIENTRY *LPALGETPROCADDRESS)(const ALchar *fname); -typedef ALenum (AL_APIENTRY *LPALGETENUMVALUE)(const ALchar *ename); -typedef void (AL_APIENTRY *LPALLISTENERF)(ALenum param, ALfloat value); -typedef void (AL_APIENTRY *LPALLISTENER3F)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (AL_APIENTRY *LPALLISTENERFV)(ALenum param, const ALfloat *values); -typedef void (AL_APIENTRY *LPALLISTENERI)(ALenum param, ALint value); -typedef void (AL_APIENTRY *LPALLISTENER3I)(ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (AL_APIENTRY *LPALLISTENERIV)(ALenum param, const ALint *values); -typedef void (AL_APIENTRY *LPALGETLISTENERF)(ALenum param, ALfloat *value); -typedef void (AL_APIENTRY *LPALGETLISTENER3F)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (AL_APIENTRY *LPALGETLISTENERFV)(ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETLISTENERI)(ALenum param, ALint *value); -typedef void (AL_APIENTRY *LPALGETLISTENER3I)(ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (AL_APIENTRY *LPALGETLISTENERIV)(ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALGENSOURCES)(ALsizei n, ALuint *sources); -typedef void (AL_APIENTRY *LPALDELETESOURCES)(ALsizei n, const ALuint *sources); -typedef ALboolean (AL_APIENTRY *LPALISSOURCE)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEF)(ALuint source, ALenum param, ALfloat value); -typedef void (AL_APIENTRY *LPALSOURCE3F)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (AL_APIENTRY *LPALSOURCEFV)(ALuint source, ALenum param, const ALfloat *values); -typedef void (AL_APIENTRY *LPALSOURCEI)(ALuint source, ALenum param, ALint value); -typedef void (AL_APIENTRY *LPALSOURCE3I)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (AL_APIENTRY *LPALSOURCEIV)(ALuint source, ALenum param, const ALint *values); -typedef void (AL_APIENTRY *LPALGETSOURCEF)(ALuint source, ALenum param, ALfloat *value); -typedef void (AL_APIENTRY *LPALGETSOURCE3F)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (AL_APIENTRY *LPALGETSOURCEFV)(ALuint source, ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETSOURCEI)(ALuint source, ALenum param, ALint *value); -typedef void (AL_APIENTRY *LPALGETSOURCE3I)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (AL_APIENTRY *LPALGETSOURCEIV)(ALuint source, ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALSOURCEPLAYV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCESTOPV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCEREWINDV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCEPAUSEV)(ALsizei n, const ALuint *sources); -typedef void (AL_APIENTRY *LPALSOURCEPLAY)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCESTOP)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEREWIND)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEPAUSE)(ALuint source); -typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint source, ALsizei nb, const ALuint *buffers); -typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint source, ALsizei nb, ALuint *buffers); -typedef void (AL_APIENTRY *LPALGENBUFFERS)(ALsizei n, ALuint *buffers); -typedef void (AL_APIENTRY *LPALDELETEBUFFERS)(ALsizei n, const ALuint *buffers); -typedef ALboolean (AL_APIENTRY *LPALISBUFFER)(ALuint buffer); -typedef void (AL_APIENTRY *LPALBUFFERDATA)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate); -typedef void (AL_APIENTRY *LPALBUFFERF)(ALuint buffer, ALenum param, ALfloat value); -typedef void (AL_APIENTRY *LPALBUFFER3F)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3); -typedef void (AL_APIENTRY *LPALBUFFERFV)(ALuint buffer, ALenum param, const ALfloat *values); -typedef void (AL_APIENTRY *LPALBUFFERI)(ALuint buffer, ALenum param, ALint value); -typedef void (AL_APIENTRY *LPALBUFFER3I)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3); -typedef void (AL_APIENTRY *LPALBUFFERIV)(ALuint buffer, ALenum param, const ALint *values); -typedef void (AL_APIENTRY *LPALGETBUFFERF)(ALuint buffer, ALenum param, ALfloat *value); -typedef void (AL_APIENTRY *LPALGETBUFFER3F)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3); -typedef void (AL_APIENTRY *LPALGETBUFFERFV)(ALuint buffer, ALenum param, ALfloat *values); -typedef void (AL_APIENTRY *LPALGETBUFFERI)(ALuint buffer, ALenum param, ALint *value); -typedef void (AL_APIENTRY *LPALGETBUFFER3I)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3); -typedef void (AL_APIENTRY *LPALGETBUFFERIV)(ALuint buffer, ALenum param, ALint *values); -typedef void (AL_APIENTRY *LPALDOPPLERFACTOR)(ALfloat value); -typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)(ALfloat value); -typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)(ALfloat value); -typedef void (AL_APIENTRY *LPALDISTANCEMODEL)(ALenum distanceModel); +typedef void (AL_APIENTRY *LPALENABLE)(ALenum capability) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISABLE)(ALenum capability) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISENABLED)(ALenum capability) AL_API_NOEXCEPT17; +typedef const ALchar* (AL_APIENTRY *LPALGETSTRING)(ALenum param) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBOOLEANV)(ALenum param, ALboolean *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETINTEGERV)(ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFLOATV)(ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETDOUBLEV)(ALenum param, ALdouble *values) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALGETBOOLEAN)(ALenum param) AL_API_NOEXCEPT17; +typedef ALint (AL_APIENTRY *LPALGETINTEGER)(ALenum param) AL_API_NOEXCEPT17; +typedef ALfloat (AL_APIENTRY *LPALGETFLOAT)(ALenum param) AL_API_NOEXCEPT17; +typedef ALdouble (AL_APIENTRY *LPALGETDOUBLE)(ALenum param) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETERROR)(void) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENT)(const ALchar *extname) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY *LPALGETPROCADDRESS)(const ALchar *fname) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETENUMVALUE)(const ALchar *ename) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERF)(ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3F)(ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERFV)(ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERI)(ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3I)(ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERIV)(ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERF)(ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3F)(ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERFV)(ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERI)(ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3I)(ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERIV)(ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENSOURCES)(ALsizei n, ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETESOURCES)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISSOURCE)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEF)(ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3F)(ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEFV)(ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEI)(ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3I)(ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEIV)(ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEF)(ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3F)(ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEFV)(ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEI)(ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3I)(ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEIV)(ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOPV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWINDV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSEV)(ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAY)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOP)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWIND)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSE)(ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERS)(ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERS)(ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENBUFFERS)(ALsizei n, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEBUFFERS)(ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISBUFFER)(ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERDATA)(ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERF)(ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3F)(ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERFV)(ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERI)(ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3I)(ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERIV)(ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERF)(ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3F)(ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERFV)(ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERI)(ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3I)(ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERIV)(ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDOPPLERFACTOR)(ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDOPPLERVELOCITY)(ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSPEEDOFSOUND)(ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISTANCEMODEL)(ALenum distanceModel) AL_API_NOEXCEPT17; #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_AL_H */ diff --git a/Engine/lib/openal-soft/include/AL/alc.h b/Engine/lib/openal-soft/include/AL/alc.h index 6d2103335..3311b57fb 100644 --- a/Engine/lib/openal-soft/include/AL/alc.h +++ b/Engine/lib/openal-soft/include/AL/alc.h @@ -1,8 +1,28 @@ #ifndef AL_ALC_H #define AL_ALC_H +/* NOLINTBEGIN */ #ifdef __cplusplus extern "C" { + +#ifndef AL_DISABLE_NOEXCEPT +#define ALC_API_NOEXCEPT noexcept +#if __cplusplus >= 201703L +#define ALC_API_NOEXCEPT17 noexcept +#else +#define ALC_API_NOEXCEPT17 +#endif + +#else /* AL_DISABLE_NOEXCEPT */ + +#define ALC_API_NOEXCEPT +#define ALC_API_NOEXCEPT17 +#endif + +#else /* __cplusplus */ + +#define ALC_API_NOEXCEPT +#define ALC_API_NOEXCEPT17 #endif #ifndef ALC_API @@ -172,34 +192,34 @@ typedef void ALCvoid; /* Context management. */ /** Create and attach a context to the given device. */ -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist); +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT; /** * Makes the given context the active process-wide context. Passing NULL clears * the active context. */ -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context); +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) ALC_API_NOEXCEPT; /** Resumes processing updates for the given context. */ -ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context); +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Suspends updates for the given context. */ -ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context); +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Remove a context from its device and destroys it. */ -ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context); +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) ALC_API_NOEXCEPT; /** Returns the currently active context. */ -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void); +ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) ALC_API_NOEXCEPT; /** Returns the device that a particular context is attached to. */ -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context); +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) ALC_API_NOEXCEPT; /* Device management. */ /** Opens the named playback device. */ -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename); +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) ALC_API_NOEXCEPT; /** Closes the given playback device. */ -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device); +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT; /* Error support. */ /** Obtain the most recent Device error. */ -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) ALC_API_NOEXCEPT; /* Extension support. */ @@ -207,24 +227,24 @@ ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device); * Query for the presence of an extension on the device. Pass a NULL device to * query a device-inspecific extension. */ -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname); +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT; /** * Retrieve the address of a function. Given a non-NULL device, the returned * function may be device-specific. */ -ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname); +ALC_API ALCvoid* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT; /** * Retrieve the value of an enum. Given a non-NULL device, the returned value * may be device-specific. */ -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname); +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT; /* Query functions. */ /** Returns information about the device, and error strings. */ -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param); +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT; /** Returns information about the device and the version of OpenAL. */ -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); +ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT; /* Capture functions. */ @@ -232,43 +252,44 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum pa * Opens the named capture device with the given frequency, format, and buffer * size. */ -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT; /** Closes the given capture device. */ -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device); +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) ALC_API_NOEXCEPT; /** Starts capturing samples into the device buffer. */ -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device); +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) ALC_API_NOEXCEPT; /** Stops capturing samples. Samples in the device buffer remain available. */ -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device); +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) ALC_API_NOEXCEPT; /** Reads samples from the device buffer. */ -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT; #endif /* ALC_NO_PROTOTYPES */ /* Pointer-to-function types, useful for storing dynamically loaded ALC entry * points. */ -typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist); -typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context); -typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context); -typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)(ALCcontext *context); -typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)(ALCcontext *context); -typedef ALCcontext* (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)(void); -typedef ALCdevice* (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)(ALCcontext *context); -typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename); -typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device); -typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device); -typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname); -typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname); -typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname); -typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param); -typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values); -typedef ALCdevice* (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize); -typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)(ALCdevice *device); -typedef void (ALC_APIENTRY *LPALCCAPTURESTART)(ALCdevice *device); -typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)(ALCdevice *device); -typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +typedef ALCcontext* (ALC_APIENTRY *LPALCCREATECONTEXT)(ALCdevice *device, const ALCint *attrlist) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCMAKECONTEXTCURRENT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCPROCESSCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCSUSPENDCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCDESTROYCONTEXT)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef ALCcontext* (ALC_APIENTRY *LPALCGETCURRENTCONTEXT)(void) ALC_API_NOEXCEPT17; +typedef ALCdevice* (ALC_APIENTRY *LPALCGETCONTEXTSDEVICE)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef ALCdevice* (ALC_APIENTRY *LPALCOPENDEVICE)(const ALCchar *devicename) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCCLOSEDEVICE)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef ALCenum (ALC_APIENTRY *LPALCGETERROR)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCISEXTENSIONPRESENT)(ALCdevice *device, const ALCchar *extname) ALC_API_NOEXCEPT17; +typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS)(ALCdevice *device, const ALCchar *funcname) ALC_API_NOEXCEPT17; +typedef ALCenum (ALC_APIENTRY *LPALCGETENUMVALUE)(ALCdevice *device, const ALCchar *enumname) ALC_API_NOEXCEPT17; +typedef const ALCchar* (ALC_APIENTRY *LPALCGETSTRING)(ALCdevice *device, ALCenum param) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCGETINTEGERV)(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) ALC_API_NOEXCEPT17; +typedef ALCdevice* (ALC_APIENTRY *LPALCCAPTUREOPENDEVICE)(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY *LPALCCAPTURECLOSEDEVICE)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCCAPTURESTART)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCCAPTURESTOP)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY *LPALCCAPTURESAMPLES)(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) ALC_API_NOEXCEPT17; #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_ALC_H */ diff --git a/Engine/lib/openal-soft/include/AL/alext.h b/Engine/lib/openal-soft/include/AL/alext.h index d313a999a..02cfe2db7 100644 --- a/Engine/lib/openal-soft/include/AL/alext.h +++ b/Engine/lib/openal-soft/include/AL/alext.h @@ -1,6 +1,7 @@ #ifndef AL_ALEXT_H #define AL_ALEXT_H +/* NOLINTBEGIN */ #include /* Define int64 and uint64 types */ #if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ @@ -25,6 +26,8 @@ typedef uint64_t _alsoft_uint64_t; extern "C" { #endif +struct _GUID; + #ifndef AL_LOKI_IMA_ADPCM_format #define AL_LOKI_IMA_ADPCM_format 1 #define AL_FORMAT_IMA_ADPCM_MONO16_EXT 0x10000 @@ -141,9 +144,9 @@ extern "C" { #ifndef AL_EXT_STATIC_BUFFER #define AL_EXT_STATIC_BUFFER 1 -typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALuint,ALenum,ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERDATASTATICPROC)(const ALuint,ALenum,ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq); +void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; #endif #endif @@ -159,11 +162,11 @@ void AL_APIENTRY alBufferDataStatic(const ALuint buffer, ALenum format, ALvoid * #ifndef ALC_EXT_thread_local_context #define ALC_EXT_thread_local_context 1 -typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context); -typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void); +typedef ALCboolean (ALC_APIENTRY*PFNALCSETTHREADCONTEXTPROC)(ALCcontext *context) ALC_API_NOEXCEPT17; +typedef ALCcontext* (ALC_APIENTRY*PFNALCGETTHREADCONTEXTPROC)(void) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context); -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) ALC_API_NOEXCEPT; +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) ALC_API_NOEXCEPT; #endif #endif @@ -176,9 +179,9 @@ ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void); #define AL_SOFT_buffer_sub_data 1 #define AL_BYTE_RW_OFFSETS_SOFT 0x1031 #define AL_SAMPLE_RW_OFFSETS_SOFT 0x1032 -typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei); +typedef void (AL_APIENTRY*PFNALBUFFERSUBDATASOFTPROC)(ALuint,ALenum,const ALvoid*,ALsizei,ALsizei) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length); +AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const ALvoid *data,ALsizei offset,ALsizei length) AL_API_NOEXCEPT; #endif #endif @@ -195,12 +198,12 @@ AL_API void AL_APIENTRY alBufferSubDataSOFT(ALuint buffer,ALenum format,const AL #define AL_FOLDBACK_EVENT_STOP 0x4113 #define AL_FOLDBACK_MODE_MONO 0x4101 #define AL_FOLDBACK_MODE_STEREO 0x4102 -typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK); -typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void); +typedef void (AL_APIENTRY*LPALFOLDBACKCALLBACK)(ALenum,ALsizei) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTART)(ALenum,ALsizei,ALsizei,ALfloat*,LPALFOLDBACKCALLBACK) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALREQUESTFOLDBACKSTOP)(void) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback); -AL_API void AL_APIENTRY alRequestFoldbackStop(void); +AL_API void AL_APIENTRY alRequestFoldbackStart(ALenum mode,ALsizei count,ALsizei length,ALfloat *mem,LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alRequestFoldbackStop(void) AL_API_NOEXCEPT; #endif #endif @@ -263,15 +266,15 @@ AL_API void AL_APIENTRY alRequestFoldbackStop(void); #define AL_SAMPLE_LENGTH_SOFT 0x200A #define AL_SEC_LENGTH_SOFT 0x200B -typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*); -typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*); -typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum); +typedef void (AL_APIENTRY*LPALBUFFERSAMPLESSOFT)(ALuint,ALuint,ALenum,ALsizei,ALenum,ALenum,const ALvoid*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALBUFFERSUBSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,const ALvoid*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFERSAMPLESSOFT)(ALuint,ALsizei,ALsizei,ALenum,ALenum,ALvoid*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY*LPALISBUFFERFORMATSUPPORTEDSOFT)(ALenum) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data); -AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data); -AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); +AL_API void AL_APIENTRY alBufferSamplesSOFT(ALuint buffer, ALuint samplerate, ALenum internalformat, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alBufferSubSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, const ALvoid *data) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferSamplesSOFT(ALuint buffer, ALsizei offset, ALsizei samples, ALenum channels, ALenum type, ALvoid *data) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format) AL_API_NOEXCEPT; #endif #endif @@ -302,13 +305,13 @@ AL_API ALboolean AL_APIENTRY alIsBufferFormatSupportedSOFT(ALenum format); #define ALC_6POINT1_SOFT 0x1505 #define ALC_7POINT1_SOFT 0x1506 -typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*); -typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum); -typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei); +typedef ALCdevice* (ALC_APIENTRY*LPALCLOOPBACKOPENDEVICESOFT)(const ALCchar*) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY*LPALCISRENDERFORMATSUPPORTEDSOFT)(ALCdevice*,ALCsizei,ALCenum,ALCenum) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY*LPALCRENDERSAMPLESSOFT)(ALCdevice*,ALCvoid*,ALCsizei) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName); -ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type); -ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples); +ALC_API ALCdevice* ALC_APIENTRY alcLoopbackOpenDeviceSOFT(const ALCchar *deviceName) AL_API_NOEXCEPT; +ALC_API ALCboolean ALC_APIENTRY alcIsRenderFormatSupportedSOFT(ALCdevice *device, ALCsizei freq, ALCenum channels, ALCenum type) AL_API_NOEXCEPT; +ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) AL_API_NOEXCEPT; #endif #endif @@ -328,31 +331,31 @@ ALC_API void ALC_APIENTRY alcRenderSamplesSOFT(ALCdevice *device, ALCvoid *buffe #define AL_SEC_OFFSET_LATENCY_SOFT 0x1201 typedef _alsoft_int64_t ALint64SOFT; typedef _alsoft_uint64_t ALuint64SOFT; -typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble); -typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*); -typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*); -typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT); -typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*); -typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*); +typedef void (AL_APIENTRY*LPALSOURCEDSOFT)(ALuint,ALenum,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCE3DSOFT)(ALuint,ALenum,ALdouble,ALdouble,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEDVSOFT)(ALuint,ALenum,const ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEDSOFT)(ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCE3DSOFT)(ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEDVSOFT)(ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEI64VSOFT)(ALuint,ALenum,const ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEI64SOFT)(ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCE3I64SOFT)(ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETSOURCEI64VSOFT)(ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value); -AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3); -AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values); -AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value); -AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3); -AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values); -AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value); -AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3); -AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values); -AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value); -AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3); -AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values); +AL_API void AL_APIENTRY alSourcedSOFT(ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3dSOFT(ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcedvSOFT(ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcedSOFT(ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3dSOFT(ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcedvSOFT(ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alSourcei64vSOFT(ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcei64SOFT(ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSource3i64SOFT(ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT; #endif #endif @@ -364,11 +367,11 @@ AL_API void AL_APIENTRY alGetSourcei64vSOFT(ALuint source, ALenum param, ALint64 #ifndef AL_SOFT_deferred_updates #define AL_SOFT_deferred_updates 1 #define AL_DEFERRED_UPDATES_SOFT 0xC002 -typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void); -typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void); +typedef void (AL_APIENTRY*LPALDEFERUPDATESSOFT)(void) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPROCESSUPDATESSOFT)(void) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alDeferUpdatesSOFT(void); -AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); +AL_API void AL_APIENTRY alDeferUpdatesSOFT(void) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alProcessUpdatesSOFT(void) AL_API_NOEXCEPT; #endif #endif @@ -400,11 +403,11 @@ AL_API void AL_APIENTRY alProcessUpdatesSOFT(void); #ifndef ALC_SOFT_pause_device #define ALC_SOFT_pause_device 1 -typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device); -typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device); +typedef void (ALC_APIENTRY*LPALCDEVICEPAUSESOFT)(ALCdevice *device) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY*LPALCDEVICERESUMESOFT)(ALCdevice *device) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device); -ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); +ALC_API void ALC_APIENTRY alcDevicePauseSOFT(ALCdevice *device) ALC_API_NOEXCEPT; +ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device) ALC_API_NOEXCEPT; #endif #endif @@ -448,11 +451,11 @@ ALC_API void ALC_APIENTRY alcDeviceResumeSOFT(ALCdevice *device); #define ALC_NUM_HRTF_SPECIFIERS_SOFT 0x1994 #define ALC_HRTF_SPECIFIER_SOFT 0x1995 #define ALC_HRTF_ID_SOFT 0x1996 -typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index); -typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs); +typedef const ALCchar* (ALC_APIENTRY*LPALCGETSTRINGISOFT)(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY*LPALCRESETDEVICESOFT)(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index); -ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs); +ALC_API const ALCchar* ALC_APIENTRY alcGetStringiSOFT(ALCdevice *device, ALCenum paramName, ALCsizei index) ALC_API_NOEXCEPT; +ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCint *attribs) ALC_API_NOEXCEPT; #endif #endif @@ -467,9 +470,9 @@ ALC_API ALCboolean ALC_APIENTRY alcResetDeviceSOFT(ALCdevice *device, const ALCi #define AL_DEFAULT_RESAMPLER_SOFT 0x1211 #define AL_SOURCE_RESAMPLER_SOFT 0x1212 #define AL_RESAMPLER_NAME_SOFT 0x1213 -typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index); +typedef const ALchar* (AL_APIENTRY*LPALGETSTRINGISOFT)(ALenum pname, ALsizei index) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index); +AL_API const ALchar* AL_APIENTRY alGetStringiSOFT(ALenum pname, ALsizei index) AL_API_NOEXCEPT; #endif #endif @@ -493,9 +496,9 @@ typedef _alsoft_uint64_t ALCuint64SOFT; #define ALC_DEVICE_CLOCK_LATENCY_SOFT 0x1602 #define AL_SAMPLE_OFFSET_CLOCK_SOFT 0x1202 #define AL_SEC_OFFSET_CLOCK_SOFT 0x1203 -typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values); +typedef void (ALC_APIENTRY*LPALCGETINTEGER64VSOFT)(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values); +ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, ALsizei size, ALCint64SOFT *values) ALC_API_NOEXCEPT; #endif #endif @@ -552,27 +555,26 @@ ALC_API void ALC_APIENTRY alcGetInteger64vSOFT(ALCdevice *device, ALCenum pname, #define AL_EVENT_TYPE_SOURCE_STATE_CHANGED_SOFT 0x19A5 #define AL_EVENT_TYPE_DISCONNECTED_SOFT 0x19A6 typedef void (AL_APIENTRY*ALEVENTPROCSOFT)(ALenum eventType, ALuint object, ALuint param, - ALsizei length, const ALchar *message, - void *userParam); -typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable); -typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam); -typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname); -typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values); + ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALEVENTCONTROLSOFT)(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALEVENTCALLBACKSOFT)(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY*LPALGETPOINTERSOFT)(ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETPOINTERVSOFT)(ALenum pname, void **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable); -AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam); -AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname); -AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values); +AL_API void AL_APIENTRY alEventControlSOFT(ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEventCallbackSOFT(ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT; +AL_API void* AL_APIENTRY alGetPointerSOFT(ALenum pname) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetPointervSOFT(ALenum pname, void **values) AL_API_NOEXCEPT; #endif #endif #ifndef ALC_SOFT_reopen_device #define ALC_SOFT_reopen_device typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device, - const ALCchar *deviceName, const ALCint *attribs); + const ALCchar *deviceName, const ALCint *attribs) ALC_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *deviceName, - const ALCint *attribs); + const ALCint *attribs) ALC_API_NOEXCEPT; #endif #endif @@ -580,16 +582,16 @@ ALCboolean ALC_APIENTRY alcReopenDeviceSOFT(ALCdevice *device, const ALCchar *de #define AL_SOFT_callback_buffer #define AL_BUFFER_CALLBACK_FUNCTION_SOFT 0x19A0 #define AL_BUFFER_CALLBACK_USER_PARAM_SOFT 0x19A1 -typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes); -typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value); -typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3); -typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values); +typedef ALsizei (AL_APIENTRY*ALBUFFERCALLBACKTYPESOFT)(ALvoid *userptr, ALvoid *sampledata, ALsizei numbytes) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALBUFFERCALLBACKSOFT)(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFERPTRSOFT)(ALuint buffer, ALenum param, ALvoid **value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFER3PTRSOFT)(ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETBUFFERPTRVSOFT)(ALuint buffer, ALenum param, ALvoid **values) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr); -AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr); -AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2); -AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr); +AL_API void AL_APIENTRY alBufferCallbackSOFT(ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferPtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBuffer3PtrSOFT(ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; #endif #endif @@ -640,16 +642,438 @@ AL_API void AL_APIENTRY alGetBufferPtrvSOFT(ALuint buffer, ALenum param, ALvoid #ifndef AL_SOFT_source_start_delay #define AL_SOFT_source_start_delay -typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time); -typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time); +typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMESOFT)(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALSOURCEPLAYATTIMEVSOFT)(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time); -void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time); +void AL_APIENTRY alSourcePlayAtTimeSOFT(ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayAtTimevSOFT(ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT; +#endif +#endif + +#ifndef ALC_EXT_debug +#define ALC_EXT_debug +#define ALC_CONTEXT_FLAGS_EXT 0x19CF +#define ALC_CONTEXT_DEBUG_BIT_EXT 0x0001 +#endif + +#ifndef AL_EXT_debug +#define AL_EXT_debug +#define AL_DONT_CARE_EXT 0x0002 +#define AL_DEBUG_OUTPUT_EXT 0x19B2 +#define AL_DEBUG_CALLBACK_FUNCTION_EXT 0x19B3 +#define AL_DEBUG_CALLBACK_USER_PARAM_EXT 0x19B4 +#define AL_DEBUG_SOURCE_API_EXT 0x19B5 +#define AL_DEBUG_SOURCE_AUDIO_SYSTEM_EXT 0x19B6 +#define AL_DEBUG_SOURCE_THIRD_PARTY_EXT 0x19B7 +#define AL_DEBUG_SOURCE_APPLICATION_EXT 0x19B8 +#define AL_DEBUG_SOURCE_OTHER_EXT 0x19B9 +#define AL_DEBUG_TYPE_ERROR_EXT 0x19BA +#define AL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_EXT 0x19BB +#define AL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_EXT 0x19BC +#define AL_DEBUG_TYPE_PORTABILITY_EXT 0x19BD +#define AL_DEBUG_TYPE_PERFORMANCE_EXT 0x19BE +#define AL_DEBUG_TYPE_MARKER_EXT 0x19BF +#define AL_DEBUG_TYPE_PUSH_GROUP_EXT 0x19C0 +#define AL_DEBUG_TYPE_POP_GROUP_EXT 0x19C1 +#define AL_DEBUG_TYPE_OTHER_EXT 0x19C2 +#define AL_DEBUG_SEVERITY_HIGH_EXT 0x19C3 +#define AL_DEBUG_SEVERITY_MEDIUM_EXT 0x19C4 +#define AL_DEBUG_SEVERITY_LOW_EXT 0x19C5 +#define AL_DEBUG_SEVERITY_NOTIFICATION_EXT 0x19C6 +#define AL_DEBUG_LOGGED_MESSAGES_EXT 0x19C7 +#define AL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_EXT 0x19C8 +#define AL_MAX_DEBUG_MESSAGE_LENGTH_EXT 0x19C9 +#define AL_MAX_DEBUG_LOGGED_MESSAGES_EXT 0x19CA +#define AL_MAX_DEBUG_GROUP_STACK_DEPTH_EXT 0x19CB +#define AL_MAX_LABEL_LENGTH_EXT 0x19CC +#define AL_STACK_OVERFLOW_EXT 0x19CD +#define AL_STACK_UNDERFLOW_EXT 0x19CE +#define AL_CONTEXT_FLAGS_EXT 0x19CF +#define AL_BUFFER_EXT 0x1009 /* Same as AL_BUFFER */ +#define AL_SOURCE_EXT 0x19D0 +#define AL_FILTER_EXT 0x19D1 +#define AL_EFFECT_EXT 0x19D2 +#define AL_AUXILIARY_EFFECT_SLOT_EXT 0x19D3 + +typedef void (AL_APIENTRY*ALDEBUGPROCEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECALLBACKEXT)(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGEINSERTEXT)(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECONTROLEXT)(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPUSHDEBUGGROUPEXT)(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPEXT)(void) AL_API_NOEXCEPT17; +typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGEXT)(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETOBJECTLABELEXT)(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +#ifdef AL_ALEXT_PROTOTYPES +void AL_APIENTRY alDebugMessageCallbackEXT(ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageInsertEXT(ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageControlEXT(ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alPushDebugGroupEXT(ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alPopDebugGroupEXT(void) AL_API_NOEXCEPT; +ALuint AL_APIENTRY alGetDebugMessageLogEXT(ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; +void AL_APIENTRY alObjectLabelEXT(ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; +void AL_APIENTRY alGetObjectLabelEXT(ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; +#endif +#endif + +#ifndef ALC_SOFT_system_events +#define ALC_SOFT_system_events +#define ALC_PLAYBACK_DEVICE_SOFT 0x19D4 +#define ALC_CAPTURE_DEVICE_SOFT 0x19D5 +#define ALC_EVENT_TYPE_DEFAULT_DEVICE_CHANGED_SOFT 0x19D6 +#define ALC_EVENT_TYPE_DEVICE_ADDED_SOFT 0x19D7 +#define ALC_EVENT_TYPE_DEVICE_REMOVED_SOFT 0x19D8 +#define ALC_EVENT_SUPPORTED_SOFT 0x19D9 +#define ALC_EVENT_NOT_SUPPORTED_SOFT 0x19DA +typedef void (ALC_APIENTRY*ALCEVENTPROCTYPESOFT)(ALCenum eventType, ALCenum deviceType, + ALCdevice *device, ALCsizei length, const ALCchar *message, void *userParam) ALC_API_NOEXCEPT17; +typedef ALCenum (ALC_APIENTRY*LPALCEVENTISSUPPORTEDSOFT)(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT17; +typedef ALCboolean (ALC_APIENTRY*LPALCEVENTCONTROLSOFT)(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT17; +typedef void (ALC_APIENTRY*LPALCEVENTCALLBACKSOFT)(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT17; +#ifdef AL_ALEXT_PROTOTYPES +ALCenum ALC_APIENTRY alcEventIsSupportedSOFT(ALCenum eventType, ALCenum deviceType) ALC_API_NOEXCEPT; +ALCboolean ALC_APIENTRY alcEventControlSOFT(ALCsizei count, const ALCenum *events, ALCboolean enable) ALC_API_NOEXCEPT; +void ALC_APIENTRY alcEventCallbackSOFT(ALCEVENTPROCTYPESOFT callback, void *userParam) ALC_API_NOEXCEPT; +#endif +#endif + +#ifndef AL_EXT_direct_context +#define AL_EXT_direct_context +typedef ALCvoid* (ALC_APIENTRY *LPALCGETPROCADDRESS2)(ALCdevice *device, const ALCchar *funcname) AL_API_NOEXCEPT17; + +typedef void (AL_APIENTRY *LPALENABLEDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISABLEDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISENABLEDDIRECT)(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDOPPLERFACTORDIRECT)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSPEEDOFSOUNDDIRECT)(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDISTANCEMODELDIRECT)(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT17; +typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBOOLEANVDIRECT)(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETINTEGERVDIRECT)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFLOATVDIRECT)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETDOUBLEVDIRECT)(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALGETBOOLEANDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALint (AL_APIENTRY *LPALGETINTEGERDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALfloat (AL_APIENTRY *LPALGETFLOATDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALdouble (AL_APIENTRY *LPALGETDOUBLEDIRECT)(ALCcontext *context, ALenum param) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETERRORDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEXTENSIONPRESENTDIRECT)(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY *LPALGETPROCADDRESSDIRECT)(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPALGETENUMVALUEDIRECT)(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERFDIRECT)(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3FDIRECT)(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERFVDIRECT)(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERIDIRECT)(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENER3IDIRECT)(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALLISTENERIVDIRECT)(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERFDIRECT)(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3FDIRECT)(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERFVDIRECT)(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERIDIRECT)(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENER3IDIRECT)(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETLISTENERIVDIRECT)(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENSOURCESDIRECT)(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETESOURCESDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISSOURCEDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEFDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3FDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEFVDIRECT)(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEIDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3IDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEIVDIRECT)(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEFDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3FDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEFVDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEIDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3IDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEIVDIRECT)(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOPDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWINDDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSEDIRECT)(ALCcontext *context, ALuint source) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCESTOPVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEREWINDVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPAUSEVDIRECT)(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEQUEUEBUFFERSDIRECT)(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEUNQUEUEBUFFERSDIRECT)(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENBUFFERSDIRECT)(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEBUFFERSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISBUFFERDIRECT)(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERDATADIRECT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERFDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3FDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERFVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERIDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFER3IDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALBUFFERIVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERFDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3FDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERFVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERIDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3IDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERIVDIRECT)(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT17; +/* ALC_EXT_EFX */ +typedef void (AL_APIENTRY *LPALGENEFFECTSDIRECT)(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEEFFECTSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEFFECTDIRECT)(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTIDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTIVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTFDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTFVDIRECT)(ALCcontext *context, ALuint effect, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENFILTERSDIRECT)(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEFILTERSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISFILTERDIRECT)(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERIDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERIVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERFDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERFVDIRECT)(ALCcontext *context, ALuint filter, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTSDIRECT)(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTSDIRECT)(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOTDIRECT)(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALint *values) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFVDIRECT)(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *values) AL_API_NOEXCEPT17; +/* AL_EXT_BUFFER_DATA_STATIC */ +typedef void (AL_APIENTRY *LPALBUFFERDATASTATICDIRECT)(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT17; +/* AL_EXT_debug */ +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECALLBACKDIRECTEXT)(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGEINSERTDIRECTEXT)(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALDEBUGMESSAGECONTROLDIRECTEXT)(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPUSHDEBUGGROUPDIRECTEXT)(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALPOPDEBUGGROUPDIRECTEXT)(ALCcontext *context) AL_API_NOEXCEPT17; +typedef ALuint (AL_APIENTRY*LPALGETDEBUGMESSAGELOGDIRECTEXT)(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY*LPALGETOBJECTLABELDIRECTEXT)(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT17; +/* AL_EXT_FOLDBACK */ +typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTARTDIRECT)(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALREQUESTFOLDBACKSTOPDIRECT)(ALCcontext *context) AL_API_NOEXCEPT17; +/* AL_SOFT_buffer_sub_data */ +typedef void (AL_APIENTRY *LPALBUFFERSUBDATADIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT17; +/* AL_SOFT_source_latency */ +typedef void (AL_APIENTRY *LPALSOURCEDDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3DDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble,ALdouble,ALdouble) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEDVDIRECTSOFT)(ALCcontext*,ALuint,ALenum,const ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEDDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3DDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*,ALdouble*,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEDVDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALdouble*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEI64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCE3I64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT,ALint64SOFT,ALint64SOFT) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEI64VDIRECTSOFT)(ALCcontext*,ALuint,ALenum,const ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEI64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCE3I64DIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*,ALint64SOFT*,ALint64SOFT*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETSOURCEI64VDIRECTSOFT)(ALCcontext*,ALuint,ALenum,ALint64SOFT*) AL_API_NOEXCEPT17; +/* AL_SOFT_deferred_updates */ +typedef void (AL_APIENTRY *LPALDEFERUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALPROCESSUPDATESDIRECTSOFT)(ALCcontext *context) AL_API_NOEXCEPT17; +/* AL_SOFT_source_resampler */ +typedef const ALchar* (AL_APIENTRY *LPALGETSTRINGIDIRECTSOFT)(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT17; +/* AL_SOFT_events */ +typedef void (AL_APIENTRY *LPALEVENTCONTROLDIRECTSOFT)(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEVENTCALLBACKDIRECTSOFT)(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT17; +typedef void* (AL_APIENTRY *LPALGETPOINTERDIRECTSOFT)(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETPOINTERVDIRECTSOFT)(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT17; +/* AL_SOFT_callback_buffer */ +typedef void (AL_APIENTRY *LPALBUFFERCALLBACKDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERPTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFER3PTRDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **value1, ALvoid **value2, ALvoid **value3) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETBUFFERPTRVDIRECTSOFT)(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **values) AL_API_NOEXCEPT17; +/* AL_SOFT_source_start_delay */ +typedef void (AL_APIENTRY *LPALSOURCEPLAYATTIMEDIRECTSOFT)(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALSOURCEPLAYATTIMEVDIRECTSOFT)(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT17; +/* EAX */ +typedef ALenum (AL_APIENTRY *LPEAXSETDIRECT)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPEAXGETDIRECT)(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPEAXSETBUFFERMODEDIRECT)(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT17; +typedef ALenum (AL_APIENTRY *LPEAXGETBUFFERMODEDIRECT)(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT17; +#ifdef AL_ALEXT_PROTOTYPES +ALCvoid* AL_APIENTRY alcGetProcAddress2(ALCdevice *device, const ALchar *funcName) AL_API_NOEXCEPT; + +void AL_APIENTRY alEnableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; +void AL_APIENTRY alDisableDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsEnabledDirect(ALCcontext *context, ALenum capability) AL_API_NOEXCEPT; + +void AL_APIENTRY alDopplerFactorDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alSpeedOfSoundDirect(ALCcontext *context, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alDistanceModelDirect(ALCcontext *context, ALenum distanceModel) AL_API_NOEXCEPT; + +const ALchar* AL_APIENTRY alGetStringDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBooleanvDirect(ALCcontext *context, ALenum param, ALboolean *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetIntegervDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFloatvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetDoublevDirect(ALCcontext *context, ALenum param, ALdouble *values) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alGetBooleanDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +ALint AL_APIENTRY alGetIntegerDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +ALfloat AL_APIENTRY alGetFloatDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; +ALdouble AL_APIENTRY alGetDoubleDirect(ALCcontext *context, ALenum param) AL_API_NOEXCEPT; + +ALenum AL_APIENTRY alGetErrorDirect(ALCcontext *context) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsExtensionPresentDirect(ALCcontext *context, const ALchar *extname) AL_API_NOEXCEPT; +void* AL_APIENTRY alGetProcAddressDirect(ALCcontext *context, const ALchar *fname) AL_API_NOEXCEPT; +ALenum AL_APIENTRY alGetEnumValueDirect(ALCcontext *context, const ALchar *ename) AL_API_NOEXCEPT; + +void AL_APIENTRY alListenerfDirect(ALCcontext *context, ALenum param, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alListener3fDirect(ALCcontext *context, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +void AL_APIENTRY alListenerfvDirect(ALCcontext *context, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alListeneriDirect(ALCcontext *context, ALenum param, ALint value) AL_API_NOEXCEPT; +void AL_APIENTRY alListener3iDirect(ALCcontext *context, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +void AL_APIENTRY alListenerivDirect(ALCcontext *context, ALenum param, const ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListenerfDirect(ALCcontext *context, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListener3fDirect(ALCcontext *context, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListenerfvDirect(ALCcontext *context, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListeneriDirect(ALCcontext *context, ALenum param, ALint *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListener3iDirect(ALCcontext *context, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetListenerivDirect(ALCcontext *context, ALenum param, ALint *values) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenSourcesDirect(ALCcontext *context, ALsizei n, ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteSourcesDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsSourceDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceivDirect(ALCcontext *context, ALuint source, ALenum param, const ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcefDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3fDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcefvDirect(ALCcontext *context, ALuint source, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourceiDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3iDirect(ALCcontext *context, ALuint source, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourceivDirect(ALCcontext *context, ALuint source, ALenum param, ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceStopDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceRewindDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePauseDirect(ALCcontext *context, ALuint source) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceStopvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceRewindvDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePausevDirect(ALCcontext *context, ALsizei n, const ALuint *sources) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceQueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, const ALuint *buffers) AL_API_NOEXCEPT; +void AL_APIENTRY alSourceUnqueueBuffersDirect(ALCcontext *context, ALuint source, ALsizei nb, ALuint *buffers) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenBuffersDirect(ALCcontext *context, ALsizei n, ALuint *buffers) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteBuffersDirect(ALCcontext *context, ALsizei n, const ALuint *buffers) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsBufferDirect(ALCcontext *context, ALuint buffer) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferDataDirect(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei size, ALsizei samplerate) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value) AL_API_NOEXCEPT; +void AL_APIENTRY alBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat value1, ALfloat value2, ALfloat value3) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value) AL_API_NOEXCEPT; +void AL_APIENTRY alBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint value1, ALint value2, ALint value3) AL_API_NOEXCEPT; +void AL_APIENTRY alBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, const ALint *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferfDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBuffer3fDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *value1, ALfloat *value2, ALfloat *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferfvDirect(ALCcontext *context, ALuint buffer, ALenum param, ALfloat *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferiDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBuffer3iDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *value1, ALint *value2, ALint *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferivDirect(ALCcontext *context, ALuint buffer, ALenum param, ALint *values) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenEffectsDirect(ALCcontext *context, ALsizei n, ALuint *effects) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteEffectsDirect(ALCcontext *context, ALsizei n, const ALuint *effects) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsEffectDirect(ALCcontext *context, ALuint effect) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +void AL_APIENTRY alEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectiDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectivDirect(ALCcontext *context, ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectfDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetEffectfvDirect(ALCcontext *context, ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenFiltersDirect(ALCcontext *context, ALsizei n, ALuint *filters) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteFiltersDirect(ALCcontext *context, ALsizei n, const ALuint *filters) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsFilterDirect(ALCcontext *context, ALuint filter) AL_API_NOEXCEPT; +void AL_APIENTRY alFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT; +void AL_APIENTRY alFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +void AL_APIENTRY alFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilteriDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilterivDirect(ALCcontext *context, ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilterfDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetFilterfvDirect(ALCcontext *context, ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; + +void AL_APIENTRY alGenAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT; +void AL_APIENTRY alDeleteAuxiliaryEffectSlotsDirect(ALCcontext *context, ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY alIsAuxiliaryEffectSlotDirect(ALCcontext *context, ALuint effectslot) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +void AL_APIENTRY alAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotiDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotivDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotfDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +void AL_APIENTRY alGetAuxiliaryEffectSlotfvDirect(ALCcontext *context, ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; + +void AL_APIENTRY alBufferDataStaticDirect(ALCcontext *context, ALuint buffer, ALenum format, ALvoid *data, ALsizei size, ALsizei freq) AL_API_NOEXCEPT; + +void AL_APIENTRY alDebugMessageCallbackDirectEXT(ALCcontext *context, ALDEBUGPROCEXT callback, void *userParam) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageInsertDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALuint id, ALenum severity, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alDebugMessageControlDirectEXT(ALCcontext *context, ALenum source, ALenum type, ALenum severity, ALsizei count, const ALuint *ids, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alPushDebugGroupDirectEXT(ALCcontext *context, ALenum source, ALuint id, ALsizei length, const ALchar *message) AL_API_NOEXCEPT; +void AL_APIENTRY alPopDebugGroupDirectEXT(ALCcontext *context) AL_API_NOEXCEPT; +ALuint AL_APIENTRY alGetDebugMessageLogDirectEXT(ALCcontext *context, ALuint count, ALsizei logBufSize, ALenum *sources, ALenum *types, ALuint *ids, ALenum *severities, ALsizei *lengths, ALchar *logBuf) AL_API_NOEXCEPT; +void AL_APIENTRY alObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei length, const ALchar *label) AL_API_NOEXCEPT; +void AL_APIENTRY alGetObjectLabelDirectEXT(ALCcontext *context, ALenum identifier, ALuint name, ALsizei bufSize, ALsizei *length, ALchar *label) AL_API_NOEXCEPT; + +void AL_APIENTRY alRequestFoldbackStartDirect(ALCcontext *context, ALenum mode, ALsizei count, ALsizei length, ALfloat *mem, LPALFOLDBACKCALLBACK callback) AL_API_NOEXCEPT; +void AL_APIENTRY alRequestFoldbackStopDirect(ALCcontext *context) AL_API_NOEXCEPT; + +void AL_APIENTRY alBufferSubDataDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, const ALvoid *data, ALsizei offset, ALsizei length) AL_API_NOEXCEPT; + +void AL_APIENTRY alSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble value1, ALdouble value2, ALdouble value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALdouble *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcedDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3dDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *value1, ALdouble *value2, ALdouble *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcedvDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALdouble *values) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value) AL_API_NOEXCEPT; +void AL_APIENTRY alSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT value1, ALint64SOFT value2, ALint64SOFT value3) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, const ALint64SOFT *values) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcei64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSource3i64DirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *value1, ALint64SOFT *value2, ALint64SOFT *value3) AL_API_NOEXCEPT; +void AL_APIENTRY alGetSourcei64vDirectSOFT(ALCcontext *context, ALuint source, ALenum param, ALint64SOFT *values) AL_API_NOEXCEPT; + +void AL_APIENTRY alDeferUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT; +void AL_APIENTRY alProcessUpdatesDirectSOFT(ALCcontext *context) AL_API_NOEXCEPT; + +const ALchar* AL_APIENTRY alGetStringiDirectSOFT(ALCcontext *context, ALenum pname, ALsizei index) AL_API_NOEXCEPT; + +void AL_APIENTRY alEventControlDirectSOFT(ALCcontext *context, ALsizei count, const ALenum *types, ALboolean enable) AL_API_NOEXCEPT; +void AL_APIENTRY alEventCallbackDirectSOFT(ALCcontext *context, ALEVENTPROCSOFT callback, void *userParam) AL_API_NOEXCEPT; +void* AL_APIENTRY alGetPointerDirectSOFT(ALCcontext *context, ALenum pname) AL_API_NOEXCEPT; +void AL_APIENTRY alGetPointervDirectSOFT(ALCcontext *context, ALenum pname, void **values) AL_API_NOEXCEPT; + +void AL_APIENTRY alBufferCallbackDirectSOFT(ALCcontext *context, ALuint buffer, ALenum format, ALsizei freq, ALBUFFERCALLBACKTYPESOFT callback, ALvoid *userptr) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferPtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBuffer3PtrDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr0, ALvoid **ptr1, ALvoid **ptr2) AL_API_NOEXCEPT; +void AL_APIENTRY alGetBufferPtrvDirectSOFT(ALCcontext *context, ALuint buffer, ALenum param, ALvoid **ptr) AL_API_NOEXCEPT; + +void AL_APIENTRY alSourcePlayAtTimeDirectSOFT(ALCcontext *context, ALuint source, ALint64SOFT start_time) AL_API_NOEXCEPT; +void AL_APIENTRY alSourcePlayAtTimevDirectSOFT(ALCcontext *context, ALsizei n, const ALuint *sources, ALint64SOFT start_time) AL_API_NOEXCEPT; + +ALenum AL_APIENTRY EAXSetDirect(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT; +ALenum AL_APIENTRY EAXGetDirect(ALCcontext *context, const struct _GUID *property_set_id, ALuint property_id, ALuint source_id, ALvoid *value, ALuint value_size) AL_API_NOEXCEPT; +ALboolean AL_APIENTRY EAXSetBufferModeDirect(ALCcontext *context, ALsizei n, const ALuint *buffers, ALint value) AL_API_NOEXCEPT; +ALenum AL_APIENTRY EAXGetBufferModeDirect(ALCcontext *context, ALuint buffer, ALint *pReserved) AL_API_NOEXCEPT; #endif #endif #ifdef __cplusplus } #endif +/* NOLINTEND */ #endif diff --git a/Engine/lib/openal-soft/include/AL/efx-presets.h b/Engine/lib/openal-soft/include/AL/efx-presets.h index 8539fd517..acd5bf398 100644 --- a/Engine/lib/openal-soft/include/AL/efx-presets.h +++ b/Engine/lib/openal-soft/include/AL/efx-presets.h @@ -2,6 +2,7 @@ #ifndef EFX_PRESETS_H #define EFX_PRESETS_H +/* NOLINTBEGIN */ #ifndef EFXEAXREVERBPROPERTIES_DEFINED #define EFXEAXREVERBPROPERTIES_DEFINED @@ -399,4 +400,5 @@ typedef struct { #define EFX_REVERB_PRESET_SMALLWATERROOM \ { 1.0000f, 0.7000f, 0.3162f, 0.4477f, 1.0000f, 1.5100f, 1.2500f, 1.1400f, 0.8913f, 0.0200f, { 0.0000f, 0.0000f, 0.0000f }, 1.4125f, 0.0300f, { 0.0000f, 0.0000f, 0.0000f }, 0.1790f, 0.1500f, 0.8950f, 0.1900f, 0.9920f, 5000.0000f, 250.0000f, 0.0000f, 0x0 } +/* NOLINTEND */ #endif /* EFX_PRESETS_H */ diff --git a/Engine/lib/openal-soft/include/AL/efx.h b/Engine/lib/openal-soft/include/AL/efx.h index 5ab64a64d..1e93bf222 100644 --- a/Engine/lib/openal-soft/include/AL/efx.h +++ b/Engine/lib/openal-soft/include/AL/efx.h @@ -1,6 +1,7 @@ #ifndef AL_EFX_H #define AL_EFX_H +/* NOLINTBEGIN */ #include #include "alc.h" @@ -204,80 +205,80 @@ extern "C" { /* Effect object function types. */ -typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint); -typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGENEFFECTS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEEFFECTS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISEFFECT)(ALuint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALEFFECTFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETEFFECTFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; /* Filter object function types. */ -typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint); -typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGENFILTERS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEFILTERS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISFILTER)(ALuint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALFILTERFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETFILTERFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; /* Auxiliary Effect Slot object function types. */ -typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*); -typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*); -typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat); -typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*); -typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*); +typedef void (AL_APIENTRY *LPALGENAUXILIARYEFFECTSLOTS)(ALsizei, ALuint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALDELETEAUXILIARYEFFECTSLOTS)(ALsizei, const ALuint*) AL_API_NOEXCEPT17; +typedef ALboolean (AL_APIENTRY *LPALISAUXILIARYEFFECTSLOT)(ALuint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, const ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, const ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTI)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTIV)(ALuint, ALenum, ALint*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTF)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; +typedef void (AL_APIENTRY *LPALGETAUXILIARYEFFECTSLOTFV)(ALuint, ALenum, ALfloat*) AL_API_NOEXCEPT17; #ifdef AL_ALEXT_PROTOTYPES -AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects); -AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects); -AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect); -AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue); -AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues); -AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue); -AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues); -AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue); -AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues); -AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue); -AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alGenEffects(ALsizei n, ALuint *effects) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDeleteEffects(ALsizei n, const ALuint *effects) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsEffect(ALuint effect) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffecti(ALuint effect, ALenum param, ALint iValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffectiv(ALuint effect, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffectf(ALuint effect, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alEffectfv(ALuint effect, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffecti(ALuint effect, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffectiv(ALuint effect, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffectf(ALuint effect, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetEffectfv(ALuint effect, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters); -AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters); -AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter); -AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue); -AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues); -AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue); -AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues); -AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue); -AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues); -AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue); -AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alGenFilters(ALsizei n, ALuint *filters) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDeleteFilters(ALsizei n, const ALuint *filters) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsFilter(ALuint filter) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilteri(ALuint filter, ALenum param, ALint iValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilteriv(ALuint filter, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilterf(ALuint filter, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alFilterfv(ALuint filter, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilteri(ALuint filter, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilteriv(ALuint filter, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilterf(ALuint filter, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetFilterfv(ALuint filter, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; -AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots); -AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots); -AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot); -AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue); -AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue); -AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues); +AL_API void AL_APIENTRY alGenAuxiliaryEffectSlots(ALsizei n, ALuint *effectslots) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alDeleteAuxiliaryEffectSlots(ALsizei n, const ALuint *effectslots) AL_API_NOEXCEPT; +AL_API ALboolean AL_APIENTRY alIsAuxiliaryEffectSlot(ALuint effectslot) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint iValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, const ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat flValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, const ALfloat *pflValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSloti(ALuint effectslot, ALenum param, ALint *piValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotiv(ALuint effectslot, ALenum param, ALint *piValues) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotf(ALuint effectslot, ALenum param, ALfloat *pflValue) AL_API_NOEXCEPT; +AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum param, ALfloat *pflValues) AL_API_NOEXCEPT; #endif /* Filter ranges and defaults. */ @@ -758,5 +759,6 @@ AL_API void AL_APIENTRY alGetAuxiliaryEffectSlotfv(ALuint effectslot, ALenum par #ifdef __cplusplus } /* extern "C" */ #endif +/* NOLINTEND */ #endif /* AL_EFX_H */ diff --git a/Engine/lib/openal-soft/router/al.cpp b/Engine/lib/openal-soft/router/al.cpp index 06c314eb8..57c8d1797 100644 --- a/Engine/lib/openal-soft/router/al.cpp +++ b/Engine/lib/openal-soft/router/al.cpp @@ -1,39 +1,42 @@ #include "config.h" -#include +#include #include "AL/al.h" #include "router.h" -std::atomic CurrentCtxDriver{nullptr}; - -#define DECL_THUNK1(R,n,T1) AL_API R AL_APIENTRY n(T1 a) \ +#define DECL_THUNK1(R,n,T1) \ +AL_API auto AL_APIENTRY n(T1 a) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a); \ } -#define DECL_THUNK2(R,n,T1,T2) AL_API R AL_APIENTRY n(T1 a, T2 b) \ +#define DECL_THUNK2(R,n,T1,T2) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b); \ } -#define DECL_THUNK3(R,n,T1,T2,T3) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c) \ +#define DECL_THUNK3(R,n,T1,T2,T3) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c); \ } -#define DECL_THUNK4(R,n,T1,T2,T3,T4) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) \ +#define DECL_THUNK4(R,n,T1,T2,T3,T4) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ return iface->n(a, b, c, d); \ } -#define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) AL_API R AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) \ +#define DECL_THUNK5(R,n,T1,T2,T3,T4,T5) \ +AL_API auto AL_APIENTRY n(T1 a, T2 b, T3 c, T4 d, T5 e) noexcept -> R \ { \ DriverIface *iface = GetThreadDriver(); \ if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); \ @@ -44,7 +47,7 @@ std::atomic CurrentCtxDriver{nullptr}; /* Ugly hack for some apps calling alGetError without a current context, and * expecting it to be AL_NO_ERROR. */ -AL_API ALenum AL_APIENTRY alGetError(void) +AL_API auto AL_APIENTRY alGetError() noexcept -> ALenum { DriverIface *iface = GetThreadDriver(); if(!iface) iface = CurrentCtxDriver.load(std::memory_order_acquire); diff --git a/Engine/lib/openal-soft/router/alc.cpp b/Engine/lib/openal-soft/router/alc.cpp index 3aa3382be..312ac6b73 100644 --- a/Engine/lib/openal-soft/router/alc.cpp +++ b/Engine/lib/openal-soft/router/alc.cpp @@ -1,26 +1,34 @@ #include "config.h" -#include -#include -#include -#include - -#include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include "AL/alc.h" -#include "alstring.h" + +#include "almalloc.h" #include "router.h" -#define DECL(x) { #x, reinterpret_cast(x) } +namespace { + +using namespace std::string_view_literals; + struct FuncExportEntry { const char *funcName; void *address; }; -static const std::array alcFunctions{{ +#define DECL(x) FuncExportEntry{ #x, reinterpret_cast(x) } +const std::array alcFunctions{ DECL(alcCreateContext), DECL(alcMakeContextCurrent), DECL(alcProcessContext), @@ -153,15 +161,15 @@ static const std::array alcFunctions{{ DECL(alGetAuxiliaryEffectSlotfv), DECL(alGetAuxiliaryEffectSloti), DECL(alGetAuxiliaryEffectSlotiv), -}}; +}; #undef DECL -#define DECL(x) { #x, (x) } struct EnumExportEntry { const ALCchar *enumName; ALCenum value; }; -static const std::array alcEnumerations{{ +#define DECL(x) EnumExportEntry{ #x, (x) } +const std::array alcEnumerations{ DECL(ALC_INVALID), DECL(ALC_FALSE), DECL(ALC_TRUE), @@ -267,84 +275,100 @@ static const std::array alcEnumerations{{ DECL(AL_LINEAR_DISTANCE_CLAMPED), DECL(AL_EXPONENT_DISTANCE), DECL(AL_EXPONENT_DISTANCE_CLAMPED), -}}; +}; #undef DECL -static const ALCchar alcNoError[] = "No Error"; -static const ALCchar alcErrInvalidDevice[] = "Invalid Device"; -static const ALCchar alcErrInvalidContext[] = "Invalid Context"; -static const ALCchar alcErrInvalidEnum[] = "Invalid Enum"; -static const ALCchar alcErrInvalidValue[] = "Invalid Value"; -static const ALCchar alcErrOutOfMemory[] = "Out of Memory"; -static const ALCchar alcExtensionList[] = - "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " - "ALC_EXT_thread_local_context"; +[[nodiscard]] constexpr auto GetNoErrorString() noexcept { return "No Error"; } +[[nodiscard]] constexpr auto GetInvalidDeviceString() noexcept { return "Invalid Device"; } +[[nodiscard]] constexpr auto GetInvalidContextString() noexcept { return "Invalid Context"; } +[[nodiscard]] constexpr auto GetInvalidEnumString() noexcept { return "Invalid Enum"; } +[[nodiscard]] constexpr auto GetInvalidValueString() noexcept { return "Invalid Value"; } +[[nodiscard]] constexpr auto GetOutOfMemoryString() noexcept { return "Out of Memory"; } -static const ALCint alcMajorVersion = 1; -static const ALCint alcMinorVersion = 1; +[[nodiscard]] constexpr auto GetExtensionList() noexcept -> std::string_view +{ + return "ALC_ENUMERATE_ALL_EXT ALC_ENUMERATION_EXT ALC_EXT_CAPTURE " + "ALC_EXT_thread_local_context"sv; +} + +constexpr ALCint alcMajorVersion = 1; +constexpr ALCint alcMinorVersion = 1; -static std::recursive_mutex EnumerationLock; -static std::mutex ContextSwitchLock; +std::recursive_mutex EnumerationLock; +std::mutex ContextSwitchLock; -static std::atomic LastError{ALC_NO_ERROR}; -static PtrIntMap DeviceIfaceMap; -static PtrIntMap ContextIfaceMap; +std::atomic LastError{ALC_NO_ERROR}; +std::unordered_map DeviceIfaceMap; +std::unordered_map ContextIfaceMap; + +template +auto maybe_get(std::unordered_map &list, V&& key) -> std::optional +{ + auto iter = list.find(std::forward(key)); + if(iter != list.end()) return iter->second; + return std::nullopt; +} -typedef struct EnumeratedList { +struct EnumeratedList { std::vector Names; - std::vector Indicies; + std::vector Indicies; void clear() { Names.clear(); Indicies.clear(); } -} EnumeratedList; -static EnumeratedList DevicesList; -static EnumeratedList AllDevicesList; -static EnumeratedList CaptureDevicesList; -static void AppendDeviceList(EnumeratedList *list, const ALCchar *names, ALint idx) + void AppendDeviceList(const ALCchar *names, ALCuint idx); + [[nodiscard]] + auto GetDriverIndexForName(const std::string_view name) const -> std::optional; +}; +EnumeratedList DevicesList; +EnumeratedList AllDevicesList; +EnumeratedList CaptureDevicesList; + +void EnumeratedList::AppendDeviceList(const ALCchar* names, ALCuint idx) { const ALCchar *name_end = names; if(!name_end) return; - ALCsizei count = 0; + size_t count{0}; while(*name_end) { - TRACE("Enumerated \"%s\", driver %d\n", name_end, idx); + TRACE("Enumerated \"%s\", driver %u\n", name_end, idx); ++count; - name_end += strlen(name_end)+1; + name_end += strlen(name_end)+1; /* NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) */ } if(names == name_end) return; - list->Names.reserve(list->Names.size() + (name_end - names) + 1); - list->Names.insert(list->Names.cend(), names, name_end); + Names.reserve(Names.size() + static_cast(name_end - names) + 1); + Names.insert(Names.cend(), names, name_end); - list->Indicies.reserve(list->Indicies.size() + count); - list->Indicies.insert(list->Indicies.cend(), count, idx); + Indicies.reserve(Indicies.size() + count); + Indicies.insert(Indicies.cend(), count, idx); } -static ALint GetDriverIndexForName(const EnumeratedList *list, const ALCchar *name) +auto EnumeratedList::GetDriverIndexForName(const std::string_view name) const -> std::optional { - const ALCchar *devnames = list->Names.data(); - const ALCint *index = list->Indicies.data(); + auto devnames = Names.cbegin(); + auto index = Indicies.cbegin(); - while(devnames && *devnames) + while(devnames != Names.cend() && *devnames) { - if(strcmp(name, devnames) == 0) - return *index; - devnames += strlen(devnames)+1; - index++; + const auto devname = std::string_view{al::to_address(devnames)}; + if(name == devname) return *index; + + devnames += ptrdiff_t(devname.size()+1); + ++index; } - return -1; + return std::nullopt; } -static void InitCtxFuncs(DriverIface &iface) +void InitCtxFuncs(DriverIface &iface) { ALCdevice *device{iface.alcGetContextsDevice(iface.alcGetCurrentContext())}; @@ -393,65 +417,67 @@ static void InitCtxFuncs(DriverIface &iface) #undef LOAD_PROC } +} /* namespace */ -ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) + +ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) noexcept { - ALCdevice *device = nullptr; - ALint idx = 0; + ALCdevice *device{nullptr}; + std::optional idx; /* Prior to the enumeration extension, apps would hardcode these names as a * quality hint for the wrapper driver. Ignore them since there's no sane * way to map them. */ - if(devicename && (devicename[0] == '\0' || - strcmp(devicename, "DirectSound3D") == 0 || - strcmp(devicename, "DirectSound") == 0 || - strcmp(devicename, "MMSYSTEM") == 0)) - devicename = nullptr; - if(devicename) + if(devicename && *devicename != '\0' && devicename != "DirectSound3D"sv + && devicename != "DirectSound"sv && devicename != "MMSYSTEM"sv) { { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; if(DevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_DEVICE_SPECIFIER); - idx = GetDriverIndexForName(&DevicesList, devicename); - if(idx < 0) + std::ignore = alcGetString(nullptr, ALC_DEVICE_SPECIFIER); + idx = DevicesList.GetDriverIndexForName(devicename); + if(!idx) { if(AllDevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); - idx = GetDriverIndexForName(&AllDevicesList, devicename); + std::ignore = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); + idx = AllDevicesList.GetDriverIndexForName(devicename); } } - if(idx < 0) + if(!idx) { LastError.store(ALC_INVALID_VALUE); TRACE("Failed to find driver for name \"%s\"\n", devicename); return nullptr; } - TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx]->alcOpenDevice(devicename); + TRACE("Found driver %u for name \"%s\"\n", *idx, devicename); + device = DriverList[*idx]->alcOpenDevice(devicename); } else { + ALCuint drvidx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) { - TRACE("Using default device from driver %d\n", idx); + TRACE("Using default device from driver %u\n", drvidx); device = drv->alcOpenDevice(nullptr); + idx = drvidx; break; } - ++idx; + ++drvidx; } } if(device) { - if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcCloseDevice(device); + try { + DeviceIfaceMap.emplace(device, idx.value()); + } + catch(...) { + DriverList[idx.value()]->alcCloseDevice(device); device = nullptr; } } @@ -459,38 +485,38 @@ ALC_API ALCdevice* ALC_APIENTRY alcOpenDevice(const ALCchar *devicename) return device; } -ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) +ALC_API ALCboolean ALC_APIENTRY alcCloseDevice(ALCdevice *device) noexcept { - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + if(const auto idx = maybe_get(DeviceIfaceMap, device)) { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; + if(!DriverList[*idx]->alcCloseDevice(device)) + return ALC_FALSE; + DeviceIfaceMap.erase(device); + return ALC_TRUE; } - if(!DriverList[idx]->alcCloseDevice(device)) - return ALC_FALSE; - DeviceIfaceMap.removeByKey(device); - return ALC_TRUE; + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } -ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) +ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCint *attrlist) noexcept { - ALCcontext *context; - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + const auto idx = maybe_get(DeviceIfaceMap, device); + if(!idx) { LastError.store(ALC_INVALID_DEVICE); return nullptr; } - context = DriverList[idx]->alcCreateContext(device, attrlist); + + ALCcontext *context{DriverList[*idx]->alcCreateContext(device, attrlist)}; if(context) { - if(ContextIfaceMap.insert(context, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcDestroyContext(context); + try { + ContextIfaceMap.emplace(context, *idx); + } + catch(...) { + DriverList[*idx]->alcDestroyContext(context); context = nullptr; } } @@ -498,43 +524,42 @@ ALC_API ALCcontext* ALC_APIENTRY alcCreateContext(ALCdevice *device, const ALCin return context; } -ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) +ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) noexcept { - ALint idx = -1; + std::lock_guard ctxlock{ContextSwitchLock}; - std::lock_guard _{ContextSwitchLock}; + std::optional idx; if(context) { - idx = ContextIfaceMap.lookupByKey(context); - if(idx < 0) + idx = maybe_get(ContextIfaceMap, context); + if(!idx) { LastError.store(ALC_INVALID_CONTEXT); return ALC_FALSE; } - if(!DriverList[idx]->alcMakeContextCurrent(context)) + if(!DriverList[*idx]->alcMakeContextCurrent(context)) return ALC_FALSE; - auto do_init = [idx]() { InitCtxFuncs(*DriverList[idx]); }; - std::call_once(DriverList[idx]->InitOnceCtx, do_init); + std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{ InitCtxFuncs(*DriverList[*idx]); }); } /* Unset the context from the old driver if it's different from the new * current one. */ - if(idx < 0) + if(!idx) { - DriverIface *oldiface = GetThreadDriver(); + DriverIface *oldiface{GetThreadDriver()}; if(oldiface) oldiface->alcSetThreadContext(nullptr); oldiface = CurrentCtxDriver.exchange(nullptr); if(oldiface) oldiface->alcMakeContextCurrent(nullptr); } else { - DriverIface *oldiface = GetThreadDriver(); - if(oldiface && oldiface != DriverList[idx].get()) + DriverIface *oldiface{GetThreadDriver()}; + if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcSetThreadContext(nullptr); - oldiface = CurrentCtxDriver.exchange(DriverList[idx].get()); - if(oldiface && oldiface != DriverList[idx].get()) + oldiface = CurrentCtxDriver.exchange(DriverList[*idx].get()); + if(oldiface && oldiface != DriverList[*idx].get()) oldiface->alcMakeContextCurrent(nullptr); } SetThreadDriver(nullptr); @@ -542,116 +567,95 @@ ALC_API ALCboolean ALC_APIENTRY alcMakeContextCurrent(ALCcontext *context) return ALC_TRUE; } -ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcProcessContext(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcProcessContext(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcProcessContext(context); + LastError.store(ALC_INVALID_CONTEXT); } -ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcSuspendContext(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcSuspendContext(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcSuspendContext(context); + LastError.store(ALC_INVALID_CONTEXT); } -ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) +ALC_API void ALC_APIENTRY alcDestroyContext(ALCcontext *context) noexcept { - ALint idx; - - if(!context || (idx=ContextIfaceMap.lookupByKey(context)) < 0) + if(const auto idx = maybe_get(ContextIfaceMap, context)) { - LastError.store(ALC_INVALID_CONTEXT); + DriverList[*idx]->alcDestroyContext(context); + ContextIfaceMap.erase(context); return; } - - DriverList[idx]->alcDestroyContext(context); - ContextIfaceMap.removeByKey(context); + LastError.store(ALC_INVALID_CONTEXT); } -ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext(void) +ALC_API ALCcontext* ALC_APIENTRY alcGetCurrentContext() noexcept { - DriverIface *iface = GetThreadDriver(); + DriverIface *iface{GetThreadDriver()}; if(!iface) iface = CurrentCtxDriver.load(); return iface ? iface->alcGetCurrentContext() : nullptr; } -ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) +ALC_API ALCdevice* ALC_APIENTRY alcGetContextsDevice(ALCcontext *context) noexcept { - if(context) - { - ALint idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) - return DriverList[idx]->alcGetContextsDevice(context); - } + if(const auto idx = maybe_get(ContextIfaceMap, context)) + return DriverList[*idx]->alcGetContextsDevice(context); + LastError.store(ALC_INVALID_CONTEXT); return nullptr; } -ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) +ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) return ALC_INVALID_DEVICE; - return DriverList[idx]->alcGetError(device); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetError(device); + return ALC_INVALID_DEVICE; } return LastError.exchange(ALC_NO_ERROR); } -ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) +ALC_API ALCboolean ALC_APIENTRY alcIsExtensionPresent(ALCdevice *device, const ALCchar *extname) noexcept { - const char *ptr; - size_t len; - if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; - } - return DriverList[idx]->alcIsExtensionPresent(device, extname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcIsExtensionPresent(device, extname); + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } - len = strlen(extname); - ptr = alcExtensionList; - while(ptr && *ptr) + const auto tofind = std::string_view{extname}; + const auto extlist = GetExtensionList(); + auto matchpos = extlist.find(tofind); + while(matchpos != std::string_view::npos) { - if(al::strncasecmp(ptr, extname, len) == 0 && (ptr[len] == '\0' || isspace(ptr[len]))) + const auto endpos = matchpos + tofind.size(); + if((matchpos == 0 || std::isspace(extlist[matchpos-1])) + && (endpos == extlist.size() || std::isspace(extlist[endpos]))) return ALC_TRUE; - if((ptr=strchr(ptr, ' ')) != nullptr) - { - do { - ++ptr; - } while(isspace(*ptr)); - } + matchpos = extlist.find(tofind, matchpos+1); } return ALC_FALSE; } -ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) +ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *funcname) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return nullptr; - } - return DriverList[idx]->alcGetProcAddress(device, funcname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetProcAddress(device, funcname); + + LastError.store(ALC_INVALID_DEVICE); + return nullptr; } auto iter = std::find_if(alcFunctions.cbegin(), alcFunctions.cend(), @@ -661,17 +665,15 @@ ALC_API void* ALC_APIENTRY alcGetProcAddress(ALCdevice *device, const ALCchar *f return (iter != alcFunctions.cend()) ? iter->address : nullptr; } -ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) +ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *enumname) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return 0; - } - return DriverList[idx]->alcGetEnumValue(device, enumname); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetEnumValue(device, enumname); + + LastError.store(ALC_INVALID_DEVICE); + return 0; } auto iter = std::find_if(alcEnumerations.cbegin(), alcEnumerations.cend(), @@ -681,48 +683,38 @@ ALC_API ALCenum ALC_APIENTRY alcGetEnumValue(ALCdevice *device, const ALCchar *e return (iter != alcEnumerations.cend()) ? iter->value : 0; } -ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) +ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum param) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return nullptr; - } - return DriverList[idx]->alcGetString(device, param); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetString(device, param); + + LastError.store(ALC_INVALID_DEVICE); + return nullptr; } switch(param) { - case ALC_NO_ERROR: - return alcNoError; - case ALC_INVALID_ENUM: - return alcErrInvalidEnum; - case ALC_INVALID_VALUE: - return alcErrInvalidValue; - case ALC_INVALID_DEVICE: - return alcErrInvalidDevice; - case ALC_INVALID_CONTEXT: - return alcErrInvalidContext; - case ALC_OUT_OF_MEMORY: - return alcErrOutOfMemory; - case ALC_EXTENSIONS: - return alcExtensionList; + case ALC_NO_ERROR: return GetNoErrorString(); + case ALC_INVALID_ENUM: return GetInvalidEnumString(); + case ALC_INVALID_VALUE: return GetInvalidValueString(); + case ALC_INVALID_DEVICE: return GetInvalidDeviceString(); + case ALC_INVALID_CONTEXT: return GetInvalidContextString(); + case ALC_OUT_OF_MEMORY: return GetOutOfMemoryString(); + case ALC_EXTENSIONS: return GetExtensionList().data(); case ALC_DEVICE_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; DevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { /* Only enumerate names from drivers that support it. */ if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) - AppendDeviceList(&DevicesList, - drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); + DevicesList.AppendDeviceList(drv->alcGetString(nullptr,ALC_DEVICE_SPECIFIER), idx); ++idx; } /* Ensure the list is double-null termianted. */ @@ -734,20 +726,20 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para case ALC_ALL_DEVICES_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; AllDevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { /* If the driver doesn't support ALC_ENUMERATE_ALL_EXT, substitute * standard enumeration. */ if(drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT")) - AppendDeviceList(&AllDevicesList, + AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER), idx); else if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT")) - AppendDeviceList(&AllDevicesList, + AllDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_DEVICE_SPECIFIER), idx); ++idx; } @@ -760,14 +752,14 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para case ALC_CAPTURE_DEVICE_SPECIFIER: { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; CaptureDevicesList.clear(); - ALint idx{0}; + ALCuint idx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) - AppendDeviceList(&CaptureDevicesList, + CaptureDevicesList.AppendDeviceList( drv->alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER), idx); ++idx; } @@ -817,17 +809,15 @@ ALC_API const ALCchar* ALC_APIENTRY alcGetString(ALCdevice *device, ALCenum para return nullptr; } -ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) +ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsizei size, ALCint *values) noexcept { if(device) { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx < 0) - { - LastError.store(ALC_INVALID_DEVICE); - return; - } - return DriverList[idx]->alcGetIntegerv(device, param, size, values); + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcGetIntegerv(device, param, size, values); + + LastError.store(ALC_INVALID_DEVICE); + return; } if(size <= 0 || values == nullptr) @@ -841,14 +831,15 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi case ALC_MAJOR_VERSION: if(size >= 1) { - values[0] = alcMajorVersion; + *values = alcMajorVersion; return; } - /*fall-through*/ + LastError.store(ALC_INVALID_VALUE); + return; case ALC_MINOR_VERSION: if(size >= 1) { - values[0] = alcMinorVersion; + *values = alcMinorVersion; return; } LastError.store(ALC_INVALID_VALUE); @@ -872,51 +863,54 @@ ALC_API void ALC_APIENTRY alcGetIntegerv(ALCdevice *device, ALCenum param, ALCsi } -ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, ALCenum format, ALCsizei buffersize) +ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, ALCuint frequency, + ALCenum format, ALCsizei buffersize) noexcept { - ALCdevice *device = nullptr; - ALint idx = 0; + ALCdevice *device{nullptr}; + std::optional idx; - if(devicename && devicename[0] == '\0') - devicename = nullptr; - if(devicename) + if(devicename && *devicename != '\0') { { - std::lock_guard _{EnumerationLock}; + std::lock_guard enumlock{EnumerationLock}; if(CaptureDevicesList.Names.empty()) - (void)alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); - idx = GetDriverIndexForName(&CaptureDevicesList, devicename); + std::ignore = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER); + idx = CaptureDevicesList.GetDriverIndexForName(devicename); } - if(idx < 0) + if(!idx) { LastError.store(ALC_INVALID_VALUE); TRACE("Failed to find driver for name \"%s\"\n", devicename); return nullptr; } - TRACE("Found driver %d for name \"%s\"\n", idx, devicename); - device = DriverList[idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); + TRACE("Found driver %u for name \"%s\"\n", *idx, devicename); + device = DriverList[*idx]->alcCaptureOpenDevice(devicename, frequency, format, buffersize); } else { + ALCuint drvidx{0}; for(const auto &drv : DriverList) { if(drv->ALCVer >= MAKE_ALC_VER(1, 1) || drv->alcIsExtensionPresent(nullptr, "ALC_EXT_CAPTURE")) { - TRACE("Using default capture device from driver %d\n", idx); + TRACE("Using default capture device from driver %u\n", drvidx); device = drv->alcCaptureOpenDevice(nullptr, frequency, format, buffersize); + idx = drvidx; break; } - ++idx; + ++drvidx; } } if(device) { - if(DeviceIfaceMap.insert(device, idx) != ALC_NO_ERROR) - { - DriverList[idx]->alcCaptureCloseDevice(device); + try { + DeviceIfaceMap.emplace(device, idx.value()); + } + catch(...) { + DriverList[idx.value()]->alcCaptureCloseDevice(device); device = nullptr; } } @@ -924,94 +918,77 @@ ALC_API ALCdevice* ALC_APIENTRY alcCaptureOpenDevice(const ALCchar *devicename, return device; } -ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) +ALC_API ALCboolean ALC_APIENTRY alcCaptureCloseDevice(ALCdevice *device) noexcept { - ALint idx; - - if(!device || (idx=DeviceIfaceMap.lookupByKey(device)) < 0) + if(const auto idx = maybe_get(DeviceIfaceMap, device)) { - LastError.store(ALC_INVALID_DEVICE); - return ALC_FALSE; + if(!DriverList[*idx]->alcCaptureCloseDevice(device)) + return ALC_FALSE; + DeviceIfaceMap.erase(device); + return ALC_TRUE; } - if(!DriverList[idx]->alcCaptureCloseDevice(device)) - return ALC_FALSE; - DeviceIfaceMap.removeByKey(device); - return ALC_TRUE; + + LastError.store(ALC_INVALID_DEVICE); + return ALC_FALSE; } -ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) +ALC_API void ALC_APIENTRY alcCaptureStart(ALCdevice *device) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureStart(device); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureStart(device); LastError.store(ALC_INVALID_DEVICE); } -ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) +ALC_API void ALC_APIENTRY alcCaptureStop(ALCdevice *device) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureStop(device); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureStop(device); LastError.store(ALC_INVALID_DEVICE); } -ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) +ALC_API void ALC_APIENTRY alcCaptureSamples(ALCdevice *device, ALCvoid *buffer, ALCsizei samples) noexcept { - if(device) - { - ALint idx = DeviceIfaceMap.lookupByKey(device); - if(idx >= 0) - return DriverList[idx]->alcCaptureSamples(device, buffer, samples); - } + if(const auto idx = maybe_get(DeviceIfaceMap, device)) + return DriverList[*idx]->alcCaptureSamples(device, buffer, samples); LastError.store(ALC_INVALID_DEVICE); } -ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) +ALC_API ALCboolean ALC_APIENTRY alcSetThreadContext(ALCcontext *context) noexcept { - ALCenum err = ALC_INVALID_CONTEXT; - ALint idx; - if(!context) { - DriverIface *oldiface = GetThreadDriver(); + DriverIface *oldiface{GetThreadDriver()}; if(oldiface && !oldiface->alcSetThreadContext(nullptr)) return ALC_FALSE; SetThreadDriver(nullptr); return ALC_TRUE; } - idx = ContextIfaceMap.lookupByKey(context); - if(idx >= 0) + ALCenum err{ALC_INVALID_CONTEXT}; + if(const auto idx = maybe_get(ContextIfaceMap, context)) { - if(DriverList[idx]->alcSetThreadContext(context)) + if(DriverList[*idx]->alcSetThreadContext(context)) { - auto do_init = [idx]() { InitCtxFuncs(*DriverList[idx]); }; - std::call_once(DriverList[idx]->InitOnceCtx, do_init); + std::call_once(DriverList[*idx]->InitOnceCtx, [idx]{InitCtxFuncs(*DriverList[*idx]);}); - DriverIface *oldiface = GetThreadDriver(); - if(oldiface != DriverList[idx].get()) + DriverIface *oldiface{GetThreadDriver()}; + if(oldiface != DriverList[*idx].get()) { - SetThreadDriver(DriverList[idx].get()); + SetThreadDriver(DriverList[*idx].get()); if(oldiface) oldiface->alcSetThreadContext(nullptr); } return ALC_TRUE; } - err = DriverList[idx]->alcGetError(nullptr); + err = DriverList[*idx]->alcGetError(nullptr); } LastError.store(err); return ALC_FALSE; } -ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext(void) +ALC_API ALCcontext* ALC_APIENTRY alcGetThreadContext() noexcept { - DriverIface *iface = GetThreadDriver(); - if(iface) return iface->alcGetThreadContext(); + if(DriverIface *iface{GetThreadDriver()}) + return iface->alcGetThreadContext(); return nullptr; } diff --git a/Engine/lib/openal-soft/router/router.cpp b/Engine/lib/openal-soft/router/router.cpp index 3c8910535..e6f4a62bf 100644 --- a/Engine/lib/openal-soft/router/router.cpp +++ b/Engine/lib/openal-soft/router/router.cpp @@ -4,83 +4,34 @@ #include "router.h" #include +#include #include #include #include +#include +#include +#include #include "AL/alc.h" #include "AL/al.h" -#include "almalloc.h" +#include "alstring.h" +#include "opthelpers.h" #include "strutils.h" #include "version.h" -std::vector DriverList; +eLogLevel LogLevel{eLogLevel::Error}; +gsl::owner LogFile; -thread_local DriverIface *ThreadCtxDriver; +namespace { -enum LogLevel LogLevel = LogLevel_Error; -FILE *LogFile; - -#ifdef __MINGW32__ -DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } -void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } -#endif - -static void LoadDriverList(void); +std::vector gAcceptList; +std::vector gRejectList; -BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) -{ - switch(reason) - { - case DLL_PROCESS_ATTACH: - LogFile = stderr; - if(auto logfname = al::getenv("ALROUTER_LOGFILE")) - { - FILE *f = fopen(logfname->c_str(), "w"); - if(f == nullptr) - ERR("Could not open log file: %s\n", logfname->c_str()); - else - LogFile = f; - } - if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) - { - char *end = nullptr; - long l = strtol(loglev->c_str(), &end, 0); - if(!end || *end != '\0') - ERR("Invalid log level value: %s\n", loglev->c_str()); - else if(l < LogLevel_None || l > LogLevel_Trace) - ERR("Log level out of range: %s\n", loglev->c_str()); - else - LogLevel = static_cast(l); - } - TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); - LoadDriverList(); - - break; - - case DLL_THREAD_ATTACH: - break; - case DLL_THREAD_DETACH: - break; - - case DLL_PROCESS_DETACH: - DriverList.clear(); - - if(LogFile && LogFile != stderr) - fclose(LogFile); - LogFile = nullptr; - - break; - } - return TRUE; -} - - -static void AddModule(HMODULE module, const WCHAR *name) +void AddModule(HMODULE module, const std::wstring_view name) { for(auto &drv : DriverList) { @@ -92,26 +43,55 @@ static void AddModule(HMODULE module, const WCHAR *name) } if(drv->Name == name) { - TRACE("Skipping similarly-named module %ls\n", name); + TRACE("Skipping similarly-named module %.*ls\n", al::sizei(name), name.data()); + FreeLibrary(module); + return; + } + } + if(!gAcceptList.empty()) + { + auto iter = std::find_if(gAcceptList.cbegin(), gAcceptList.cend(), + [name](const std::wstring_view accept) + { return al::case_compare(name, accept) == 0; }); + if(iter == gAcceptList.cend()) + { + TRACE("%.*ls not found in ALROUTER_ACCEPT, skipping\n", al::sizei(name), name.data()); + FreeLibrary(module); + return; + } + } + if(!gRejectList.empty()) + { + auto iter = std::find_if(gRejectList.cbegin(), gRejectList.cend(), + [name](const std::wstring_view accept) + { return al::case_compare(name, accept) == 0; }); + if(iter != gRejectList.cend()) + { + TRACE("%.*ls found in ALROUTER_REJECT, skipping\n", al::sizei(name), name.data()); FreeLibrary(module); return; } } - DriverList.emplace_back(std::make_unique(name, module)); - DriverIface &newdrv = *DriverList.back(); + DriverIface &newdrv = *DriverList.emplace_back(std::make_unique(name, module)); /* Load required functions. */ - int err = 0; -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast(reinterpret_cast( \ - GetProcAddress(module, #x))); \ - if(!newdrv.x) \ - { \ - ERR("Failed to find entry point for %s in %ls\n", #x, name); \ - err = 1; \ - } \ -} while(0) + bool loadok{true}; + auto do_load = [module,name](auto &func, const char *fname) -> bool + { + using func_t = std::remove_reference_t; + auto ptr = GetProcAddress(module, fname); + if(!ptr) + { + ERR("Failed to find entry point for %s in %.*ls\n", fname, al::sizei(name), + name.data()); + return false; + } + + func = reinterpret_cast(reinterpret_cast(ptr)); + return true; + }; +#define LOAD_PROC(x) loadok &= do_load(newdrv.x, #x) LOAD_PROC(alcCreateContext); LOAD_PROC(alcMakeContextCurrent); LOAD_PROC(alcProcessContext); @@ -194,264 +174,269 @@ static void AddModule(HMODULE module, const WCHAR *name) LOAD_PROC(alDopplerVelocity); LOAD_PROC(alSpeedOfSound); LOAD_PROC(alDistanceModel); - if(!err) +#undef LOAD_PROC + if(loadok) { - ALCint alc_ver[2] = { 0, 0 }; + std::array alc_ver{0, 0}; newdrv.alcGetIntegerv(nullptr, ALC_MAJOR_VERSION, 1, &alc_ver[0]); newdrv.alcGetIntegerv(nullptr, ALC_MINOR_VERSION, 1, &alc_ver[1]); if(newdrv.alcGetError(nullptr) == ALC_NO_ERROR) newdrv.ALCVer = MAKE_ALC_VER(alc_ver[0], alc_ver[1]); else { - WARN("Failed to query ALC version for %ls, assuming 1.0\n", name); + WARN("Failed to query ALC version for %.*ls, assuming 1.0\n", al::sizei(name), + name.data()); newdrv.ALCVer = MAKE_ALC_VER(1, 0); } + auto do_load2 = [module,name](auto &func, const char *fname) -> void + { + using func_t = std::remove_reference_t; + auto ptr = GetProcAddress(module, fname); + if(!ptr) + WARN("Failed to find optional entry point for %s in %.*ls\n", fname, + al::sizei(name), name.data()); + else + func = reinterpret_cast(reinterpret_cast(ptr)); + }; +#define LOAD_PROC(x) do_load2(newdrv.x, #x) + LOAD_PROC(alBufferf); + LOAD_PROC(alBuffer3f); + LOAD_PROC(alBufferfv); + LOAD_PROC(alBufferi); + LOAD_PROC(alBuffer3i); + LOAD_PROC(alBufferiv); + LOAD_PROC(alGetBufferf); + LOAD_PROC(alGetBuffer3f); + LOAD_PROC(alGetBufferfv); + LOAD_PROC(alGetBufferi); + LOAD_PROC(alGetBuffer3i); + LOAD_PROC(alGetBufferiv); #undef LOAD_PROC -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast(reinterpret_cast( \ - GetProcAddress(module, #x))); \ - if(!newdrv.x) \ - { \ - WARN("Failed to find optional entry point for %s in %ls\n", #x, name); \ - } \ -} while(0) - LOAD_PROC(alBufferf); - LOAD_PROC(alBuffer3f); - LOAD_PROC(alBufferfv); - LOAD_PROC(alBufferi); - LOAD_PROC(alBuffer3i); - LOAD_PROC(alBufferiv); - LOAD_PROC(alGetBufferf); - LOAD_PROC(alGetBuffer3f); - LOAD_PROC(alGetBufferfv); - LOAD_PROC(alGetBufferi); - LOAD_PROC(alGetBuffer3i); - LOAD_PROC(alGetBufferiv); -#undef LOAD_PROC -#define LOAD_PROC(x) do { \ - newdrv.x = reinterpret_cast( \ - newdrv.alcGetProcAddress(nullptr, #x)); \ - if(!newdrv.x) \ - { \ - ERR("Failed to find entry point for %s in %ls\n", #x, name); \ - err = 1; \ - } \ -} while(0) + auto do_load3 = [name,&newdrv](auto &func, const char *fname) -> bool + { + using func_t = std::remove_reference_t; + auto ptr = newdrv.alcGetProcAddress(nullptr, fname); + if(!ptr) + { + ERR("Failed to find entry point for %s in %.*ls\n", fname, al::sizei(name), + name.data()); + return false; + } + + func = reinterpret_cast(ptr); + return true; + }; +#define LOAD_PROC(x) loadok &= do_load3(newdrv.x, #x) if(newdrv.alcIsExtensionPresent(nullptr, "ALC_EXT_thread_local_context")) { LOAD_PROC(alcSetThreadContext); LOAD_PROC(alcGetThreadContext); } +#undef LOAD_PROC } - if(err) + if(!loadok) { DriverList.pop_back(); return; } - TRACE("Loaded module %p, %ls, ALC %d.%d\n", decltype(std::declval()){module}, name, - newdrv.ALCVer>>8, newdrv.ALCVer&255); -#undef LOAD_PROC + TRACE("Loaded module %p, %.*ls, ALC %d.%d\n", decltype(std::declval()){module}, + al::sizei(name), name.data(), newdrv.ALCVer>>8, newdrv.ALCVer&255); } -static void SearchDrivers(WCHAR *path) +void SearchDrivers(const std::wstring_view path) { - WIN32_FIND_DATAW fdata; - - TRACE("Searching for drivers in %ls...\n", path); - std::wstring srchPath = path; + TRACE("Searching for drivers in %.*ls...\n", al::sizei(path), path.data()); + std::wstring srchPath{path}; srchPath += L"\\*oal.dll"; - HANDLE srchHdl = FindFirstFileW(srchPath.c_str(), &fdata); - if(srchHdl != INVALID_HANDLE_VALUE) - { - do { - HMODULE mod; + WIN32_FIND_DATAW fdata{}; + HANDLE srchHdl{FindFirstFileW(srchPath.c_str(), &fdata)}; + if(srchHdl == INVALID_HANDLE_VALUE) return; - srchPath = path; - srchPath += L"\\"; - srchPath += fdata.cFileName; - TRACE("Found %ls\n", srchPath.c_str()); + do { + srchPath = path; + srchPath += L"\\"; + srchPath += std::data(fdata.cFileName); + TRACE("Found %ls\n", srchPath.c_str()); - mod = LoadLibraryW(srchPath.c_str()); - if(!mod) - WARN("Could not load %ls\n", srchPath.c_str()); - else - AddModule(mod, fdata.cFileName); - } while(FindNextFileW(srchHdl, &fdata)); - FindClose(srchHdl); - } + HMODULE mod{LoadLibraryW(srchPath.c_str())}; + if(!mod) + WARN("Could not load %ls\n", srchPath.c_str()); + else + AddModule(mod, std::data(fdata.cFileName)); + } while(FindNextFileW(srchHdl, &fdata)); + FindClose(srchHdl); } -static WCHAR *strrchrW(WCHAR *str, WCHAR ch) +bool GetLoadedModuleDirectory(const WCHAR *name, std::wstring *moddir) { - WCHAR *res = nullptr; - while(str && *str != '\0') - { - if(*str == ch) - res = str; - ++str; - } - return res; -} - -static int GetLoadedModuleDirectory(const WCHAR *name, WCHAR *moddir, DWORD length) -{ - HMODULE module = nullptr; - WCHAR *sep0, *sep1; + HMODULE module{nullptr}; if(name) { module = GetModuleHandleW(name); - if(!module) return 0; + if(!module) return false; } - if(GetModuleFileNameW(module, moddir, length) == 0) - return 0; + moddir->assign(256, '\0'); + DWORD res{GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size()))}; + if(res >= moddir->size()) + { + do { + moddir->append(256, '\0'); + res = GetModuleFileNameW(module, moddir->data(), static_cast(moddir->size())); + } while(res >= moddir->size()); + } + moddir->resize(res); - sep0 = strrchrW(moddir, '/'); - if(sep0) sep1 = strrchrW(sep0+1, '\\'); - else sep1 = strrchrW(moddir, '\\'); + auto sep0 = moddir->rfind('/'); + auto sep1 = moddir->rfind('\\'); + if(sep0 < moddir->size() && sep1 < moddir->size()) + moddir->resize(std::max(sep0, sep1)); + else if(sep0 < moddir->size()) + moddir->resize(sep0); + else if(sep1 < moddir->size()) + moddir->resize(sep1); + else + moddir->resize(0); - if(sep1) *sep1 = '\0'; - else if(sep0) *sep0 = '\0'; - else *moddir = '\0'; - - return 1; + return !moddir->empty(); } -void LoadDriverList(void) +void LoadDriverList() { - WCHAR dll_path[MAX_PATH+1] = L""; - WCHAR cwd_path[MAX_PATH+1] = L""; - WCHAR proc_path[MAX_PATH+1] = L""; - WCHAR sys_path[MAX_PATH+1] = L""; - int len; + if(auto list = al::getenv(L"ALROUTER_ACCEPT")) + { + std::wstring_view namelist{*list}; + while(!namelist.empty()) + { + auto seppos = namelist.find(','); + if(seppos > 0) + gAcceptList.emplace_back(namelist.substr(0, seppos)); + if(seppos < namelist.size()) + namelist.remove_prefix(seppos+1); + else + namelist.remove_prefix(namelist.size()); + } + } + if(auto list = al::getenv(L"ALROUTER_REJECT")) + { + std::wstring_view namelist{*list}; + while(!namelist.empty()) + { + auto seppos = namelist.find(','); + if(seppos > 0) + gRejectList.emplace_back(namelist.substr(0, seppos)); + if(seppos < namelist.size()) + namelist.remove_prefix(seppos+1); + else + namelist.remove_prefix(namelist.size()); + } + } - if(GetLoadedModuleDirectory(L"OpenAL32.dll", dll_path, MAX_PATH)) - TRACE("Got DLL path %ls\n", dll_path); + std::wstring dll_path; + if(GetLoadedModuleDirectory(L"OpenAL32.dll", &dll_path)) + TRACE("Got DLL path %ls\n", dll_path.c_str()); - GetCurrentDirectoryW(MAX_PATH, cwd_path); - len = lstrlenW(cwd_path); - if(len > 0 && (cwd_path[len-1] == '\\' || cwd_path[len-1] == '/')) - cwd_path[len-1] = '\0'; - TRACE("Got current working directory %ls\n", cwd_path); + std::wstring cwd_path; + if(DWORD pathlen{GetCurrentDirectoryW(0, nullptr)}) + { + do { + cwd_path.resize(pathlen); + pathlen = GetCurrentDirectoryW(pathlen, cwd_path.data()); + } while(pathlen >= cwd_path.size()); + cwd_path.resize(pathlen); + } + if(!cwd_path.empty() && (cwd_path.back() == '\\' || cwd_path.back() == '/')) + cwd_path.pop_back(); + if(!cwd_path.empty()) + TRACE("Got current working directory %ls\n", cwd_path.c_str()); - if(GetLoadedModuleDirectory(nullptr, proc_path, MAX_PATH)) - TRACE("Got proc path %ls\n", proc_path); + std::wstring proc_path; + if(GetLoadedModuleDirectory(nullptr, &proc_path)) + TRACE("Got proc path %ls\n", proc_path.c_str()); - GetSystemDirectoryW(sys_path, MAX_PATH); - len = lstrlenW(sys_path); - if(len > 0 && (sys_path[len-1] == '\\' || sys_path[len-1] == '/')) - sys_path[len-1] = '\0'; - TRACE("Got system path %ls\n", sys_path); + std::wstring sys_path; + if(UINT pathlen{GetSystemDirectoryW(nullptr, 0)}) + { + do { + sys_path.resize(pathlen); + pathlen = GetSystemDirectoryW(sys_path.data(), pathlen); + } while(pathlen >= sys_path.size()); + sys_path.resize(pathlen); + } + if(!sys_path.empty() && (sys_path.back() == '\\' || sys_path.back() == '/')) + sys_path.pop_back(); + if(!sys_path.empty()) + TRACE("Got system path %ls\n", sys_path.c_str()); /* Don't search the DLL's path if it is the same as the current working * directory, app's path, or system path (don't want to do duplicate * searches, or increase the priority of the app or system path). */ - if(dll_path[0] && - (!cwd_path[0] || wcscmp(dll_path, cwd_path) != 0) && - (!proc_path[0] || wcscmp(dll_path, proc_path) != 0) && - (!sys_path[0] || wcscmp(dll_path, sys_path) != 0)) + if(!dll_path.empty() && + (cwd_path.empty() || dll_path != cwd_path) && + (proc_path.empty() || dll_path != proc_path) && + (sys_path.empty() || dll_path != sys_path)) SearchDrivers(dll_path); - if(cwd_path[0] && - (!proc_path[0] || wcscmp(cwd_path, proc_path) != 0) && - (!sys_path[0] || wcscmp(cwd_path, sys_path) != 0)) + if(!cwd_path.empty() && + (proc_path.empty() || cwd_path != proc_path) && + (sys_path.empty() || cwd_path != sys_path)) SearchDrivers(cwd_path); - if(proc_path[0] && (!sys_path[0] || wcscmp(proc_path, sys_path) != 0)) + if(!proc_path.empty() && (sys_path.empty() || proc_path != sys_path)) SearchDrivers(proc_path); - if(sys_path[0]) + if(!sys_path.empty()) SearchDrivers(sys_path); } +} // namespace -PtrIntMap::~PtrIntMap() +BOOL APIENTRY DllMain(HINSTANCE, DWORD reason, void*) { - std::lock_guard maplock{mLock}; - al_free(mKeys); - mKeys = nullptr; - mValues = nullptr; - mSize = 0; - mCapacity = 0; -} - -ALenum PtrIntMap::insert(void *key, int value) -{ - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - - if(pos == mSize || mKeys[pos] != key) + switch(reason) { - if(mSize == mCapacity) + case DLL_PROCESS_ATTACH: + if(auto logfname = al::getenv("ALROUTER_LOGFILE")) { - void **newkeys{nullptr}; - ALsizei newcap{mCapacity ? (mCapacity<<1) : 4}; - if(newcap > mCapacity) - newkeys = static_cast( - al_calloc(16, (sizeof(mKeys[0])+sizeof(mValues[0]))*newcap) - ); - if(!newkeys) - return AL_OUT_OF_MEMORY; - auto newvalues = reinterpret_cast(&newkeys[newcap]); - - if(mKeys) - { - std::copy_n(mKeys, mSize, newkeys); - std::copy_n(mValues, mSize, newvalues); - } - al_free(mKeys); - mKeys = newkeys; - mValues = newvalues; - mCapacity = newcap; + gsl::owner f{fopen(logfname->c_str(), "w")}; + if(f == nullptr) + ERR("Could not open log file: %s\n", logfname->c_str()); + else + LogFile = f; } - - if(pos < mSize) + if(auto loglev = al::getenv("ALROUTER_LOGLEVEL")) { - std::copy_backward(mKeys+pos, mKeys+mSize, mKeys+mSize+1); - std::copy_backward(mValues+pos, mValues+mSize, mValues+mSize+1); + char *end = nullptr; + long l{strtol(loglev->c_str(), &end, 0)}; + if(!end || *end != '\0') + ERR("Invalid log level value: %s\n", loglev->c_str()); + else if(l < al::to_underlying(eLogLevel::None) + || l > al::to_underlying(eLogLevel::Trace)) + ERR("Log level out of range: %s\n", loglev->c_str()); + else + LogLevel = static_cast(l); } - mSize++; + TRACE("Initializing router v0.1-%s %s\n", ALSOFT_GIT_COMMIT_HASH, ALSOFT_GIT_BRANCH); + LoadDriverList(); + + break; + + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + + case DLL_PROCESS_DETACH: + DriverList.clear(); + + if(LogFile) + fclose(LogFile); + LogFile = nullptr; + + break; } - mKeys[pos] = key; - mValues[pos] = value; - - return AL_NO_ERROR; -} - -int PtrIntMap::removeByKey(void *key) -{ - int ret = -1; - - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - if(pos < mSize && mKeys[pos] == key) - { - ret = mValues[pos]; - if(pos+1 < mSize) - { - std::copy(mKeys+pos+1, mKeys+mSize, mKeys+pos); - std::copy(mValues+pos+1, mValues+mSize, mValues+pos); - } - mSize--; - } - - return ret; -} - -int PtrIntMap::lookupByKey(void *key) -{ - int ret = -1; - - std::lock_guard maplock{mLock}; - auto iter = std::lower_bound(mKeys, mKeys+mSize, key); - auto pos = static_cast(std::distance(mKeys, iter)); - if(pos < mSize && mKeys[pos] == key) - ret = mValues[pos]; - - return ret; + return TRUE; } diff --git a/Engine/lib/openal-soft/router/router.h b/Engine/lib/openal-soft/router/router.h index 2a126d42c..bd5c907a0 100644 --- a/Engine/lib/openal-soft/router/router.h +++ b/Engine/lib/openal-soft/router/router.h @@ -5,9 +5,8 @@ #include #include -#include - #include +#include #include #include #include @@ -18,15 +17,12 @@ #include "AL/al.h" #include "AL/alext.h" +#include "almalloc.h" + #define MAKE_ALC_VER(major, minor) (((major)<<8) | (minor)) struct DriverIface { - std::wstring Name; - HMODULE Module{nullptr}; - int ALCVer{0}; - std::once_flag InitOnceCtx{}; - LPALCCREATECONTEXT alcCreateContext{nullptr}; LPALCMAKECONTEXTCURRENT alcMakeContextCurrent{nullptr}; LPALCPROCESSCONTEXT alcProcessContext{nullptr}; @@ -160,83 +156,62 @@ struct DriverIface { LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti{nullptr}; LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv{nullptr}; + std::wstring Name; + HMODULE Module{nullptr}; + int ALCVer{0}; + std::once_flag InitOnceCtx{}; + template - DriverIface(T&& name, HMODULE mod) - : Name(std::forward(name)), Module(mod) - { } - ~DriverIface() - { - if(Module) - FreeLibrary(Module); - Module = nullptr; - } + DriverIface(T&& name, HMODULE mod) : Name(std::forward(name)), Module(mod) { } + ~DriverIface() { if(Module) FreeLibrary(Module); } + + DriverIface(const DriverIface&) = delete; + DriverIface(DriverIface&&) = delete; + DriverIface& operator=(const DriverIface&) = delete; + DriverIface& operator=(DriverIface&&) = delete; }; using DriverIfacePtr = std::unique_ptr; -extern std::vector DriverList; +inline std::vector DriverList; -extern thread_local DriverIface *ThreadCtxDriver; -extern std::atomic CurrentCtxDriver; +inline thread_local DriverIface *ThreadCtxDriver{}; +inline std::atomic CurrentCtxDriver{}; -/* HACK: MinGW generates bad code when accessing an extern thread_local object. - * Add a wrapper function for it that only accesses it where it's defined. - */ -#ifdef __MINGW32__ -DriverIface *GetThreadDriver() noexcept; -void SetThreadDriver(DriverIface *driver) noexcept; -#else inline DriverIface *GetThreadDriver() noexcept { return ThreadCtxDriver; } inline void SetThreadDriver(DriverIface *driver) noexcept { ThreadCtxDriver = driver; } -#endif -class PtrIntMap { - void **mKeys{nullptr}; - /* Shares memory with keys. */ - int *mValues{nullptr}; - - ALsizei mSize{0}; - ALsizei mCapacity{0}; - std::mutex mLock; - -public: - PtrIntMap() = default; - ~PtrIntMap(); - - ALenum insert(void *key, int value); - int removeByKey(void *key); - int lookupByKey(void *key); +enum class eLogLevel { + None = 0, + Error = 1, + Warn = 2, + Trace = 3, }; - - -enum LogLevel { - LogLevel_None = 0, - LogLevel_Error = 1, - LogLevel_Warn = 2, - LogLevel_Trace = 3, -}; -extern enum LogLevel LogLevel; -extern FILE *LogFile; +extern eLogLevel LogLevel; +extern gsl::owner LogFile; #define TRACE(...) do { \ - if(LogLevel >= LogLevel_Trace) \ + if(LogLevel >= eLogLevel::Trace) \ { \ - fprintf(LogFile, "AL Router (II): " __VA_ARGS__); \ - fflush(LogFile); \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fprintf(file, "AL Router (II): " __VA_ARGS__); \ + fflush(file); \ } \ } while(0) #define WARN(...) do { \ - if(LogLevel >= LogLevel_Warn) \ + if(LogLevel >= eLogLevel::Warn) \ { \ - fprintf(LogFile, "AL Router (WW): " __VA_ARGS__); \ - fflush(LogFile); \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fprintf(file, "AL Router (WW): " __VA_ARGS__); \ + fflush(file); \ } \ } while(0) #define ERR(...) do { \ - if(LogLevel >= LogLevel_Error) \ + if(LogLevel >= eLogLevel::Error) \ { \ - fprintf(LogFile, "AL Router (EE): " __VA_ARGS__); \ - fflush(LogFile); \ + std::FILE *file{LogFile ? LogFile : stderr}; \ + fprintf(file, "AL Router (EE): " __VA_ARGS__); \ + fflush(file); \ } \ } while(0) diff --git a/Engine/lib/openal-soft/tests/CMakeLists.txt b/Engine/lib/openal-soft/tests/CMakeLists.txt new file mode 100644 index 000000000..5c5a823cd --- /dev/null +++ b/Engine/lib/openal-soft/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +add_executable(OpenAL_Tests) + +include(FetchContent) +FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main +) +# For Windows: Prevent overriding the parent project's compiler/linker settings +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +target_link_libraries(OpenAL_Tests PRIVATE + OpenAL + GTest::gtest_main +) + +target_sources(OpenAL_Tests PRIVATE +example.t.cpp +) + +# This needs to come last +include(GoogleTest) +gtest_discover_tests(OpenAL_Tests) diff --git a/Engine/lib/openal-soft/tests/example.t.cpp b/Engine/lib/openal-soft/tests/example.t.cpp new file mode 100644 index 000000000..3d21900a7 --- /dev/null +++ b/Engine/lib/openal-soft/tests/example.t.cpp @@ -0,0 +1,16 @@ +#include +#include + +class ExampleTest : public ::testing::Test { +}; + + +TEST_F(ExampleTest, Basic) +{ + // just making sure we compile + ALuint source, buffer; + ALfloat offset; + ALenum state; +} + + diff --git a/Engine/lib/openal-soft/utils/CIAIR.def b/Engine/lib/openal-soft/utils/CIAIR.def index 5fabdb3f7..79910417d 100644 --- a/Engine/lib/openal-soft/utils/CIAIR.def +++ b/Engine/lib/openal-soft/utils/CIAIR.def @@ -37,3922 +37,3922 @@ azimuths = 1, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72, 72 # The extension of the source data may be misleading, they're ASCII text # lists of floating point values (one per line). Left and right ear HRIRs # (from the respective files) are used to create a stereo HRTF. -[ 9, 0 ] = ascii (fp) : "./hrtfs/elev-45/L-45e000a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e000a.dat right -[ 9, 1 ] = ascii (fp) : "./hrtfs/elev-45/L-45e355a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e355a.dat right -[ 9, 2 ] = ascii (fp) : "./hrtfs/elev-45/L-45e350a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e350a.dat right -[ 9, 3 ] = ascii (fp) : "./hrtfs/elev-45/L-45e345a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e345a.dat right -[ 9, 4 ] = ascii (fp) : "./hrtfs/elev-45/L-45e340a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e340a.dat right -[ 9, 5 ] = ascii (fp) : "./hrtfs/elev-45/L-45e335a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e335a.dat right -[ 9, 6 ] = ascii (fp) : "./hrtfs/elev-45/L-45e330a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e330a.dat right -[ 9, 7 ] = ascii (fp) : "./hrtfs/elev-45/L-45e325a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e325a.dat right -[ 9, 8 ] = ascii (fp) : "./hrtfs/elev-45/L-45e320a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e320a.dat right -[ 9, 9 ] = ascii (fp) : "./hrtfs/elev-45/L-45e315a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e315a.dat right -[ 9, 10 ] = ascii (fp) : "./hrtfs/elev-45/L-45e310a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e310a.dat right -[ 9, 11 ] = ascii (fp) : "./hrtfs/elev-45/L-45e305a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e305a.dat right -[ 9, 12 ] = ascii (fp) : "./hrtfs/elev-45/L-45e300a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e300a.dat right -[ 9, 13 ] = ascii (fp) : "./hrtfs/elev-45/L-45e295a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e295a.dat right -[ 9, 14 ] = ascii (fp) : "./hrtfs/elev-45/L-45e290a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e290a.dat right -[ 9, 15 ] = ascii (fp) : "./hrtfs/elev-45/L-45e285a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e285a.dat right -[ 9, 16 ] = ascii (fp) : "./hrtfs/elev-45/L-45e280a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e280a.dat right -[ 9, 17 ] = ascii (fp) : "./hrtfs/elev-45/L-45e275a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e275a.dat right -[ 9, 18 ] = ascii (fp) : "./hrtfs/elev-45/L-45e270a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e270a.dat right -[ 9, 19 ] = ascii (fp) : "./hrtfs/elev-45/L-45e265a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e265a.dat right -[ 9, 20 ] = ascii (fp) : "./hrtfs/elev-45/L-45e260a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e260a.dat right -[ 9, 21 ] = ascii (fp) : "./hrtfs/elev-45/L-45e255a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e255a.dat right -[ 9, 22 ] = ascii (fp) : "./hrtfs/elev-45/L-45e250a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e250a.dat right -[ 9, 23 ] = ascii (fp) : "./hrtfs/elev-45/L-45e245a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e245a.dat right -[ 9, 24 ] = ascii (fp) : "./hrtfs/elev-45/L-45e240a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e240a.dat right -[ 9, 25 ] = ascii (fp) : "./hrtfs/elev-45/L-45e235a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e235a.dat right -[ 9, 26 ] = ascii (fp) : "./hrtfs/elev-45/L-45e230a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e230a.dat right -[ 9, 27 ] = ascii (fp) : "./hrtfs/elev-45/L-45e225a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e225a.dat right -[ 9, 28 ] = ascii (fp) : "./hrtfs/elev-45/L-45e220a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e220a.dat right -[ 9, 29 ] = ascii (fp) : "./hrtfs/elev-45/L-45e215a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e215a.dat right -[ 9, 30 ] = ascii (fp) : "./hrtfs/elev-45/L-45e210a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e210a.dat right -[ 9, 31 ] = ascii (fp) : "./hrtfs/elev-45/L-45e205a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e205a.dat right -[ 9, 32 ] = ascii (fp) : "./hrtfs/elev-45/L-45e200a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e200a.dat right -[ 9, 33 ] = ascii (fp) : "./hrtfs/elev-45/L-45e195a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e195a.dat right -[ 9, 34 ] = ascii (fp) : "./hrtfs/elev-45/L-45e190a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e190a.dat right -[ 9, 35 ] = ascii (fp) : "./hrtfs/elev-45/L-45e185a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e185a.dat right -[ 9, 36 ] = ascii (fp) : "./hrtfs/elev-45/L-45e180a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e180a.dat right -[ 9, 37 ] = ascii (fp) : "./hrtfs/elev-45/L-45e175a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e175a.dat right -[ 9, 38 ] = ascii (fp) : "./hrtfs/elev-45/L-45e170a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e170a.dat right -[ 9, 39 ] = ascii (fp) : "./hrtfs/elev-45/L-45e165a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e165a.dat right -[ 9, 40 ] = ascii (fp) : "./hrtfs/elev-45/L-45e160a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e160a.dat right -[ 9, 41 ] = ascii (fp) : "./hrtfs/elev-45/L-45e155a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e155a.dat right -[ 9, 42 ] = ascii (fp) : "./hrtfs/elev-45/L-45e150a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e150a.dat right -[ 9, 43 ] = ascii (fp) : "./hrtfs/elev-45/L-45e145a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e145a.dat right -[ 9, 44 ] = ascii (fp) : "./hrtfs/elev-45/L-45e140a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e140a.dat right -[ 9, 45 ] = ascii (fp) : "./hrtfs/elev-45/L-45e135a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e135a.dat right -[ 9, 46 ] = ascii (fp) : "./hrtfs/elev-45/L-45e130a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e130a.dat right -[ 9, 47 ] = ascii (fp) : "./hrtfs/elev-45/L-45e125a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e125a.dat right -[ 9, 48 ] = ascii (fp) : "./hrtfs/elev-45/L-45e120a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e120a.dat right -[ 9, 49 ] = ascii (fp) : "./hrtfs/elev-45/L-45e115a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e115a.dat right -[ 9, 50 ] = ascii (fp) : "./hrtfs/elev-45/L-45e110a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e110a.dat right -[ 9, 51 ] = ascii (fp) : "./hrtfs/elev-45/L-45e105a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e105a.dat right -[ 9, 52 ] = ascii (fp) : "./hrtfs/elev-45/L-45e100a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e100a.dat right -[ 9, 53 ] = ascii (fp) : "./hrtfs/elev-45/L-45e095a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e095a.dat right -[ 9, 54 ] = ascii (fp) : "./hrtfs/elev-45/L-45e090a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e090a.dat right -[ 9, 55 ] = ascii (fp) : "./hrtfs/elev-45/L-45e085a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e085a.dat right -[ 9, 56 ] = ascii (fp) : "./hrtfs/elev-45/L-45e080a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e080a.dat right -[ 9, 57 ] = ascii (fp) : "./hrtfs/elev-45/L-45e075a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e075a.dat right -[ 9, 58 ] = ascii (fp) : "./hrtfs/elev-45/L-45e070a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e070a.dat right -[ 9, 59 ] = ascii (fp) : "./hrtfs/elev-45/L-45e065a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e065a.dat right -[ 9, 60 ] = ascii (fp) : "./hrtfs/elev-45/L-45e060a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e060a.dat right -[ 9, 61 ] = ascii (fp) : "./hrtfs/elev-45/L-45e055a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e055a.dat right -[ 9, 62 ] = ascii (fp) : "./hrtfs/elev-45/L-45e050a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e050a.dat right -[ 9, 63 ] = ascii (fp) : "./hrtfs/elev-45/L-45e045a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e045a.dat right -[ 9, 64 ] = ascii (fp) : "./hrtfs/elev-45/L-45e040a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e040a.dat right -[ 9, 65 ] = ascii (fp) : "./hrtfs/elev-45/L-45e035a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e035a.dat right -[ 9, 66 ] = ascii (fp) : "./hrtfs/elev-45/L-45e030a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e030a.dat right -[ 9, 67 ] = ascii (fp) : "./hrtfs/elev-45/L-45e025a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e025a.dat right -[ 9, 68 ] = ascii (fp) : "./hrtfs/elev-45/L-45e020a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e020a.dat right -[ 9, 69 ] = ascii (fp) : "./hrtfs/elev-45/L-45e015a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e015a.dat right -[ 9, 70 ] = ascii (fp) : "./hrtfs/elev-45/L-45e010a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e010a.dat right -[ 9, 71 ] = ascii (fp) : "./hrtfs/elev-45/L-45e005a.dat left - + ascii (fp) : "./hrtfs/elev-45/R-45e005a.dat right +[ 9, 0 ] = ascii (fp) : "./hrtfs/elev-45/L-45e000a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e000a.dat" right +[ 9, 1 ] = ascii (fp) : "./hrtfs/elev-45/L-45e355a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e355a.dat" right +[ 9, 2 ] = ascii (fp) : "./hrtfs/elev-45/L-45e350a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e350a.dat" right +[ 9, 3 ] = ascii (fp) : "./hrtfs/elev-45/L-45e345a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e345a.dat" right +[ 9, 4 ] = ascii (fp) : "./hrtfs/elev-45/L-45e340a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e340a.dat" right +[ 9, 5 ] = ascii (fp) : "./hrtfs/elev-45/L-45e335a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e335a.dat" right +[ 9, 6 ] = ascii (fp) : "./hrtfs/elev-45/L-45e330a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e330a.dat" right +[ 9, 7 ] = ascii (fp) : "./hrtfs/elev-45/L-45e325a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e325a.dat" right +[ 9, 8 ] = ascii (fp) : "./hrtfs/elev-45/L-45e320a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e320a.dat" right +[ 9, 9 ] = ascii (fp) : "./hrtfs/elev-45/L-45e315a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e315a.dat" right +[ 9, 10 ] = ascii (fp) : "./hrtfs/elev-45/L-45e310a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e310a.dat" right +[ 9, 11 ] = ascii (fp) : "./hrtfs/elev-45/L-45e305a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e305a.dat" right +[ 9, 12 ] = ascii (fp) : "./hrtfs/elev-45/L-45e300a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e300a.dat" right +[ 9, 13 ] = ascii (fp) : "./hrtfs/elev-45/L-45e295a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e295a.dat" right +[ 9, 14 ] = ascii (fp) : "./hrtfs/elev-45/L-45e290a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e290a.dat" right +[ 9, 15 ] = ascii (fp) : "./hrtfs/elev-45/L-45e285a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e285a.dat" right +[ 9, 16 ] = ascii (fp) : "./hrtfs/elev-45/L-45e280a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e280a.dat" right +[ 9, 17 ] = ascii (fp) : "./hrtfs/elev-45/L-45e275a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e275a.dat" right +[ 9, 18 ] = ascii (fp) : "./hrtfs/elev-45/L-45e270a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e270a.dat" right +[ 9, 19 ] = ascii (fp) : "./hrtfs/elev-45/L-45e265a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e265a.dat" right +[ 9, 20 ] = ascii (fp) : "./hrtfs/elev-45/L-45e260a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e260a.dat" right +[ 9, 21 ] = ascii (fp) : "./hrtfs/elev-45/L-45e255a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e255a.dat" right +[ 9, 22 ] = ascii (fp) : "./hrtfs/elev-45/L-45e250a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e250a.dat" right +[ 9, 23 ] = ascii (fp) : "./hrtfs/elev-45/L-45e245a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e245a.dat" right +[ 9, 24 ] = ascii (fp) : "./hrtfs/elev-45/L-45e240a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e240a.dat" right +[ 9, 25 ] = ascii (fp) : "./hrtfs/elev-45/L-45e235a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e235a.dat" right +[ 9, 26 ] = ascii (fp) : "./hrtfs/elev-45/L-45e230a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e230a.dat" right +[ 9, 27 ] = ascii (fp) : "./hrtfs/elev-45/L-45e225a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e225a.dat" right +[ 9, 28 ] = ascii (fp) : "./hrtfs/elev-45/L-45e220a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e220a.dat" right +[ 9, 29 ] = ascii (fp) : "./hrtfs/elev-45/L-45e215a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e215a.dat" right +[ 9, 30 ] = ascii (fp) : "./hrtfs/elev-45/L-45e210a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e210a.dat" right +[ 9, 31 ] = ascii (fp) : "./hrtfs/elev-45/L-45e205a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e205a.dat" right +[ 9, 32 ] = ascii (fp) : "./hrtfs/elev-45/L-45e200a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e200a.dat" right +[ 9, 33 ] = ascii (fp) : "./hrtfs/elev-45/L-45e195a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e195a.dat" right +[ 9, 34 ] = ascii (fp) : "./hrtfs/elev-45/L-45e190a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e190a.dat" right +[ 9, 35 ] = ascii (fp) : "./hrtfs/elev-45/L-45e185a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e185a.dat" right +[ 9, 36 ] = ascii (fp) : "./hrtfs/elev-45/L-45e180a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e180a.dat" right +[ 9, 37 ] = ascii (fp) : "./hrtfs/elev-45/L-45e175a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e175a.dat" right +[ 9, 38 ] = ascii (fp) : "./hrtfs/elev-45/L-45e170a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e170a.dat" right +[ 9, 39 ] = ascii (fp) : "./hrtfs/elev-45/L-45e165a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e165a.dat" right +[ 9, 40 ] = ascii (fp) : "./hrtfs/elev-45/L-45e160a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e160a.dat" right +[ 9, 41 ] = ascii (fp) : "./hrtfs/elev-45/L-45e155a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e155a.dat" right +[ 9, 42 ] = ascii (fp) : "./hrtfs/elev-45/L-45e150a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e150a.dat" right +[ 9, 43 ] = ascii (fp) : "./hrtfs/elev-45/L-45e145a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e145a.dat" right +[ 9, 44 ] = ascii (fp) : "./hrtfs/elev-45/L-45e140a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e140a.dat" right +[ 9, 45 ] = ascii (fp) : "./hrtfs/elev-45/L-45e135a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e135a.dat" right +[ 9, 46 ] = ascii (fp) : "./hrtfs/elev-45/L-45e130a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e130a.dat" right +[ 9, 47 ] = ascii (fp) : "./hrtfs/elev-45/L-45e125a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e125a.dat" right +[ 9, 48 ] = ascii (fp) : "./hrtfs/elev-45/L-45e120a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e120a.dat" right +[ 9, 49 ] = ascii (fp) : "./hrtfs/elev-45/L-45e115a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e115a.dat" right +[ 9, 50 ] = ascii (fp) : "./hrtfs/elev-45/L-45e110a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e110a.dat" right +[ 9, 51 ] = ascii (fp) : "./hrtfs/elev-45/L-45e105a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e105a.dat" right +[ 9, 52 ] = ascii (fp) : "./hrtfs/elev-45/L-45e100a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e100a.dat" right +[ 9, 53 ] = ascii (fp) : "./hrtfs/elev-45/L-45e095a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e095a.dat" right +[ 9, 54 ] = ascii (fp) : "./hrtfs/elev-45/L-45e090a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e090a.dat" right +[ 9, 55 ] = ascii (fp) : "./hrtfs/elev-45/L-45e085a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e085a.dat" right +[ 9, 56 ] = ascii (fp) : "./hrtfs/elev-45/L-45e080a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e080a.dat" right +[ 9, 57 ] = ascii (fp) : "./hrtfs/elev-45/L-45e075a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e075a.dat" right +[ 9, 58 ] = ascii (fp) : "./hrtfs/elev-45/L-45e070a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e070a.dat" right +[ 9, 59 ] = ascii (fp) : "./hrtfs/elev-45/L-45e065a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e065a.dat" right +[ 9, 60 ] = ascii (fp) : "./hrtfs/elev-45/L-45e060a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e060a.dat" right +[ 9, 61 ] = ascii (fp) : "./hrtfs/elev-45/L-45e055a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e055a.dat" right +[ 9, 62 ] = ascii (fp) : "./hrtfs/elev-45/L-45e050a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e050a.dat" right +[ 9, 63 ] = ascii (fp) : "./hrtfs/elev-45/L-45e045a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e045a.dat" right +[ 9, 64 ] = ascii (fp) : "./hrtfs/elev-45/L-45e040a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e040a.dat" right +[ 9, 65 ] = ascii (fp) : "./hrtfs/elev-45/L-45e035a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e035a.dat" right +[ 9, 66 ] = ascii (fp) : "./hrtfs/elev-45/L-45e030a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e030a.dat" right +[ 9, 67 ] = ascii (fp) : "./hrtfs/elev-45/L-45e025a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e025a.dat" right +[ 9, 68 ] = ascii (fp) : "./hrtfs/elev-45/L-45e020a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e020a.dat" right +[ 9, 69 ] = ascii (fp) : "./hrtfs/elev-45/L-45e015a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e015a.dat" right +[ 9, 70 ] = ascii (fp) : "./hrtfs/elev-45/L-45e010a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e010a.dat" right +[ 9, 71 ] = ascii (fp) : "./hrtfs/elev-45/L-45e005a.dat" left + + ascii (fp) : "./hrtfs/elev-45/R-45e005a.dat" right -[ 10, 0 ] = ascii (fp) : "./hrtfs/elev-40/L-40e000a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e000a.dat right -[ 10, 1 ] = ascii (fp) : "./hrtfs/elev-40/L-40e355a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e355a.dat right -[ 10, 2 ] = ascii (fp) : "./hrtfs/elev-40/L-40e350a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e350a.dat right -[ 10, 3 ] = ascii (fp) : "./hrtfs/elev-40/L-40e345a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e345a.dat right -[ 10, 4 ] = ascii (fp) : "./hrtfs/elev-40/L-40e340a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e340a.dat right -[ 10, 5 ] = ascii (fp) : "./hrtfs/elev-40/L-40e335a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e335a.dat right -[ 10, 6 ] = ascii (fp) : "./hrtfs/elev-40/L-40e330a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e330a.dat right -[ 10, 7 ] = ascii (fp) : "./hrtfs/elev-40/L-40e325a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e325a.dat right -[ 10, 8 ] = ascii (fp) : "./hrtfs/elev-40/L-40e320a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e320a.dat right -[ 10, 9 ] = ascii (fp) : "./hrtfs/elev-40/L-40e315a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e315a.dat right -[ 10, 10 ] = ascii (fp) : "./hrtfs/elev-40/L-40e310a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e310a.dat right -[ 10, 11 ] = ascii (fp) : "./hrtfs/elev-40/L-40e305a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e305a.dat right -[ 10, 12 ] = ascii (fp) : "./hrtfs/elev-40/L-40e300a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e300a.dat right -[ 10, 13 ] = ascii (fp) : "./hrtfs/elev-40/L-40e295a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e295a.dat right -[ 10, 14 ] = ascii (fp) : "./hrtfs/elev-40/L-40e290a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e290a.dat right -[ 10, 15 ] = ascii (fp) : "./hrtfs/elev-40/L-40e285a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e285a.dat right -[ 10, 16 ] = ascii (fp) : "./hrtfs/elev-40/L-40e280a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e280a.dat right -[ 10, 17 ] = ascii (fp) : "./hrtfs/elev-40/L-40e275a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e275a.dat right -[ 10, 18 ] = ascii (fp) : "./hrtfs/elev-40/L-40e270a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e270a.dat right -[ 10, 19 ] = ascii (fp) : "./hrtfs/elev-40/L-40e265a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e265a.dat right -[ 10, 20 ] = ascii (fp) : "./hrtfs/elev-40/L-40e260a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e260a.dat right -[ 10, 21 ] = ascii (fp) : "./hrtfs/elev-40/L-40e255a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e255a.dat right -[ 10, 22 ] = ascii (fp) : "./hrtfs/elev-40/L-40e250a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e250a.dat right -[ 10, 23 ] = ascii (fp) : "./hrtfs/elev-40/L-40e245a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e245a.dat right -[ 10, 24 ] = ascii (fp) : "./hrtfs/elev-40/L-40e240a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e240a.dat right -[ 10, 25 ] = ascii (fp) : "./hrtfs/elev-40/L-40e235a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e235a.dat right -[ 10, 26 ] = ascii (fp) : "./hrtfs/elev-40/L-40e230a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e230a.dat right -[ 10, 27 ] = ascii (fp) : "./hrtfs/elev-40/L-40e225a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e225a.dat right -[ 10, 28 ] = ascii (fp) : "./hrtfs/elev-40/L-40e220a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e220a.dat right -[ 10, 29 ] = ascii (fp) : "./hrtfs/elev-40/L-40e215a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e215a.dat right -[ 10, 30 ] = ascii (fp) : "./hrtfs/elev-40/L-40e210a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e210a.dat right -[ 10, 31 ] = ascii (fp) : "./hrtfs/elev-40/L-40e205a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e205a.dat right -[ 10, 32 ] = ascii (fp) : "./hrtfs/elev-40/L-40e200a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e200a.dat right -[ 10, 33 ] = ascii (fp) : "./hrtfs/elev-40/L-40e195a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e195a.dat right -[ 10, 34 ] = ascii (fp) : "./hrtfs/elev-40/L-40e190a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e190a.dat right -[ 10, 35 ] = ascii (fp) : "./hrtfs/elev-40/L-40e185a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e185a.dat right -[ 10, 36 ] = ascii (fp) : "./hrtfs/elev-40/L-40e180a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e180a.dat right -[ 10, 37 ] = ascii (fp) : "./hrtfs/elev-40/L-40e175a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e175a.dat right -[ 10, 38 ] = ascii (fp) : "./hrtfs/elev-40/L-40e170a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e170a.dat right -[ 10, 39 ] = ascii (fp) : "./hrtfs/elev-40/L-40e165a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e165a.dat right -[ 10, 40 ] = ascii (fp) : "./hrtfs/elev-40/L-40e160a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e160a.dat right -[ 10, 41 ] = ascii (fp) : "./hrtfs/elev-40/L-40e155a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e155a.dat right -[ 10, 42 ] = ascii (fp) : "./hrtfs/elev-40/L-40e150a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e150a.dat right -[ 10, 43 ] = ascii (fp) : "./hrtfs/elev-40/L-40e145a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e145a.dat right -[ 10, 44 ] = ascii (fp) : "./hrtfs/elev-40/L-40e140a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e140a.dat right -[ 10, 45 ] = ascii (fp) : "./hrtfs/elev-40/L-40e135a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e135a.dat right -[ 10, 46 ] = ascii (fp) : "./hrtfs/elev-40/L-40e130a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e130a.dat right -[ 10, 47 ] = ascii (fp) : "./hrtfs/elev-40/L-40e125a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e125a.dat right -[ 10, 48 ] = ascii (fp) : "./hrtfs/elev-40/L-40e120a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e120a.dat right -[ 10, 49 ] = ascii (fp) : "./hrtfs/elev-40/L-40e115a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e115a.dat right -[ 10, 50 ] = ascii (fp) : "./hrtfs/elev-40/L-40e110a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e110a.dat right -[ 10, 51 ] = ascii (fp) : "./hrtfs/elev-40/L-40e105a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e105a.dat right -[ 10, 52 ] = ascii (fp) : "./hrtfs/elev-40/L-40e100a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e100a.dat right -[ 10, 53 ] = ascii (fp) : "./hrtfs/elev-40/L-40e095a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e095a.dat right -[ 10, 54 ] = ascii (fp) : "./hrtfs/elev-40/L-40e090a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e090a.dat right -[ 10, 55 ] = ascii (fp) : "./hrtfs/elev-40/L-40e085a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e085a.dat right -[ 10, 56 ] = ascii (fp) : "./hrtfs/elev-40/L-40e080a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e080a.dat right -[ 10, 57 ] = ascii (fp) : "./hrtfs/elev-40/L-40e075a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e075a.dat right -[ 10, 58 ] = ascii (fp) : "./hrtfs/elev-40/L-40e070a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e070a.dat right -[ 10, 59 ] = ascii (fp) : "./hrtfs/elev-40/L-40e065a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e065a.dat right -[ 10, 60 ] = ascii (fp) : "./hrtfs/elev-40/L-40e060a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e060a.dat right -[ 10, 61 ] = ascii (fp) : "./hrtfs/elev-40/L-40e055a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e055a.dat right -[ 10, 62 ] = ascii (fp) : "./hrtfs/elev-40/L-40e050a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e050a.dat right -[ 10, 63 ] = ascii (fp) : "./hrtfs/elev-40/L-40e045a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e045a.dat right -[ 10, 64 ] = ascii (fp) : "./hrtfs/elev-40/L-40e040a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e040a.dat right -[ 10, 65 ] = ascii (fp) : "./hrtfs/elev-40/L-40e035a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e035a.dat right -[ 10, 66 ] = ascii (fp) : "./hrtfs/elev-40/L-40e030a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e030a.dat right -[ 10, 67 ] = ascii (fp) : "./hrtfs/elev-40/L-40e025a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e025a.dat right -[ 10, 68 ] = ascii (fp) : "./hrtfs/elev-40/L-40e020a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e020a.dat right -[ 10, 69 ] = ascii (fp) : "./hrtfs/elev-40/L-40e015a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e015a.dat right -[ 10, 70 ] = ascii (fp) : "./hrtfs/elev-40/L-40e010a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e010a.dat right -[ 10, 71 ] = ascii (fp) : "./hrtfs/elev-40/L-40e005a.dat left - + ascii (fp) : "./hrtfs/elev-40/R-40e005a.dat right +[ 10, 0 ] = ascii (fp) : "./hrtfs/elev-40/L-40e000a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e000a.dat" right +[ 10, 1 ] = ascii (fp) : "./hrtfs/elev-40/L-40e355a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e355a.dat" right +[ 10, 2 ] = ascii (fp) : "./hrtfs/elev-40/L-40e350a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e350a.dat" right +[ 10, 3 ] = ascii (fp) : "./hrtfs/elev-40/L-40e345a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e345a.dat" right +[ 10, 4 ] = ascii (fp) : "./hrtfs/elev-40/L-40e340a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e340a.dat" right +[ 10, 5 ] = ascii (fp) : "./hrtfs/elev-40/L-40e335a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e335a.dat" right +[ 10, 6 ] = ascii (fp) : "./hrtfs/elev-40/L-40e330a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e330a.dat" right +[ 10, 7 ] = ascii (fp) : "./hrtfs/elev-40/L-40e325a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e325a.dat" right +[ 10, 8 ] = ascii (fp) : "./hrtfs/elev-40/L-40e320a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e320a.dat" right +[ 10, 9 ] = ascii (fp) : "./hrtfs/elev-40/L-40e315a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e315a.dat" right +[ 10, 10 ] = ascii (fp) : "./hrtfs/elev-40/L-40e310a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e310a.dat" right +[ 10, 11 ] = ascii (fp) : "./hrtfs/elev-40/L-40e305a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e305a.dat" right +[ 10, 12 ] = ascii (fp) : "./hrtfs/elev-40/L-40e300a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e300a.dat" right +[ 10, 13 ] = ascii (fp) : "./hrtfs/elev-40/L-40e295a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e295a.dat" right +[ 10, 14 ] = ascii (fp) : "./hrtfs/elev-40/L-40e290a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e290a.dat" right +[ 10, 15 ] = ascii (fp) : "./hrtfs/elev-40/L-40e285a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e285a.dat" right +[ 10, 16 ] = ascii (fp) : "./hrtfs/elev-40/L-40e280a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e280a.dat" right +[ 10, 17 ] = ascii (fp) : "./hrtfs/elev-40/L-40e275a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e275a.dat" right +[ 10, 18 ] = ascii (fp) : "./hrtfs/elev-40/L-40e270a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e270a.dat" right +[ 10, 19 ] = ascii (fp) : "./hrtfs/elev-40/L-40e265a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e265a.dat" right +[ 10, 20 ] = ascii (fp) : "./hrtfs/elev-40/L-40e260a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e260a.dat" right +[ 10, 21 ] = ascii (fp) : "./hrtfs/elev-40/L-40e255a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e255a.dat" right +[ 10, 22 ] = ascii (fp) : "./hrtfs/elev-40/L-40e250a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e250a.dat" right +[ 10, 23 ] = ascii (fp) : "./hrtfs/elev-40/L-40e245a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e245a.dat" right +[ 10, 24 ] = ascii (fp) : "./hrtfs/elev-40/L-40e240a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e240a.dat" right +[ 10, 25 ] = ascii (fp) : "./hrtfs/elev-40/L-40e235a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e235a.dat" right +[ 10, 26 ] = ascii (fp) : "./hrtfs/elev-40/L-40e230a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e230a.dat" right +[ 10, 27 ] = ascii (fp) : "./hrtfs/elev-40/L-40e225a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e225a.dat" right +[ 10, 28 ] = ascii (fp) : "./hrtfs/elev-40/L-40e220a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e220a.dat" right +[ 10, 29 ] = ascii (fp) : "./hrtfs/elev-40/L-40e215a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e215a.dat" right +[ 10, 30 ] = ascii (fp) : "./hrtfs/elev-40/L-40e210a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e210a.dat" right +[ 10, 31 ] = ascii (fp) : "./hrtfs/elev-40/L-40e205a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e205a.dat" right +[ 10, 32 ] = ascii (fp) : "./hrtfs/elev-40/L-40e200a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e200a.dat" right +[ 10, 33 ] = ascii (fp) : "./hrtfs/elev-40/L-40e195a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e195a.dat" right +[ 10, 34 ] = ascii (fp) : "./hrtfs/elev-40/L-40e190a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e190a.dat" right +[ 10, 35 ] = ascii (fp) : "./hrtfs/elev-40/L-40e185a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e185a.dat" right +[ 10, 36 ] = ascii (fp) : "./hrtfs/elev-40/L-40e180a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e180a.dat" right +[ 10, 37 ] = ascii (fp) : "./hrtfs/elev-40/L-40e175a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e175a.dat" right +[ 10, 38 ] = ascii (fp) : "./hrtfs/elev-40/L-40e170a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e170a.dat" right +[ 10, 39 ] = ascii (fp) : "./hrtfs/elev-40/L-40e165a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e165a.dat" right +[ 10, 40 ] = ascii (fp) : "./hrtfs/elev-40/L-40e160a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e160a.dat" right +[ 10, 41 ] = ascii (fp) : "./hrtfs/elev-40/L-40e155a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e155a.dat" right +[ 10, 42 ] = ascii (fp) : "./hrtfs/elev-40/L-40e150a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e150a.dat" right +[ 10, 43 ] = ascii (fp) : "./hrtfs/elev-40/L-40e145a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e145a.dat" right +[ 10, 44 ] = ascii (fp) : "./hrtfs/elev-40/L-40e140a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e140a.dat" right +[ 10, 45 ] = ascii (fp) : "./hrtfs/elev-40/L-40e135a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e135a.dat" right +[ 10, 46 ] = ascii (fp) : "./hrtfs/elev-40/L-40e130a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e130a.dat" right +[ 10, 47 ] = ascii (fp) : "./hrtfs/elev-40/L-40e125a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e125a.dat" right +[ 10, 48 ] = ascii (fp) : "./hrtfs/elev-40/L-40e120a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e120a.dat" right +[ 10, 49 ] = ascii (fp) : "./hrtfs/elev-40/L-40e115a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e115a.dat" right +[ 10, 50 ] = ascii (fp) : "./hrtfs/elev-40/L-40e110a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e110a.dat" right +[ 10, 51 ] = ascii (fp) : "./hrtfs/elev-40/L-40e105a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e105a.dat" right +[ 10, 52 ] = ascii (fp) : "./hrtfs/elev-40/L-40e100a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e100a.dat" right +[ 10, 53 ] = ascii (fp) : "./hrtfs/elev-40/L-40e095a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e095a.dat" right +[ 10, 54 ] = ascii (fp) : "./hrtfs/elev-40/L-40e090a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e090a.dat" right +[ 10, 55 ] = ascii (fp) : "./hrtfs/elev-40/L-40e085a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e085a.dat" right +[ 10, 56 ] = ascii (fp) : "./hrtfs/elev-40/L-40e080a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e080a.dat" right +[ 10, 57 ] = ascii (fp) : "./hrtfs/elev-40/L-40e075a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e075a.dat" right +[ 10, 58 ] = ascii (fp) : "./hrtfs/elev-40/L-40e070a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e070a.dat" right +[ 10, 59 ] = ascii (fp) : "./hrtfs/elev-40/L-40e065a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e065a.dat" right +[ 10, 60 ] = ascii (fp) : "./hrtfs/elev-40/L-40e060a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e060a.dat" right +[ 10, 61 ] = ascii (fp) : "./hrtfs/elev-40/L-40e055a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e055a.dat" right +[ 10, 62 ] = ascii (fp) : "./hrtfs/elev-40/L-40e050a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e050a.dat" right +[ 10, 63 ] = ascii (fp) : "./hrtfs/elev-40/L-40e045a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e045a.dat" right +[ 10, 64 ] = ascii (fp) : "./hrtfs/elev-40/L-40e040a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e040a.dat" right +[ 10, 65 ] = ascii (fp) : "./hrtfs/elev-40/L-40e035a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e035a.dat" right +[ 10, 66 ] = ascii (fp) : "./hrtfs/elev-40/L-40e030a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e030a.dat" right +[ 10, 67 ] = ascii (fp) : "./hrtfs/elev-40/L-40e025a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e025a.dat" right +[ 10, 68 ] = ascii (fp) : "./hrtfs/elev-40/L-40e020a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e020a.dat" right +[ 10, 69 ] = ascii (fp) : "./hrtfs/elev-40/L-40e015a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e015a.dat" right +[ 10, 70 ] = ascii (fp) : "./hrtfs/elev-40/L-40e010a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e010a.dat" right +[ 10, 71 ] = ascii (fp) : "./hrtfs/elev-40/L-40e005a.dat" left + + ascii (fp) : "./hrtfs/elev-40/R-40e005a.dat" right -[ 11, 0 ] = ascii (fp) : "./hrtfs/elev-35/L-35e000a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e000a.dat right -[ 11, 1 ] = ascii (fp) : "./hrtfs/elev-35/L-35e355a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e355a.dat right -[ 11, 2 ] = ascii (fp) : "./hrtfs/elev-35/L-35e350a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e350a.dat right -[ 11, 3 ] = ascii (fp) : "./hrtfs/elev-35/L-35e345a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e345a.dat right -[ 11, 4 ] = ascii (fp) : "./hrtfs/elev-35/L-35e340a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e340a.dat right -[ 11, 5 ] = ascii (fp) : "./hrtfs/elev-35/L-35e335a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e335a.dat right -[ 11, 6 ] = ascii (fp) : "./hrtfs/elev-35/L-35e330a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e330a.dat right -[ 11, 7 ] = ascii (fp) : "./hrtfs/elev-35/L-35e325a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e325a.dat right -[ 11, 8 ] = ascii (fp) : "./hrtfs/elev-35/L-35e320a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e320a.dat right -[ 11, 9 ] = ascii (fp) : "./hrtfs/elev-35/L-35e315a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e315a.dat right -[ 11, 10 ] = ascii (fp) : "./hrtfs/elev-35/L-35e310a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e310a.dat right -[ 11, 11 ] = ascii (fp) : "./hrtfs/elev-35/L-35e305a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e305a.dat right -[ 11, 12 ] = ascii (fp) : "./hrtfs/elev-35/L-35e300a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e300a.dat right -[ 11, 13 ] = ascii (fp) : "./hrtfs/elev-35/L-35e295a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e295a.dat right -[ 11, 14 ] = ascii (fp) : "./hrtfs/elev-35/L-35e290a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e290a.dat right -[ 11, 15 ] = ascii (fp) : "./hrtfs/elev-35/L-35e285a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e285a.dat right -[ 11, 16 ] = ascii (fp) : "./hrtfs/elev-35/L-35e280a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e280a.dat right -[ 11, 17 ] = ascii (fp) : "./hrtfs/elev-35/L-35e275a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e275a.dat right -[ 11, 18 ] = ascii (fp) : "./hrtfs/elev-35/L-35e270a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e270a.dat right -[ 11, 19 ] = ascii (fp) : "./hrtfs/elev-35/L-35e265a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e265a.dat right -[ 11, 20 ] = ascii (fp) : "./hrtfs/elev-35/L-35e260a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e260a.dat right -[ 11, 21 ] = ascii (fp) : "./hrtfs/elev-35/L-35e255a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e255a.dat right -[ 11, 22 ] = ascii (fp) : "./hrtfs/elev-35/L-35e250a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e250a.dat right -[ 11, 23 ] = ascii (fp) : "./hrtfs/elev-35/L-35e245a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e245a.dat right -[ 11, 24 ] = ascii (fp) : "./hrtfs/elev-35/L-35e240a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e240a.dat right -[ 11, 25 ] = ascii (fp) : "./hrtfs/elev-35/L-35e235a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e235a.dat right -[ 11, 26 ] = ascii (fp) : "./hrtfs/elev-35/L-35e230a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e230a.dat right -[ 11, 27 ] = ascii (fp) : "./hrtfs/elev-35/L-35e225a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e225a.dat right -[ 11, 28 ] = ascii (fp) : "./hrtfs/elev-35/L-35e220a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e220a.dat right -[ 11, 29 ] = ascii (fp) : "./hrtfs/elev-35/L-35e215a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e215a.dat right -[ 11, 30 ] = ascii (fp) : "./hrtfs/elev-35/L-35e210a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e210a.dat right -[ 11, 31 ] = ascii (fp) : "./hrtfs/elev-35/L-35e205a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e205a.dat right -[ 11, 32 ] = ascii (fp) : "./hrtfs/elev-35/L-35e200a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e200a.dat right -[ 11, 33 ] = ascii (fp) : "./hrtfs/elev-35/L-35e195a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e195a.dat right -[ 11, 34 ] = ascii (fp) : "./hrtfs/elev-35/L-35e190a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e190a.dat right -[ 11, 35 ] = ascii (fp) : "./hrtfs/elev-35/L-35e185a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e185a.dat right -[ 11, 36 ] = ascii (fp) : "./hrtfs/elev-35/L-35e180a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e180a.dat right -[ 11, 37 ] = ascii (fp) : "./hrtfs/elev-35/L-35e175a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e175a.dat right -[ 11, 38 ] = ascii (fp) : "./hrtfs/elev-35/L-35e170a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e170a.dat right -[ 11, 39 ] = ascii (fp) : "./hrtfs/elev-35/L-35e165a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e165a.dat right -[ 11, 40 ] = ascii (fp) : "./hrtfs/elev-35/L-35e160a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e160a.dat right -[ 11, 41 ] = ascii (fp) : "./hrtfs/elev-35/L-35e155a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e155a.dat right -[ 11, 42 ] = ascii (fp) : "./hrtfs/elev-35/L-35e150a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e150a.dat right -[ 11, 43 ] = ascii (fp) : "./hrtfs/elev-35/L-35e145a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e145a.dat right -[ 11, 44 ] = ascii (fp) : "./hrtfs/elev-35/L-35e140a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e140a.dat right -[ 11, 45 ] = ascii (fp) : "./hrtfs/elev-35/L-35e135a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e135a.dat right -[ 11, 46 ] = ascii (fp) : "./hrtfs/elev-35/L-35e130a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e130a.dat right -[ 11, 47 ] = ascii (fp) : "./hrtfs/elev-35/L-35e125a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e125a.dat right -[ 11, 48 ] = ascii (fp) : "./hrtfs/elev-35/L-35e120a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e120a.dat right -[ 11, 49 ] = ascii (fp) : "./hrtfs/elev-35/L-35e115a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e115a.dat right -[ 11, 50 ] = ascii (fp) : "./hrtfs/elev-35/L-35e110a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e110a.dat right -[ 11, 51 ] = ascii (fp) : "./hrtfs/elev-35/L-35e105a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e105a.dat right -[ 11, 52 ] = ascii (fp) : "./hrtfs/elev-35/L-35e100a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e100a.dat right -[ 11, 53 ] = ascii (fp) : "./hrtfs/elev-35/L-35e095a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e095a.dat right -[ 11, 54 ] = ascii (fp) : "./hrtfs/elev-35/L-35e090a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e090a.dat right -[ 11, 55 ] = ascii (fp) : "./hrtfs/elev-35/L-35e085a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e085a.dat right -[ 11, 56 ] = ascii (fp) : "./hrtfs/elev-35/L-35e080a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e080a.dat right -[ 11, 57 ] = ascii (fp) : "./hrtfs/elev-35/L-35e075a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e075a.dat right -[ 11, 58 ] = ascii (fp) : "./hrtfs/elev-35/L-35e070a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e070a.dat right -[ 11, 59 ] = ascii (fp) : "./hrtfs/elev-35/L-35e065a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e065a.dat right -[ 11, 60 ] = ascii (fp) : "./hrtfs/elev-35/L-35e060a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e060a.dat right -[ 11, 61 ] = ascii (fp) : "./hrtfs/elev-35/L-35e055a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e055a.dat right -[ 11, 62 ] = ascii (fp) : "./hrtfs/elev-35/L-35e050a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e050a.dat right -[ 11, 63 ] = ascii (fp) : "./hrtfs/elev-35/L-35e045a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e045a.dat right -[ 11, 64 ] = ascii (fp) : "./hrtfs/elev-35/L-35e040a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e040a.dat right -[ 11, 65 ] = ascii (fp) : "./hrtfs/elev-35/L-35e035a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e035a.dat right -[ 11, 66 ] = ascii (fp) : "./hrtfs/elev-35/L-35e030a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e030a.dat right -[ 11, 67 ] = ascii (fp) : "./hrtfs/elev-35/L-35e025a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e025a.dat right -[ 11, 68 ] = ascii (fp) : "./hrtfs/elev-35/L-35e020a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e020a.dat right -[ 11, 69 ] = ascii (fp) : "./hrtfs/elev-35/L-35e015a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e015a.dat right -[ 11, 70 ] = ascii (fp) : "./hrtfs/elev-35/L-35e010a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e010a.dat right -[ 11, 71 ] = ascii (fp) : "./hrtfs/elev-35/L-35e005a.dat left - + ascii (fp) : "./hrtfs/elev-35/R-35e005a.dat right +[ 11, 0 ] = ascii (fp) : "./hrtfs/elev-35/L-35e000a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e000a.dat" right +[ 11, 1 ] = ascii (fp) : "./hrtfs/elev-35/L-35e355a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e355a.dat" right +[ 11, 2 ] = ascii (fp) : "./hrtfs/elev-35/L-35e350a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e350a.dat" right +[ 11, 3 ] = ascii (fp) : "./hrtfs/elev-35/L-35e345a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e345a.dat" right +[ 11, 4 ] = ascii (fp) : "./hrtfs/elev-35/L-35e340a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e340a.dat" right +[ 11, 5 ] = ascii (fp) : "./hrtfs/elev-35/L-35e335a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e335a.dat" right +[ 11, 6 ] = ascii (fp) : "./hrtfs/elev-35/L-35e330a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e330a.dat" right +[ 11, 7 ] = ascii (fp) : "./hrtfs/elev-35/L-35e325a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e325a.dat" right +[ 11, 8 ] = ascii (fp) : "./hrtfs/elev-35/L-35e320a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e320a.dat" right +[ 11, 9 ] = ascii (fp) : "./hrtfs/elev-35/L-35e315a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e315a.dat" right +[ 11, 10 ] = ascii (fp) : "./hrtfs/elev-35/L-35e310a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e310a.dat" right +[ 11, 11 ] = ascii (fp) : "./hrtfs/elev-35/L-35e305a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e305a.dat" right +[ 11, 12 ] = ascii (fp) : "./hrtfs/elev-35/L-35e300a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e300a.dat" right +[ 11, 13 ] = ascii (fp) : "./hrtfs/elev-35/L-35e295a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e295a.dat" right +[ 11, 14 ] = ascii (fp) : "./hrtfs/elev-35/L-35e290a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e290a.dat" right +[ 11, 15 ] = ascii (fp) : "./hrtfs/elev-35/L-35e285a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e285a.dat" right +[ 11, 16 ] = ascii (fp) : "./hrtfs/elev-35/L-35e280a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e280a.dat" right +[ 11, 17 ] = ascii (fp) : "./hrtfs/elev-35/L-35e275a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e275a.dat" right +[ 11, 18 ] = ascii (fp) : "./hrtfs/elev-35/L-35e270a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e270a.dat" right +[ 11, 19 ] = ascii (fp) : "./hrtfs/elev-35/L-35e265a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e265a.dat" right +[ 11, 20 ] = ascii (fp) : "./hrtfs/elev-35/L-35e260a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e260a.dat" right +[ 11, 21 ] = ascii (fp) : "./hrtfs/elev-35/L-35e255a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e255a.dat" right +[ 11, 22 ] = ascii (fp) : "./hrtfs/elev-35/L-35e250a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e250a.dat" right +[ 11, 23 ] = ascii (fp) : "./hrtfs/elev-35/L-35e245a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e245a.dat" right +[ 11, 24 ] = ascii (fp) : "./hrtfs/elev-35/L-35e240a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e240a.dat" right +[ 11, 25 ] = ascii (fp) : "./hrtfs/elev-35/L-35e235a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e235a.dat" right +[ 11, 26 ] = ascii (fp) : "./hrtfs/elev-35/L-35e230a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e230a.dat" right +[ 11, 27 ] = ascii (fp) : "./hrtfs/elev-35/L-35e225a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e225a.dat" right +[ 11, 28 ] = ascii (fp) : "./hrtfs/elev-35/L-35e220a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e220a.dat" right +[ 11, 29 ] = ascii (fp) : "./hrtfs/elev-35/L-35e215a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e215a.dat" right +[ 11, 30 ] = ascii (fp) : "./hrtfs/elev-35/L-35e210a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e210a.dat" right +[ 11, 31 ] = ascii (fp) : "./hrtfs/elev-35/L-35e205a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e205a.dat" right +[ 11, 32 ] = ascii (fp) : "./hrtfs/elev-35/L-35e200a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e200a.dat" right +[ 11, 33 ] = ascii (fp) : "./hrtfs/elev-35/L-35e195a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e195a.dat" right +[ 11, 34 ] = ascii (fp) : "./hrtfs/elev-35/L-35e190a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e190a.dat" right +[ 11, 35 ] = ascii (fp) : "./hrtfs/elev-35/L-35e185a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e185a.dat" right +[ 11, 36 ] = ascii (fp) : "./hrtfs/elev-35/L-35e180a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e180a.dat" right +[ 11, 37 ] = ascii (fp) : "./hrtfs/elev-35/L-35e175a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e175a.dat" right +[ 11, 38 ] = ascii (fp) : "./hrtfs/elev-35/L-35e170a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e170a.dat" right +[ 11, 39 ] = ascii (fp) : "./hrtfs/elev-35/L-35e165a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e165a.dat" right +[ 11, 40 ] = ascii (fp) : "./hrtfs/elev-35/L-35e160a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e160a.dat" right +[ 11, 41 ] = ascii (fp) : "./hrtfs/elev-35/L-35e155a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e155a.dat" right +[ 11, 42 ] = ascii (fp) : "./hrtfs/elev-35/L-35e150a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e150a.dat" right +[ 11, 43 ] = ascii (fp) : "./hrtfs/elev-35/L-35e145a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e145a.dat" right +[ 11, 44 ] = ascii (fp) : "./hrtfs/elev-35/L-35e140a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e140a.dat" right +[ 11, 45 ] = ascii (fp) : "./hrtfs/elev-35/L-35e135a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e135a.dat" right +[ 11, 46 ] = ascii (fp) : "./hrtfs/elev-35/L-35e130a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e130a.dat" right +[ 11, 47 ] = ascii (fp) : "./hrtfs/elev-35/L-35e125a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e125a.dat" right +[ 11, 48 ] = ascii (fp) : "./hrtfs/elev-35/L-35e120a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e120a.dat" right +[ 11, 49 ] = ascii (fp) : "./hrtfs/elev-35/L-35e115a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e115a.dat" right +[ 11, 50 ] = ascii (fp) : "./hrtfs/elev-35/L-35e110a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e110a.dat" right +[ 11, 51 ] = ascii (fp) : "./hrtfs/elev-35/L-35e105a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e105a.dat" right +[ 11, 52 ] = ascii (fp) : "./hrtfs/elev-35/L-35e100a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e100a.dat" right +[ 11, 53 ] = ascii (fp) : "./hrtfs/elev-35/L-35e095a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e095a.dat" right +[ 11, 54 ] = ascii (fp) : "./hrtfs/elev-35/L-35e090a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e090a.dat" right +[ 11, 55 ] = ascii (fp) : "./hrtfs/elev-35/L-35e085a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e085a.dat" right +[ 11, 56 ] = ascii (fp) : "./hrtfs/elev-35/L-35e080a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e080a.dat" right +[ 11, 57 ] = ascii (fp) : "./hrtfs/elev-35/L-35e075a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e075a.dat" right +[ 11, 58 ] = ascii (fp) : "./hrtfs/elev-35/L-35e070a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e070a.dat" right +[ 11, 59 ] = ascii (fp) : "./hrtfs/elev-35/L-35e065a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e065a.dat" right +[ 11, 60 ] = ascii (fp) : "./hrtfs/elev-35/L-35e060a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e060a.dat" right +[ 11, 61 ] = ascii (fp) : "./hrtfs/elev-35/L-35e055a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e055a.dat" right +[ 11, 62 ] = ascii (fp) : "./hrtfs/elev-35/L-35e050a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e050a.dat" right +[ 11, 63 ] = ascii (fp) : "./hrtfs/elev-35/L-35e045a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e045a.dat" right +[ 11, 64 ] = ascii (fp) : "./hrtfs/elev-35/L-35e040a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e040a.dat" right +[ 11, 65 ] = ascii (fp) : "./hrtfs/elev-35/L-35e035a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e035a.dat" right +[ 11, 66 ] = ascii (fp) : "./hrtfs/elev-35/L-35e030a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e030a.dat" right +[ 11, 67 ] = ascii (fp) : "./hrtfs/elev-35/L-35e025a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e025a.dat" right +[ 11, 68 ] = ascii (fp) : "./hrtfs/elev-35/L-35e020a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e020a.dat" right +[ 11, 69 ] = ascii (fp) : "./hrtfs/elev-35/L-35e015a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e015a.dat" right +[ 11, 70 ] = ascii (fp) : "./hrtfs/elev-35/L-35e010a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e010a.dat" right +[ 11, 71 ] = ascii (fp) : "./hrtfs/elev-35/L-35e005a.dat" left + + ascii (fp) : "./hrtfs/elev-35/R-35e005a.dat" right -[ 12, 0 ] = ascii (fp) : "./hrtfs/elev-30/L-30e000a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e000a.dat right -[ 12, 1 ] = ascii (fp) : "./hrtfs/elev-30/L-30e355a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e355a.dat right -[ 12, 2 ] = ascii (fp) : "./hrtfs/elev-30/L-30e350a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e350a.dat right -[ 12, 3 ] = ascii (fp) : "./hrtfs/elev-30/L-30e345a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e345a.dat right -[ 12, 4 ] = ascii (fp) : "./hrtfs/elev-30/L-30e340a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e340a.dat right -[ 12, 5 ] = ascii (fp) : "./hrtfs/elev-30/L-30e335a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e335a.dat right -[ 12, 6 ] = ascii (fp) : "./hrtfs/elev-30/L-30e330a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e330a.dat right -[ 12, 7 ] = ascii (fp) : "./hrtfs/elev-30/L-30e325a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e325a.dat right -[ 12, 8 ] = ascii (fp) : "./hrtfs/elev-30/L-30e320a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e320a.dat right -[ 12, 9 ] = ascii (fp) : "./hrtfs/elev-30/L-30e315a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e315a.dat right -[ 12, 10 ] = ascii (fp) : "./hrtfs/elev-30/L-30e310a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e310a.dat right -[ 12, 11 ] = ascii (fp) : "./hrtfs/elev-30/L-30e305a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e305a.dat right -[ 12, 12 ] = ascii (fp) : "./hrtfs/elev-30/L-30e300a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e300a.dat right -[ 12, 13 ] = ascii (fp) : "./hrtfs/elev-30/L-30e295a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e295a.dat right -[ 12, 14 ] = ascii (fp) : "./hrtfs/elev-30/L-30e290a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e290a.dat right -[ 12, 15 ] = ascii (fp) : "./hrtfs/elev-30/L-30e285a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e285a.dat right -[ 12, 16 ] = ascii (fp) : "./hrtfs/elev-30/L-30e280a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e280a.dat right -[ 12, 17 ] = ascii (fp) : "./hrtfs/elev-30/L-30e275a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e275a.dat right -[ 12, 18 ] = ascii (fp) : "./hrtfs/elev-30/L-30e270a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e270a.dat right -[ 12, 19 ] = ascii (fp) : "./hrtfs/elev-30/L-30e265a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e265a.dat right -[ 12, 20 ] = ascii (fp) : "./hrtfs/elev-30/L-30e260a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e260a.dat right -[ 12, 21 ] = ascii (fp) : "./hrtfs/elev-30/L-30e255a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e255a.dat right -[ 12, 22 ] = ascii (fp) : "./hrtfs/elev-30/L-30e250a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e250a.dat right -[ 12, 23 ] = ascii (fp) : "./hrtfs/elev-30/L-30e245a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e245a.dat right -[ 12, 24 ] = ascii (fp) : "./hrtfs/elev-30/L-30e240a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e240a.dat right -[ 12, 25 ] = ascii (fp) : "./hrtfs/elev-30/L-30e235a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e235a.dat right -[ 12, 26 ] = ascii (fp) : "./hrtfs/elev-30/L-30e230a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e230a.dat right -[ 12, 27 ] = ascii (fp) : "./hrtfs/elev-30/L-30e225a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e225a.dat right -[ 12, 28 ] = ascii (fp) : "./hrtfs/elev-30/L-30e220a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e220a.dat right -[ 12, 29 ] = ascii (fp) : "./hrtfs/elev-30/L-30e215a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e215a.dat right -[ 12, 30 ] = ascii (fp) : "./hrtfs/elev-30/L-30e210a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e210a.dat right -[ 12, 31 ] = ascii (fp) : "./hrtfs/elev-30/L-30e205a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e205a.dat right -[ 12, 32 ] = ascii (fp) : "./hrtfs/elev-30/L-30e200a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e200a.dat right -[ 12, 33 ] = ascii (fp) : "./hrtfs/elev-30/L-30e195a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e195a.dat right -[ 12, 34 ] = ascii (fp) : "./hrtfs/elev-30/L-30e190a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e190a.dat right -[ 12, 35 ] = ascii (fp) : "./hrtfs/elev-30/L-30e185a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e185a.dat right -[ 12, 36 ] = ascii (fp) : "./hrtfs/elev-30/L-30e180a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e180a.dat right -[ 12, 37 ] = ascii (fp) : "./hrtfs/elev-30/L-30e175a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e175a.dat right -[ 12, 38 ] = ascii (fp) : "./hrtfs/elev-30/L-30e170a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e170a.dat right -[ 12, 39 ] = ascii (fp) : "./hrtfs/elev-30/L-30e165a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e165a.dat right -[ 12, 40 ] = ascii (fp) : "./hrtfs/elev-30/L-30e160a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e160a.dat right -[ 12, 41 ] = ascii (fp) : "./hrtfs/elev-30/L-30e155a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e155a.dat right -[ 12, 42 ] = ascii (fp) : "./hrtfs/elev-30/L-30e150a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e150a.dat right -[ 12, 43 ] = ascii (fp) : "./hrtfs/elev-30/L-30e145a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e145a.dat right -[ 12, 44 ] = ascii (fp) : "./hrtfs/elev-30/L-30e140a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e140a.dat right -[ 12, 45 ] = ascii (fp) : "./hrtfs/elev-30/L-30e135a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e135a.dat right -[ 12, 46 ] = ascii (fp) : "./hrtfs/elev-30/L-30e130a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e130a.dat right -[ 12, 47 ] = ascii (fp) : "./hrtfs/elev-30/L-30e125a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e125a.dat right -[ 12, 48 ] = ascii (fp) : "./hrtfs/elev-30/L-30e120a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e120a.dat right -[ 12, 49 ] = ascii (fp) : "./hrtfs/elev-30/L-30e115a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e115a.dat right -[ 12, 50 ] = ascii (fp) : "./hrtfs/elev-30/L-30e110a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e110a.dat right -[ 12, 51 ] = ascii (fp) : "./hrtfs/elev-30/L-30e105a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e105a.dat right -[ 12, 52 ] = ascii (fp) : "./hrtfs/elev-30/L-30e100a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e100a.dat right -[ 12, 53 ] = ascii (fp) : "./hrtfs/elev-30/L-30e095a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e095a.dat right -[ 12, 54 ] = ascii (fp) : "./hrtfs/elev-30/L-30e090a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e090a.dat right -[ 12, 55 ] = ascii (fp) : "./hrtfs/elev-30/L-30e085a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e085a.dat right -[ 12, 56 ] = ascii (fp) : "./hrtfs/elev-30/L-30e080a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e080a.dat right -[ 12, 57 ] = ascii (fp) : "./hrtfs/elev-30/L-30e075a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e075a.dat right -[ 12, 58 ] = ascii (fp) : "./hrtfs/elev-30/L-30e070a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e070a.dat right -[ 12, 59 ] = ascii (fp) : "./hrtfs/elev-30/L-30e065a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e065a.dat right -[ 12, 60 ] = ascii (fp) : "./hrtfs/elev-30/L-30e060a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e060a.dat right -[ 12, 61 ] = ascii (fp) : "./hrtfs/elev-30/L-30e055a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e055a.dat right -[ 12, 62 ] = ascii (fp) : "./hrtfs/elev-30/L-30e050a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e050a.dat right -[ 12, 63 ] = ascii (fp) : "./hrtfs/elev-30/L-30e045a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e045a.dat right -[ 12, 64 ] = ascii (fp) : "./hrtfs/elev-30/L-30e040a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e040a.dat right -[ 12, 65 ] = ascii (fp) : "./hrtfs/elev-30/L-30e035a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e035a.dat right -[ 12, 66 ] = ascii (fp) : "./hrtfs/elev-30/L-30e030a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e030a.dat right -[ 12, 67 ] = ascii (fp) : "./hrtfs/elev-30/L-30e025a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e025a.dat right -[ 12, 68 ] = ascii (fp) : "./hrtfs/elev-30/L-30e020a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e020a.dat right -[ 12, 69 ] = ascii (fp) : "./hrtfs/elev-30/L-30e015a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e015a.dat right -[ 12, 70 ] = ascii (fp) : "./hrtfs/elev-30/L-30e010a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e010a.dat right -[ 12, 71 ] = ascii (fp) : "./hrtfs/elev-30/L-30e005a.dat left - + ascii (fp) : "./hrtfs/elev-30/R-30e005a.dat right +[ 12, 0 ] = ascii (fp) : "./hrtfs/elev-30/L-30e000a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e000a.dat" right +[ 12, 1 ] = ascii (fp) : "./hrtfs/elev-30/L-30e355a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e355a.dat" right +[ 12, 2 ] = ascii (fp) : "./hrtfs/elev-30/L-30e350a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e350a.dat" right +[ 12, 3 ] = ascii (fp) : "./hrtfs/elev-30/L-30e345a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e345a.dat" right +[ 12, 4 ] = ascii (fp) : "./hrtfs/elev-30/L-30e340a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e340a.dat" right +[ 12, 5 ] = ascii (fp) : "./hrtfs/elev-30/L-30e335a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e335a.dat" right +[ 12, 6 ] = ascii (fp) : "./hrtfs/elev-30/L-30e330a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e330a.dat" right +[ 12, 7 ] = ascii (fp) : "./hrtfs/elev-30/L-30e325a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e325a.dat" right +[ 12, 8 ] = ascii (fp) : "./hrtfs/elev-30/L-30e320a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e320a.dat" right +[ 12, 9 ] = ascii (fp) : "./hrtfs/elev-30/L-30e315a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e315a.dat" right +[ 12, 10 ] = ascii (fp) : "./hrtfs/elev-30/L-30e310a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e310a.dat" right +[ 12, 11 ] = ascii (fp) : "./hrtfs/elev-30/L-30e305a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e305a.dat" right +[ 12, 12 ] = ascii (fp) : "./hrtfs/elev-30/L-30e300a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e300a.dat" right +[ 12, 13 ] = ascii (fp) : "./hrtfs/elev-30/L-30e295a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e295a.dat" right +[ 12, 14 ] = ascii (fp) : "./hrtfs/elev-30/L-30e290a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e290a.dat" right +[ 12, 15 ] = ascii (fp) : "./hrtfs/elev-30/L-30e285a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e285a.dat" right +[ 12, 16 ] = ascii (fp) : "./hrtfs/elev-30/L-30e280a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e280a.dat" right +[ 12, 17 ] = ascii (fp) : "./hrtfs/elev-30/L-30e275a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e275a.dat" right +[ 12, 18 ] = ascii (fp) : "./hrtfs/elev-30/L-30e270a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e270a.dat" right +[ 12, 19 ] = ascii (fp) : "./hrtfs/elev-30/L-30e265a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e265a.dat" right +[ 12, 20 ] = ascii (fp) : "./hrtfs/elev-30/L-30e260a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e260a.dat" right +[ 12, 21 ] = ascii (fp) : "./hrtfs/elev-30/L-30e255a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e255a.dat" right +[ 12, 22 ] = ascii (fp) : "./hrtfs/elev-30/L-30e250a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e250a.dat" right +[ 12, 23 ] = ascii (fp) : "./hrtfs/elev-30/L-30e245a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e245a.dat" right +[ 12, 24 ] = ascii (fp) : "./hrtfs/elev-30/L-30e240a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e240a.dat" right +[ 12, 25 ] = ascii (fp) : "./hrtfs/elev-30/L-30e235a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e235a.dat" right +[ 12, 26 ] = ascii (fp) : "./hrtfs/elev-30/L-30e230a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e230a.dat" right +[ 12, 27 ] = ascii (fp) : "./hrtfs/elev-30/L-30e225a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e225a.dat" right +[ 12, 28 ] = ascii (fp) : "./hrtfs/elev-30/L-30e220a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e220a.dat" right +[ 12, 29 ] = ascii (fp) : "./hrtfs/elev-30/L-30e215a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e215a.dat" right +[ 12, 30 ] = ascii (fp) : "./hrtfs/elev-30/L-30e210a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e210a.dat" right +[ 12, 31 ] = ascii (fp) : "./hrtfs/elev-30/L-30e205a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e205a.dat" right +[ 12, 32 ] = ascii (fp) : "./hrtfs/elev-30/L-30e200a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e200a.dat" right +[ 12, 33 ] = ascii (fp) : "./hrtfs/elev-30/L-30e195a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e195a.dat" right +[ 12, 34 ] = ascii (fp) : "./hrtfs/elev-30/L-30e190a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e190a.dat" right +[ 12, 35 ] = ascii (fp) : "./hrtfs/elev-30/L-30e185a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e185a.dat" right +[ 12, 36 ] = ascii (fp) : "./hrtfs/elev-30/L-30e180a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e180a.dat" right +[ 12, 37 ] = ascii (fp) : "./hrtfs/elev-30/L-30e175a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e175a.dat" right +[ 12, 38 ] = ascii (fp) : "./hrtfs/elev-30/L-30e170a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e170a.dat" right +[ 12, 39 ] = ascii (fp) : "./hrtfs/elev-30/L-30e165a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e165a.dat" right +[ 12, 40 ] = ascii (fp) : "./hrtfs/elev-30/L-30e160a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e160a.dat" right +[ 12, 41 ] = ascii (fp) : "./hrtfs/elev-30/L-30e155a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e155a.dat" right +[ 12, 42 ] = ascii (fp) : "./hrtfs/elev-30/L-30e150a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e150a.dat" right +[ 12, 43 ] = ascii (fp) : "./hrtfs/elev-30/L-30e145a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e145a.dat" right +[ 12, 44 ] = ascii (fp) : "./hrtfs/elev-30/L-30e140a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e140a.dat" right +[ 12, 45 ] = ascii (fp) : "./hrtfs/elev-30/L-30e135a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e135a.dat" right +[ 12, 46 ] = ascii (fp) : "./hrtfs/elev-30/L-30e130a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e130a.dat" right +[ 12, 47 ] = ascii (fp) : "./hrtfs/elev-30/L-30e125a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e125a.dat" right +[ 12, 48 ] = ascii (fp) : "./hrtfs/elev-30/L-30e120a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e120a.dat" right +[ 12, 49 ] = ascii (fp) : "./hrtfs/elev-30/L-30e115a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e115a.dat" right +[ 12, 50 ] = ascii (fp) : "./hrtfs/elev-30/L-30e110a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e110a.dat" right +[ 12, 51 ] = ascii (fp) : "./hrtfs/elev-30/L-30e105a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e105a.dat" right +[ 12, 52 ] = ascii (fp) : "./hrtfs/elev-30/L-30e100a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e100a.dat" right +[ 12, 53 ] = ascii (fp) : "./hrtfs/elev-30/L-30e095a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e095a.dat" right +[ 12, 54 ] = ascii (fp) : "./hrtfs/elev-30/L-30e090a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e090a.dat" right +[ 12, 55 ] = ascii (fp) : "./hrtfs/elev-30/L-30e085a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e085a.dat" right +[ 12, 56 ] = ascii (fp) : "./hrtfs/elev-30/L-30e080a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e080a.dat" right +[ 12, 57 ] = ascii (fp) : "./hrtfs/elev-30/L-30e075a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e075a.dat" right +[ 12, 58 ] = ascii (fp) : "./hrtfs/elev-30/L-30e070a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e070a.dat" right +[ 12, 59 ] = ascii (fp) : "./hrtfs/elev-30/L-30e065a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e065a.dat" right +[ 12, 60 ] = ascii (fp) : "./hrtfs/elev-30/L-30e060a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e060a.dat" right +[ 12, 61 ] = ascii (fp) : "./hrtfs/elev-30/L-30e055a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e055a.dat" right +[ 12, 62 ] = ascii (fp) : "./hrtfs/elev-30/L-30e050a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e050a.dat" right +[ 12, 63 ] = ascii (fp) : "./hrtfs/elev-30/L-30e045a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e045a.dat" right +[ 12, 64 ] = ascii (fp) : "./hrtfs/elev-30/L-30e040a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e040a.dat" right +[ 12, 65 ] = ascii (fp) : "./hrtfs/elev-30/L-30e035a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e035a.dat" right +[ 12, 66 ] = ascii (fp) : "./hrtfs/elev-30/L-30e030a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e030a.dat" right +[ 12, 67 ] = ascii (fp) : "./hrtfs/elev-30/L-30e025a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e025a.dat" right +[ 12, 68 ] = ascii (fp) : "./hrtfs/elev-30/L-30e020a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e020a.dat" right +[ 12, 69 ] = ascii (fp) : "./hrtfs/elev-30/L-30e015a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e015a.dat" right +[ 12, 70 ] = ascii (fp) : "./hrtfs/elev-30/L-30e010a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e010a.dat" right +[ 12, 71 ] = ascii (fp) : "./hrtfs/elev-30/L-30e005a.dat" left + + ascii (fp) : "./hrtfs/elev-30/R-30e005a.dat" right -[ 13, 0 ] = ascii (fp) : "./hrtfs/elev-25/L-25e000a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e000a.dat right -[ 13, 1 ] = ascii (fp) : "./hrtfs/elev-25/L-25e355a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e355a.dat right -[ 13, 2 ] = ascii (fp) : "./hrtfs/elev-25/L-25e350a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e350a.dat right -[ 13, 3 ] = ascii (fp) : "./hrtfs/elev-25/L-25e345a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e345a.dat right -[ 13, 4 ] = ascii (fp) : "./hrtfs/elev-25/L-25e340a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e340a.dat right -[ 13, 5 ] = ascii (fp) : "./hrtfs/elev-25/L-25e335a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e335a.dat right -[ 13, 6 ] = ascii (fp) : "./hrtfs/elev-25/L-25e330a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e330a.dat right -[ 13, 7 ] = ascii (fp) : "./hrtfs/elev-25/L-25e325a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e325a.dat right -[ 13, 8 ] = ascii (fp) : "./hrtfs/elev-25/L-25e320a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e320a.dat right -[ 13, 9 ] = ascii (fp) : "./hrtfs/elev-25/L-25e315a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e315a.dat right -[ 13, 10 ] = ascii (fp) : "./hrtfs/elev-25/L-25e310a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e310a.dat right -[ 13, 11 ] = ascii (fp) : "./hrtfs/elev-25/L-25e305a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e305a.dat right -[ 13, 12 ] = ascii (fp) : "./hrtfs/elev-25/L-25e300a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e300a.dat right -[ 13, 13 ] = ascii (fp) : "./hrtfs/elev-25/L-25e295a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e295a.dat right -[ 13, 14 ] = ascii (fp) : "./hrtfs/elev-25/L-25e290a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e290a.dat right -[ 13, 15 ] = ascii (fp) : "./hrtfs/elev-25/L-25e285a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e285a.dat right -[ 13, 16 ] = ascii (fp) : "./hrtfs/elev-25/L-25e280a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e280a.dat right -[ 13, 17 ] = ascii (fp) : "./hrtfs/elev-25/L-25e275a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e275a.dat right -[ 13, 18 ] = ascii (fp) : "./hrtfs/elev-25/L-25e270a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e270a.dat right -[ 13, 19 ] = ascii (fp) : "./hrtfs/elev-25/L-25e265a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e265a.dat right -[ 13, 20 ] = ascii (fp) : "./hrtfs/elev-25/L-25e260a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e260a.dat right -[ 13, 21 ] = ascii (fp) : "./hrtfs/elev-25/L-25e255a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e255a.dat right -[ 13, 22 ] = ascii (fp) : "./hrtfs/elev-25/L-25e250a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e250a.dat right -[ 13, 23 ] = ascii (fp) : "./hrtfs/elev-25/L-25e245a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e245a.dat right -[ 13, 24 ] = ascii (fp) : "./hrtfs/elev-25/L-25e240a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e240a.dat right -[ 13, 25 ] = ascii (fp) : "./hrtfs/elev-25/L-25e235a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e235a.dat right -[ 13, 26 ] = ascii (fp) : "./hrtfs/elev-25/L-25e230a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e230a.dat right -[ 13, 27 ] = ascii (fp) : "./hrtfs/elev-25/L-25e225a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e225a.dat right -[ 13, 28 ] = ascii (fp) : "./hrtfs/elev-25/L-25e220a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e220a.dat right -[ 13, 29 ] = ascii (fp) : "./hrtfs/elev-25/L-25e215a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e215a.dat right -[ 13, 30 ] = ascii (fp) : "./hrtfs/elev-25/L-25e210a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e210a.dat right -[ 13, 31 ] = ascii (fp) : "./hrtfs/elev-25/L-25e205a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e205a.dat right -[ 13, 32 ] = ascii (fp) : "./hrtfs/elev-25/L-25e200a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e200a.dat right -[ 13, 33 ] = ascii (fp) : "./hrtfs/elev-25/L-25e195a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e195a.dat right -[ 13, 34 ] = ascii (fp) : "./hrtfs/elev-25/L-25e190a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e190a.dat right -[ 13, 35 ] = ascii (fp) : "./hrtfs/elev-25/L-25e185a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e185a.dat right -[ 13, 36 ] = ascii (fp) : "./hrtfs/elev-25/L-25e180a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e180a.dat right -[ 13, 37 ] = ascii (fp) : "./hrtfs/elev-25/L-25e175a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e175a.dat right -[ 13, 38 ] = ascii (fp) : "./hrtfs/elev-25/L-25e170a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e170a.dat right -[ 13, 39 ] = ascii (fp) : "./hrtfs/elev-25/L-25e165a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e165a.dat right -[ 13, 40 ] = ascii (fp) : "./hrtfs/elev-25/L-25e160a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e160a.dat right -[ 13, 41 ] = ascii (fp) : "./hrtfs/elev-25/L-25e155a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e155a.dat right -[ 13, 42 ] = ascii (fp) : "./hrtfs/elev-25/L-25e150a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e150a.dat right -[ 13, 43 ] = ascii (fp) : "./hrtfs/elev-25/L-25e145a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e145a.dat right -[ 13, 44 ] = ascii (fp) : "./hrtfs/elev-25/L-25e140a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e140a.dat right -[ 13, 45 ] = ascii (fp) : "./hrtfs/elev-25/L-25e135a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e135a.dat right -[ 13, 46 ] = ascii (fp) : "./hrtfs/elev-25/L-25e130a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e130a.dat right -[ 13, 47 ] = ascii (fp) : "./hrtfs/elev-25/L-25e125a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e125a.dat right -[ 13, 48 ] = ascii (fp) : "./hrtfs/elev-25/L-25e120a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e120a.dat right -[ 13, 49 ] = ascii (fp) : "./hrtfs/elev-25/L-25e115a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e115a.dat right -[ 13, 50 ] = ascii (fp) : "./hrtfs/elev-25/L-25e110a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e110a.dat right -[ 13, 51 ] = ascii (fp) : "./hrtfs/elev-25/L-25e105a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e105a.dat right -[ 13, 52 ] = ascii (fp) : "./hrtfs/elev-25/L-25e100a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e100a.dat right -[ 13, 53 ] = ascii (fp) : "./hrtfs/elev-25/L-25e095a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e095a.dat right -[ 13, 54 ] = ascii (fp) : "./hrtfs/elev-25/L-25e090a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e090a.dat right -[ 13, 55 ] = ascii (fp) : "./hrtfs/elev-25/L-25e085a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e085a.dat right -[ 13, 56 ] = ascii (fp) : "./hrtfs/elev-25/L-25e080a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e080a.dat right -[ 13, 57 ] = ascii (fp) : "./hrtfs/elev-25/L-25e075a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e075a.dat right -[ 13, 58 ] = ascii (fp) : "./hrtfs/elev-25/L-25e070a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e070a.dat right -[ 13, 59 ] = ascii (fp) : "./hrtfs/elev-25/L-25e065a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e065a.dat right -[ 13, 60 ] = ascii (fp) : "./hrtfs/elev-25/L-25e060a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e060a.dat right -[ 13, 61 ] = ascii (fp) : "./hrtfs/elev-25/L-25e055a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e055a.dat right -[ 13, 62 ] = ascii (fp) : "./hrtfs/elev-25/L-25e050a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e050a.dat right -[ 13, 63 ] = ascii (fp) : "./hrtfs/elev-25/L-25e045a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e045a.dat right -[ 13, 64 ] = ascii (fp) : "./hrtfs/elev-25/L-25e040a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e040a.dat right -[ 13, 65 ] = ascii (fp) : "./hrtfs/elev-25/L-25e035a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e035a.dat right -[ 13, 66 ] = ascii (fp) : "./hrtfs/elev-25/L-25e030a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e030a.dat right -[ 13, 67 ] = ascii (fp) : "./hrtfs/elev-25/L-25e025a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e025a.dat right -[ 13, 68 ] = ascii (fp) : "./hrtfs/elev-25/L-25e020a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e020a.dat right -[ 13, 69 ] = ascii (fp) : "./hrtfs/elev-25/L-25e015a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e015a.dat right -[ 13, 70 ] = ascii (fp) : "./hrtfs/elev-25/L-25e010a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e010a.dat right -[ 13, 71 ] = ascii (fp) : "./hrtfs/elev-25/L-25e005a.dat left - + ascii (fp) : "./hrtfs/elev-25/R-25e005a.dat right +[ 13, 0 ] = ascii (fp) : "./hrtfs/elev-25/L-25e000a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e000a.dat" right +[ 13, 1 ] = ascii (fp) : "./hrtfs/elev-25/L-25e355a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e355a.dat" right +[ 13, 2 ] = ascii (fp) : "./hrtfs/elev-25/L-25e350a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e350a.dat" right +[ 13, 3 ] = ascii (fp) : "./hrtfs/elev-25/L-25e345a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e345a.dat" right +[ 13, 4 ] = ascii (fp) : "./hrtfs/elev-25/L-25e340a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e340a.dat" right +[ 13, 5 ] = ascii (fp) : "./hrtfs/elev-25/L-25e335a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e335a.dat" right +[ 13, 6 ] = ascii (fp) : "./hrtfs/elev-25/L-25e330a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e330a.dat" right +[ 13, 7 ] = ascii (fp) : "./hrtfs/elev-25/L-25e325a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e325a.dat" right +[ 13, 8 ] = ascii (fp) : "./hrtfs/elev-25/L-25e320a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e320a.dat" right +[ 13, 9 ] = ascii (fp) : "./hrtfs/elev-25/L-25e315a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e315a.dat" right +[ 13, 10 ] = ascii (fp) : "./hrtfs/elev-25/L-25e310a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e310a.dat" right +[ 13, 11 ] = ascii (fp) : "./hrtfs/elev-25/L-25e305a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e305a.dat" right +[ 13, 12 ] = ascii (fp) : "./hrtfs/elev-25/L-25e300a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e300a.dat" right +[ 13, 13 ] = ascii (fp) : "./hrtfs/elev-25/L-25e295a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e295a.dat" right +[ 13, 14 ] = ascii (fp) : "./hrtfs/elev-25/L-25e290a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e290a.dat" right +[ 13, 15 ] = ascii (fp) : "./hrtfs/elev-25/L-25e285a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e285a.dat" right +[ 13, 16 ] = ascii (fp) : "./hrtfs/elev-25/L-25e280a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e280a.dat" right +[ 13, 17 ] = ascii (fp) : "./hrtfs/elev-25/L-25e275a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e275a.dat" right +[ 13, 18 ] = ascii (fp) : "./hrtfs/elev-25/L-25e270a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e270a.dat" right +[ 13, 19 ] = ascii (fp) : "./hrtfs/elev-25/L-25e265a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e265a.dat" right +[ 13, 20 ] = ascii (fp) : "./hrtfs/elev-25/L-25e260a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e260a.dat" right +[ 13, 21 ] = ascii (fp) : "./hrtfs/elev-25/L-25e255a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e255a.dat" right +[ 13, 22 ] = ascii (fp) : "./hrtfs/elev-25/L-25e250a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e250a.dat" right +[ 13, 23 ] = ascii (fp) : "./hrtfs/elev-25/L-25e245a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e245a.dat" right +[ 13, 24 ] = ascii (fp) : "./hrtfs/elev-25/L-25e240a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e240a.dat" right +[ 13, 25 ] = ascii (fp) : "./hrtfs/elev-25/L-25e235a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e235a.dat" right +[ 13, 26 ] = ascii (fp) : "./hrtfs/elev-25/L-25e230a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e230a.dat" right +[ 13, 27 ] = ascii (fp) : "./hrtfs/elev-25/L-25e225a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e225a.dat" right +[ 13, 28 ] = ascii (fp) : "./hrtfs/elev-25/L-25e220a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e220a.dat" right +[ 13, 29 ] = ascii (fp) : "./hrtfs/elev-25/L-25e215a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e215a.dat" right +[ 13, 30 ] = ascii (fp) : "./hrtfs/elev-25/L-25e210a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e210a.dat" right +[ 13, 31 ] = ascii (fp) : "./hrtfs/elev-25/L-25e205a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e205a.dat" right +[ 13, 32 ] = ascii (fp) : "./hrtfs/elev-25/L-25e200a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e200a.dat" right +[ 13, 33 ] = ascii (fp) : "./hrtfs/elev-25/L-25e195a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e195a.dat" right +[ 13, 34 ] = ascii (fp) : "./hrtfs/elev-25/L-25e190a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e190a.dat" right +[ 13, 35 ] = ascii (fp) : "./hrtfs/elev-25/L-25e185a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e185a.dat" right +[ 13, 36 ] = ascii (fp) : "./hrtfs/elev-25/L-25e180a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e180a.dat" right +[ 13, 37 ] = ascii (fp) : "./hrtfs/elev-25/L-25e175a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e175a.dat" right +[ 13, 38 ] = ascii (fp) : "./hrtfs/elev-25/L-25e170a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e170a.dat" right +[ 13, 39 ] = ascii (fp) : "./hrtfs/elev-25/L-25e165a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e165a.dat" right +[ 13, 40 ] = ascii (fp) : "./hrtfs/elev-25/L-25e160a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e160a.dat" right +[ 13, 41 ] = ascii (fp) : "./hrtfs/elev-25/L-25e155a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e155a.dat" right +[ 13, 42 ] = ascii (fp) : "./hrtfs/elev-25/L-25e150a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e150a.dat" right +[ 13, 43 ] = ascii (fp) : "./hrtfs/elev-25/L-25e145a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e145a.dat" right +[ 13, 44 ] = ascii (fp) : "./hrtfs/elev-25/L-25e140a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e140a.dat" right +[ 13, 45 ] = ascii (fp) : "./hrtfs/elev-25/L-25e135a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e135a.dat" right +[ 13, 46 ] = ascii (fp) : "./hrtfs/elev-25/L-25e130a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e130a.dat" right +[ 13, 47 ] = ascii (fp) : "./hrtfs/elev-25/L-25e125a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e125a.dat" right +[ 13, 48 ] = ascii (fp) : "./hrtfs/elev-25/L-25e120a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e120a.dat" right +[ 13, 49 ] = ascii (fp) : "./hrtfs/elev-25/L-25e115a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e115a.dat" right +[ 13, 50 ] = ascii (fp) : "./hrtfs/elev-25/L-25e110a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e110a.dat" right +[ 13, 51 ] = ascii (fp) : "./hrtfs/elev-25/L-25e105a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e105a.dat" right +[ 13, 52 ] = ascii (fp) : "./hrtfs/elev-25/L-25e100a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e100a.dat" right +[ 13, 53 ] = ascii (fp) : "./hrtfs/elev-25/L-25e095a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e095a.dat" right +[ 13, 54 ] = ascii (fp) : "./hrtfs/elev-25/L-25e090a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e090a.dat" right +[ 13, 55 ] = ascii (fp) : "./hrtfs/elev-25/L-25e085a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e085a.dat" right +[ 13, 56 ] = ascii (fp) : "./hrtfs/elev-25/L-25e080a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e080a.dat" right +[ 13, 57 ] = ascii (fp) : "./hrtfs/elev-25/L-25e075a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e075a.dat" right +[ 13, 58 ] = ascii (fp) : "./hrtfs/elev-25/L-25e070a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e070a.dat" right +[ 13, 59 ] = ascii (fp) : "./hrtfs/elev-25/L-25e065a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e065a.dat" right +[ 13, 60 ] = ascii (fp) : "./hrtfs/elev-25/L-25e060a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e060a.dat" right +[ 13, 61 ] = ascii (fp) : "./hrtfs/elev-25/L-25e055a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e055a.dat" right +[ 13, 62 ] = ascii (fp) : "./hrtfs/elev-25/L-25e050a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e050a.dat" right +[ 13, 63 ] = ascii (fp) : "./hrtfs/elev-25/L-25e045a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e045a.dat" right +[ 13, 64 ] = ascii (fp) : "./hrtfs/elev-25/L-25e040a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e040a.dat" right +[ 13, 65 ] = ascii (fp) : "./hrtfs/elev-25/L-25e035a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e035a.dat" right +[ 13, 66 ] = ascii (fp) : "./hrtfs/elev-25/L-25e030a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e030a.dat" right +[ 13, 67 ] = ascii (fp) : "./hrtfs/elev-25/L-25e025a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e025a.dat" right +[ 13, 68 ] = ascii (fp) : "./hrtfs/elev-25/L-25e020a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e020a.dat" right +[ 13, 69 ] = ascii (fp) : "./hrtfs/elev-25/L-25e015a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e015a.dat" right +[ 13, 70 ] = ascii (fp) : "./hrtfs/elev-25/L-25e010a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e010a.dat" right +[ 13, 71 ] = ascii (fp) : "./hrtfs/elev-25/L-25e005a.dat" left + + ascii (fp) : "./hrtfs/elev-25/R-25e005a.dat" right -[ 14, 0 ] = ascii (fp) : "./hrtfs/elev-20/L-20e000a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e000a.dat right -[ 14, 1 ] = ascii (fp) : "./hrtfs/elev-20/L-20e355a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e355a.dat right -[ 14, 2 ] = ascii (fp) : "./hrtfs/elev-20/L-20e350a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e350a.dat right -[ 14, 3 ] = ascii (fp) : "./hrtfs/elev-20/L-20e345a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e345a.dat right -[ 14, 4 ] = ascii (fp) : "./hrtfs/elev-20/L-20e340a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e340a.dat right -[ 14, 5 ] = ascii (fp) : "./hrtfs/elev-20/L-20e335a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e335a.dat right -[ 14, 6 ] = ascii (fp) : "./hrtfs/elev-20/L-20e330a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e330a.dat right -[ 14, 7 ] = ascii (fp) : "./hrtfs/elev-20/L-20e325a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e325a.dat right -[ 14, 8 ] = ascii (fp) : "./hrtfs/elev-20/L-20e320a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e320a.dat right -[ 14, 9 ] = ascii (fp) : "./hrtfs/elev-20/L-20e315a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e315a.dat right -[ 14, 10 ] = ascii (fp) : "./hrtfs/elev-20/L-20e310a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e310a.dat right -[ 14, 11 ] = ascii (fp) : "./hrtfs/elev-20/L-20e305a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e305a.dat right -[ 14, 12 ] = ascii (fp) : "./hrtfs/elev-20/L-20e300a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e300a.dat right -[ 14, 13 ] = ascii (fp) : "./hrtfs/elev-20/L-20e295a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e295a.dat right -[ 14, 14 ] = ascii (fp) : "./hrtfs/elev-20/L-20e290a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e290a.dat right -[ 14, 15 ] = ascii (fp) : "./hrtfs/elev-20/L-20e285a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e285a.dat right -[ 14, 16 ] = ascii (fp) : "./hrtfs/elev-20/L-20e280a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e280a.dat right -[ 14, 17 ] = ascii (fp) : "./hrtfs/elev-20/L-20e275a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e275a.dat right -[ 14, 18 ] = ascii (fp) : "./hrtfs/elev-20/L-20e270a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e270a.dat right -[ 14, 19 ] = ascii (fp) : "./hrtfs/elev-20/L-20e265a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e265a.dat right -[ 14, 20 ] = ascii (fp) : "./hrtfs/elev-20/L-20e260a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e260a.dat right -[ 14, 21 ] = ascii (fp) : "./hrtfs/elev-20/L-20e255a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e255a.dat right -[ 14, 22 ] = ascii (fp) : "./hrtfs/elev-20/L-20e250a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e250a.dat right -[ 14, 23 ] = ascii (fp) : "./hrtfs/elev-20/L-20e245a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e245a.dat right -[ 14, 24 ] = ascii (fp) : "./hrtfs/elev-20/L-20e240a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e240a.dat right -[ 14, 25 ] = ascii (fp) : "./hrtfs/elev-20/L-20e235a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e235a.dat right -[ 14, 26 ] = ascii (fp) : "./hrtfs/elev-20/L-20e230a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e230a.dat right -[ 14, 27 ] = ascii (fp) : "./hrtfs/elev-20/L-20e225a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e225a.dat right -[ 14, 28 ] = ascii (fp) : "./hrtfs/elev-20/L-20e220a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e220a.dat right -[ 14, 29 ] = ascii (fp) : "./hrtfs/elev-20/L-20e215a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e215a.dat right -[ 14, 30 ] = ascii (fp) : "./hrtfs/elev-20/L-20e210a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e210a.dat right -[ 14, 31 ] = ascii (fp) : "./hrtfs/elev-20/L-20e205a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e205a.dat right -[ 14, 32 ] = ascii (fp) : "./hrtfs/elev-20/L-20e200a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e200a.dat right -[ 14, 33 ] = ascii (fp) : "./hrtfs/elev-20/L-20e195a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e195a.dat right -[ 14, 34 ] = ascii (fp) : "./hrtfs/elev-20/L-20e190a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e190a.dat right -[ 14, 35 ] = ascii (fp) : "./hrtfs/elev-20/L-20e185a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e185a.dat right -[ 14, 36 ] = ascii (fp) : "./hrtfs/elev-20/L-20e180a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e180a.dat right -[ 14, 37 ] = ascii (fp) : "./hrtfs/elev-20/L-20e175a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e175a.dat right -[ 14, 38 ] = ascii (fp) : "./hrtfs/elev-20/L-20e170a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e170a.dat right -[ 14, 39 ] = ascii (fp) : "./hrtfs/elev-20/L-20e165a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e165a.dat right -[ 14, 40 ] = ascii (fp) : "./hrtfs/elev-20/L-20e160a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e160a.dat right -[ 14, 41 ] = ascii (fp) : "./hrtfs/elev-20/L-20e155a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e155a.dat right -[ 14, 42 ] = ascii (fp) : "./hrtfs/elev-20/L-20e150a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e150a.dat right -[ 14, 43 ] = ascii (fp) : "./hrtfs/elev-20/L-20e145a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e145a.dat right -[ 14, 44 ] = ascii (fp) : "./hrtfs/elev-20/L-20e140a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e140a.dat right -[ 14, 45 ] = ascii (fp) : "./hrtfs/elev-20/L-20e135a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e135a.dat right -[ 14, 46 ] = ascii (fp) : "./hrtfs/elev-20/L-20e130a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e130a.dat right -[ 14, 47 ] = ascii (fp) : "./hrtfs/elev-20/L-20e125a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e125a.dat right -[ 14, 48 ] = ascii (fp) : "./hrtfs/elev-20/L-20e120a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e120a.dat right -[ 14, 49 ] = ascii (fp) : "./hrtfs/elev-20/L-20e115a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e115a.dat right -[ 14, 50 ] = ascii (fp) : "./hrtfs/elev-20/L-20e110a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e110a.dat right -[ 14, 51 ] = ascii (fp) : "./hrtfs/elev-20/L-20e105a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e105a.dat right -[ 14, 52 ] = ascii (fp) : "./hrtfs/elev-20/L-20e100a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e100a.dat right -[ 14, 53 ] = ascii (fp) : "./hrtfs/elev-20/L-20e095a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e095a.dat right -[ 14, 54 ] = ascii (fp) : "./hrtfs/elev-20/L-20e090a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e090a.dat right -[ 14, 55 ] = ascii (fp) : "./hrtfs/elev-20/L-20e085a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e085a.dat right -[ 14, 56 ] = ascii (fp) : "./hrtfs/elev-20/L-20e080a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e080a.dat right -[ 14, 57 ] = ascii (fp) : "./hrtfs/elev-20/L-20e075a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e075a.dat right -[ 14, 58 ] = ascii (fp) : "./hrtfs/elev-20/L-20e070a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e070a.dat right -[ 14, 59 ] = ascii (fp) : "./hrtfs/elev-20/L-20e065a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e065a.dat right -[ 14, 60 ] = ascii (fp) : "./hrtfs/elev-20/L-20e060a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e060a.dat right -[ 14, 61 ] = ascii (fp) : "./hrtfs/elev-20/L-20e055a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e055a.dat right -[ 14, 62 ] = ascii (fp) : "./hrtfs/elev-20/L-20e050a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e050a.dat right -[ 14, 63 ] = ascii (fp) : "./hrtfs/elev-20/L-20e045a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e045a.dat right -[ 14, 64 ] = ascii (fp) : "./hrtfs/elev-20/L-20e040a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e040a.dat right -[ 14, 65 ] = ascii (fp) : "./hrtfs/elev-20/L-20e035a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e035a.dat right -[ 14, 66 ] = ascii (fp) : "./hrtfs/elev-20/L-20e030a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e030a.dat right -[ 14, 67 ] = ascii (fp) : "./hrtfs/elev-20/L-20e025a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e025a.dat right -[ 14, 68 ] = ascii (fp) : "./hrtfs/elev-20/L-20e020a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e020a.dat right -[ 14, 69 ] = ascii (fp) : "./hrtfs/elev-20/L-20e015a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e015a.dat right -[ 14, 70 ] = ascii (fp) : "./hrtfs/elev-20/L-20e010a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e010a.dat right -[ 14, 71 ] = ascii (fp) : "./hrtfs/elev-20/L-20e005a.dat left - + ascii (fp) : "./hrtfs/elev-20/R-20e005a.dat right +[ 14, 0 ] = ascii (fp) : "./hrtfs/elev-20/L-20e000a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e000a.dat" right +[ 14, 1 ] = ascii (fp) : "./hrtfs/elev-20/L-20e355a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e355a.dat" right +[ 14, 2 ] = ascii (fp) : "./hrtfs/elev-20/L-20e350a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e350a.dat" right +[ 14, 3 ] = ascii (fp) : "./hrtfs/elev-20/L-20e345a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e345a.dat" right +[ 14, 4 ] = ascii (fp) : "./hrtfs/elev-20/L-20e340a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e340a.dat" right +[ 14, 5 ] = ascii (fp) : "./hrtfs/elev-20/L-20e335a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e335a.dat" right +[ 14, 6 ] = ascii (fp) : "./hrtfs/elev-20/L-20e330a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e330a.dat" right +[ 14, 7 ] = ascii (fp) : "./hrtfs/elev-20/L-20e325a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e325a.dat" right +[ 14, 8 ] = ascii (fp) : "./hrtfs/elev-20/L-20e320a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e320a.dat" right +[ 14, 9 ] = ascii (fp) : "./hrtfs/elev-20/L-20e315a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e315a.dat" right +[ 14, 10 ] = ascii (fp) : "./hrtfs/elev-20/L-20e310a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e310a.dat" right +[ 14, 11 ] = ascii (fp) : "./hrtfs/elev-20/L-20e305a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e305a.dat" right +[ 14, 12 ] = ascii (fp) : "./hrtfs/elev-20/L-20e300a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e300a.dat" right +[ 14, 13 ] = ascii (fp) : "./hrtfs/elev-20/L-20e295a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e295a.dat" right +[ 14, 14 ] = ascii (fp) : "./hrtfs/elev-20/L-20e290a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e290a.dat" right +[ 14, 15 ] = ascii (fp) : "./hrtfs/elev-20/L-20e285a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e285a.dat" right +[ 14, 16 ] = ascii (fp) : "./hrtfs/elev-20/L-20e280a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e280a.dat" right +[ 14, 17 ] = ascii (fp) : "./hrtfs/elev-20/L-20e275a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e275a.dat" right +[ 14, 18 ] = ascii (fp) : "./hrtfs/elev-20/L-20e270a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e270a.dat" right +[ 14, 19 ] = ascii (fp) : "./hrtfs/elev-20/L-20e265a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e265a.dat" right +[ 14, 20 ] = ascii (fp) : "./hrtfs/elev-20/L-20e260a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e260a.dat" right +[ 14, 21 ] = ascii (fp) : "./hrtfs/elev-20/L-20e255a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e255a.dat" right +[ 14, 22 ] = ascii (fp) : "./hrtfs/elev-20/L-20e250a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e250a.dat" right +[ 14, 23 ] = ascii (fp) : "./hrtfs/elev-20/L-20e245a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e245a.dat" right +[ 14, 24 ] = ascii (fp) : "./hrtfs/elev-20/L-20e240a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e240a.dat" right +[ 14, 25 ] = ascii (fp) : "./hrtfs/elev-20/L-20e235a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e235a.dat" right +[ 14, 26 ] = ascii (fp) : "./hrtfs/elev-20/L-20e230a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e230a.dat" right +[ 14, 27 ] = ascii (fp) : "./hrtfs/elev-20/L-20e225a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e225a.dat" right +[ 14, 28 ] = ascii (fp) : "./hrtfs/elev-20/L-20e220a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e220a.dat" right +[ 14, 29 ] = ascii (fp) : "./hrtfs/elev-20/L-20e215a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e215a.dat" right +[ 14, 30 ] = ascii (fp) : "./hrtfs/elev-20/L-20e210a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e210a.dat" right +[ 14, 31 ] = ascii (fp) : "./hrtfs/elev-20/L-20e205a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e205a.dat" right +[ 14, 32 ] = ascii (fp) : "./hrtfs/elev-20/L-20e200a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e200a.dat" right +[ 14, 33 ] = ascii (fp) : "./hrtfs/elev-20/L-20e195a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e195a.dat" right +[ 14, 34 ] = ascii (fp) : "./hrtfs/elev-20/L-20e190a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e190a.dat" right +[ 14, 35 ] = ascii (fp) : "./hrtfs/elev-20/L-20e185a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e185a.dat" right +[ 14, 36 ] = ascii (fp) : "./hrtfs/elev-20/L-20e180a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e180a.dat" right +[ 14, 37 ] = ascii (fp) : "./hrtfs/elev-20/L-20e175a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e175a.dat" right +[ 14, 38 ] = ascii (fp) : "./hrtfs/elev-20/L-20e170a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e170a.dat" right +[ 14, 39 ] = ascii (fp) : "./hrtfs/elev-20/L-20e165a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e165a.dat" right +[ 14, 40 ] = ascii (fp) : "./hrtfs/elev-20/L-20e160a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e160a.dat" right +[ 14, 41 ] = ascii (fp) : "./hrtfs/elev-20/L-20e155a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e155a.dat" right +[ 14, 42 ] = ascii (fp) : "./hrtfs/elev-20/L-20e150a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e150a.dat" right +[ 14, 43 ] = ascii (fp) : "./hrtfs/elev-20/L-20e145a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e145a.dat" right +[ 14, 44 ] = ascii (fp) : "./hrtfs/elev-20/L-20e140a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e140a.dat" right +[ 14, 45 ] = ascii (fp) : "./hrtfs/elev-20/L-20e135a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e135a.dat" right +[ 14, 46 ] = ascii (fp) : "./hrtfs/elev-20/L-20e130a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e130a.dat" right +[ 14, 47 ] = ascii (fp) : "./hrtfs/elev-20/L-20e125a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e125a.dat" right +[ 14, 48 ] = ascii (fp) : "./hrtfs/elev-20/L-20e120a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e120a.dat" right +[ 14, 49 ] = ascii (fp) : "./hrtfs/elev-20/L-20e115a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e115a.dat" right +[ 14, 50 ] = ascii (fp) : "./hrtfs/elev-20/L-20e110a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e110a.dat" right +[ 14, 51 ] = ascii (fp) : "./hrtfs/elev-20/L-20e105a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e105a.dat" right +[ 14, 52 ] = ascii (fp) : "./hrtfs/elev-20/L-20e100a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e100a.dat" right +[ 14, 53 ] = ascii (fp) : "./hrtfs/elev-20/L-20e095a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e095a.dat" right +[ 14, 54 ] = ascii (fp) : "./hrtfs/elev-20/L-20e090a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e090a.dat" right +[ 14, 55 ] = ascii (fp) : "./hrtfs/elev-20/L-20e085a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e085a.dat" right +[ 14, 56 ] = ascii (fp) : "./hrtfs/elev-20/L-20e080a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e080a.dat" right +[ 14, 57 ] = ascii (fp) : "./hrtfs/elev-20/L-20e075a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e075a.dat" right +[ 14, 58 ] = ascii (fp) : "./hrtfs/elev-20/L-20e070a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e070a.dat" right +[ 14, 59 ] = ascii (fp) : "./hrtfs/elev-20/L-20e065a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e065a.dat" right +[ 14, 60 ] = ascii (fp) : "./hrtfs/elev-20/L-20e060a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e060a.dat" right +[ 14, 61 ] = ascii (fp) : "./hrtfs/elev-20/L-20e055a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e055a.dat" right +[ 14, 62 ] = ascii (fp) : "./hrtfs/elev-20/L-20e050a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e050a.dat" right +[ 14, 63 ] = ascii (fp) : "./hrtfs/elev-20/L-20e045a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e045a.dat" right +[ 14, 64 ] = ascii (fp) : "./hrtfs/elev-20/L-20e040a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e040a.dat" right +[ 14, 65 ] = ascii (fp) : "./hrtfs/elev-20/L-20e035a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e035a.dat" right +[ 14, 66 ] = ascii (fp) : "./hrtfs/elev-20/L-20e030a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e030a.dat" right +[ 14, 67 ] = ascii (fp) : "./hrtfs/elev-20/L-20e025a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e025a.dat" right +[ 14, 68 ] = ascii (fp) : "./hrtfs/elev-20/L-20e020a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e020a.dat" right +[ 14, 69 ] = ascii (fp) : "./hrtfs/elev-20/L-20e015a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e015a.dat" right +[ 14, 70 ] = ascii (fp) : "./hrtfs/elev-20/L-20e010a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e010a.dat" right +[ 14, 71 ] = ascii (fp) : "./hrtfs/elev-20/L-20e005a.dat" left + + ascii (fp) : "./hrtfs/elev-20/R-20e005a.dat" right -[ 15, 0 ] = ascii (fp) : "./hrtfs/elev-15/L-15e000a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e000a.dat right -[ 15, 1 ] = ascii (fp) : "./hrtfs/elev-15/L-15e355a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e355a.dat right -[ 15, 2 ] = ascii (fp) : "./hrtfs/elev-15/L-15e350a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e350a.dat right -[ 15, 3 ] = ascii (fp) : "./hrtfs/elev-15/L-15e345a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e345a.dat right -[ 15, 4 ] = ascii (fp) : "./hrtfs/elev-15/L-15e340a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e340a.dat right -[ 15, 5 ] = ascii (fp) : "./hrtfs/elev-15/L-15e335a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e335a.dat right -[ 15, 6 ] = ascii (fp) : "./hrtfs/elev-15/L-15e330a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e330a.dat right -[ 15, 7 ] = ascii (fp) : "./hrtfs/elev-15/L-15e325a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e325a.dat right -[ 15, 8 ] = ascii (fp) : "./hrtfs/elev-15/L-15e320a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e320a.dat right -[ 15, 9 ] = ascii (fp) : "./hrtfs/elev-15/L-15e315a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e315a.dat right -[ 15, 10 ] = ascii (fp) : "./hrtfs/elev-15/L-15e310a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e310a.dat right -[ 15, 11 ] = ascii (fp) : "./hrtfs/elev-15/L-15e305a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e305a.dat right -[ 15, 12 ] = ascii (fp) : "./hrtfs/elev-15/L-15e300a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e300a.dat right -[ 15, 13 ] = ascii (fp) : "./hrtfs/elev-15/L-15e295a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e295a.dat right -[ 15, 14 ] = ascii (fp) : "./hrtfs/elev-15/L-15e290a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e290a.dat right -[ 15, 15 ] = ascii (fp) : "./hrtfs/elev-15/L-15e285a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e285a.dat right -[ 15, 16 ] = ascii (fp) : "./hrtfs/elev-15/L-15e280a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e280a.dat right -[ 15, 17 ] = ascii (fp) : "./hrtfs/elev-15/L-15e275a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e275a.dat right -[ 15, 18 ] = ascii (fp) : "./hrtfs/elev-15/L-15e270a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e270a.dat right -[ 15, 19 ] = ascii (fp) : "./hrtfs/elev-15/L-15e265a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e265a.dat right -[ 15, 20 ] = ascii (fp) : "./hrtfs/elev-15/L-15e260a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e260a.dat right -[ 15, 21 ] = ascii (fp) : "./hrtfs/elev-15/L-15e255a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e255a.dat right -[ 15, 22 ] = ascii (fp) : "./hrtfs/elev-15/L-15e250a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e250a.dat right -[ 15, 23 ] = ascii (fp) : "./hrtfs/elev-15/L-15e245a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e245a.dat right -[ 15, 24 ] = ascii (fp) : "./hrtfs/elev-15/L-15e240a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e240a.dat right -[ 15, 25 ] = ascii (fp) : "./hrtfs/elev-15/L-15e235a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e235a.dat right -[ 15, 26 ] = ascii (fp) : "./hrtfs/elev-15/L-15e230a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e230a.dat right -[ 15, 27 ] = ascii (fp) : "./hrtfs/elev-15/L-15e225a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e225a.dat right -[ 15, 28 ] = ascii (fp) : "./hrtfs/elev-15/L-15e220a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e220a.dat right -[ 15, 29 ] = ascii (fp) : "./hrtfs/elev-15/L-15e215a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e215a.dat right -[ 15, 30 ] = ascii (fp) : "./hrtfs/elev-15/L-15e210a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e210a.dat right -[ 15, 31 ] = ascii (fp) : "./hrtfs/elev-15/L-15e205a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e205a.dat right -[ 15, 32 ] = ascii (fp) : "./hrtfs/elev-15/L-15e200a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e200a.dat right -[ 15, 33 ] = ascii (fp) : "./hrtfs/elev-15/L-15e195a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e195a.dat right -[ 15, 34 ] = ascii (fp) : "./hrtfs/elev-15/L-15e190a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e190a.dat right -[ 15, 35 ] = ascii (fp) : "./hrtfs/elev-15/L-15e185a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e185a.dat right -[ 15, 36 ] = ascii (fp) : "./hrtfs/elev-15/L-15e180a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e180a.dat right -[ 15, 37 ] = ascii (fp) : "./hrtfs/elev-15/L-15e175a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e175a.dat right -[ 15, 38 ] = ascii (fp) : "./hrtfs/elev-15/L-15e170a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e170a.dat right -[ 15, 39 ] = ascii (fp) : "./hrtfs/elev-15/L-15e165a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e165a.dat right -[ 15, 40 ] = ascii (fp) : "./hrtfs/elev-15/L-15e160a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e160a.dat right -[ 15, 41 ] = ascii (fp) : "./hrtfs/elev-15/L-15e155a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e155a.dat right -[ 15, 42 ] = ascii (fp) : "./hrtfs/elev-15/L-15e150a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e150a.dat right -[ 15, 43 ] = ascii (fp) : "./hrtfs/elev-15/L-15e145a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e145a.dat right -[ 15, 44 ] = ascii (fp) : "./hrtfs/elev-15/L-15e140a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e140a.dat right -[ 15, 45 ] = ascii (fp) : "./hrtfs/elev-15/L-15e135a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e135a.dat right -[ 15, 46 ] = ascii (fp) : "./hrtfs/elev-15/L-15e130a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e130a.dat right -[ 15, 47 ] = ascii (fp) : "./hrtfs/elev-15/L-15e125a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e125a.dat right -[ 15, 48 ] = ascii (fp) : "./hrtfs/elev-15/L-15e120a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e120a.dat right -[ 15, 49 ] = ascii (fp) : "./hrtfs/elev-15/L-15e115a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e115a.dat right -[ 15, 50 ] = ascii (fp) : "./hrtfs/elev-15/L-15e110a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e110a.dat right -[ 15, 51 ] = ascii (fp) : "./hrtfs/elev-15/L-15e105a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e105a.dat right -[ 15, 52 ] = ascii (fp) : "./hrtfs/elev-15/L-15e100a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e100a.dat right -[ 15, 53 ] = ascii (fp) : "./hrtfs/elev-15/L-15e095a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e095a.dat right -[ 15, 54 ] = ascii (fp) : "./hrtfs/elev-15/L-15e090a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e090a.dat right -[ 15, 55 ] = ascii (fp) : "./hrtfs/elev-15/L-15e085a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e085a.dat right -[ 15, 56 ] = ascii (fp) : "./hrtfs/elev-15/L-15e080a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e080a.dat right -[ 15, 57 ] = ascii (fp) : "./hrtfs/elev-15/L-15e075a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e075a.dat right -[ 15, 58 ] = ascii (fp) : "./hrtfs/elev-15/L-15e070a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e070a.dat right -[ 15, 59 ] = ascii (fp) : "./hrtfs/elev-15/L-15e065a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e065a.dat right -[ 15, 60 ] = ascii (fp) : "./hrtfs/elev-15/L-15e060a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e060a.dat right -[ 15, 61 ] = ascii (fp) : "./hrtfs/elev-15/L-15e055a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e055a.dat right -[ 15, 62 ] = ascii (fp) : "./hrtfs/elev-15/L-15e050a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e050a.dat right -[ 15, 63 ] = ascii (fp) : "./hrtfs/elev-15/L-15e045a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e045a.dat right -[ 15, 64 ] = ascii (fp) : "./hrtfs/elev-15/L-15e040a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e040a.dat right -[ 15, 65 ] = ascii (fp) : "./hrtfs/elev-15/L-15e035a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e035a.dat right -[ 15, 66 ] = ascii (fp) : "./hrtfs/elev-15/L-15e030a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e030a.dat right -[ 15, 67 ] = ascii (fp) : "./hrtfs/elev-15/L-15e025a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e025a.dat right -[ 15, 68 ] = ascii (fp) : "./hrtfs/elev-15/L-15e020a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e020a.dat right -[ 15, 69 ] = ascii (fp) : "./hrtfs/elev-15/L-15e015a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e015a.dat right -[ 15, 70 ] = ascii (fp) : "./hrtfs/elev-15/L-15e010a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e010a.dat right -[ 15, 71 ] = ascii (fp) : "./hrtfs/elev-15/L-15e005a.dat left - + ascii (fp) : "./hrtfs/elev-15/R-15e005a.dat right +[ 15, 0 ] = ascii (fp) : "./hrtfs/elev-15/L-15e000a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e000a.dat" right +[ 15, 1 ] = ascii (fp) : "./hrtfs/elev-15/L-15e355a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e355a.dat" right +[ 15, 2 ] = ascii (fp) : "./hrtfs/elev-15/L-15e350a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e350a.dat" right +[ 15, 3 ] = ascii (fp) : "./hrtfs/elev-15/L-15e345a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e345a.dat" right +[ 15, 4 ] = ascii (fp) : "./hrtfs/elev-15/L-15e340a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e340a.dat" right +[ 15, 5 ] = ascii (fp) : "./hrtfs/elev-15/L-15e335a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e335a.dat" right +[ 15, 6 ] = ascii (fp) : "./hrtfs/elev-15/L-15e330a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e330a.dat" right +[ 15, 7 ] = ascii (fp) : "./hrtfs/elev-15/L-15e325a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e325a.dat" right +[ 15, 8 ] = ascii (fp) : "./hrtfs/elev-15/L-15e320a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e320a.dat" right +[ 15, 9 ] = ascii (fp) : "./hrtfs/elev-15/L-15e315a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e315a.dat" right +[ 15, 10 ] = ascii (fp) : "./hrtfs/elev-15/L-15e310a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e310a.dat" right +[ 15, 11 ] = ascii (fp) : "./hrtfs/elev-15/L-15e305a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e305a.dat" right +[ 15, 12 ] = ascii (fp) : "./hrtfs/elev-15/L-15e300a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e300a.dat" right +[ 15, 13 ] = ascii (fp) : "./hrtfs/elev-15/L-15e295a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e295a.dat" right +[ 15, 14 ] = ascii (fp) : "./hrtfs/elev-15/L-15e290a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e290a.dat" right +[ 15, 15 ] = ascii (fp) : "./hrtfs/elev-15/L-15e285a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e285a.dat" right +[ 15, 16 ] = ascii (fp) : "./hrtfs/elev-15/L-15e280a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e280a.dat" right +[ 15, 17 ] = ascii (fp) : "./hrtfs/elev-15/L-15e275a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e275a.dat" right +[ 15, 18 ] = ascii (fp) : "./hrtfs/elev-15/L-15e270a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e270a.dat" right +[ 15, 19 ] = ascii (fp) : "./hrtfs/elev-15/L-15e265a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e265a.dat" right +[ 15, 20 ] = ascii (fp) : "./hrtfs/elev-15/L-15e260a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e260a.dat" right +[ 15, 21 ] = ascii (fp) : "./hrtfs/elev-15/L-15e255a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e255a.dat" right +[ 15, 22 ] = ascii (fp) : "./hrtfs/elev-15/L-15e250a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e250a.dat" right +[ 15, 23 ] = ascii (fp) : "./hrtfs/elev-15/L-15e245a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e245a.dat" right +[ 15, 24 ] = ascii (fp) : "./hrtfs/elev-15/L-15e240a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e240a.dat" right +[ 15, 25 ] = ascii (fp) : "./hrtfs/elev-15/L-15e235a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e235a.dat" right +[ 15, 26 ] = ascii (fp) : "./hrtfs/elev-15/L-15e230a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e230a.dat" right +[ 15, 27 ] = ascii (fp) : "./hrtfs/elev-15/L-15e225a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e225a.dat" right +[ 15, 28 ] = ascii (fp) : "./hrtfs/elev-15/L-15e220a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e220a.dat" right +[ 15, 29 ] = ascii (fp) : "./hrtfs/elev-15/L-15e215a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e215a.dat" right +[ 15, 30 ] = ascii (fp) : "./hrtfs/elev-15/L-15e210a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e210a.dat" right +[ 15, 31 ] = ascii (fp) : "./hrtfs/elev-15/L-15e205a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e205a.dat" right +[ 15, 32 ] = ascii (fp) : "./hrtfs/elev-15/L-15e200a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e200a.dat" right +[ 15, 33 ] = ascii (fp) : "./hrtfs/elev-15/L-15e195a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e195a.dat" right +[ 15, 34 ] = ascii (fp) : "./hrtfs/elev-15/L-15e190a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e190a.dat" right +[ 15, 35 ] = ascii (fp) : "./hrtfs/elev-15/L-15e185a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e185a.dat" right +[ 15, 36 ] = ascii (fp) : "./hrtfs/elev-15/L-15e180a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e180a.dat" right +[ 15, 37 ] = ascii (fp) : "./hrtfs/elev-15/L-15e175a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e175a.dat" right +[ 15, 38 ] = ascii (fp) : "./hrtfs/elev-15/L-15e170a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e170a.dat" right +[ 15, 39 ] = ascii (fp) : "./hrtfs/elev-15/L-15e165a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e165a.dat" right +[ 15, 40 ] = ascii (fp) : "./hrtfs/elev-15/L-15e160a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e160a.dat" right +[ 15, 41 ] = ascii (fp) : "./hrtfs/elev-15/L-15e155a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e155a.dat" right +[ 15, 42 ] = ascii (fp) : "./hrtfs/elev-15/L-15e150a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e150a.dat" right +[ 15, 43 ] = ascii (fp) : "./hrtfs/elev-15/L-15e145a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e145a.dat" right +[ 15, 44 ] = ascii (fp) : "./hrtfs/elev-15/L-15e140a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e140a.dat" right +[ 15, 45 ] = ascii (fp) : "./hrtfs/elev-15/L-15e135a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e135a.dat" right +[ 15, 46 ] = ascii (fp) : "./hrtfs/elev-15/L-15e130a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e130a.dat" right +[ 15, 47 ] = ascii (fp) : "./hrtfs/elev-15/L-15e125a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e125a.dat" right +[ 15, 48 ] = ascii (fp) : "./hrtfs/elev-15/L-15e120a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e120a.dat" right +[ 15, 49 ] = ascii (fp) : "./hrtfs/elev-15/L-15e115a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e115a.dat" right +[ 15, 50 ] = ascii (fp) : "./hrtfs/elev-15/L-15e110a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e110a.dat" right +[ 15, 51 ] = ascii (fp) : "./hrtfs/elev-15/L-15e105a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e105a.dat" right +[ 15, 52 ] = ascii (fp) : "./hrtfs/elev-15/L-15e100a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e100a.dat" right +[ 15, 53 ] = ascii (fp) : "./hrtfs/elev-15/L-15e095a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e095a.dat" right +[ 15, 54 ] = ascii (fp) : "./hrtfs/elev-15/L-15e090a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e090a.dat" right +[ 15, 55 ] = ascii (fp) : "./hrtfs/elev-15/L-15e085a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e085a.dat" right +[ 15, 56 ] = ascii (fp) : "./hrtfs/elev-15/L-15e080a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e080a.dat" right +[ 15, 57 ] = ascii (fp) : "./hrtfs/elev-15/L-15e075a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e075a.dat" right +[ 15, 58 ] = ascii (fp) : "./hrtfs/elev-15/L-15e070a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e070a.dat" right +[ 15, 59 ] = ascii (fp) : "./hrtfs/elev-15/L-15e065a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e065a.dat" right +[ 15, 60 ] = ascii (fp) : "./hrtfs/elev-15/L-15e060a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e060a.dat" right +[ 15, 61 ] = ascii (fp) : "./hrtfs/elev-15/L-15e055a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e055a.dat" right +[ 15, 62 ] = ascii (fp) : "./hrtfs/elev-15/L-15e050a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e050a.dat" right +[ 15, 63 ] = ascii (fp) : "./hrtfs/elev-15/L-15e045a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e045a.dat" right +[ 15, 64 ] = ascii (fp) : "./hrtfs/elev-15/L-15e040a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e040a.dat" right +[ 15, 65 ] = ascii (fp) : "./hrtfs/elev-15/L-15e035a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e035a.dat" right +[ 15, 66 ] = ascii (fp) : "./hrtfs/elev-15/L-15e030a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e030a.dat" right +[ 15, 67 ] = ascii (fp) : "./hrtfs/elev-15/L-15e025a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e025a.dat" right +[ 15, 68 ] = ascii (fp) : "./hrtfs/elev-15/L-15e020a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e020a.dat" right +[ 15, 69 ] = ascii (fp) : "./hrtfs/elev-15/L-15e015a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e015a.dat" right +[ 15, 70 ] = ascii (fp) : "./hrtfs/elev-15/L-15e010a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e010a.dat" right +[ 15, 71 ] = ascii (fp) : "./hrtfs/elev-15/L-15e005a.dat" left + + ascii (fp) : "./hrtfs/elev-15/R-15e005a.dat" right -[ 16, 0 ] = ascii (fp) : "./hrtfs/elev-10/L-10e000a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e000a.dat right -[ 16, 1 ] = ascii (fp) : "./hrtfs/elev-10/L-10e355a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e355a.dat right -[ 16, 2 ] = ascii (fp) : "./hrtfs/elev-10/L-10e350a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e350a.dat right -[ 16, 3 ] = ascii (fp) : "./hrtfs/elev-10/L-10e345a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e345a.dat right -[ 16, 4 ] = ascii (fp) : "./hrtfs/elev-10/L-10e340a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e340a.dat right -[ 16, 5 ] = ascii (fp) : "./hrtfs/elev-10/L-10e335a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e335a.dat right -[ 16, 6 ] = ascii (fp) : "./hrtfs/elev-10/L-10e330a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e330a.dat right -[ 16, 7 ] = ascii (fp) : "./hrtfs/elev-10/L-10e325a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e325a.dat right -[ 16, 8 ] = ascii (fp) : "./hrtfs/elev-10/L-10e320a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e320a.dat right -[ 16, 9 ] = ascii (fp) : "./hrtfs/elev-10/L-10e315a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e315a.dat right -[ 16, 10 ] = ascii (fp) : "./hrtfs/elev-10/L-10e310a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e310a.dat right -[ 16, 11 ] = ascii (fp) : "./hrtfs/elev-10/L-10e305a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e305a.dat right -[ 16, 12 ] = ascii (fp) : "./hrtfs/elev-10/L-10e300a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e300a.dat right -[ 16, 13 ] = ascii (fp) : "./hrtfs/elev-10/L-10e295a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e295a.dat right -[ 16, 14 ] = ascii (fp) : "./hrtfs/elev-10/L-10e290a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e290a.dat right -[ 16, 15 ] = ascii (fp) : "./hrtfs/elev-10/L-10e285a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e285a.dat right -[ 16, 16 ] = ascii (fp) : "./hrtfs/elev-10/L-10e280a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e280a.dat right -[ 16, 17 ] = ascii (fp) : "./hrtfs/elev-10/L-10e275a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e275a.dat right -[ 16, 18 ] = ascii (fp) : "./hrtfs/elev-10/L-10e270a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e270a.dat right -[ 16, 19 ] = ascii (fp) : "./hrtfs/elev-10/L-10e265a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e265a.dat right -[ 16, 20 ] = ascii (fp) : "./hrtfs/elev-10/L-10e260a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e260a.dat right -[ 16, 21 ] = ascii (fp) : "./hrtfs/elev-10/L-10e255a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e255a.dat right -[ 16, 22 ] = ascii (fp) : "./hrtfs/elev-10/L-10e250a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e250a.dat right -[ 16, 23 ] = ascii (fp) : "./hrtfs/elev-10/L-10e245a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e245a.dat right -[ 16, 24 ] = ascii (fp) : "./hrtfs/elev-10/L-10e240a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e240a.dat right -[ 16, 25 ] = ascii (fp) : "./hrtfs/elev-10/L-10e235a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e235a.dat right -[ 16, 26 ] = ascii (fp) : "./hrtfs/elev-10/L-10e230a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e230a.dat right -[ 16, 27 ] = ascii (fp) : "./hrtfs/elev-10/L-10e225a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e225a.dat right -[ 16, 28 ] = ascii (fp) : "./hrtfs/elev-10/L-10e220a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e220a.dat right -[ 16, 29 ] = ascii (fp) : "./hrtfs/elev-10/L-10e215a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e215a.dat right -[ 16, 30 ] = ascii (fp) : "./hrtfs/elev-10/L-10e210a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e210a.dat right -[ 16, 31 ] = ascii (fp) : "./hrtfs/elev-10/L-10e205a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e205a.dat right -[ 16, 32 ] = ascii (fp) : "./hrtfs/elev-10/L-10e200a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e200a.dat right -[ 16, 33 ] = ascii (fp) : "./hrtfs/elev-10/L-10e195a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e195a.dat right -[ 16, 34 ] = ascii (fp) : "./hrtfs/elev-10/L-10e190a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e190a.dat right -[ 16, 35 ] = ascii (fp) : "./hrtfs/elev-10/L-10e185a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e185a.dat right -[ 16, 36 ] = ascii (fp) : "./hrtfs/elev-10/L-10e180a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e180a.dat right -[ 16, 37 ] = ascii (fp) : "./hrtfs/elev-10/L-10e175a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e175a.dat right -[ 16, 38 ] = ascii (fp) : "./hrtfs/elev-10/L-10e170a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e170a.dat right -[ 16, 39 ] = ascii (fp) : "./hrtfs/elev-10/L-10e165a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e165a.dat right -[ 16, 40 ] = ascii (fp) : "./hrtfs/elev-10/L-10e160a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e160a.dat right -[ 16, 41 ] = ascii (fp) : "./hrtfs/elev-10/L-10e155a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e155a.dat right -[ 16, 42 ] = ascii (fp) : "./hrtfs/elev-10/L-10e150a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e150a.dat right -[ 16, 43 ] = ascii (fp) : "./hrtfs/elev-10/L-10e145a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e145a.dat right -[ 16, 44 ] = ascii (fp) : "./hrtfs/elev-10/L-10e140a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e140a.dat right -[ 16, 45 ] = ascii (fp) : "./hrtfs/elev-10/L-10e135a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e135a.dat right -[ 16, 46 ] = ascii (fp) : "./hrtfs/elev-10/L-10e130a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e130a.dat right -[ 16, 47 ] = ascii (fp) : "./hrtfs/elev-10/L-10e125a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e125a.dat right -[ 16, 48 ] = ascii (fp) : "./hrtfs/elev-10/L-10e120a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e120a.dat right -[ 16, 49 ] = ascii (fp) : "./hrtfs/elev-10/L-10e115a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e115a.dat right -[ 16, 50 ] = ascii (fp) : "./hrtfs/elev-10/L-10e110a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e110a.dat right -[ 16, 51 ] = ascii (fp) : "./hrtfs/elev-10/L-10e105a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e105a.dat right -[ 16, 52 ] = ascii (fp) : "./hrtfs/elev-10/L-10e100a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e100a.dat right -[ 16, 53 ] = ascii (fp) : "./hrtfs/elev-10/L-10e095a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e095a.dat right -[ 16, 54 ] = ascii (fp) : "./hrtfs/elev-10/L-10e090a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e090a.dat right -[ 16, 55 ] = ascii (fp) : "./hrtfs/elev-10/L-10e085a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e085a.dat right -[ 16, 56 ] = ascii (fp) : "./hrtfs/elev-10/L-10e080a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e080a.dat right -[ 16, 57 ] = ascii (fp) : "./hrtfs/elev-10/L-10e075a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e075a.dat right -[ 16, 58 ] = ascii (fp) : "./hrtfs/elev-10/L-10e070a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e070a.dat right -[ 16, 59 ] = ascii (fp) : "./hrtfs/elev-10/L-10e065a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e065a.dat right -[ 16, 60 ] = ascii (fp) : "./hrtfs/elev-10/L-10e060a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e060a.dat right -[ 16, 61 ] = ascii (fp) : "./hrtfs/elev-10/L-10e055a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e055a.dat right -[ 16, 62 ] = ascii (fp) : "./hrtfs/elev-10/L-10e050a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e050a.dat right -[ 16, 63 ] = ascii (fp) : "./hrtfs/elev-10/L-10e045a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e045a.dat right -[ 16, 64 ] = ascii (fp) : "./hrtfs/elev-10/L-10e040a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e040a.dat right -[ 16, 65 ] = ascii (fp) : "./hrtfs/elev-10/L-10e035a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e035a.dat right -[ 16, 66 ] = ascii (fp) : "./hrtfs/elev-10/L-10e030a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e030a.dat right -[ 16, 67 ] = ascii (fp) : "./hrtfs/elev-10/L-10e025a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e025a.dat right -[ 16, 68 ] = ascii (fp) : "./hrtfs/elev-10/L-10e020a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e020a.dat right -[ 16, 69 ] = ascii (fp) : "./hrtfs/elev-10/L-10e015a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e015a.dat right -[ 16, 70 ] = ascii (fp) : "./hrtfs/elev-10/L-10e010a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e010a.dat right -[ 16, 71 ] = ascii (fp) : "./hrtfs/elev-10/L-10e005a.dat left - + ascii (fp) : "./hrtfs/elev-10/R-10e005a.dat right +[ 16, 0 ] = ascii (fp) : "./hrtfs/elev-10/L-10e000a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e000a.dat" right +[ 16, 1 ] = ascii (fp) : "./hrtfs/elev-10/L-10e355a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e355a.dat" right +[ 16, 2 ] = ascii (fp) : "./hrtfs/elev-10/L-10e350a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e350a.dat" right +[ 16, 3 ] = ascii (fp) : "./hrtfs/elev-10/L-10e345a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e345a.dat" right +[ 16, 4 ] = ascii (fp) : "./hrtfs/elev-10/L-10e340a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e340a.dat" right +[ 16, 5 ] = ascii (fp) : "./hrtfs/elev-10/L-10e335a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e335a.dat" right +[ 16, 6 ] = ascii (fp) : "./hrtfs/elev-10/L-10e330a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e330a.dat" right +[ 16, 7 ] = ascii (fp) : "./hrtfs/elev-10/L-10e325a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e325a.dat" right +[ 16, 8 ] = ascii (fp) : "./hrtfs/elev-10/L-10e320a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e320a.dat" right +[ 16, 9 ] = ascii (fp) : "./hrtfs/elev-10/L-10e315a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e315a.dat" right +[ 16, 10 ] = ascii (fp) : "./hrtfs/elev-10/L-10e310a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e310a.dat" right +[ 16, 11 ] = ascii (fp) : "./hrtfs/elev-10/L-10e305a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e305a.dat" right +[ 16, 12 ] = ascii (fp) : "./hrtfs/elev-10/L-10e300a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e300a.dat" right +[ 16, 13 ] = ascii (fp) : "./hrtfs/elev-10/L-10e295a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e295a.dat" right +[ 16, 14 ] = ascii (fp) : "./hrtfs/elev-10/L-10e290a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e290a.dat" right +[ 16, 15 ] = ascii (fp) : "./hrtfs/elev-10/L-10e285a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e285a.dat" right +[ 16, 16 ] = ascii (fp) : "./hrtfs/elev-10/L-10e280a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e280a.dat" right +[ 16, 17 ] = ascii (fp) : "./hrtfs/elev-10/L-10e275a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e275a.dat" right +[ 16, 18 ] = ascii (fp) : "./hrtfs/elev-10/L-10e270a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e270a.dat" right +[ 16, 19 ] = ascii (fp) : "./hrtfs/elev-10/L-10e265a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e265a.dat" right +[ 16, 20 ] = ascii (fp) : "./hrtfs/elev-10/L-10e260a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e260a.dat" right +[ 16, 21 ] = ascii (fp) : "./hrtfs/elev-10/L-10e255a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e255a.dat" right +[ 16, 22 ] = ascii (fp) : "./hrtfs/elev-10/L-10e250a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e250a.dat" right +[ 16, 23 ] = ascii (fp) : "./hrtfs/elev-10/L-10e245a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e245a.dat" right +[ 16, 24 ] = ascii (fp) : "./hrtfs/elev-10/L-10e240a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e240a.dat" right +[ 16, 25 ] = ascii (fp) : "./hrtfs/elev-10/L-10e235a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e235a.dat" right +[ 16, 26 ] = ascii (fp) : "./hrtfs/elev-10/L-10e230a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e230a.dat" right +[ 16, 27 ] = ascii (fp) : "./hrtfs/elev-10/L-10e225a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e225a.dat" right +[ 16, 28 ] = ascii (fp) : "./hrtfs/elev-10/L-10e220a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e220a.dat" right +[ 16, 29 ] = ascii (fp) : "./hrtfs/elev-10/L-10e215a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e215a.dat" right +[ 16, 30 ] = ascii (fp) : "./hrtfs/elev-10/L-10e210a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e210a.dat" right +[ 16, 31 ] = ascii (fp) : "./hrtfs/elev-10/L-10e205a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e205a.dat" right +[ 16, 32 ] = ascii (fp) : "./hrtfs/elev-10/L-10e200a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e200a.dat" right +[ 16, 33 ] = ascii (fp) : "./hrtfs/elev-10/L-10e195a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e195a.dat" right +[ 16, 34 ] = ascii (fp) : "./hrtfs/elev-10/L-10e190a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e190a.dat" right +[ 16, 35 ] = ascii (fp) : "./hrtfs/elev-10/L-10e185a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e185a.dat" right +[ 16, 36 ] = ascii (fp) : "./hrtfs/elev-10/L-10e180a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e180a.dat" right +[ 16, 37 ] = ascii (fp) : "./hrtfs/elev-10/L-10e175a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e175a.dat" right +[ 16, 38 ] = ascii (fp) : "./hrtfs/elev-10/L-10e170a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e170a.dat" right +[ 16, 39 ] = ascii (fp) : "./hrtfs/elev-10/L-10e165a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e165a.dat" right +[ 16, 40 ] = ascii (fp) : "./hrtfs/elev-10/L-10e160a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e160a.dat" right +[ 16, 41 ] = ascii (fp) : "./hrtfs/elev-10/L-10e155a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e155a.dat" right +[ 16, 42 ] = ascii (fp) : "./hrtfs/elev-10/L-10e150a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e150a.dat" right +[ 16, 43 ] = ascii (fp) : "./hrtfs/elev-10/L-10e145a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e145a.dat" right +[ 16, 44 ] = ascii (fp) : "./hrtfs/elev-10/L-10e140a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e140a.dat" right +[ 16, 45 ] = ascii (fp) : "./hrtfs/elev-10/L-10e135a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e135a.dat" right +[ 16, 46 ] = ascii (fp) : "./hrtfs/elev-10/L-10e130a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e130a.dat" right +[ 16, 47 ] = ascii (fp) : "./hrtfs/elev-10/L-10e125a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e125a.dat" right +[ 16, 48 ] = ascii (fp) : "./hrtfs/elev-10/L-10e120a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e120a.dat" right +[ 16, 49 ] = ascii (fp) : "./hrtfs/elev-10/L-10e115a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e115a.dat" right +[ 16, 50 ] = ascii (fp) : "./hrtfs/elev-10/L-10e110a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e110a.dat" right +[ 16, 51 ] = ascii (fp) : "./hrtfs/elev-10/L-10e105a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e105a.dat" right +[ 16, 52 ] = ascii (fp) : "./hrtfs/elev-10/L-10e100a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e100a.dat" right +[ 16, 53 ] = ascii (fp) : "./hrtfs/elev-10/L-10e095a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e095a.dat" right +[ 16, 54 ] = ascii (fp) : "./hrtfs/elev-10/L-10e090a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e090a.dat" right +[ 16, 55 ] = ascii (fp) : "./hrtfs/elev-10/L-10e085a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e085a.dat" right +[ 16, 56 ] = ascii (fp) : "./hrtfs/elev-10/L-10e080a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e080a.dat" right +[ 16, 57 ] = ascii (fp) : "./hrtfs/elev-10/L-10e075a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e075a.dat" right +[ 16, 58 ] = ascii (fp) : "./hrtfs/elev-10/L-10e070a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e070a.dat" right +[ 16, 59 ] = ascii (fp) : "./hrtfs/elev-10/L-10e065a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e065a.dat" right +[ 16, 60 ] = ascii (fp) : "./hrtfs/elev-10/L-10e060a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e060a.dat" right +[ 16, 61 ] = ascii (fp) : "./hrtfs/elev-10/L-10e055a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e055a.dat" right +[ 16, 62 ] = ascii (fp) : "./hrtfs/elev-10/L-10e050a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e050a.dat" right +[ 16, 63 ] = ascii (fp) : "./hrtfs/elev-10/L-10e045a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e045a.dat" right +[ 16, 64 ] = ascii (fp) : "./hrtfs/elev-10/L-10e040a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e040a.dat" right +[ 16, 65 ] = ascii (fp) : "./hrtfs/elev-10/L-10e035a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e035a.dat" right +[ 16, 66 ] = ascii (fp) : "./hrtfs/elev-10/L-10e030a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e030a.dat" right +[ 16, 67 ] = ascii (fp) : "./hrtfs/elev-10/L-10e025a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e025a.dat" right +[ 16, 68 ] = ascii (fp) : "./hrtfs/elev-10/L-10e020a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e020a.dat" right +[ 16, 69 ] = ascii (fp) : "./hrtfs/elev-10/L-10e015a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e015a.dat" right +[ 16, 70 ] = ascii (fp) : "./hrtfs/elev-10/L-10e010a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e010a.dat" right +[ 16, 71 ] = ascii (fp) : "./hrtfs/elev-10/L-10e005a.dat" left + + ascii (fp) : "./hrtfs/elev-10/R-10e005a.dat" right -[ 17, 0 ] = ascii (fp) : "./hrtfs/elev-5/L-5e000a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e000a.dat right -[ 17, 1 ] = ascii (fp) : "./hrtfs/elev-5/L-5e355a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e355a.dat right -[ 17, 2 ] = ascii (fp) : "./hrtfs/elev-5/L-5e350a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e350a.dat right -[ 17, 3 ] = ascii (fp) : "./hrtfs/elev-5/L-5e345a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e345a.dat right -[ 17, 4 ] = ascii (fp) : "./hrtfs/elev-5/L-5e340a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e340a.dat right -[ 17, 5 ] = ascii (fp) : "./hrtfs/elev-5/L-5e335a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e335a.dat right -[ 17, 6 ] = ascii (fp) : "./hrtfs/elev-5/L-5e330a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e330a.dat right -[ 17, 7 ] = ascii (fp) : "./hrtfs/elev-5/L-5e325a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e325a.dat right -[ 17, 8 ] = ascii (fp) : "./hrtfs/elev-5/L-5e320a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e320a.dat right -[ 17, 9 ] = ascii (fp) : "./hrtfs/elev-5/L-5e315a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e315a.dat right -[ 17, 10 ] = ascii (fp) : "./hrtfs/elev-5/L-5e310a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e310a.dat right -[ 17, 11 ] = ascii (fp) : "./hrtfs/elev-5/L-5e305a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e305a.dat right -[ 17, 12 ] = ascii (fp) : "./hrtfs/elev-5/L-5e300a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e300a.dat right -[ 17, 13 ] = ascii (fp) : "./hrtfs/elev-5/L-5e295a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e295a.dat right -[ 17, 14 ] = ascii (fp) : "./hrtfs/elev-5/L-5e290a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e290a.dat right -[ 17, 15 ] = ascii (fp) : "./hrtfs/elev-5/L-5e285a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e285a.dat right -[ 17, 16 ] = ascii (fp) : "./hrtfs/elev-5/L-5e280a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e280a.dat right -[ 17, 17 ] = ascii (fp) : "./hrtfs/elev-5/L-5e275a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e275a.dat right -[ 17, 18 ] = ascii (fp) : "./hrtfs/elev-5/L-5e270a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e270a.dat right -[ 17, 19 ] = ascii (fp) : "./hrtfs/elev-5/L-5e265a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e265a.dat right -[ 17, 20 ] = ascii (fp) : "./hrtfs/elev-5/L-5e260a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e260a.dat right -[ 17, 21 ] = ascii (fp) : "./hrtfs/elev-5/L-5e255a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e255a.dat right -[ 17, 22 ] = ascii (fp) : "./hrtfs/elev-5/L-5e250a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e250a.dat right -[ 17, 23 ] = ascii (fp) : "./hrtfs/elev-5/L-5e245a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e245a.dat right -[ 17, 24 ] = ascii (fp) : "./hrtfs/elev-5/L-5e240a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e240a.dat right -[ 17, 25 ] = ascii (fp) : "./hrtfs/elev-5/L-5e235a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e235a.dat right -[ 17, 26 ] = ascii (fp) : "./hrtfs/elev-5/L-5e230a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e230a.dat right -[ 17, 27 ] = ascii (fp) : "./hrtfs/elev-5/L-5e225a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e225a.dat right -[ 17, 28 ] = ascii (fp) : "./hrtfs/elev-5/L-5e220a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e220a.dat right -[ 17, 29 ] = ascii (fp) : "./hrtfs/elev-5/L-5e215a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e215a.dat right -[ 17, 30 ] = ascii (fp) : "./hrtfs/elev-5/L-5e210a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e210a.dat right -[ 17, 31 ] = ascii (fp) : "./hrtfs/elev-5/L-5e205a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e205a.dat right -[ 17, 32 ] = ascii (fp) : "./hrtfs/elev-5/L-5e200a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e200a.dat right -[ 17, 33 ] = ascii (fp) : "./hrtfs/elev-5/L-5e195a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e195a.dat right -[ 17, 34 ] = ascii (fp) : "./hrtfs/elev-5/L-5e190a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e190a.dat right -[ 17, 35 ] = ascii (fp) : "./hrtfs/elev-5/L-5e185a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e185a.dat right -[ 17, 36 ] = ascii (fp) : "./hrtfs/elev-5/L-5e180a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e180a.dat right -[ 17, 37 ] = ascii (fp) : "./hrtfs/elev-5/L-5e175a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e175a.dat right -[ 17, 38 ] = ascii (fp) : "./hrtfs/elev-5/L-5e170a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e170a.dat right -[ 17, 39 ] = ascii (fp) : "./hrtfs/elev-5/L-5e165a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e165a.dat right -[ 17, 40 ] = ascii (fp) : "./hrtfs/elev-5/L-5e160a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e160a.dat right -[ 17, 41 ] = ascii (fp) : "./hrtfs/elev-5/L-5e155a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e155a.dat right -[ 17, 42 ] = ascii (fp) : "./hrtfs/elev-5/L-5e150a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e150a.dat right -[ 17, 43 ] = ascii (fp) : "./hrtfs/elev-5/L-5e145a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e145a.dat right -[ 17, 44 ] = ascii (fp) : "./hrtfs/elev-5/L-5e140a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e140a.dat right -[ 17, 45 ] = ascii (fp) : "./hrtfs/elev-5/L-5e135a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e135a.dat right -[ 17, 46 ] = ascii (fp) : "./hrtfs/elev-5/L-5e130a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e130a.dat right -[ 17, 47 ] = ascii (fp) : "./hrtfs/elev-5/L-5e125a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e125a.dat right -[ 17, 48 ] = ascii (fp) : "./hrtfs/elev-5/L-5e120a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e120a.dat right -[ 17, 49 ] = ascii (fp) : "./hrtfs/elev-5/L-5e115a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e115a.dat right -[ 17, 50 ] = ascii (fp) : "./hrtfs/elev-5/L-5e110a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e110a.dat right -[ 17, 51 ] = ascii (fp) : "./hrtfs/elev-5/L-5e105a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e105a.dat right -[ 17, 52 ] = ascii (fp) : "./hrtfs/elev-5/L-5e100a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e100a.dat right -[ 17, 53 ] = ascii (fp) : "./hrtfs/elev-5/L-5e095a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e095a.dat right -[ 17, 54 ] = ascii (fp) : "./hrtfs/elev-5/L-5e090a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e090a.dat right -[ 17, 55 ] = ascii (fp) : "./hrtfs/elev-5/L-5e085a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e085a.dat right -[ 17, 56 ] = ascii (fp) : "./hrtfs/elev-5/L-5e080a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e080a.dat right -[ 17, 57 ] = ascii (fp) : "./hrtfs/elev-5/L-5e075a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e075a.dat right -[ 17, 58 ] = ascii (fp) : "./hrtfs/elev-5/L-5e070a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e070a.dat right -[ 17, 59 ] = ascii (fp) : "./hrtfs/elev-5/L-5e065a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e065a.dat right -[ 17, 60 ] = ascii (fp) : "./hrtfs/elev-5/L-5e060a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e060a.dat right -[ 17, 61 ] = ascii (fp) : "./hrtfs/elev-5/L-5e055a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e055a.dat right -[ 17, 62 ] = ascii (fp) : "./hrtfs/elev-5/L-5e050a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e050a.dat right -[ 17, 63 ] = ascii (fp) : "./hrtfs/elev-5/L-5e045a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e045a.dat right -[ 17, 64 ] = ascii (fp) : "./hrtfs/elev-5/L-5e040a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e040a.dat right -[ 17, 65 ] = ascii (fp) : "./hrtfs/elev-5/L-5e035a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e035a.dat right -[ 17, 66 ] = ascii (fp) : "./hrtfs/elev-5/L-5e030a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e030a.dat right -[ 17, 67 ] = ascii (fp) : "./hrtfs/elev-5/L-5e025a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e025a.dat right -[ 17, 68 ] = ascii (fp) : "./hrtfs/elev-5/L-5e020a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e020a.dat right -[ 17, 69 ] = ascii (fp) : "./hrtfs/elev-5/L-5e015a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e015a.dat right -[ 17, 70 ] = ascii (fp) : "./hrtfs/elev-5/L-5e010a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e010a.dat right -[ 17, 71 ] = ascii (fp) : "./hrtfs/elev-5/L-5e005a.dat left - + ascii (fp) : "./hrtfs/elev-5/R-5e005a.dat right +[ 17, 0 ] = ascii (fp) : "./hrtfs/elev-5/L-5e000a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e000a.dat" right +[ 17, 1 ] = ascii (fp) : "./hrtfs/elev-5/L-5e355a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e355a.dat" right +[ 17, 2 ] = ascii (fp) : "./hrtfs/elev-5/L-5e350a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e350a.dat" right +[ 17, 3 ] = ascii (fp) : "./hrtfs/elev-5/L-5e345a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e345a.dat" right +[ 17, 4 ] = ascii (fp) : "./hrtfs/elev-5/L-5e340a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e340a.dat" right +[ 17, 5 ] = ascii (fp) : "./hrtfs/elev-5/L-5e335a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e335a.dat" right +[ 17, 6 ] = ascii (fp) : "./hrtfs/elev-5/L-5e330a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e330a.dat" right +[ 17, 7 ] = ascii (fp) : "./hrtfs/elev-5/L-5e325a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e325a.dat" right +[ 17, 8 ] = ascii (fp) : "./hrtfs/elev-5/L-5e320a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e320a.dat" right +[ 17, 9 ] = ascii (fp) : "./hrtfs/elev-5/L-5e315a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e315a.dat" right +[ 17, 10 ] = ascii (fp) : "./hrtfs/elev-5/L-5e310a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e310a.dat" right +[ 17, 11 ] = ascii (fp) : "./hrtfs/elev-5/L-5e305a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e305a.dat" right +[ 17, 12 ] = ascii (fp) : "./hrtfs/elev-5/L-5e300a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e300a.dat" right +[ 17, 13 ] = ascii (fp) : "./hrtfs/elev-5/L-5e295a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e295a.dat" right +[ 17, 14 ] = ascii (fp) : "./hrtfs/elev-5/L-5e290a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e290a.dat" right +[ 17, 15 ] = ascii (fp) : "./hrtfs/elev-5/L-5e285a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e285a.dat" right +[ 17, 16 ] = ascii (fp) : "./hrtfs/elev-5/L-5e280a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e280a.dat" right +[ 17, 17 ] = ascii (fp) : "./hrtfs/elev-5/L-5e275a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e275a.dat" right +[ 17, 18 ] = ascii (fp) : "./hrtfs/elev-5/L-5e270a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e270a.dat" right +[ 17, 19 ] = ascii (fp) : "./hrtfs/elev-5/L-5e265a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e265a.dat" right +[ 17, 20 ] = ascii (fp) : "./hrtfs/elev-5/L-5e260a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e260a.dat" right +[ 17, 21 ] = ascii (fp) : "./hrtfs/elev-5/L-5e255a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e255a.dat" right +[ 17, 22 ] = ascii (fp) : "./hrtfs/elev-5/L-5e250a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e250a.dat" right +[ 17, 23 ] = ascii (fp) : "./hrtfs/elev-5/L-5e245a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e245a.dat" right +[ 17, 24 ] = ascii (fp) : "./hrtfs/elev-5/L-5e240a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e240a.dat" right +[ 17, 25 ] = ascii (fp) : "./hrtfs/elev-5/L-5e235a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e235a.dat" right +[ 17, 26 ] = ascii (fp) : "./hrtfs/elev-5/L-5e230a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e230a.dat" right +[ 17, 27 ] = ascii (fp) : "./hrtfs/elev-5/L-5e225a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e225a.dat" right +[ 17, 28 ] = ascii (fp) : "./hrtfs/elev-5/L-5e220a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e220a.dat" right +[ 17, 29 ] = ascii (fp) : "./hrtfs/elev-5/L-5e215a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e215a.dat" right +[ 17, 30 ] = ascii (fp) : "./hrtfs/elev-5/L-5e210a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e210a.dat" right +[ 17, 31 ] = ascii (fp) : "./hrtfs/elev-5/L-5e205a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e205a.dat" right +[ 17, 32 ] = ascii (fp) : "./hrtfs/elev-5/L-5e200a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e200a.dat" right +[ 17, 33 ] = ascii (fp) : "./hrtfs/elev-5/L-5e195a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e195a.dat" right +[ 17, 34 ] = ascii (fp) : "./hrtfs/elev-5/L-5e190a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e190a.dat" right +[ 17, 35 ] = ascii (fp) : "./hrtfs/elev-5/L-5e185a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e185a.dat" right +[ 17, 36 ] = ascii (fp) : "./hrtfs/elev-5/L-5e180a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e180a.dat" right +[ 17, 37 ] = ascii (fp) : "./hrtfs/elev-5/L-5e175a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e175a.dat" right +[ 17, 38 ] = ascii (fp) : "./hrtfs/elev-5/L-5e170a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e170a.dat" right +[ 17, 39 ] = ascii (fp) : "./hrtfs/elev-5/L-5e165a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e165a.dat" right +[ 17, 40 ] = ascii (fp) : "./hrtfs/elev-5/L-5e160a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e160a.dat" right +[ 17, 41 ] = ascii (fp) : "./hrtfs/elev-5/L-5e155a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e155a.dat" right +[ 17, 42 ] = ascii (fp) : "./hrtfs/elev-5/L-5e150a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e150a.dat" right +[ 17, 43 ] = ascii (fp) : "./hrtfs/elev-5/L-5e145a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e145a.dat" right +[ 17, 44 ] = ascii (fp) : "./hrtfs/elev-5/L-5e140a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e140a.dat" right +[ 17, 45 ] = ascii (fp) : "./hrtfs/elev-5/L-5e135a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e135a.dat" right +[ 17, 46 ] = ascii (fp) : "./hrtfs/elev-5/L-5e130a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e130a.dat" right +[ 17, 47 ] = ascii (fp) : "./hrtfs/elev-5/L-5e125a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e125a.dat" right +[ 17, 48 ] = ascii (fp) : "./hrtfs/elev-5/L-5e120a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e120a.dat" right +[ 17, 49 ] = ascii (fp) : "./hrtfs/elev-5/L-5e115a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e115a.dat" right +[ 17, 50 ] = ascii (fp) : "./hrtfs/elev-5/L-5e110a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e110a.dat" right +[ 17, 51 ] = ascii (fp) : "./hrtfs/elev-5/L-5e105a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e105a.dat" right +[ 17, 52 ] = ascii (fp) : "./hrtfs/elev-5/L-5e100a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e100a.dat" right +[ 17, 53 ] = ascii (fp) : "./hrtfs/elev-5/L-5e095a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e095a.dat" right +[ 17, 54 ] = ascii (fp) : "./hrtfs/elev-5/L-5e090a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e090a.dat" right +[ 17, 55 ] = ascii (fp) : "./hrtfs/elev-5/L-5e085a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e085a.dat" right +[ 17, 56 ] = ascii (fp) : "./hrtfs/elev-5/L-5e080a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e080a.dat" right +[ 17, 57 ] = ascii (fp) : "./hrtfs/elev-5/L-5e075a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e075a.dat" right +[ 17, 58 ] = ascii (fp) : "./hrtfs/elev-5/L-5e070a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e070a.dat" right +[ 17, 59 ] = ascii (fp) : "./hrtfs/elev-5/L-5e065a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e065a.dat" right +[ 17, 60 ] = ascii (fp) : "./hrtfs/elev-5/L-5e060a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e060a.dat" right +[ 17, 61 ] = ascii (fp) : "./hrtfs/elev-5/L-5e055a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e055a.dat" right +[ 17, 62 ] = ascii (fp) : "./hrtfs/elev-5/L-5e050a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e050a.dat" right +[ 17, 63 ] = ascii (fp) : "./hrtfs/elev-5/L-5e045a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e045a.dat" right +[ 17, 64 ] = ascii (fp) : "./hrtfs/elev-5/L-5e040a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e040a.dat" right +[ 17, 65 ] = ascii (fp) : "./hrtfs/elev-5/L-5e035a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e035a.dat" right +[ 17, 66 ] = ascii (fp) : "./hrtfs/elev-5/L-5e030a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e030a.dat" right +[ 17, 67 ] = ascii (fp) : "./hrtfs/elev-5/L-5e025a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e025a.dat" right +[ 17, 68 ] = ascii (fp) : "./hrtfs/elev-5/L-5e020a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e020a.dat" right +[ 17, 69 ] = ascii (fp) : "./hrtfs/elev-5/L-5e015a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e015a.dat" right +[ 17, 70 ] = ascii (fp) : "./hrtfs/elev-5/L-5e010a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e010a.dat" right +[ 17, 71 ] = ascii (fp) : "./hrtfs/elev-5/L-5e005a.dat" left + + ascii (fp) : "./hrtfs/elev-5/R-5e005a.dat" right -[ 18, 0 ] = ascii (fp) : "./hrtfs/elev0/L0e000a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e000a.dat right -[ 18, 1 ] = ascii (fp) : "./hrtfs/elev0/L0e355a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e355a.dat right -[ 18, 2 ] = ascii (fp) : "./hrtfs/elev0/L0e350a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e350a.dat right -[ 18, 3 ] = ascii (fp) : "./hrtfs/elev0/L0e345a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e345a.dat right -[ 18, 4 ] = ascii (fp) : "./hrtfs/elev0/L0e340a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e340a.dat right -[ 18, 5 ] = ascii (fp) : "./hrtfs/elev0/L0e335a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e335a.dat right -[ 18, 6 ] = ascii (fp) : "./hrtfs/elev0/L0e330a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e330a.dat right -[ 18, 7 ] = ascii (fp) : "./hrtfs/elev0/L0e325a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e325a.dat right -[ 18, 8 ] = ascii (fp) : "./hrtfs/elev0/L0e320a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e320a.dat right -[ 18, 9 ] = ascii (fp) : "./hrtfs/elev0/L0e315a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e315a.dat right -[ 18, 10 ] = ascii (fp) : "./hrtfs/elev0/L0e310a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e310a.dat right -[ 18, 11 ] = ascii (fp) : "./hrtfs/elev0/L0e305a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e305a.dat right -[ 18, 12 ] = ascii (fp) : "./hrtfs/elev0/L0e300a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e300a.dat right -[ 18, 13 ] = ascii (fp) : "./hrtfs/elev0/L0e295a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e295a.dat right -[ 18, 14 ] = ascii (fp) : "./hrtfs/elev0/L0e290a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e290a.dat right -[ 18, 15 ] = ascii (fp) : "./hrtfs/elev0/L0e285a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e285a.dat right -[ 18, 16 ] = ascii (fp) : "./hrtfs/elev0/L0e280a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e280a.dat right -[ 18, 17 ] = ascii (fp) : "./hrtfs/elev0/L0e275a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e275a.dat right -[ 18, 18 ] = ascii (fp) : "./hrtfs/elev0/L0e270a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e270a.dat right -[ 18, 19 ] = ascii (fp) : "./hrtfs/elev0/L0e265a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e265a.dat right -[ 18, 20 ] = ascii (fp) : "./hrtfs/elev0/L0e260a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e260a.dat right -[ 18, 21 ] = ascii (fp) : "./hrtfs/elev0/L0e255a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e255a.dat right -[ 18, 22 ] = ascii (fp) : "./hrtfs/elev0/L0e250a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e250a.dat right -[ 18, 23 ] = ascii (fp) : "./hrtfs/elev0/L0e245a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e245a.dat right -[ 18, 24 ] = ascii (fp) : "./hrtfs/elev0/L0e240a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e240a.dat right -[ 18, 25 ] = ascii (fp) : "./hrtfs/elev0/L0e235a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e235a.dat right -[ 18, 26 ] = ascii (fp) : "./hrtfs/elev0/L0e230a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e230a.dat right -[ 18, 27 ] = ascii (fp) : "./hrtfs/elev0/L0e225a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e225a.dat right -[ 18, 28 ] = ascii (fp) : "./hrtfs/elev0/L0e220a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e220a.dat right -[ 18, 29 ] = ascii (fp) : "./hrtfs/elev0/L0e215a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e215a.dat right -[ 18, 30 ] = ascii (fp) : "./hrtfs/elev0/L0e210a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e210a.dat right -[ 18, 31 ] = ascii (fp) : "./hrtfs/elev0/L0e205a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e205a.dat right -[ 18, 32 ] = ascii (fp) : "./hrtfs/elev0/L0e200a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e200a.dat right -[ 18, 33 ] = ascii (fp) : "./hrtfs/elev0/L0e195a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e195a.dat right -[ 18, 34 ] = ascii (fp) : "./hrtfs/elev0/L0e190a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e190a.dat right -[ 18, 35 ] = ascii (fp) : "./hrtfs/elev0/L0e185a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e185a.dat right -[ 18, 36 ] = ascii (fp) : "./hrtfs/elev0/L0e180a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e180a.dat right -[ 18, 37 ] = ascii (fp) : "./hrtfs/elev0/L0e175a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e175a.dat right -[ 18, 38 ] = ascii (fp) : "./hrtfs/elev0/L0e170a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e170a.dat right -[ 18, 39 ] = ascii (fp) : "./hrtfs/elev0/L0e165a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e165a.dat right -[ 18, 40 ] = ascii (fp) : "./hrtfs/elev0/L0e160a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e160a.dat right -[ 18, 41 ] = ascii (fp) : "./hrtfs/elev0/L0e155a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e155a.dat right -[ 18, 42 ] = ascii (fp) : "./hrtfs/elev0/L0e150a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e150a.dat right -[ 18, 43 ] = ascii (fp) : "./hrtfs/elev0/L0e145a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e145a.dat right -[ 18, 44 ] = ascii (fp) : "./hrtfs/elev0/L0e140a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e140a.dat right -[ 18, 45 ] = ascii (fp) : "./hrtfs/elev0/L0e135a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e135a.dat right -[ 18, 46 ] = ascii (fp) : "./hrtfs/elev0/L0e130a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e130a.dat right -[ 18, 47 ] = ascii (fp) : "./hrtfs/elev0/L0e125a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e125a.dat right -[ 18, 48 ] = ascii (fp) : "./hrtfs/elev0/L0e120a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e120a.dat right -[ 18, 49 ] = ascii (fp) : "./hrtfs/elev0/L0e115a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e115a.dat right -[ 18, 50 ] = ascii (fp) : "./hrtfs/elev0/L0e110a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e110a.dat right -[ 18, 51 ] = ascii (fp) : "./hrtfs/elev0/L0e105a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e105a.dat right -[ 18, 52 ] = ascii (fp) : "./hrtfs/elev0/L0e100a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e100a.dat right -[ 18, 53 ] = ascii (fp) : "./hrtfs/elev0/L0e095a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e095a.dat right -[ 18, 54 ] = ascii (fp) : "./hrtfs/elev0/L0e090a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e090a.dat right -[ 18, 55 ] = ascii (fp) : "./hrtfs/elev0/L0e085a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e085a.dat right -[ 18, 56 ] = ascii (fp) : "./hrtfs/elev0/L0e080a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e080a.dat right -[ 18, 57 ] = ascii (fp) : "./hrtfs/elev0/L0e075a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e075a.dat right -[ 18, 58 ] = ascii (fp) : "./hrtfs/elev0/L0e070a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e070a.dat right -[ 18, 59 ] = ascii (fp) : "./hrtfs/elev0/L0e065a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e065a.dat right -[ 18, 60 ] = ascii (fp) : "./hrtfs/elev0/L0e060a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e060a.dat right -[ 18, 61 ] = ascii (fp) : "./hrtfs/elev0/L0e055a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e055a.dat right -[ 18, 62 ] = ascii (fp) : "./hrtfs/elev0/L0e050a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e050a.dat right -[ 18, 63 ] = ascii (fp) : "./hrtfs/elev0/L0e045a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e045a.dat right -[ 18, 64 ] = ascii (fp) : "./hrtfs/elev0/L0e040a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e040a.dat right -[ 18, 65 ] = ascii (fp) : "./hrtfs/elev0/L0e035a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e035a.dat right -[ 18, 66 ] = ascii (fp) : "./hrtfs/elev0/L0e030a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e030a.dat right -[ 18, 67 ] = ascii (fp) : "./hrtfs/elev0/L0e025a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e025a.dat right -[ 18, 68 ] = ascii (fp) : "./hrtfs/elev0/L0e020a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e020a.dat right -[ 18, 69 ] = ascii (fp) : "./hrtfs/elev0/L0e015a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e015a.dat right -[ 18, 70 ] = ascii (fp) : "./hrtfs/elev0/L0e010a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e010a.dat right -[ 18, 71 ] = ascii (fp) : "./hrtfs/elev0/L0e005a.dat left - + ascii (fp) : "./hrtfs/elev0/R0e005a.dat right +[ 18, 0 ] = ascii (fp) : "./hrtfs/elev0/L0e000a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e000a.dat" right +[ 18, 1 ] = ascii (fp) : "./hrtfs/elev0/L0e355a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e355a.dat" right +[ 18, 2 ] = ascii (fp) : "./hrtfs/elev0/L0e350a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e350a.dat" right +[ 18, 3 ] = ascii (fp) : "./hrtfs/elev0/L0e345a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e345a.dat" right +[ 18, 4 ] = ascii (fp) : "./hrtfs/elev0/L0e340a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e340a.dat" right +[ 18, 5 ] = ascii (fp) : "./hrtfs/elev0/L0e335a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e335a.dat" right +[ 18, 6 ] = ascii (fp) : "./hrtfs/elev0/L0e330a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e330a.dat" right +[ 18, 7 ] = ascii (fp) : "./hrtfs/elev0/L0e325a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e325a.dat" right +[ 18, 8 ] = ascii (fp) : "./hrtfs/elev0/L0e320a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e320a.dat" right +[ 18, 9 ] = ascii (fp) : "./hrtfs/elev0/L0e315a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e315a.dat" right +[ 18, 10 ] = ascii (fp) : "./hrtfs/elev0/L0e310a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e310a.dat" right +[ 18, 11 ] = ascii (fp) : "./hrtfs/elev0/L0e305a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e305a.dat" right +[ 18, 12 ] = ascii (fp) : "./hrtfs/elev0/L0e300a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e300a.dat" right +[ 18, 13 ] = ascii (fp) : "./hrtfs/elev0/L0e295a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e295a.dat" right +[ 18, 14 ] = ascii (fp) : "./hrtfs/elev0/L0e290a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e290a.dat" right +[ 18, 15 ] = ascii (fp) : "./hrtfs/elev0/L0e285a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e285a.dat" right +[ 18, 16 ] = ascii (fp) : "./hrtfs/elev0/L0e280a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e280a.dat" right +[ 18, 17 ] = ascii (fp) : "./hrtfs/elev0/L0e275a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e275a.dat" right +[ 18, 18 ] = ascii (fp) : "./hrtfs/elev0/L0e270a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e270a.dat" right +[ 18, 19 ] = ascii (fp) : "./hrtfs/elev0/L0e265a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e265a.dat" right +[ 18, 20 ] = ascii (fp) : "./hrtfs/elev0/L0e260a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e260a.dat" right +[ 18, 21 ] = ascii (fp) : "./hrtfs/elev0/L0e255a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e255a.dat" right +[ 18, 22 ] = ascii (fp) : "./hrtfs/elev0/L0e250a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e250a.dat" right +[ 18, 23 ] = ascii (fp) : "./hrtfs/elev0/L0e245a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e245a.dat" right +[ 18, 24 ] = ascii (fp) : "./hrtfs/elev0/L0e240a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e240a.dat" right +[ 18, 25 ] = ascii (fp) : "./hrtfs/elev0/L0e235a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e235a.dat" right +[ 18, 26 ] = ascii (fp) : "./hrtfs/elev0/L0e230a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e230a.dat" right +[ 18, 27 ] = ascii (fp) : "./hrtfs/elev0/L0e225a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e225a.dat" right +[ 18, 28 ] = ascii (fp) : "./hrtfs/elev0/L0e220a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e220a.dat" right +[ 18, 29 ] = ascii (fp) : "./hrtfs/elev0/L0e215a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e215a.dat" right +[ 18, 30 ] = ascii (fp) : "./hrtfs/elev0/L0e210a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e210a.dat" right +[ 18, 31 ] = ascii (fp) : "./hrtfs/elev0/L0e205a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e205a.dat" right +[ 18, 32 ] = ascii (fp) : "./hrtfs/elev0/L0e200a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e200a.dat" right +[ 18, 33 ] = ascii (fp) : "./hrtfs/elev0/L0e195a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e195a.dat" right +[ 18, 34 ] = ascii (fp) : "./hrtfs/elev0/L0e190a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e190a.dat" right +[ 18, 35 ] = ascii (fp) : "./hrtfs/elev0/L0e185a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e185a.dat" right +[ 18, 36 ] = ascii (fp) : "./hrtfs/elev0/L0e180a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e180a.dat" right +[ 18, 37 ] = ascii (fp) : "./hrtfs/elev0/L0e175a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e175a.dat" right +[ 18, 38 ] = ascii (fp) : "./hrtfs/elev0/L0e170a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e170a.dat" right +[ 18, 39 ] = ascii (fp) : "./hrtfs/elev0/L0e165a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e165a.dat" right +[ 18, 40 ] = ascii (fp) : "./hrtfs/elev0/L0e160a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e160a.dat" right +[ 18, 41 ] = ascii (fp) : "./hrtfs/elev0/L0e155a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e155a.dat" right +[ 18, 42 ] = ascii (fp) : "./hrtfs/elev0/L0e150a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e150a.dat" right +[ 18, 43 ] = ascii (fp) : "./hrtfs/elev0/L0e145a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e145a.dat" right +[ 18, 44 ] = ascii (fp) : "./hrtfs/elev0/L0e140a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e140a.dat" right +[ 18, 45 ] = ascii (fp) : "./hrtfs/elev0/L0e135a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e135a.dat" right +[ 18, 46 ] = ascii (fp) : "./hrtfs/elev0/L0e130a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e130a.dat" right +[ 18, 47 ] = ascii (fp) : "./hrtfs/elev0/L0e125a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e125a.dat" right +[ 18, 48 ] = ascii (fp) : "./hrtfs/elev0/L0e120a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e120a.dat" right +[ 18, 49 ] = ascii (fp) : "./hrtfs/elev0/L0e115a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e115a.dat" right +[ 18, 50 ] = ascii (fp) : "./hrtfs/elev0/L0e110a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e110a.dat" right +[ 18, 51 ] = ascii (fp) : "./hrtfs/elev0/L0e105a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e105a.dat" right +[ 18, 52 ] = ascii (fp) : "./hrtfs/elev0/L0e100a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e100a.dat" right +[ 18, 53 ] = ascii (fp) : "./hrtfs/elev0/L0e095a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e095a.dat" right +[ 18, 54 ] = ascii (fp) : "./hrtfs/elev0/L0e090a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e090a.dat" right +[ 18, 55 ] = ascii (fp) : "./hrtfs/elev0/L0e085a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e085a.dat" right +[ 18, 56 ] = ascii (fp) : "./hrtfs/elev0/L0e080a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e080a.dat" right +[ 18, 57 ] = ascii (fp) : "./hrtfs/elev0/L0e075a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e075a.dat" right +[ 18, 58 ] = ascii (fp) : "./hrtfs/elev0/L0e070a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e070a.dat" right +[ 18, 59 ] = ascii (fp) : "./hrtfs/elev0/L0e065a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e065a.dat" right +[ 18, 60 ] = ascii (fp) : "./hrtfs/elev0/L0e060a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e060a.dat" right +[ 18, 61 ] = ascii (fp) : "./hrtfs/elev0/L0e055a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e055a.dat" right +[ 18, 62 ] = ascii (fp) : "./hrtfs/elev0/L0e050a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e050a.dat" right +[ 18, 63 ] = ascii (fp) : "./hrtfs/elev0/L0e045a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e045a.dat" right +[ 18, 64 ] = ascii (fp) : "./hrtfs/elev0/L0e040a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e040a.dat" right +[ 18, 65 ] = ascii (fp) : "./hrtfs/elev0/L0e035a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e035a.dat" right +[ 18, 66 ] = ascii (fp) : "./hrtfs/elev0/L0e030a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e030a.dat" right +[ 18, 67 ] = ascii (fp) : "./hrtfs/elev0/L0e025a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e025a.dat" right +[ 18, 68 ] = ascii (fp) : "./hrtfs/elev0/L0e020a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e020a.dat" right +[ 18, 69 ] = ascii (fp) : "./hrtfs/elev0/L0e015a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e015a.dat" right +[ 18, 70 ] = ascii (fp) : "./hrtfs/elev0/L0e010a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e010a.dat" right +[ 18, 71 ] = ascii (fp) : "./hrtfs/elev0/L0e005a.dat" left + + ascii (fp) : "./hrtfs/elev0/R0e005a.dat" right -[ 19, 0 ] = ascii (fp) : "./hrtfs/elev5/L5e000a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e000a.dat right -[ 19, 1 ] = ascii (fp) : "./hrtfs/elev5/L5e355a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e355a.dat right -[ 19, 2 ] = ascii (fp) : "./hrtfs/elev5/L5e350a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e350a.dat right -[ 19, 3 ] = ascii (fp) : "./hrtfs/elev5/L5e345a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e345a.dat right -[ 19, 4 ] = ascii (fp) : "./hrtfs/elev5/L5e340a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e340a.dat right -[ 19, 5 ] = ascii (fp) : "./hrtfs/elev5/L5e335a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e335a.dat right -[ 19, 6 ] = ascii (fp) : "./hrtfs/elev5/L5e330a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e330a.dat right -[ 19, 7 ] = ascii (fp) : "./hrtfs/elev5/L5e325a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e325a.dat right -[ 19, 8 ] = ascii (fp) : "./hrtfs/elev5/L5e320a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e320a.dat right -[ 19, 9 ] = ascii (fp) : "./hrtfs/elev5/L5e315a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e315a.dat right -[ 19, 10 ] = ascii (fp) : "./hrtfs/elev5/L5e310a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e310a.dat right -[ 19, 11 ] = ascii (fp) : "./hrtfs/elev5/L5e305a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e305a.dat right -[ 19, 12 ] = ascii (fp) : "./hrtfs/elev5/L5e300a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e300a.dat right -[ 19, 13 ] = ascii (fp) : "./hrtfs/elev5/L5e295a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e295a.dat right -[ 19, 14 ] = ascii (fp) : "./hrtfs/elev5/L5e290a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e290a.dat right -[ 19, 15 ] = ascii (fp) : "./hrtfs/elev5/L5e285a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e285a.dat right -[ 19, 16 ] = ascii (fp) : "./hrtfs/elev5/L5e280a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e280a.dat right -[ 19, 17 ] = ascii (fp) : "./hrtfs/elev5/L5e275a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e275a.dat right -[ 19, 18 ] = ascii (fp) : "./hrtfs/elev5/L5e270a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e270a.dat right -[ 19, 19 ] = ascii (fp) : "./hrtfs/elev5/L5e265a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e265a.dat right -[ 19, 20 ] = ascii (fp) : "./hrtfs/elev5/L5e260a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e260a.dat right -[ 19, 21 ] = ascii (fp) : "./hrtfs/elev5/L5e255a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e255a.dat right -[ 19, 22 ] = ascii (fp) : "./hrtfs/elev5/L5e250a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e250a.dat right -[ 19, 23 ] = ascii (fp) : "./hrtfs/elev5/L5e245a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e245a.dat right -[ 19, 24 ] = ascii (fp) : "./hrtfs/elev5/L5e240a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e240a.dat right -[ 19, 25 ] = ascii (fp) : "./hrtfs/elev5/L5e235a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e235a.dat right -[ 19, 26 ] = ascii (fp) : "./hrtfs/elev5/L5e230a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e230a.dat right -[ 19, 27 ] = ascii (fp) : "./hrtfs/elev5/L5e225a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e225a.dat right -[ 19, 28 ] = ascii (fp) : "./hrtfs/elev5/L5e220a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e220a.dat right -[ 19, 29 ] = ascii (fp) : "./hrtfs/elev5/L5e215a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e215a.dat right -[ 19, 30 ] = ascii (fp) : "./hrtfs/elev5/L5e210a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e210a.dat right -[ 19, 31 ] = ascii (fp) : "./hrtfs/elev5/L5e205a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e205a.dat right -[ 19, 32 ] = ascii (fp) : "./hrtfs/elev5/L5e200a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e200a.dat right -[ 19, 33 ] = ascii (fp) : "./hrtfs/elev5/L5e195a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e195a.dat right -[ 19, 34 ] = ascii (fp) : "./hrtfs/elev5/L5e190a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e190a.dat right -[ 19, 35 ] = ascii (fp) : "./hrtfs/elev5/L5e185a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e185a.dat right -[ 19, 36 ] = ascii (fp) : "./hrtfs/elev5/L5e180a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e180a.dat right -[ 19, 37 ] = ascii (fp) : "./hrtfs/elev5/L5e175a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e175a.dat right -[ 19, 38 ] = ascii (fp) : "./hrtfs/elev5/L5e170a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e170a.dat right -[ 19, 39 ] = ascii (fp) : "./hrtfs/elev5/L5e165a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e165a.dat right -[ 19, 40 ] = ascii (fp) : "./hrtfs/elev5/L5e160a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e160a.dat right -[ 19, 41 ] = ascii (fp) : "./hrtfs/elev5/L5e155a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e155a.dat right -[ 19, 42 ] = ascii (fp) : "./hrtfs/elev5/L5e150a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e150a.dat right -[ 19, 43 ] = ascii (fp) : "./hrtfs/elev5/L5e145a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e145a.dat right -[ 19, 44 ] = ascii (fp) : "./hrtfs/elev5/L5e140a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e140a.dat right -[ 19, 45 ] = ascii (fp) : "./hrtfs/elev5/L5e135a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e135a.dat right -[ 19, 46 ] = ascii (fp) : "./hrtfs/elev5/L5e130a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e130a.dat right -[ 19, 47 ] = ascii (fp) : "./hrtfs/elev5/L5e125a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e125a.dat right -[ 19, 48 ] = ascii (fp) : "./hrtfs/elev5/L5e120a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e120a.dat right -[ 19, 49 ] = ascii (fp) : "./hrtfs/elev5/L5e115a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e115a.dat right -[ 19, 50 ] = ascii (fp) : "./hrtfs/elev5/L5e110a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e110a.dat right -[ 19, 51 ] = ascii (fp) : "./hrtfs/elev5/L5e105a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e105a.dat right -[ 19, 52 ] = ascii (fp) : "./hrtfs/elev5/L5e100a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e100a.dat right -[ 19, 53 ] = ascii (fp) : "./hrtfs/elev5/L5e095a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e095a.dat right -[ 19, 54 ] = ascii (fp) : "./hrtfs/elev5/L5e090a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e090a.dat right -[ 19, 55 ] = ascii (fp) : "./hrtfs/elev5/L5e085a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e085a.dat right -[ 19, 56 ] = ascii (fp) : "./hrtfs/elev5/L5e080a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e080a.dat right -[ 19, 57 ] = ascii (fp) : "./hrtfs/elev5/L5e075a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e075a.dat right -[ 19, 58 ] = ascii (fp) : "./hrtfs/elev5/L5e070a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e070a.dat right -[ 19, 59 ] = ascii (fp) : "./hrtfs/elev5/L5e065a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e065a.dat right -[ 19, 60 ] = ascii (fp) : "./hrtfs/elev5/L5e060a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e060a.dat right -[ 19, 61 ] = ascii (fp) : "./hrtfs/elev5/L5e055a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e055a.dat right -[ 19, 62 ] = ascii (fp) : "./hrtfs/elev5/L5e050a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e050a.dat right -[ 19, 63 ] = ascii (fp) : "./hrtfs/elev5/L5e045a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e045a.dat right -[ 19, 64 ] = ascii (fp) : "./hrtfs/elev5/L5e040a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e040a.dat right -[ 19, 65 ] = ascii (fp) : "./hrtfs/elev5/L5e035a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e035a.dat right -[ 19, 66 ] = ascii (fp) : "./hrtfs/elev5/L5e030a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e030a.dat right -[ 19, 67 ] = ascii (fp) : "./hrtfs/elev5/L5e025a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e025a.dat right -[ 19, 68 ] = ascii (fp) : "./hrtfs/elev5/L5e020a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e020a.dat right -[ 19, 69 ] = ascii (fp) : "./hrtfs/elev5/L5e015a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e015a.dat right -[ 19, 70 ] = ascii (fp) : "./hrtfs/elev5/L5e010a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e010a.dat right -[ 19, 71 ] = ascii (fp) : "./hrtfs/elev5/L5e005a.dat left - + ascii (fp) : "./hrtfs/elev5/R5e005a.dat right +[ 19, 0 ] = ascii (fp) : "./hrtfs/elev5/L5e000a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e000a.dat" right +[ 19, 1 ] = ascii (fp) : "./hrtfs/elev5/L5e355a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e355a.dat" right +[ 19, 2 ] = ascii (fp) : "./hrtfs/elev5/L5e350a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e350a.dat" right +[ 19, 3 ] = ascii (fp) : "./hrtfs/elev5/L5e345a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e345a.dat" right +[ 19, 4 ] = ascii (fp) : "./hrtfs/elev5/L5e340a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e340a.dat" right +[ 19, 5 ] = ascii (fp) : "./hrtfs/elev5/L5e335a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e335a.dat" right +[ 19, 6 ] = ascii (fp) : "./hrtfs/elev5/L5e330a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e330a.dat" right +[ 19, 7 ] = ascii (fp) : "./hrtfs/elev5/L5e325a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e325a.dat" right +[ 19, 8 ] = ascii (fp) : "./hrtfs/elev5/L5e320a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e320a.dat" right +[ 19, 9 ] = ascii (fp) : "./hrtfs/elev5/L5e315a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e315a.dat" right +[ 19, 10 ] = ascii (fp) : "./hrtfs/elev5/L5e310a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e310a.dat" right +[ 19, 11 ] = ascii (fp) : "./hrtfs/elev5/L5e305a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e305a.dat" right +[ 19, 12 ] = ascii (fp) : "./hrtfs/elev5/L5e300a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e300a.dat" right +[ 19, 13 ] = ascii (fp) : "./hrtfs/elev5/L5e295a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e295a.dat" right +[ 19, 14 ] = ascii (fp) : "./hrtfs/elev5/L5e290a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e290a.dat" right +[ 19, 15 ] = ascii (fp) : "./hrtfs/elev5/L5e285a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e285a.dat" right +[ 19, 16 ] = ascii (fp) : "./hrtfs/elev5/L5e280a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e280a.dat" right +[ 19, 17 ] = ascii (fp) : "./hrtfs/elev5/L5e275a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e275a.dat" right +[ 19, 18 ] = ascii (fp) : "./hrtfs/elev5/L5e270a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e270a.dat" right +[ 19, 19 ] = ascii (fp) : "./hrtfs/elev5/L5e265a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e265a.dat" right +[ 19, 20 ] = ascii (fp) : "./hrtfs/elev5/L5e260a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e260a.dat" right +[ 19, 21 ] = ascii (fp) : "./hrtfs/elev5/L5e255a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e255a.dat" right +[ 19, 22 ] = ascii (fp) : "./hrtfs/elev5/L5e250a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e250a.dat" right +[ 19, 23 ] = ascii (fp) : "./hrtfs/elev5/L5e245a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e245a.dat" right +[ 19, 24 ] = ascii (fp) : "./hrtfs/elev5/L5e240a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e240a.dat" right +[ 19, 25 ] = ascii (fp) : "./hrtfs/elev5/L5e235a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e235a.dat" right +[ 19, 26 ] = ascii (fp) : "./hrtfs/elev5/L5e230a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e230a.dat" right +[ 19, 27 ] = ascii (fp) : "./hrtfs/elev5/L5e225a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e225a.dat" right +[ 19, 28 ] = ascii (fp) : "./hrtfs/elev5/L5e220a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e220a.dat" right +[ 19, 29 ] = ascii (fp) : "./hrtfs/elev5/L5e215a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e215a.dat" right +[ 19, 30 ] = ascii (fp) : "./hrtfs/elev5/L5e210a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e210a.dat" right +[ 19, 31 ] = ascii (fp) : "./hrtfs/elev5/L5e205a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e205a.dat" right +[ 19, 32 ] = ascii (fp) : "./hrtfs/elev5/L5e200a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e200a.dat" right +[ 19, 33 ] = ascii (fp) : "./hrtfs/elev5/L5e195a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e195a.dat" right +[ 19, 34 ] = ascii (fp) : "./hrtfs/elev5/L5e190a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e190a.dat" right +[ 19, 35 ] = ascii (fp) : "./hrtfs/elev5/L5e185a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e185a.dat" right +[ 19, 36 ] = ascii (fp) : "./hrtfs/elev5/L5e180a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e180a.dat" right +[ 19, 37 ] = ascii (fp) : "./hrtfs/elev5/L5e175a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e175a.dat" right +[ 19, 38 ] = ascii (fp) : "./hrtfs/elev5/L5e170a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e170a.dat" right +[ 19, 39 ] = ascii (fp) : "./hrtfs/elev5/L5e165a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e165a.dat" right +[ 19, 40 ] = ascii (fp) : "./hrtfs/elev5/L5e160a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e160a.dat" right +[ 19, 41 ] = ascii (fp) : "./hrtfs/elev5/L5e155a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e155a.dat" right +[ 19, 42 ] = ascii (fp) : "./hrtfs/elev5/L5e150a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e150a.dat" right +[ 19, 43 ] = ascii (fp) : "./hrtfs/elev5/L5e145a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e145a.dat" right +[ 19, 44 ] = ascii (fp) : "./hrtfs/elev5/L5e140a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e140a.dat" right +[ 19, 45 ] = ascii (fp) : "./hrtfs/elev5/L5e135a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e135a.dat" right +[ 19, 46 ] = ascii (fp) : "./hrtfs/elev5/L5e130a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e130a.dat" right +[ 19, 47 ] = ascii (fp) : "./hrtfs/elev5/L5e125a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e125a.dat" right +[ 19, 48 ] = ascii (fp) : "./hrtfs/elev5/L5e120a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e120a.dat" right +[ 19, 49 ] = ascii (fp) : "./hrtfs/elev5/L5e115a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e115a.dat" right +[ 19, 50 ] = ascii (fp) : "./hrtfs/elev5/L5e110a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e110a.dat" right +[ 19, 51 ] = ascii (fp) : "./hrtfs/elev5/L5e105a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e105a.dat" right +[ 19, 52 ] = ascii (fp) : "./hrtfs/elev5/L5e100a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e100a.dat" right +[ 19, 53 ] = ascii (fp) : "./hrtfs/elev5/L5e095a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e095a.dat" right +[ 19, 54 ] = ascii (fp) : "./hrtfs/elev5/L5e090a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e090a.dat" right +[ 19, 55 ] = ascii (fp) : "./hrtfs/elev5/L5e085a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e085a.dat" right +[ 19, 56 ] = ascii (fp) : "./hrtfs/elev5/L5e080a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e080a.dat" right +[ 19, 57 ] = ascii (fp) : "./hrtfs/elev5/L5e075a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e075a.dat" right +[ 19, 58 ] = ascii (fp) : "./hrtfs/elev5/L5e070a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e070a.dat" right +[ 19, 59 ] = ascii (fp) : "./hrtfs/elev5/L5e065a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e065a.dat" right +[ 19, 60 ] = ascii (fp) : "./hrtfs/elev5/L5e060a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e060a.dat" right +[ 19, 61 ] = ascii (fp) : "./hrtfs/elev5/L5e055a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e055a.dat" right +[ 19, 62 ] = ascii (fp) : "./hrtfs/elev5/L5e050a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e050a.dat" right +[ 19, 63 ] = ascii (fp) : "./hrtfs/elev5/L5e045a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e045a.dat" right +[ 19, 64 ] = ascii (fp) : "./hrtfs/elev5/L5e040a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e040a.dat" right +[ 19, 65 ] = ascii (fp) : "./hrtfs/elev5/L5e035a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e035a.dat" right +[ 19, 66 ] = ascii (fp) : "./hrtfs/elev5/L5e030a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e030a.dat" right +[ 19, 67 ] = ascii (fp) : "./hrtfs/elev5/L5e025a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e025a.dat" right +[ 19, 68 ] = ascii (fp) : "./hrtfs/elev5/L5e020a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e020a.dat" right +[ 19, 69 ] = ascii (fp) : "./hrtfs/elev5/L5e015a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e015a.dat" right +[ 19, 70 ] = ascii (fp) : "./hrtfs/elev5/L5e010a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e010a.dat" right +[ 19, 71 ] = ascii (fp) : "./hrtfs/elev5/L5e005a.dat" left + + ascii (fp) : "./hrtfs/elev5/R5e005a.dat" right -[ 20, 0 ] = ascii (fp) : "./hrtfs/elev10/L10e000a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e000a.dat right -[ 20, 1 ] = ascii (fp) : "./hrtfs/elev10/L10e355a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e355a.dat right -[ 20, 2 ] = ascii (fp) : "./hrtfs/elev10/L10e350a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e350a.dat right -[ 20, 3 ] = ascii (fp) : "./hrtfs/elev10/L10e345a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e345a.dat right -[ 20, 4 ] = ascii (fp) : "./hrtfs/elev10/L10e340a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e340a.dat right -[ 20, 5 ] = ascii (fp) : "./hrtfs/elev10/L10e335a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e335a.dat right -[ 20, 6 ] = ascii (fp) : "./hrtfs/elev10/L10e330a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e330a.dat right -[ 20, 7 ] = ascii (fp) : "./hrtfs/elev10/L10e325a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e325a.dat right -[ 20, 8 ] = ascii (fp) : "./hrtfs/elev10/L10e320a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e320a.dat right -[ 20, 9 ] = ascii (fp) : "./hrtfs/elev10/L10e315a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e315a.dat right -[ 20, 10 ] = ascii (fp) : "./hrtfs/elev10/L10e310a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e310a.dat right -[ 20, 11 ] = ascii (fp) : "./hrtfs/elev10/L10e305a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e305a.dat right -[ 20, 12 ] = ascii (fp) : "./hrtfs/elev10/L10e300a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e300a.dat right -[ 20, 13 ] = ascii (fp) : "./hrtfs/elev10/L10e295a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e295a.dat right -[ 20, 14 ] = ascii (fp) : "./hrtfs/elev10/L10e290a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e290a.dat right -[ 20, 15 ] = ascii (fp) : "./hrtfs/elev10/L10e285a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e285a.dat right -[ 20, 16 ] = ascii (fp) : "./hrtfs/elev10/L10e280a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e280a.dat right -[ 20, 17 ] = ascii (fp) : "./hrtfs/elev10/L10e275a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e275a.dat right -[ 20, 18 ] = ascii (fp) : "./hrtfs/elev10/L10e270a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e270a.dat right -[ 20, 19 ] = ascii (fp) : "./hrtfs/elev10/L10e265a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e265a.dat right -[ 20, 20 ] = ascii (fp) : "./hrtfs/elev10/L10e260a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e260a.dat right -[ 20, 21 ] = ascii (fp) : "./hrtfs/elev10/L10e255a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e255a.dat right -[ 20, 22 ] = ascii (fp) : "./hrtfs/elev10/L10e250a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e250a.dat right -[ 20, 23 ] = ascii (fp) : "./hrtfs/elev10/L10e245a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e245a.dat right -[ 20, 24 ] = ascii (fp) : "./hrtfs/elev10/L10e240a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e240a.dat right -[ 20, 25 ] = ascii (fp) : "./hrtfs/elev10/L10e235a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e235a.dat right -[ 20, 26 ] = ascii (fp) : "./hrtfs/elev10/L10e230a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e230a.dat right -[ 20, 27 ] = ascii (fp) : "./hrtfs/elev10/L10e225a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e225a.dat right -[ 20, 28 ] = ascii (fp) : "./hrtfs/elev10/L10e220a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e220a.dat right -[ 20, 29 ] = ascii (fp) : "./hrtfs/elev10/L10e215a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e215a.dat right -[ 20, 30 ] = ascii (fp) : "./hrtfs/elev10/L10e210a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e210a.dat right -[ 20, 31 ] = ascii (fp) : "./hrtfs/elev10/L10e205a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e205a.dat right -[ 20, 32 ] = ascii (fp) : "./hrtfs/elev10/L10e200a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e200a.dat right -[ 20, 33 ] = ascii (fp) : "./hrtfs/elev10/L10e195a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e195a.dat right -[ 20, 34 ] = ascii (fp) : "./hrtfs/elev10/L10e190a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e190a.dat right -[ 20, 35 ] = ascii (fp) : "./hrtfs/elev10/L10e185a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e185a.dat right -[ 20, 36 ] = ascii (fp) : "./hrtfs/elev10/L10e180a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e180a.dat right -[ 20, 37 ] = ascii (fp) : "./hrtfs/elev10/L10e175a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e175a.dat right -[ 20, 38 ] = ascii (fp) : "./hrtfs/elev10/L10e170a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e170a.dat right -[ 20, 39 ] = ascii (fp) : "./hrtfs/elev10/L10e165a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e165a.dat right -[ 20, 40 ] = ascii (fp) : "./hrtfs/elev10/L10e160a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e160a.dat right -[ 20, 41 ] = ascii (fp) : "./hrtfs/elev10/L10e155a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e155a.dat right -[ 20, 42 ] = ascii (fp) : "./hrtfs/elev10/L10e150a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e150a.dat right -[ 20, 43 ] = ascii (fp) : "./hrtfs/elev10/L10e145a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e145a.dat right -[ 20, 44 ] = ascii (fp) : "./hrtfs/elev10/L10e140a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e140a.dat right -[ 20, 45 ] = ascii (fp) : "./hrtfs/elev10/L10e135a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e135a.dat right -[ 20, 46 ] = ascii (fp) : "./hrtfs/elev10/L10e130a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e130a.dat right -[ 20, 47 ] = ascii (fp) : "./hrtfs/elev10/L10e125a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e125a.dat right -[ 20, 48 ] = ascii (fp) : "./hrtfs/elev10/L10e120a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e120a.dat right -[ 20, 49 ] = ascii (fp) : "./hrtfs/elev10/L10e115a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e115a.dat right -[ 20, 50 ] = ascii (fp) : "./hrtfs/elev10/L10e110a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e110a.dat right -[ 20, 51 ] = ascii (fp) : "./hrtfs/elev10/L10e105a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e105a.dat right -[ 20, 52 ] = ascii (fp) : "./hrtfs/elev10/L10e100a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e100a.dat right -[ 20, 53 ] = ascii (fp) : "./hrtfs/elev10/L10e095a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e095a.dat right -[ 20, 54 ] = ascii (fp) : "./hrtfs/elev10/L10e090a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e090a.dat right -[ 20, 55 ] = ascii (fp) : "./hrtfs/elev10/L10e085a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e085a.dat right -[ 20, 56 ] = ascii (fp) : "./hrtfs/elev10/L10e080a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e080a.dat right -[ 20, 57 ] = ascii (fp) : "./hrtfs/elev10/L10e075a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e075a.dat right -[ 20, 58 ] = ascii (fp) : "./hrtfs/elev10/L10e070a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e070a.dat right -[ 20, 59 ] = ascii (fp) : "./hrtfs/elev10/L10e065a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e065a.dat right -[ 20, 60 ] = ascii (fp) : "./hrtfs/elev10/L10e060a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e060a.dat right -[ 20, 61 ] = ascii (fp) : "./hrtfs/elev10/L10e055a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e055a.dat right -[ 20, 62 ] = ascii (fp) : "./hrtfs/elev10/L10e050a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e050a.dat right -[ 20, 63 ] = ascii (fp) : "./hrtfs/elev10/L10e045a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e045a.dat right -[ 20, 64 ] = ascii (fp) : "./hrtfs/elev10/L10e040a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e040a.dat right -[ 20, 65 ] = ascii (fp) : "./hrtfs/elev10/L10e035a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e035a.dat right -[ 20, 66 ] = ascii (fp) : "./hrtfs/elev10/L10e030a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e030a.dat right -[ 20, 67 ] = ascii (fp) : "./hrtfs/elev10/L10e025a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e025a.dat right -[ 20, 68 ] = ascii (fp) : "./hrtfs/elev10/L10e020a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e020a.dat right -[ 20, 69 ] = ascii (fp) : "./hrtfs/elev10/L10e015a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e015a.dat right -[ 20, 70 ] = ascii (fp) : "./hrtfs/elev10/L10e010a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e010a.dat right -[ 20, 71 ] = ascii (fp) : "./hrtfs/elev10/L10e005a.dat left - + ascii (fp) : "./hrtfs/elev10/R10e005a.dat right +[ 20, 0 ] = ascii (fp) : "./hrtfs/elev10/L10e000a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e000a.dat" right +[ 20, 1 ] = ascii (fp) : "./hrtfs/elev10/L10e355a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e355a.dat" right +[ 20, 2 ] = ascii (fp) : "./hrtfs/elev10/L10e350a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e350a.dat" right +[ 20, 3 ] = ascii (fp) : "./hrtfs/elev10/L10e345a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e345a.dat" right +[ 20, 4 ] = ascii (fp) : "./hrtfs/elev10/L10e340a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e340a.dat" right +[ 20, 5 ] = ascii (fp) : "./hrtfs/elev10/L10e335a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e335a.dat" right +[ 20, 6 ] = ascii (fp) : "./hrtfs/elev10/L10e330a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e330a.dat" right +[ 20, 7 ] = ascii (fp) : "./hrtfs/elev10/L10e325a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e325a.dat" right +[ 20, 8 ] = ascii (fp) : "./hrtfs/elev10/L10e320a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e320a.dat" right +[ 20, 9 ] = ascii (fp) : "./hrtfs/elev10/L10e315a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e315a.dat" right +[ 20, 10 ] = ascii (fp) : "./hrtfs/elev10/L10e310a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e310a.dat" right +[ 20, 11 ] = ascii (fp) : "./hrtfs/elev10/L10e305a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e305a.dat" right +[ 20, 12 ] = ascii (fp) : "./hrtfs/elev10/L10e300a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e300a.dat" right +[ 20, 13 ] = ascii (fp) : "./hrtfs/elev10/L10e295a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e295a.dat" right +[ 20, 14 ] = ascii (fp) : "./hrtfs/elev10/L10e290a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e290a.dat" right +[ 20, 15 ] = ascii (fp) : "./hrtfs/elev10/L10e285a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e285a.dat" right +[ 20, 16 ] = ascii (fp) : "./hrtfs/elev10/L10e280a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e280a.dat" right +[ 20, 17 ] = ascii (fp) : "./hrtfs/elev10/L10e275a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e275a.dat" right +[ 20, 18 ] = ascii (fp) : "./hrtfs/elev10/L10e270a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e270a.dat" right +[ 20, 19 ] = ascii (fp) : "./hrtfs/elev10/L10e265a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e265a.dat" right +[ 20, 20 ] = ascii (fp) : "./hrtfs/elev10/L10e260a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e260a.dat" right +[ 20, 21 ] = ascii (fp) : "./hrtfs/elev10/L10e255a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e255a.dat" right +[ 20, 22 ] = ascii (fp) : "./hrtfs/elev10/L10e250a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e250a.dat" right +[ 20, 23 ] = ascii (fp) : "./hrtfs/elev10/L10e245a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e245a.dat" right +[ 20, 24 ] = ascii (fp) : "./hrtfs/elev10/L10e240a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e240a.dat" right +[ 20, 25 ] = ascii (fp) : "./hrtfs/elev10/L10e235a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e235a.dat" right +[ 20, 26 ] = ascii (fp) : "./hrtfs/elev10/L10e230a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e230a.dat" right +[ 20, 27 ] = ascii (fp) : "./hrtfs/elev10/L10e225a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e225a.dat" right +[ 20, 28 ] = ascii (fp) : "./hrtfs/elev10/L10e220a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e220a.dat" right +[ 20, 29 ] = ascii (fp) : "./hrtfs/elev10/L10e215a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e215a.dat" right +[ 20, 30 ] = ascii (fp) : "./hrtfs/elev10/L10e210a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e210a.dat" right +[ 20, 31 ] = ascii (fp) : "./hrtfs/elev10/L10e205a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e205a.dat" right +[ 20, 32 ] = ascii (fp) : "./hrtfs/elev10/L10e200a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e200a.dat" right +[ 20, 33 ] = ascii (fp) : "./hrtfs/elev10/L10e195a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e195a.dat" right +[ 20, 34 ] = ascii (fp) : "./hrtfs/elev10/L10e190a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e190a.dat" right +[ 20, 35 ] = ascii (fp) : "./hrtfs/elev10/L10e185a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e185a.dat" right +[ 20, 36 ] = ascii (fp) : "./hrtfs/elev10/L10e180a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e180a.dat" right +[ 20, 37 ] = ascii (fp) : "./hrtfs/elev10/L10e175a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e175a.dat" right +[ 20, 38 ] = ascii (fp) : "./hrtfs/elev10/L10e170a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e170a.dat" right +[ 20, 39 ] = ascii (fp) : "./hrtfs/elev10/L10e165a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e165a.dat" right +[ 20, 40 ] = ascii (fp) : "./hrtfs/elev10/L10e160a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e160a.dat" right +[ 20, 41 ] = ascii (fp) : "./hrtfs/elev10/L10e155a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e155a.dat" right +[ 20, 42 ] = ascii (fp) : "./hrtfs/elev10/L10e150a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e150a.dat" right +[ 20, 43 ] = ascii (fp) : "./hrtfs/elev10/L10e145a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e145a.dat" right +[ 20, 44 ] = ascii (fp) : "./hrtfs/elev10/L10e140a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e140a.dat" right +[ 20, 45 ] = ascii (fp) : "./hrtfs/elev10/L10e135a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e135a.dat" right +[ 20, 46 ] = ascii (fp) : "./hrtfs/elev10/L10e130a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e130a.dat" right +[ 20, 47 ] = ascii (fp) : "./hrtfs/elev10/L10e125a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e125a.dat" right +[ 20, 48 ] = ascii (fp) : "./hrtfs/elev10/L10e120a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e120a.dat" right +[ 20, 49 ] = ascii (fp) : "./hrtfs/elev10/L10e115a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e115a.dat" right +[ 20, 50 ] = ascii (fp) : "./hrtfs/elev10/L10e110a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e110a.dat" right +[ 20, 51 ] = ascii (fp) : "./hrtfs/elev10/L10e105a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e105a.dat" right +[ 20, 52 ] = ascii (fp) : "./hrtfs/elev10/L10e100a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e100a.dat" right +[ 20, 53 ] = ascii (fp) : "./hrtfs/elev10/L10e095a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e095a.dat" right +[ 20, 54 ] = ascii (fp) : "./hrtfs/elev10/L10e090a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e090a.dat" right +[ 20, 55 ] = ascii (fp) : "./hrtfs/elev10/L10e085a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e085a.dat" right +[ 20, 56 ] = ascii (fp) : "./hrtfs/elev10/L10e080a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e080a.dat" right +[ 20, 57 ] = ascii (fp) : "./hrtfs/elev10/L10e075a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e075a.dat" right +[ 20, 58 ] = ascii (fp) : "./hrtfs/elev10/L10e070a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e070a.dat" right +[ 20, 59 ] = ascii (fp) : "./hrtfs/elev10/L10e065a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e065a.dat" right +[ 20, 60 ] = ascii (fp) : "./hrtfs/elev10/L10e060a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e060a.dat" right +[ 20, 61 ] = ascii (fp) : "./hrtfs/elev10/L10e055a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e055a.dat" right +[ 20, 62 ] = ascii (fp) : "./hrtfs/elev10/L10e050a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e050a.dat" right +[ 20, 63 ] = ascii (fp) : "./hrtfs/elev10/L10e045a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e045a.dat" right +[ 20, 64 ] = ascii (fp) : "./hrtfs/elev10/L10e040a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e040a.dat" right +[ 20, 65 ] = ascii (fp) : "./hrtfs/elev10/L10e035a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e035a.dat" right +[ 20, 66 ] = ascii (fp) : "./hrtfs/elev10/L10e030a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e030a.dat" right +[ 20, 67 ] = ascii (fp) : "./hrtfs/elev10/L10e025a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e025a.dat" right +[ 20, 68 ] = ascii (fp) : "./hrtfs/elev10/L10e020a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e020a.dat" right +[ 20, 69 ] = ascii (fp) : "./hrtfs/elev10/L10e015a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e015a.dat" right +[ 20, 70 ] = ascii (fp) : "./hrtfs/elev10/L10e010a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e010a.dat" right +[ 20, 71 ] = ascii (fp) : "./hrtfs/elev10/L10e005a.dat" left + + ascii (fp) : "./hrtfs/elev10/R10e005a.dat" right -[ 21, 0 ] = ascii (fp) : "./hrtfs/elev15/L15e000a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e000a.dat right -[ 21, 1 ] = ascii (fp) : "./hrtfs/elev15/L15e355a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e355a.dat right -[ 21, 2 ] = ascii (fp) : "./hrtfs/elev15/L15e350a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e350a.dat right -[ 21, 3 ] = ascii (fp) : "./hrtfs/elev15/L15e345a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e345a.dat right -[ 21, 4 ] = ascii (fp) : "./hrtfs/elev15/L15e340a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e340a.dat right -[ 21, 5 ] = ascii (fp) : "./hrtfs/elev15/L15e335a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e335a.dat right -[ 21, 6 ] = ascii (fp) : "./hrtfs/elev15/L15e330a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e330a.dat right -[ 21, 7 ] = ascii (fp) : "./hrtfs/elev15/L15e325a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e325a.dat right -[ 21, 8 ] = ascii (fp) : "./hrtfs/elev15/L15e320a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e320a.dat right -[ 21, 9 ] = ascii (fp) : "./hrtfs/elev15/L15e315a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e315a.dat right -[ 21, 10 ] = ascii (fp) : "./hrtfs/elev15/L15e310a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e310a.dat right -[ 21, 11 ] = ascii (fp) : "./hrtfs/elev15/L15e305a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e305a.dat right -[ 21, 12 ] = ascii (fp) : "./hrtfs/elev15/L15e300a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e300a.dat right -[ 21, 13 ] = ascii (fp) : "./hrtfs/elev15/L15e295a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e295a.dat right -[ 21, 14 ] = ascii (fp) : "./hrtfs/elev15/L15e290a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e290a.dat right -[ 21, 15 ] = ascii (fp) : "./hrtfs/elev15/L15e285a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e285a.dat right -[ 21, 16 ] = ascii (fp) : "./hrtfs/elev15/L15e280a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e280a.dat right -[ 21, 17 ] = ascii (fp) : "./hrtfs/elev15/L15e275a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e275a.dat right -[ 21, 18 ] = ascii (fp) : "./hrtfs/elev15/L15e270a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e270a.dat right -[ 21, 19 ] = ascii (fp) : "./hrtfs/elev15/L15e265a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e265a.dat right -[ 21, 20 ] = ascii (fp) : "./hrtfs/elev15/L15e260a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e260a.dat right -[ 21, 21 ] = ascii (fp) : "./hrtfs/elev15/L15e255a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e255a.dat right -[ 21, 22 ] = ascii (fp) : "./hrtfs/elev15/L15e250a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e250a.dat right -[ 21, 23 ] = ascii (fp) : "./hrtfs/elev15/L15e245a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e245a.dat right -[ 21, 24 ] = ascii (fp) : "./hrtfs/elev15/L15e240a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e240a.dat right -[ 21, 25 ] = ascii (fp) : "./hrtfs/elev15/L15e235a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e235a.dat right -[ 21, 26 ] = ascii (fp) : "./hrtfs/elev15/L15e230a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e230a.dat right -[ 21, 27 ] = ascii (fp) : "./hrtfs/elev15/L15e225a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e225a.dat right -[ 21, 28 ] = ascii (fp) : "./hrtfs/elev15/L15e220a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e220a.dat right -[ 21, 29 ] = ascii (fp) : "./hrtfs/elev15/L15e215a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e215a.dat right -[ 21, 30 ] = ascii (fp) : "./hrtfs/elev15/L15e210a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e210a.dat right -[ 21, 31 ] = ascii (fp) : "./hrtfs/elev15/L15e205a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e205a.dat right -[ 21, 32 ] = ascii (fp) : "./hrtfs/elev15/L15e200a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e200a.dat right -[ 21, 33 ] = ascii (fp) : "./hrtfs/elev15/L15e195a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e195a.dat right -[ 21, 34 ] = ascii (fp) : "./hrtfs/elev15/L15e190a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e190a.dat right -[ 21, 35 ] = ascii (fp) : "./hrtfs/elev15/L15e185a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e185a.dat right -[ 21, 36 ] = ascii (fp) : "./hrtfs/elev15/L15e180a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e180a.dat right -[ 21, 37 ] = ascii (fp) : "./hrtfs/elev15/L15e175a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e175a.dat right -[ 21, 38 ] = ascii (fp) : "./hrtfs/elev15/L15e170a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e170a.dat right -[ 21, 39 ] = ascii (fp) : "./hrtfs/elev15/L15e165a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e165a.dat right -[ 21, 40 ] = ascii (fp) : "./hrtfs/elev15/L15e160a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e160a.dat right -[ 21, 41 ] = ascii (fp) : "./hrtfs/elev15/L15e155a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e155a.dat right -[ 21, 42 ] = ascii (fp) : "./hrtfs/elev15/L15e150a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e150a.dat right -[ 21, 43 ] = ascii (fp) : "./hrtfs/elev15/L15e145a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e145a.dat right -[ 21, 44 ] = ascii (fp) : "./hrtfs/elev15/L15e140a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e140a.dat right -[ 21, 45 ] = ascii (fp) : "./hrtfs/elev15/L15e135a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e135a.dat right -[ 21, 46 ] = ascii (fp) : "./hrtfs/elev15/L15e130a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e130a.dat right -[ 21, 47 ] = ascii (fp) : "./hrtfs/elev15/L15e125a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e125a.dat right -[ 21, 48 ] = ascii (fp) : "./hrtfs/elev15/L15e120a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e120a.dat right -[ 21, 49 ] = ascii (fp) : "./hrtfs/elev15/L15e115a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e115a.dat right -[ 21, 50 ] = ascii (fp) : "./hrtfs/elev15/L15e110a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e110a.dat right -[ 21, 51 ] = ascii (fp) : "./hrtfs/elev15/L15e105a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e105a.dat right -[ 21, 52 ] = ascii (fp) : "./hrtfs/elev15/L15e100a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e100a.dat right -[ 21, 53 ] = ascii (fp) : "./hrtfs/elev15/L15e095a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e095a.dat right -[ 21, 54 ] = ascii (fp) : "./hrtfs/elev15/L15e090a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e090a.dat right -[ 21, 55 ] = ascii (fp) : "./hrtfs/elev15/L15e085a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e085a.dat right -[ 21, 56 ] = ascii (fp) : "./hrtfs/elev15/L15e080a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e080a.dat right -[ 21, 57 ] = ascii (fp) : "./hrtfs/elev15/L15e075a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e075a.dat right -[ 21, 58 ] = ascii (fp) : "./hrtfs/elev15/L15e070a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e070a.dat right -[ 21, 59 ] = ascii (fp) : "./hrtfs/elev15/L15e065a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e065a.dat right -[ 21, 60 ] = ascii (fp) : "./hrtfs/elev15/L15e060a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e060a.dat right -[ 21, 61 ] = ascii (fp) : "./hrtfs/elev15/L15e055a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e055a.dat right -[ 21, 62 ] = ascii (fp) : "./hrtfs/elev15/L15e050a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e050a.dat right -[ 21, 63 ] = ascii (fp) : "./hrtfs/elev15/L15e045a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e045a.dat right -[ 21, 64 ] = ascii (fp) : "./hrtfs/elev15/L15e040a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e040a.dat right -[ 21, 65 ] = ascii (fp) : "./hrtfs/elev15/L15e035a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e035a.dat right -[ 21, 66 ] = ascii (fp) : "./hrtfs/elev15/L15e030a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e030a.dat right -[ 21, 67 ] = ascii (fp) : "./hrtfs/elev15/L15e025a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e025a.dat right -[ 21, 68 ] = ascii (fp) : "./hrtfs/elev15/L15e020a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e020a.dat right -[ 21, 69 ] = ascii (fp) : "./hrtfs/elev15/L15e015a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e015a.dat right -[ 21, 70 ] = ascii (fp) : "./hrtfs/elev15/L15e010a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e010a.dat right -[ 21, 71 ] = ascii (fp) : "./hrtfs/elev15/L15e005a.dat left - + ascii (fp) : "./hrtfs/elev15/R15e005a.dat right +[ 21, 0 ] = ascii (fp) : "./hrtfs/elev15/L15e000a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e000a.dat" right +[ 21, 1 ] = ascii (fp) : "./hrtfs/elev15/L15e355a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e355a.dat" right +[ 21, 2 ] = ascii (fp) : "./hrtfs/elev15/L15e350a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e350a.dat" right +[ 21, 3 ] = ascii (fp) : "./hrtfs/elev15/L15e345a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e345a.dat" right +[ 21, 4 ] = ascii (fp) : "./hrtfs/elev15/L15e340a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e340a.dat" right +[ 21, 5 ] = ascii (fp) : "./hrtfs/elev15/L15e335a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e335a.dat" right +[ 21, 6 ] = ascii (fp) : "./hrtfs/elev15/L15e330a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e330a.dat" right +[ 21, 7 ] = ascii (fp) : "./hrtfs/elev15/L15e325a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e325a.dat" right +[ 21, 8 ] = ascii (fp) : "./hrtfs/elev15/L15e320a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e320a.dat" right +[ 21, 9 ] = ascii (fp) : "./hrtfs/elev15/L15e315a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e315a.dat" right +[ 21, 10 ] = ascii (fp) : "./hrtfs/elev15/L15e310a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e310a.dat" right +[ 21, 11 ] = ascii (fp) : "./hrtfs/elev15/L15e305a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e305a.dat" right +[ 21, 12 ] = ascii (fp) : "./hrtfs/elev15/L15e300a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e300a.dat" right +[ 21, 13 ] = ascii (fp) : "./hrtfs/elev15/L15e295a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e295a.dat" right +[ 21, 14 ] = ascii (fp) : "./hrtfs/elev15/L15e290a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e290a.dat" right +[ 21, 15 ] = ascii (fp) : "./hrtfs/elev15/L15e285a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e285a.dat" right +[ 21, 16 ] = ascii (fp) : "./hrtfs/elev15/L15e280a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e280a.dat" right +[ 21, 17 ] = ascii (fp) : "./hrtfs/elev15/L15e275a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e275a.dat" right +[ 21, 18 ] = ascii (fp) : "./hrtfs/elev15/L15e270a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e270a.dat" right +[ 21, 19 ] = ascii (fp) : "./hrtfs/elev15/L15e265a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e265a.dat" right +[ 21, 20 ] = ascii (fp) : "./hrtfs/elev15/L15e260a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e260a.dat" right +[ 21, 21 ] = ascii (fp) : "./hrtfs/elev15/L15e255a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e255a.dat" right +[ 21, 22 ] = ascii (fp) : "./hrtfs/elev15/L15e250a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e250a.dat" right +[ 21, 23 ] = ascii (fp) : "./hrtfs/elev15/L15e245a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e245a.dat" right +[ 21, 24 ] = ascii (fp) : "./hrtfs/elev15/L15e240a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e240a.dat" right +[ 21, 25 ] = ascii (fp) : "./hrtfs/elev15/L15e235a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e235a.dat" right +[ 21, 26 ] = ascii (fp) : "./hrtfs/elev15/L15e230a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e230a.dat" right +[ 21, 27 ] = ascii (fp) : "./hrtfs/elev15/L15e225a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e225a.dat" right +[ 21, 28 ] = ascii (fp) : "./hrtfs/elev15/L15e220a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e220a.dat" right +[ 21, 29 ] = ascii (fp) : "./hrtfs/elev15/L15e215a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e215a.dat" right +[ 21, 30 ] = ascii (fp) : "./hrtfs/elev15/L15e210a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e210a.dat" right +[ 21, 31 ] = ascii (fp) : "./hrtfs/elev15/L15e205a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e205a.dat" right +[ 21, 32 ] = ascii (fp) : "./hrtfs/elev15/L15e200a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e200a.dat" right +[ 21, 33 ] = ascii (fp) : "./hrtfs/elev15/L15e195a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e195a.dat" right +[ 21, 34 ] = ascii (fp) : "./hrtfs/elev15/L15e190a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e190a.dat" right +[ 21, 35 ] = ascii (fp) : "./hrtfs/elev15/L15e185a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e185a.dat" right +[ 21, 36 ] = ascii (fp) : "./hrtfs/elev15/L15e180a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e180a.dat" right +[ 21, 37 ] = ascii (fp) : "./hrtfs/elev15/L15e175a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e175a.dat" right +[ 21, 38 ] = ascii (fp) : "./hrtfs/elev15/L15e170a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e170a.dat" right +[ 21, 39 ] = ascii (fp) : "./hrtfs/elev15/L15e165a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e165a.dat" right +[ 21, 40 ] = ascii (fp) : "./hrtfs/elev15/L15e160a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e160a.dat" right +[ 21, 41 ] = ascii (fp) : "./hrtfs/elev15/L15e155a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e155a.dat" right +[ 21, 42 ] = ascii (fp) : "./hrtfs/elev15/L15e150a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e150a.dat" right +[ 21, 43 ] = ascii (fp) : "./hrtfs/elev15/L15e145a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e145a.dat" right +[ 21, 44 ] = ascii (fp) : "./hrtfs/elev15/L15e140a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e140a.dat" right +[ 21, 45 ] = ascii (fp) : "./hrtfs/elev15/L15e135a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e135a.dat" right +[ 21, 46 ] = ascii (fp) : "./hrtfs/elev15/L15e130a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e130a.dat" right +[ 21, 47 ] = ascii (fp) : "./hrtfs/elev15/L15e125a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e125a.dat" right +[ 21, 48 ] = ascii (fp) : "./hrtfs/elev15/L15e120a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e120a.dat" right +[ 21, 49 ] = ascii (fp) : "./hrtfs/elev15/L15e115a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e115a.dat" right +[ 21, 50 ] = ascii (fp) : "./hrtfs/elev15/L15e110a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e110a.dat" right +[ 21, 51 ] = ascii (fp) : "./hrtfs/elev15/L15e105a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e105a.dat" right +[ 21, 52 ] = ascii (fp) : "./hrtfs/elev15/L15e100a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e100a.dat" right +[ 21, 53 ] = ascii (fp) : "./hrtfs/elev15/L15e095a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e095a.dat" right +[ 21, 54 ] = ascii (fp) : "./hrtfs/elev15/L15e090a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e090a.dat" right +[ 21, 55 ] = ascii (fp) : "./hrtfs/elev15/L15e085a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e085a.dat" right +[ 21, 56 ] = ascii (fp) : "./hrtfs/elev15/L15e080a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e080a.dat" right +[ 21, 57 ] = ascii (fp) : "./hrtfs/elev15/L15e075a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e075a.dat" right +[ 21, 58 ] = ascii (fp) : "./hrtfs/elev15/L15e070a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e070a.dat" right +[ 21, 59 ] = ascii (fp) : "./hrtfs/elev15/L15e065a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e065a.dat" right +[ 21, 60 ] = ascii (fp) : "./hrtfs/elev15/L15e060a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e060a.dat" right +[ 21, 61 ] = ascii (fp) : "./hrtfs/elev15/L15e055a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e055a.dat" right +[ 21, 62 ] = ascii (fp) : "./hrtfs/elev15/L15e050a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e050a.dat" right +[ 21, 63 ] = ascii (fp) : "./hrtfs/elev15/L15e045a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e045a.dat" right +[ 21, 64 ] = ascii (fp) : "./hrtfs/elev15/L15e040a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e040a.dat" right +[ 21, 65 ] = ascii (fp) : "./hrtfs/elev15/L15e035a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e035a.dat" right +[ 21, 66 ] = ascii (fp) : "./hrtfs/elev15/L15e030a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e030a.dat" right +[ 21, 67 ] = ascii (fp) : "./hrtfs/elev15/L15e025a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e025a.dat" right +[ 21, 68 ] = ascii (fp) : "./hrtfs/elev15/L15e020a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e020a.dat" right +[ 21, 69 ] = ascii (fp) : "./hrtfs/elev15/L15e015a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e015a.dat" right +[ 21, 70 ] = ascii (fp) : "./hrtfs/elev15/L15e010a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e010a.dat" right +[ 21, 71 ] = ascii (fp) : "./hrtfs/elev15/L15e005a.dat" left + + ascii (fp) : "./hrtfs/elev15/R15e005a.dat" right -[ 22, 0 ] = ascii (fp) : "./hrtfs/elev20/L20e000a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e000a.dat right -[ 22, 1 ] = ascii (fp) : "./hrtfs/elev20/L20e355a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e355a.dat right -[ 22, 2 ] = ascii (fp) : "./hrtfs/elev20/L20e350a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e350a.dat right -[ 22, 3 ] = ascii (fp) : "./hrtfs/elev20/L20e345a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e345a.dat right -[ 22, 4 ] = ascii (fp) : "./hrtfs/elev20/L20e340a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e340a.dat right -[ 22, 5 ] = ascii (fp) : "./hrtfs/elev20/L20e335a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e335a.dat right -[ 22, 6 ] = ascii (fp) : "./hrtfs/elev20/L20e330a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e330a.dat right -[ 22, 7 ] = ascii (fp) : "./hrtfs/elev20/L20e325a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e325a.dat right -[ 22, 8 ] = ascii (fp) : "./hrtfs/elev20/L20e320a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e320a.dat right -[ 22, 9 ] = ascii (fp) : "./hrtfs/elev20/L20e315a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e315a.dat right -[ 22, 10 ] = ascii (fp) : "./hrtfs/elev20/L20e310a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e310a.dat right -[ 22, 11 ] = ascii (fp) : "./hrtfs/elev20/L20e305a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e305a.dat right -[ 22, 12 ] = ascii (fp) : "./hrtfs/elev20/L20e300a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e300a.dat right -[ 22, 13 ] = ascii (fp) : "./hrtfs/elev20/L20e295a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e295a.dat right -[ 22, 14 ] = ascii (fp) : "./hrtfs/elev20/L20e290a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e290a.dat right -[ 22, 15 ] = ascii (fp) : "./hrtfs/elev20/L20e285a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e285a.dat right -[ 22, 16 ] = ascii (fp) : "./hrtfs/elev20/L20e280a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e280a.dat right -[ 22, 17 ] = ascii (fp) : "./hrtfs/elev20/L20e275a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e275a.dat right -[ 22, 18 ] = ascii (fp) : "./hrtfs/elev20/L20e270a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e270a.dat right -[ 22, 19 ] = ascii (fp) : "./hrtfs/elev20/L20e265a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e265a.dat right -[ 22, 20 ] = ascii (fp) : "./hrtfs/elev20/L20e260a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e260a.dat right -[ 22, 21 ] = ascii (fp) : "./hrtfs/elev20/L20e255a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e255a.dat right -[ 22, 22 ] = ascii (fp) : "./hrtfs/elev20/L20e250a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e250a.dat right -[ 22, 23 ] = ascii (fp) : "./hrtfs/elev20/L20e245a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e245a.dat right -[ 22, 24 ] = ascii (fp) : "./hrtfs/elev20/L20e240a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e240a.dat right -[ 22, 25 ] = ascii (fp) : "./hrtfs/elev20/L20e235a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e235a.dat right -[ 22, 26 ] = ascii (fp) : "./hrtfs/elev20/L20e230a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e230a.dat right -[ 22, 27 ] = ascii (fp) : "./hrtfs/elev20/L20e225a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e225a.dat right -[ 22, 28 ] = ascii (fp) : "./hrtfs/elev20/L20e220a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e220a.dat right -[ 22, 29 ] = ascii (fp) : "./hrtfs/elev20/L20e215a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e215a.dat right -[ 22, 30 ] = ascii (fp) : "./hrtfs/elev20/L20e210a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e210a.dat right -[ 22, 31 ] = ascii (fp) : "./hrtfs/elev20/L20e205a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e205a.dat right -[ 22, 32 ] = ascii (fp) : "./hrtfs/elev20/L20e200a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e200a.dat right -[ 22, 33 ] = ascii (fp) : "./hrtfs/elev20/L20e195a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e195a.dat right -[ 22, 34 ] = ascii (fp) : "./hrtfs/elev20/L20e190a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e190a.dat right -[ 22, 35 ] = ascii (fp) : "./hrtfs/elev20/L20e185a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e185a.dat right -[ 22, 36 ] = ascii (fp) : "./hrtfs/elev20/L20e180a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e180a.dat right -[ 22, 37 ] = ascii (fp) : "./hrtfs/elev20/L20e175a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e175a.dat right -[ 22, 38 ] = ascii (fp) : "./hrtfs/elev20/L20e170a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e170a.dat right -[ 22, 39 ] = ascii (fp) : "./hrtfs/elev20/L20e165a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e165a.dat right -[ 22, 40 ] = ascii (fp) : "./hrtfs/elev20/L20e160a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e160a.dat right -[ 22, 41 ] = ascii (fp) : "./hrtfs/elev20/L20e155a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e155a.dat right -[ 22, 42 ] = ascii (fp) : "./hrtfs/elev20/L20e150a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e150a.dat right -[ 22, 43 ] = ascii (fp) : "./hrtfs/elev20/L20e145a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e145a.dat right -[ 22, 44 ] = ascii (fp) : "./hrtfs/elev20/L20e140a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e140a.dat right -[ 22, 45 ] = ascii (fp) : "./hrtfs/elev20/L20e135a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e135a.dat right -[ 22, 46 ] = ascii (fp) : "./hrtfs/elev20/L20e130a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e130a.dat right -[ 22, 47 ] = ascii (fp) : "./hrtfs/elev20/L20e125a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e125a.dat right -[ 22, 48 ] = ascii (fp) : "./hrtfs/elev20/L20e120a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e120a.dat right -[ 22, 49 ] = ascii (fp) : "./hrtfs/elev20/L20e115a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e115a.dat right -[ 22, 50 ] = ascii (fp) : "./hrtfs/elev20/L20e110a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e110a.dat right -[ 22, 51 ] = ascii (fp) : "./hrtfs/elev20/L20e105a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e105a.dat right -[ 22, 52 ] = ascii (fp) : "./hrtfs/elev20/L20e100a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e100a.dat right -[ 22, 53 ] = ascii (fp) : "./hrtfs/elev20/L20e095a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e095a.dat right -[ 22, 54 ] = ascii (fp) : "./hrtfs/elev20/L20e090a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e090a.dat right -[ 22, 55 ] = ascii (fp) : "./hrtfs/elev20/L20e085a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e085a.dat right -[ 22, 56 ] = ascii (fp) : "./hrtfs/elev20/L20e080a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e080a.dat right -[ 22, 57 ] = ascii (fp) : "./hrtfs/elev20/L20e075a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e075a.dat right -[ 22, 58 ] = ascii (fp) : "./hrtfs/elev20/L20e070a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e070a.dat right -[ 22, 59 ] = ascii (fp) : "./hrtfs/elev20/L20e065a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e065a.dat right -[ 22, 60 ] = ascii (fp) : "./hrtfs/elev20/L20e060a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e060a.dat right -[ 22, 61 ] = ascii (fp) : "./hrtfs/elev20/L20e055a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e055a.dat right -[ 22, 62 ] = ascii (fp) : "./hrtfs/elev20/L20e050a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e050a.dat right -[ 22, 63 ] = ascii (fp) : "./hrtfs/elev20/L20e045a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e045a.dat right -[ 22, 64 ] = ascii (fp) : "./hrtfs/elev20/L20e040a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e040a.dat right -[ 22, 65 ] = ascii (fp) : "./hrtfs/elev20/L20e035a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e035a.dat right -[ 22, 66 ] = ascii (fp) : "./hrtfs/elev20/L20e030a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e030a.dat right -[ 22, 67 ] = ascii (fp) : "./hrtfs/elev20/L20e025a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e025a.dat right -[ 22, 68 ] = ascii (fp) : "./hrtfs/elev20/L20e020a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e020a.dat right -[ 22, 69 ] = ascii (fp) : "./hrtfs/elev20/L20e015a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e015a.dat right -[ 22, 70 ] = ascii (fp) : "./hrtfs/elev20/L20e010a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e010a.dat right -[ 22, 71 ] = ascii (fp) : "./hrtfs/elev20/L20e005a.dat left - + ascii (fp) : "./hrtfs/elev20/R20e005a.dat right +[ 22, 0 ] = ascii (fp) : "./hrtfs/elev20/L20e000a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e000a.dat" right +[ 22, 1 ] = ascii (fp) : "./hrtfs/elev20/L20e355a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e355a.dat" right +[ 22, 2 ] = ascii (fp) : "./hrtfs/elev20/L20e350a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e350a.dat" right +[ 22, 3 ] = ascii (fp) : "./hrtfs/elev20/L20e345a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e345a.dat" right +[ 22, 4 ] = ascii (fp) : "./hrtfs/elev20/L20e340a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e340a.dat" right +[ 22, 5 ] = ascii (fp) : "./hrtfs/elev20/L20e335a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e335a.dat" right +[ 22, 6 ] = ascii (fp) : "./hrtfs/elev20/L20e330a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e330a.dat" right +[ 22, 7 ] = ascii (fp) : "./hrtfs/elev20/L20e325a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e325a.dat" right +[ 22, 8 ] = ascii (fp) : "./hrtfs/elev20/L20e320a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e320a.dat" right +[ 22, 9 ] = ascii (fp) : "./hrtfs/elev20/L20e315a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e315a.dat" right +[ 22, 10 ] = ascii (fp) : "./hrtfs/elev20/L20e310a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e310a.dat" right +[ 22, 11 ] = ascii (fp) : "./hrtfs/elev20/L20e305a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e305a.dat" right +[ 22, 12 ] = ascii (fp) : "./hrtfs/elev20/L20e300a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e300a.dat" right +[ 22, 13 ] = ascii (fp) : "./hrtfs/elev20/L20e295a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e295a.dat" right +[ 22, 14 ] = ascii (fp) : "./hrtfs/elev20/L20e290a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e290a.dat" right +[ 22, 15 ] = ascii (fp) : "./hrtfs/elev20/L20e285a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e285a.dat" right +[ 22, 16 ] = ascii (fp) : "./hrtfs/elev20/L20e280a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e280a.dat" right +[ 22, 17 ] = ascii (fp) : "./hrtfs/elev20/L20e275a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e275a.dat" right +[ 22, 18 ] = ascii (fp) : "./hrtfs/elev20/L20e270a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e270a.dat" right +[ 22, 19 ] = ascii (fp) : "./hrtfs/elev20/L20e265a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e265a.dat" right +[ 22, 20 ] = ascii (fp) : "./hrtfs/elev20/L20e260a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e260a.dat" right +[ 22, 21 ] = ascii (fp) : "./hrtfs/elev20/L20e255a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e255a.dat" right +[ 22, 22 ] = ascii (fp) : "./hrtfs/elev20/L20e250a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e250a.dat" right +[ 22, 23 ] = ascii (fp) : "./hrtfs/elev20/L20e245a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e245a.dat" right +[ 22, 24 ] = ascii (fp) : "./hrtfs/elev20/L20e240a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e240a.dat" right +[ 22, 25 ] = ascii (fp) : "./hrtfs/elev20/L20e235a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e235a.dat" right +[ 22, 26 ] = ascii (fp) : "./hrtfs/elev20/L20e230a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e230a.dat" right +[ 22, 27 ] = ascii (fp) : "./hrtfs/elev20/L20e225a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e225a.dat" right +[ 22, 28 ] = ascii (fp) : "./hrtfs/elev20/L20e220a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e220a.dat" right +[ 22, 29 ] = ascii (fp) : "./hrtfs/elev20/L20e215a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e215a.dat" right +[ 22, 30 ] = ascii (fp) : "./hrtfs/elev20/L20e210a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e210a.dat" right +[ 22, 31 ] = ascii (fp) : "./hrtfs/elev20/L20e205a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e205a.dat" right +[ 22, 32 ] = ascii (fp) : "./hrtfs/elev20/L20e200a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e200a.dat" right +[ 22, 33 ] = ascii (fp) : "./hrtfs/elev20/L20e195a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e195a.dat" right +[ 22, 34 ] = ascii (fp) : "./hrtfs/elev20/L20e190a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e190a.dat" right +[ 22, 35 ] = ascii (fp) : "./hrtfs/elev20/L20e185a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e185a.dat" right +[ 22, 36 ] = ascii (fp) : "./hrtfs/elev20/L20e180a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e180a.dat" right +[ 22, 37 ] = ascii (fp) : "./hrtfs/elev20/L20e175a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e175a.dat" right +[ 22, 38 ] = ascii (fp) : "./hrtfs/elev20/L20e170a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e170a.dat" right +[ 22, 39 ] = ascii (fp) : "./hrtfs/elev20/L20e165a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e165a.dat" right +[ 22, 40 ] = ascii (fp) : "./hrtfs/elev20/L20e160a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e160a.dat" right +[ 22, 41 ] = ascii (fp) : "./hrtfs/elev20/L20e155a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e155a.dat" right +[ 22, 42 ] = ascii (fp) : "./hrtfs/elev20/L20e150a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e150a.dat" right +[ 22, 43 ] = ascii (fp) : "./hrtfs/elev20/L20e145a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e145a.dat" right +[ 22, 44 ] = ascii (fp) : "./hrtfs/elev20/L20e140a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e140a.dat" right +[ 22, 45 ] = ascii (fp) : "./hrtfs/elev20/L20e135a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e135a.dat" right +[ 22, 46 ] = ascii (fp) : "./hrtfs/elev20/L20e130a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e130a.dat" right +[ 22, 47 ] = ascii (fp) : "./hrtfs/elev20/L20e125a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e125a.dat" right +[ 22, 48 ] = ascii (fp) : "./hrtfs/elev20/L20e120a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e120a.dat" right +[ 22, 49 ] = ascii (fp) : "./hrtfs/elev20/L20e115a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e115a.dat" right +[ 22, 50 ] = ascii (fp) : "./hrtfs/elev20/L20e110a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e110a.dat" right +[ 22, 51 ] = ascii (fp) : "./hrtfs/elev20/L20e105a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e105a.dat" right +[ 22, 52 ] = ascii (fp) : "./hrtfs/elev20/L20e100a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e100a.dat" right +[ 22, 53 ] = ascii (fp) : "./hrtfs/elev20/L20e095a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e095a.dat" right +[ 22, 54 ] = ascii (fp) : "./hrtfs/elev20/L20e090a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e090a.dat" right +[ 22, 55 ] = ascii (fp) : "./hrtfs/elev20/L20e085a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e085a.dat" right +[ 22, 56 ] = ascii (fp) : "./hrtfs/elev20/L20e080a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e080a.dat" right +[ 22, 57 ] = ascii (fp) : "./hrtfs/elev20/L20e075a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e075a.dat" right +[ 22, 58 ] = ascii (fp) : "./hrtfs/elev20/L20e070a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e070a.dat" right +[ 22, 59 ] = ascii (fp) : "./hrtfs/elev20/L20e065a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e065a.dat" right +[ 22, 60 ] = ascii (fp) : "./hrtfs/elev20/L20e060a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e060a.dat" right +[ 22, 61 ] = ascii (fp) : "./hrtfs/elev20/L20e055a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e055a.dat" right +[ 22, 62 ] = ascii (fp) : "./hrtfs/elev20/L20e050a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e050a.dat" right +[ 22, 63 ] = ascii (fp) : "./hrtfs/elev20/L20e045a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e045a.dat" right +[ 22, 64 ] = ascii (fp) : "./hrtfs/elev20/L20e040a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e040a.dat" right +[ 22, 65 ] = ascii (fp) : "./hrtfs/elev20/L20e035a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e035a.dat" right +[ 22, 66 ] = ascii (fp) : "./hrtfs/elev20/L20e030a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e030a.dat" right +[ 22, 67 ] = ascii (fp) : "./hrtfs/elev20/L20e025a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e025a.dat" right +[ 22, 68 ] = ascii (fp) : "./hrtfs/elev20/L20e020a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e020a.dat" right +[ 22, 69 ] = ascii (fp) : "./hrtfs/elev20/L20e015a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e015a.dat" right +[ 22, 70 ] = ascii (fp) : "./hrtfs/elev20/L20e010a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e010a.dat" right +[ 22, 71 ] = ascii (fp) : "./hrtfs/elev20/L20e005a.dat" left + + ascii (fp) : "./hrtfs/elev20/R20e005a.dat" right -[ 23, 0 ] = ascii (fp) : "./hrtfs/elev25/L25e000a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e000a.dat right -[ 23, 1 ] = ascii (fp) : "./hrtfs/elev25/L25e355a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e355a.dat right -[ 23, 2 ] = ascii (fp) : "./hrtfs/elev25/L25e350a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e350a.dat right -[ 23, 3 ] = ascii (fp) : "./hrtfs/elev25/L25e345a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e345a.dat right -[ 23, 4 ] = ascii (fp) : "./hrtfs/elev25/L25e340a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e340a.dat right -[ 23, 5 ] = ascii (fp) : "./hrtfs/elev25/L25e335a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e335a.dat right -[ 23, 6 ] = ascii (fp) : "./hrtfs/elev25/L25e330a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e330a.dat right -[ 23, 7 ] = ascii (fp) : "./hrtfs/elev25/L25e325a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e325a.dat right -[ 23, 8 ] = ascii (fp) : "./hrtfs/elev25/L25e320a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e320a.dat right -[ 23, 9 ] = ascii (fp) : "./hrtfs/elev25/L25e315a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e315a.dat right -[ 23, 10 ] = ascii (fp) : "./hrtfs/elev25/L25e310a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e310a.dat right -[ 23, 11 ] = ascii (fp) : "./hrtfs/elev25/L25e305a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e305a.dat right -[ 23, 12 ] = ascii (fp) : "./hrtfs/elev25/L25e300a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e300a.dat right -[ 23, 13 ] = ascii (fp) : "./hrtfs/elev25/L25e295a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e295a.dat right -[ 23, 14 ] = ascii (fp) : "./hrtfs/elev25/L25e290a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e290a.dat right -[ 23, 15 ] = ascii (fp) : "./hrtfs/elev25/L25e285a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e285a.dat right -[ 23, 16 ] = ascii (fp) : "./hrtfs/elev25/L25e280a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e280a.dat right -[ 23, 17 ] = ascii (fp) : "./hrtfs/elev25/L25e275a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e275a.dat right -[ 23, 18 ] = ascii (fp) : "./hrtfs/elev25/L25e270a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e270a.dat right -[ 23, 19 ] = ascii (fp) : "./hrtfs/elev25/L25e265a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e265a.dat right -[ 23, 20 ] = ascii (fp) : "./hrtfs/elev25/L25e260a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e260a.dat right -[ 23, 21 ] = ascii (fp) : "./hrtfs/elev25/L25e255a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e255a.dat right -[ 23, 22 ] = ascii (fp) : "./hrtfs/elev25/L25e250a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e250a.dat right -[ 23, 23 ] = ascii (fp) : "./hrtfs/elev25/L25e245a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e245a.dat right -[ 23, 24 ] = ascii (fp) : "./hrtfs/elev25/L25e240a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e240a.dat right -[ 23, 25 ] = ascii (fp) : "./hrtfs/elev25/L25e235a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e235a.dat right -[ 23, 26 ] = ascii (fp) : "./hrtfs/elev25/L25e230a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e230a.dat right -[ 23, 27 ] = ascii (fp) : "./hrtfs/elev25/L25e225a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e225a.dat right -[ 23, 28 ] = ascii (fp) : "./hrtfs/elev25/L25e220a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e220a.dat right -[ 23, 29 ] = ascii (fp) : "./hrtfs/elev25/L25e215a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e215a.dat right -[ 23, 30 ] = ascii (fp) : "./hrtfs/elev25/L25e210a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e210a.dat right -[ 23, 31 ] = ascii (fp) : "./hrtfs/elev25/L25e205a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e205a.dat right -[ 23, 32 ] = ascii (fp) : "./hrtfs/elev25/L25e200a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e200a.dat right -[ 23, 33 ] = ascii (fp) : "./hrtfs/elev25/L25e195a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e195a.dat right -[ 23, 34 ] = ascii (fp) : "./hrtfs/elev25/L25e190a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e190a.dat right -[ 23, 35 ] = ascii (fp) : "./hrtfs/elev25/L25e185a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e185a.dat right -[ 23, 36 ] = ascii (fp) : "./hrtfs/elev25/L25e180a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e180a.dat right -[ 23, 37 ] = ascii (fp) : "./hrtfs/elev25/L25e175a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e175a.dat right -[ 23, 38 ] = ascii (fp) : "./hrtfs/elev25/L25e170a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e170a.dat right -[ 23, 39 ] = ascii (fp) : "./hrtfs/elev25/L25e165a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e165a.dat right -[ 23, 40 ] = ascii (fp) : "./hrtfs/elev25/L25e160a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e160a.dat right -[ 23, 41 ] = ascii (fp) : "./hrtfs/elev25/L25e155a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e155a.dat right -[ 23, 42 ] = ascii (fp) : "./hrtfs/elev25/L25e150a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e150a.dat right -[ 23, 43 ] = ascii (fp) : "./hrtfs/elev25/L25e145a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e145a.dat right -[ 23, 44 ] = ascii (fp) : "./hrtfs/elev25/L25e140a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e140a.dat right -[ 23, 45 ] = ascii (fp) : "./hrtfs/elev25/L25e135a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e135a.dat right -[ 23, 46 ] = ascii (fp) : "./hrtfs/elev25/L25e130a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e130a.dat right -[ 23, 47 ] = ascii (fp) : "./hrtfs/elev25/L25e125a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e125a.dat right -[ 23, 48 ] = ascii (fp) : "./hrtfs/elev25/L25e120a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e120a.dat right -[ 23, 49 ] = ascii (fp) : "./hrtfs/elev25/L25e115a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e115a.dat right -[ 23, 50 ] = ascii (fp) : "./hrtfs/elev25/L25e110a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e110a.dat right -[ 23, 51 ] = ascii (fp) : "./hrtfs/elev25/L25e105a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e105a.dat right -[ 23, 52 ] = ascii (fp) : "./hrtfs/elev25/L25e100a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e100a.dat right -[ 23, 53 ] = ascii (fp) : "./hrtfs/elev25/L25e095a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e095a.dat right -[ 23, 54 ] = ascii (fp) : "./hrtfs/elev25/L25e090a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e090a.dat right -[ 23, 55 ] = ascii (fp) : "./hrtfs/elev25/L25e085a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e085a.dat right -[ 23, 56 ] = ascii (fp) : "./hrtfs/elev25/L25e080a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e080a.dat right -[ 23, 57 ] = ascii (fp) : "./hrtfs/elev25/L25e075a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e075a.dat right -[ 23, 58 ] = ascii (fp) : "./hrtfs/elev25/L25e070a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e070a.dat right -[ 23, 59 ] = ascii (fp) : "./hrtfs/elev25/L25e065a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e065a.dat right -[ 23, 60 ] = ascii (fp) : "./hrtfs/elev25/L25e060a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e060a.dat right -[ 23, 61 ] = ascii (fp) : "./hrtfs/elev25/L25e055a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e055a.dat right -[ 23, 62 ] = ascii (fp) : "./hrtfs/elev25/L25e050a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e050a.dat right -[ 23, 63 ] = ascii (fp) : "./hrtfs/elev25/L25e045a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e045a.dat right -[ 23, 64 ] = ascii (fp) : "./hrtfs/elev25/L25e040a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e040a.dat right -[ 23, 65 ] = ascii (fp) : "./hrtfs/elev25/L25e035a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e035a.dat right -[ 23, 66 ] = ascii (fp) : "./hrtfs/elev25/L25e030a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e030a.dat right -[ 23, 67 ] = ascii (fp) : "./hrtfs/elev25/L25e025a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e025a.dat right -[ 23, 68 ] = ascii (fp) : "./hrtfs/elev25/L25e020a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e020a.dat right -[ 23, 69 ] = ascii (fp) : "./hrtfs/elev25/L25e015a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e015a.dat right -[ 23, 70 ] = ascii (fp) : "./hrtfs/elev25/L25e010a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e010a.dat right -[ 23, 71 ] = ascii (fp) : "./hrtfs/elev25/L25e005a.dat left - + ascii (fp) : "./hrtfs/elev25/R25e005a.dat right +[ 23, 0 ] = ascii (fp) : "./hrtfs/elev25/L25e000a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e000a.dat" right +[ 23, 1 ] = ascii (fp) : "./hrtfs/elev25/L25e355a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e355a.dat" right +[ 23, 2 ] = ascii (fp) : "./hrtfs/elev25/L25e350a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e350a.dat" right +[ 23, 3 ] = ascii (fp) : "./hrtfs/elev25/L25e345a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e345a.dat" right +[ 23, 4 ] = ascii (fp) : "./hrtfs/elev25/L25e340a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e340a.dat" right +[ 23, 5 ] = ascii (fp) : "./hrtfs/elev25/L25e335a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e335a.dat" right +[ 23, 6 ] = ascii (fp) : "./hrtfs/elev25/L25e330a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e330a.dat" right +[ 23, 7 ] = ascii (fp) : "./hrtfs/elev25/L25e325a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e325a.dat" right +[ 23, 8 ] = ascii (fp) : "./hrtfs/elev25/L25e320a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e320a.dat" right +[ 23, 9 ] = ascii (fp) : "./hrtfs/elev25/L25e315a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e315a.dat" right +[ 23, 10 ] = ascii (fp) : "./hrtfs/elev25/L25e310a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e310a.dat" right +[ 23, 11 ] = ascii (fp) : "./hrtfs/elev25/L25e305a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e305a.dat" right +[ 23, 12 ] = ascii (fp) : "./hrtfs/elev25/L25e300a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e300a.dat" right +[ 23, 13 ] = ascii (fp) : "./hrtfs/elev25/L25e295a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e295a.dat" right +[ 23, 14 ] = ascii (fp) : "./hrtfs/elev25/L25e290a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e290a.dat" right +[ 23, 15 ] = ascii (fp) : "./hrtfs/elev25/L25e285a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e285a.dat" right +[ 23, 16 ] = ascii (fp) : "./hrtfs/elev25/L25e280a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e280a.dat" right +[ 23, 17 ] = ascii (fp) : "./hrtfs/elev25/L25e275a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e275a.dat" right +[ 23, 18 ] = ascii (fp) : "./hrtfs/elev25/L25e270a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e270a.dat" right +[ 23, 19 ] = ascii (fp) : "./hrtfs/elev25/L25e265a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e265a.dat" right +[ 23, 20 ] = ascii (fp) : "./hrtfs/elev25/L25e260a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e260a.dat" right +[ 23, 21 ] = ascii (fp) : "./hrtfs/elev25/L25e255a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e255a.dat" right +[ 23, 22 ] = ascii (fp) : "./hrtfs/elev25/L25e250a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e250a.dat" right +[ 23, 23 ] = ascii (fp) : "./hrtfs/elev25/L25e245a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e245a.dat" right +[ 23, 24 ] = ascii (fp) : "./hrtfs/elev25/L25e240a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e240a.dat" right +[ 23, 25 ] = ascii (fp) : "./hrtfs/elev25/L25e235a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e235a.dat" right +[ 23, 26 ] = ascii (fp) : "./hrtfs/elev25/L25e230a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e230a.dat" right +[ 23, 27 ] = ascii (fp) : "./hrtfs/elev25/L25e225a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e225a.dat" right +[ 23, 28 ] = ascii (fp) : "./hrtfs/elev25/L25e220a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e220a.dat" right +[ 23, 29 ] = ascii (fp) : "./hrtfs/elev25/L25e215a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e215a.dat" right +[ 23, 30 ] = ascii (fp) : "./hrtfs/elev25/L25e210a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e210a.dat" right +[ 23, 31 ] = ascii (fp) : "./hrtfs/elev25/L25e205a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e205a.dat" right +[ 23, 32 ] = ascii (fp) : "./hrtfs/elev25/L25e200a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e200a.dat" right +[ 23, 33 ] = ascii (fp) : "./hrtfs/elev25/L25e195a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e195a.dat" right +[ 23, 34 ] = ascii (fp) : "./hrtfs/elev25/L25e190a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e190a.dat" right +[ 23, 35 ] = ascii (fp) : "./hrtfs/elev25/L25e185a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e185a.dat" right +[ 23, 36 ] = ascii (fp) : "./hrtfs/elev25/L25e180a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e180a.dat" right +[ 23, 37 ] = ascii (fp) : "./hrtfs/elev25/L25e175a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e175a.dat" right +[ 23, 38 ] = ascii (fp) : "./hrtfs/elev25/L25e170a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e170a.dat" right +[ 23, 39 ] = ascii (fp) : "./hrtfs/elev25/L25e165a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e165a.dat" right +[ 23, 40 ] = ascii (fp) : "./hrtfs/elev25/L25e160a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e160a.dat" right +[ 23, 41 ] = ascii (fp) : "./hrtfs/elev25/L25e155a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e155a.dat" right +[ 23, 42 ] = ascii (fp) : "./hrtfs/elev25/L25e150a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e150a.dat" right +[ 23, 43 ] = ascii (fp) : "./hrtfs/elev25/L25e145a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e145a.dat" right +[ 23, 44 ] = ascii (fp) : "./hrtfs/elev25/L25e140a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e140a.dat" right +[ 23, 45 ] = ascii (fp) : "./hrtfs/elev25/L25e135a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e135a.dat" right +[ 23, 46 ] = ascii (fp) : "./hrtfs/elev25/L25e130a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e130a.dat" right +[ 23, 47 ] = ascii (fp) : "./hrtfs/elev25/L25e125a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e125a.dat" right +[ 23, 48 ] = ascii (fp) : "./hrtfs/elev25/L25e120a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e120a.dat" right +[ 23, 49 ] = ascii (fp) : "./hrtfs/elev25/L25e115a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e115a.dat" right +[ 23, 50 ] = ascii (fp) : "./hrtfs/elev25/L25e110a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e110a.dat" right +[ 23, 51 ] = ascii (fp) : "./hrtfs/elev25/L25e105a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e105a.dat" right +[ 23, 52 ] = ascii (fp) : "./hrtfs/elev25/L25e100a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e100a.dat" right +[ 23, 53 ] = ascii (fp) : "./hrtfs/elev25/L25e095a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e095a.dat" right +[ 23, 54 ] = ascii (fp) : "./hrtfs/elev25/L25e090a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e090a.dat" right +[ 23, 55 ] = ascii (fp) : "./hrtfs/elev25/L25e085a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e085a.dat" right +[ 23, 56 ] = ascii (fp) : "./hrtfs/elev25/L25e080a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e080a.dat" right +[ 23, 57 ] = ascii (fp) : "./hrtfs/elev25/L25e075a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e075a.dat" right +[ 23, 58 ] = ascii (fp) : "./hrtfs/elev25/L25e070a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e070a.dat" right +[ 23, 59 ] = ascii (fp) : "./hrtfs/elev25/L25e065a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e065a.dat" right +[ 23, 60 ] = ascii (fp) : "./hrtfs/elev25/L25e060a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e060a.dat" right +[ 23, 61 ] = ascii (fp) : "./hrtfs/elev25/L25e055a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e055a.dat" right +[ 23, 62 ] = ascii (fp) : "./hrtfs/elev25/L25e050a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e050a.dat" right +[ 23, 63 ] = ascii (fp) : "./hrtfs/elev25/L25e045a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e045a.dat" right +[ 23, 64 ] = ascii (fp) : "./hrtfs/elev25/L25e040a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e040a.dat" right +[ 23, 65 ] = ascii (fp) : "./hrtfs/elev25/L25e035a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e035a.dat" right +[ 23, 66 ] = ascii (fp) : "./hrtfs/elev25/L25e030a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e030a.dat" right +[ 23, 67 ] = ascii (fp) : "./hrtfs/elev25/L25e025a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e025a.dat" right +[ 23, 68 ] = ascii (fp) : "./hrtfs/elev25/L25e020a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e020a.dat" right +[ 23, 69 ] = ascii (fp) : "./hrtfs/elev25/L25e015a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e015a.dat" right +[ 23, 70 ] = ascii (fp) : "./hrtfs/elev25/L25e010a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e010a.dat" right +[ 23, 71 ] = ascii (fp) : "./hrtfs/elev25/L25e005a.dat" left + + ascii (fp) : "./hrtfs/elev25/R25e005a.dat" right -[ 24, 0 ] = ascii (fp) : "./hrtfs/elev30/L30e000a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e000a.dat right -[ 24, 1 ] = ascii (fp) : "./hrtfs/elev30/L30e355a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e355a.dat right -[ 24, 2 ] = ascii (fp) : "./hrtfs/elev30/L30e350a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e350a.dat right -[ 24, 3 ] = ascii (fp) : "./hrtfs/elev30/L30e345a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e345a.dat right -[ 24, 4 ] = ascii (fp) : "./hrtfs/elev30/L30e340a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e340a.dat right -[ 24, 5 ] = ascii (fp) : "./hrtfs/elev30/L30e335a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e335a.dat right -[ 24, 6 ] = ascii (fp) : "./hrtfs/elev30/L30e330a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e330a.dat right -[ 24, 7 ] = ascii (fp) : "./hrtfs/elev30/L30e325a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e325a.dat right -[ 24, 8 ] = ascii (fp) : "./hrtfs/elev30/L30e320a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e320a.dat right -[ 24, 9 ] = ascii (fp) : "./hrtfs/elev30/L30e315a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e315a.dat right -[ 24, 10 ] = ascii (fp) : "./hrtfs/elev30/L30e310a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e310a.dat right -[ 24, 11 ] = ascii (fp) : "./hrtfs/elev30/L30e305a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e305a.dat right -[ 24, 12 ] = ascii (fp) : "./hrtfs/elev30/L30e300a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e300a.dat right -[ 24, 13 ] = ascii (fp) : "./hrtfs/elev30/L30e295a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e295a.dat right -[ 24, 14 ] = ascii (fp) : "./hrtfs/elev30/L30e290a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e290a.dat right -[ 24, 15 ] = ascii (fp) : "./hrtfs/elev30/L30e285a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e285a.dat right -[ 24, 16 ] = ascii (fp) : "./hrtfs/elev30/L30e280a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e280a.dat right -[ 24, 17 ] = ascii (fp) : "./hrtfs/elev30/L30e275a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e275a.dat right -[ 24, 18 ] = ascii (fp) : "./hrtfs/elev30/L30e270a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e270a.dat right -[ 24, 19 ] = ascii (fp) : "./hrtfs/elev30/L30e265a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e265a.dat right -[ 24, 20 ] = ascii (fp) : "./hrtfs/elev30/L30e260a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e260a.dat right -[ 24, 21 ] = ascii (fp) : "./hrtfs/elev30/L30e255a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e255a.dat right -[ 24, 22 ] = ascii (fp) : "./hrtfs/elev30/L30e250a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e250a.dat right -[ 24, 23 ] = ascii (fp) : "./hrtfs/elev30/L30e245a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e245a.dat right -[ 24, 24 ] = ascii (fp) : "./hrtfs/elev30/L30e240a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e240a.dat right -[ 24, 25 ] = ascii (fp) : "./hrtfs/elev30/L30e235a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e235a.dat right -[ 24, 26 ] = ascii (fp) : "./hrtfs/elev30/L30e230a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e230a.dat right -[ 24, 27 ] = ascii (fp) : "./hrtfs/elev30/L30e225a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e225a.dat right -[ 24, 28 ] = ascii (fp) : "./hrtfs/elev30/L30e220a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e220a.dat right -[ 24, 29 ] = ascii (fp) : "./hrtfs/elev30/L30e215a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e215a.dat right -[ 24, 30 ] = ascii (fp) : "./hrtfs/elev30/L30e210a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e210a.dat right -[ 24, 31 ] = ascii (fp) : "./hrtfs/elev30/L30e205a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e205a.dat right -[ 24, 32 ] = ascii (fp) : "./hrtfs/elev30/L30e200a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e200a.dat right -[ 24, 33 ] = ascii (fp) : "./hrtfs/elev30/L30e195a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e195a.dat right -[ 24, 34 ] = ascii (fp) : "./hrtfs/elev30/L30e190a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e190a.dat right -[ 24, 35 ] = ascii (fp) : "./hrtfs/elev30/L30e185a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e185a.dat right -[ 24, 36 ] = ascii (fp) : "./hrtfs/elev30/L30e180a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e180a.dat right -[ 24, 37 ] = ascii (fp) : "./hrtfs/elev30/L30e175a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e175a.dat right -[ 24, 38 ] = ascii (fp) : "./hrtfs/elev30/L30e170a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e170a.dat right -[ 24, 39 ] = ascii (fp) : "./hrtfs/elev30/L30e165a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e165a.dat right -[ 24, 40 ] = ascii (fp) : "./hrtfs/elev30/L30e160a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e160a.dat right -[ 24, 41 ] = ascii (fp) : "./hrtfs/elev30/L30e155a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e155a.dat right -[ 24, 42 ] = ascii (fp) : "./hrtfs/elev30/L30e150a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e150a.dat right -[ 24, 43 ] = ascii (fp) : "./hrtfs/elev30/L30e145a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e145a.dat right -[ 24, 44 ] = ascii (fp) : "./hrtfs/elev30/L30e140a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e140a.dat right -[ 24, 45 ] = ascii (fp) : "./hrtfs/elev30/L30e135a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e135a.dat right -[ 24, 46 ] = ascii (fp) : "./hrtfs/elev30/L30e130a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e130a.dat right -[ 24, 47 ] = ascii (fp) : "./hrtfs/elev30/L30e125a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e125a.dat right -[ 24, 48 ] = ascii (fp) : "./hrtfs/elev30/L30e120a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e120a.dat right -[ 24, 49 ] = ascii (fp) : "./hrtfs/elev30/L30e115a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e115a.dat right -[ 24, 50 ] = ascii (fp) : "./hrtfs/elev30/L30e110a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e110a.dat right -[ 24, 51 ] = ascii (fp) : "./hrtfs/elev30/L30e105a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e105a.dat right -[ 24, 52 ] = ascii (fp) : "./hrtfs/elev30/L30e100a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e100a.dat right -[ 24, 53 ] = ascii (fp) : "./hrtfs/elev30/L30e095a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e095a.dat right -[ 24, 54 ] = ascii (fp) : "./hrtfs/elev30/L30e090a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e090a.dat right -[ 24, 55 ] = ascii (fp) : "./hrtfs/elev30/L30e085a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e085a.dat right -[ 24, 56 ] = ascii (fp) : "./hrtfs/elev30/L30e080a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e080a.dat right -[ 24, 57 ] = ascii (fp) : "./hrtfs/elev30/L30e075a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e075a.dat right -[ 24, 58 ] = ascii (fp) : "./hrtfs/elev30/L30e070a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e070a.dat right -[ 24, 59 ] = ascii (fp) : "./hrtfs/elev30/L30e065a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e065a.dat right -[ 24, 60 ] = ascii (fp) : "./hrtfs/elev30/L30e060a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e060a.dat right -[ 24, 61 ] = ascii (fp) : "./hrtfs/elev30/L30e055a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e055a.dat right -[ 24, 62 ] = ascii (fp) : "./hrtfs/elev30/L30e050a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e050a.dat right -[ 24, 63 ] = ascii (fp) : "./hrtfs/elev30/L30e045a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e045a.dat right -[ 24, 64 ] = ascii (fp) : "./hrtfs/elev30/L30e040a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e040a.dat right -[ 24, 65 ] = ascii (fp) : "./hrtfs/elev30/L30e035a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e035a.dat right -[ 24, 66 ] = ascii (fp) : "./hrtfs/elev30/L30e030a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e030a.dat right -[ 24, 67 ] = ascii (fp) : "./hrtfs/elev30/L30e025a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e025a.dat right -[ 24, 68 ] = ascii (fp) : "./hrtfs/elev30/L30e020a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e020a.dat right -[ 24, 69 ] = ascii (fp) : "./hrtfs/elev30/L30e015a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e015a.dat right -[ 24, 70 ] = ascii (fp) : "./hrtfs/elev30/L30e010a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e010a.dat right -[ 24, 71 ] = ascii (fp) : "./hrtfs/elev30/L30e005a.dat left - + ascii (fp) : "./hrtfs/elev30/R30e005a.dat right +[ 24, 0 ] = ascii (fp) : "./hrtfs/elev30/L30e000a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e000a.dat" right +[ 24, 1 ] = ascii (fp) : "./hrtfs/elev30/L30e355a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e355a.dat" right +[ 24, 2 ] = ascii (fp) : "./hrtfs/elev30/L30e350a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e350a.dat" right +[ 24, 3 ] = ascii (fp) : "./hrtfs/elev30/L30e345a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e345a.dat" right +[ 24, 4 ] = ascii (fp) : "./hrtfs/elev30/L30e340a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e340a.dat" right +[ 24, 5 ] = ascii (fp) : "./hrtfs/elev30/L30e335a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e335a.dat" right +[ 24, 6 ] = ascii (fp) : "./hrtfs/elev30/L30e330a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e330a.dat" right +[ 24, 7 ] = ascii (fp) : "./hrtfs/elev30/L30e325a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e325a.dat" right +[ 24, 8 ] = ascii (fp) : "./hrtfs/elev30/L30e320a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e320a.dat" right +[ 24, 9 ] = ascii (fp) : "./hrtfs/elev30/L30e315a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e315a.dat" right +[ 24, 10 ] = ascii (fp) : "./hrtfs/elev30/L30e310a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e310a.dat" right +[ 24, 11 ] = ascii (fp) : "./hrtfs/elev30/L30e305a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e305a.dat" right +[ 24, 12 ] = ascii (fp) : "./hrtfs/elev30/L30e300a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e300a.dat" right +[ 24, 13 ] = ascii (fp) : "./hrtfs/elev30/L30e295a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e295a.dat" right +[ 24, 14 ] = ascii (fp) : "./hrtfs/elev30/L30e290a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e290a.dat" right +[ 24, 15 ] = ascii (fp) : "./hrtfs/elev30/L30e285a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e285a.dat" right +[ 24, 16 ] = ascii (fp) : "./hrtfs/elev30/L30e280a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e280a.dat" right +[ 24, 17 ] = ascii (fp) : "./hrtfs/elev30/L30e275a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e275a.dat" right +[ 24, 18 ] = ascii (fp) : "./hrtfs/elev30/L30e270a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e270a.dat" right +[ 24, 19 ] = ascii (fp) : "./hrtfs/elev30/L30e265a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e265a.dat" right +[ 24, 20 ] = ascii (fp) : "./hrtfs/elev30/L30e260a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e260a.dat" right +[ 24, 21 ] = ascii (fp) : "./hrtfs/elev30/L30e255a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e255a.dat" right +[ 24, 22 ] = ascii (fp) : "./hrtfs/elev30/L30e250a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e250a.dat" right +[ 24, 23 ] = ascii (fp) : "./hrtfs/elev30/L30e245a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e245a.dat" right +[ 24, 24 ] = ascii (fp) : "./hrtfs/elev30/L30e240a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e240a.dat" right +[ 24, 25 ] = ascii (fp) : "./hrtfs/elev30/L30e235a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e235a.dat" right +[ 24, 26 ] = ascii (fp) : "./hrtfs/elev30/L30e230a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e230a.dat" right +[ 24, 27 ] = ascii (fp) : "./hrtfs/elev30/L30e225a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e225a.dat" right +[ 24, 28 ] = ascii (fp) : "./hrtfs/elev30/L30e220a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e220a.dat" right +[ 24, 29 ] = ascii (fp) : "./hrtfs/elev30/L30e215a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e215a.dat" right +[ 24, 30 ] = ascii (fp) : "./hrtfs/elev30/L30e210a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e210a.dat" right +[ 24, 31 ] = ascii (fp) : "./hrtfs/elev30/L30e205a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e205a.dat" right +[ 24, 32 ] = ascii (fp) : "./hrtfs/elev30/L30e200a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e200a.dat" right +[ 24, 33 ] = ascii (fp) : "./hrtfs/elev30/L30e195a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e195a.dat" right +[ 24, 34 ] = ascii (fp) : "./hrtfs/elev30/L30e190a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e190a.dat" right +[ 24, 35 ] = ascii (fp) : "./hrtfs/elev30/L30e185a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e185a.dat" right +[ 24, 36 ] = ascii (fp) : "./hrtfs/elev30/L30e180a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e180a.dat" right +[ 24, 37 ] = ascii (fp) : "./hrtfs/elev30/L30e175a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e175a.dat" right +[ 24, 38 ] = ascii (fp) : "./hrtfs/elev30/L30e170a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e170a.dat" right +[ 24, 39 ] = ascii (fp) : "./hrtfs/elev30/L30e165a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e165a.dat" right +[ 24, 40 ] = ascii (fp) : "./hrtfs/elev30/L30e160a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e160a.dat" right +[ 24, 41 ] = ascii (fp) : "./hrtfs/elev30/L30e155a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e155a.dat" right +[ 24, 42 ] = ascii (fp) : "./hrtfs/elev30/L30e150a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e150a.dat" right +[ 24, 43 ] = ascii (fp) : "./hrtfs/elev30/L30e145a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e145a.dat" right +[ 24, 44 ] = ascii (fp) : "./hrtfs/elev30/L30e140a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e140a.dat" right +[ 24, 45 ] = ascii (fp) : "./hrtfs/elev30/L30e135a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e135a.dat" right +[ 24, 46 ] = ascii (fp) : "./hrtfs/elev30/L30e130a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e130a.dat" right +[ 24, 47 ] = ascii (fp) : "./hrtfs/elev30/L30e125a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e125a.dat" right +[ 24, 48 ] = ascii (fp) : "./hrtfs/elev30/L30e120a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e120a.dat" right +[ 24, 49 ] = ascii (fp) : "./hrtfs/elev30/L30e115a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e115a.dat" right +[ 24, 50 ] = ascii (fp) : "./hrtfs/elev30/L30e110a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e110a.dat" right +[ 24, 51 ] = ascii (fp) : "./hrtfs/elev30/L30e105a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e105a.dat" right +[ 24, 52 ] = ascii (fp) : "./hrtfs/elev30/L30e100a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e100a.dat" right +[ 24, 53 ] = ascii (fp) : "./hrtfs/elev30/L30e095a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e095a.dat" right +[ 24, 54 ] = ascii (fp) : "./hrtfs/elev30/L30e090a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e090a.dat" right +[ 24, 55 ] = ascii (fp) : "./hrtfs/elev30/L30e085a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e085a.dat" right +[ 24, 56 ] = ascii (fp) : "./hrtfs/elev30/L30e080a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e080a.dat" right +[ 24, 57 ] = ascii (fp) : "./hrtfs/elev30/L30e075a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e075a.dat" right +[ 24, 58 ] = ascii (fp) : "./hrtfs/elev30/L30e070a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e070a.dat" right +[ 24, 59 ] = ascii (fp) : "./hrtfs/elev30/L30e065a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e065a.dat" right +[ 24, 60 ] = ascii (fp) : "./hrtfs/elev30/L30e060a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e060a.dat" right +[ 24, 61 ] = ascii (fp) : "./hrtfs/elev30/L30e055a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e055a.dat" right +[ 24, 62 ] = ascii (fp) : "./hrtfs/elev30/L30e050a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e050a.dat" right +[ 24, 63 ] = ascii (fp) : "./hrtfs/elev30/L30e045a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e045a.dat" right +[ 24, 64 ] = ascii (fp) : "./hrtfs/elev30/L30e040a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e040a.dat" right +[ 24, 65 ] = ascii (fp) : "./hrtfs/elev30/L30e035a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e035a.dat" right +[ 24, 66 ] = ascii (fp) : "./hrtfs/elev30/L30e030a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e030a.dat" right +[ 24, 67 ] = ascii (fp) : "./hrtfs/elev30/L30e025a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e025a.dat" right +[ 24, 68 ] = ascii (fp) : "./hrtfs/elev30/L30e020a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e020a.dat" right +[ 24, 69 ] = ascii (fp) : "./hrtfs/elev30/L30e015a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e015a.dat" right +[ 24, 70 ] = ascii (fp) : "./hrtfs/elev30/L30e010a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e010a.dat" right +[ 24, 71 ] = ascii (fp) : "./hrtfs/elev30/L30e005a.dat" left + + ascii (fp) : "./hrtfs/elev30/R30e005a.dat" right -[ 25, 0 ] = ascii (fp) : "./hrtfs/elev35/L35e000a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e000a.dat right -[ 25, 1 ] = ascii (fp) : "./hrtfs/elev35/L35e355a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e355a.dat right -[ 25, 2 ] = ascii (fp) : "./hrtfs/elev35/L35e350a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e350a.dat right -[ 25, 3 ] = ascii (fp) : "./hrtfs/elev35/L35e345a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e345a.dat right -[ 25, 4 ] = ascii (fp) : "./hrtfs/elev35/L35e340a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e340a.dat right -[ 25, 5 ] = ascii (fp) : "./hrtfs/elev35/L35e335a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e335a.dat right -[ 25, 6 ] = ascii (fp) : "./hrtfs/elev35/L35e330a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e330a.dat right -[ 25, 7 ] = ascii (fp) : "./hrtfs/elev35/L35e325a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e325a.dat right -[ 25, 8 ] = ascii (fp) : "./hrtfs/elev35/L35e320a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e320a.dat right -[ 25, 9 ] = ascii (fp) : "./hrtfs/elev35/L35e315a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e315a.dat right -[ 25, 10 ] = ascii (fp) : "./hrtfs/elev35/L35e310a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e310a.dat right -[ 25, 11 ] = ascii (fp) : "./hrtfs/elev35/L35e305a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e305a.dat right -[ 25, 12 ] = ascii (fp) : "./hrtfs/elev35/L35e300a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e300a.dat right -[ 25, 13 ] = ascii (fp) : "./hrtfs/elev35/L35e295a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e295a.dat right -[ 25, 14 ] = ascii (fp) : "./hrtfs/elev35/L35e290a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e290a.dat right -[ 25, 15 ] = ascii (fp) : "./hrtfs/elev35/L35e285a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e285a.dat right -[ 25, 16 ] = ascii (fp) : "./hrtfs/elev35/L35e280a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e280a.dat right -[ 25, 17 ] = ascii (fp) : "./hrtfs/elev35/L35e275a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e275a.dat right -[ 25, 18 ] = ascii (fp) : "./hrtfs/elev35/L35e270a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e270a.dat right -[ 25, 19 ] = ascii (fp) : "./hrtfs/elev35/L35e265a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e265a.dat right -[ 25, 20 ] = ascii (fp) : "./hrtfs/elev35/L35e260a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e260a.dat right -[ 25, 21 ] = ascii (fp) : "./hrtfs/elev35/L35e255a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e255a.dat right -[ 25, 22 ] = ascii (fp) : "./hrtfs/elev35/L35e250a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e250a.dat right -[ 25, 23 ] = ascii (fp) : "./hrtfs/elev35/L35e245a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e245a.dat right -[ 25, 24 ] = ascii (fp) : "./hrtfs/elev35/L35e240a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e240a.dat right -[ 25, 25 ] = ascii (fp) : "./hrtfs/elev35/L35e235a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e235a.dat right -[ 25, 26 ] = ascii (fp) : "./hrtfs/elev35/L35e230a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e230a.dat right -[ 25, 27 ] = ascii (fp) : "./hrtfs/elev35/L35e225a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e225a.dat right -[ 25, 28 ] = ascii (fp) : "./hrtfs/elev35/L35e220a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e220a.dat right -[ 25, 29 ] = ascii (fp) : "./hrtfs/elev35/L35e215a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e215a.dat right -[ 25, 30 ] = ascii (fp) : "./hrtfs/elev35/L35e210a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e210a.dat right -[ 25, 31 ] = ascii (fp) : "./hrtfs/elev35/L35e205a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e205a.dat right -[ 25, 32 ] = ascii (fp) : "./hrtfs/elev35/L35e200a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e200a.dat right -[ 25, 33 ] = ascii (fp) : "./hrtfs/elev35/L35e195a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e195a.dat right -[ 25, 34 ] = ascii (fp) : "./hrtfs/elev35/L35e190a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e190a.dat right -[ 25, 35 ] = ascii (fp) : "./hrtfs/elev35/L35e185a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e185a.dat right -[ 25, 36 ] = ascii (fp) : "./hrtfs/elev35/L35e180a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e180a.dat right -[ 25, 37 ] = ascii (fp) : "./hrtfs/elev35/L35e175a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e175a.dat right -[ 25, 38 ] = ascii (fp) : "./hrtfs/elev35/L35e170a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e170a.dat right -[ 25, 39 ] = ascii (fp) : "./hrtfs/elev35/L35e165a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e165a.dat right -[ 25, 40 ] = ascii (fp) : "./hrtfs/elev35/L35e160a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e160a.dat right -[ 25, 41 ] = ascii (fp) : "./hrtfs/elev35/L35e155a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e155a.dat right -[ 25, 42 ] = ascii (fp) : "./hrtfs/elev35/L35e150a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e150a.dat right -[ 25, 43 ] = ascii (fp) : "./hrtfs/elev35/L35e145a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e145a.dat right -[ 25, 44 ] = ascii (fp) : "./hrtfs/elev35/L35e140a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e140a.dat right -[ 25, 45 ] = ascii (fp) : "./hrtfs/elev35/L35e135a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e135a.dat right -[ 25, 46 ] = ascii (fp) : "./hrtfs/elev35/L35e130a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e130a.dat right -[ 25, 47 ] = ascii (fp) : "./hrtfs/elev35/L35e125a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e125a.dat right -[ 25, 48 ] = ascii (fp) : "./hrtfs/elev35/L35e120a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e120a.dat right -[ 25, 49 ] = ascii (fp) : "./hrtfs/elev35/L35e115a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e115a.dat right -[ 25, 50 ] = ascii (fp) : "./hrtfs/elev35/L35e110a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e110a.dat right -[ 25, 51 ] = ascii (fp) : "./hrtfs/elev35/L35e105a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e105a.dat right -[ 25, 52 ] = ascii (fp) : "./hrtfs/elev35/L35e100a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e100a.dat right -[ 25, 53 ] = ascii (fp) : "./hrtfs/elev35/L35e095a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e095a.dat right -[ 25, 54 ] = ascii (fp) : "./hrtfs/elev35/L35e090a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e090a.dat right -[ 25, 55 ] = ascii (fp) : "./hrtfs/elev35/L35e085a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e085a.dat right -[ 25, 56 ] = ascii (fp) : "./hrtfs/elev35/L35e080a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e080a.dat right -[ 25, 57 ] = ascii (fp) : "./hrtfs/elev35/L35e075a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e075a.dat right -[ 25, 58 ] = ascii (fp) : "./hrtfs/elev35/L35e070a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e070a.dat right -[ 25, 59 ] = ascii (fp) : "./hrtfs/elev35/L35e065a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e065a.dat right -[ 25, 60 ] = ascii (fp) : "./hrtfs/elev35/L35e060a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e060a.dat right -[ 25, 61 ] = ascii (fp) : "./hrtfs/elev35/L35e055a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e055a.dat right -[ 25, 62 ] = ascii (fp) : "./hrtfs/elev35/L35e050a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e050a.dat right -[ 25, 63 ] = ascii (fp) : "./hrtfs/elev35/L35e045a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e045a.dat right -[ 25, 64 ] = ascii (fp) : "./hrtfs/elev35/L35e040a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e040a.dat right -[ 25, 65 ] = ascii (fp) : "./hrtfs/elev35/L35e035a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e035a.dat right -[ 25, 66 ] = ascii (fp) : "./hrtfs/elev35/L35e030a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e030a.dat right -[ 25, 67 ] = ascii (fp) : "./hrtfs/elev35/L35e025a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e025a.dat right -[ 25, 68 ] = ascii (fp) : "./hrtfs/elev35/L35e020a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e020a.dat right -[ 25, 69 ] = ascii (fp) : "./hrtfs/elev35/L35e015a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e015a.dat right -[ 25, 70 ] = ascii (fp) : "./hrtfs/elev35/L35e010a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e010a.dat right -[ 25, 71 ] = ascii (fp) : "./hrtfs/elev35/L35e005a.dat left - + ascii (fp) : "./hrtfs/elev35/R35e005a.dat right +[ 25, 0 ] = ascii (fp) : "./hrtfs/elev35/L35e000a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e000a.dat" right +[ 25, 1 ] = ascii (fp) : "./hrtfs/elev35/L35e355a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e355a.dat" right +[ 25, 2 ] = ascii (fp) : "./hrtfs/elev35/L35e350a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e350a.dat" right +[ 25, 3 ] = ascii (fp) : "./hrtfs/elev35/L35e345a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e345a.dat" right +[ 25, 4 ] = ascii (fp) : "./hrtfs/elev35/L35e340a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e340a.dat" right +[ 25, 5 ] = ascii (fp) : "./hrtfs/elev35/L35e335a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e335a.dat" right +[ 25, 6 ] = ascii (fp) : "./hrtfs/elev35/L35e330a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e330a.dat" right +[ 25, 7 ] = ascii (fp) : "./hrtfs/elev35/L35e325a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e325a.dat" right +[ 25, 8 ] = ascii (fp) : "./hrtfs/elev35/L35e320a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e320a.dat" right +[ 25, 9 ] = ascii (fp) : "./hrtfs/elev35/L35e315a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e315a.dat" right +[ 25, 10 ] = ascii (fp) : "./hrtfs/elev35/L35e310a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e310a.dat" right +[ 25, 11 ] = ascii (fp) : "./hrtfs/elev35/L35e305a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e305a.dat" right +[ 25, 12 ] = ascii (fp) : "./hrtfs/elev35/L35e300a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e300a.dat" right +[ 25, 13 ] = ascii (fp) : "./hrtfs/elev35/L35e295a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e295a.dat" right +[ 25, 14 ] = ascii (fp) : "./hrtfs/elev35/L35e290a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e290a.dat" right +[ 25, 15 ] = ascii (fp) : "./hrtfs/elev35/L35e285a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e285a.dat" right +[ 25, 16 ] = ascii (fp) : "./hrtfs/elev35/L35e280a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e280a.dat" right +[ 25, 17 ] = ascii (fp) : "./hrtfs/elev35/L35e275a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e275a.dat" right +[ 25, 18 ] = ascii (fp) : "./hrtfs/elev35/L35e270a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e270a.dat" right +[ 25, 19 ] = ascii (fp) : "./hrtfs/elev35/L35e265a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e265a.dat" right +[ 25, 20 ] = ascii (fp) : "./hrtfs/elev35/L35e260a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e260a.dat" right +[ 25, 21 ] = ascii (fp) : "./hrtfs/elev35/L35e255a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e255a.dat" right +[ 25, 22 ] = ascii (fp) : "./hrtfs/elev35/L35e250a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e250a.dat" right +[ 25, 23 ] = ascii (fp) : "./hrtfs/elev35/L35e245a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e245a.dat" right +[ 25, 24 ] = ascii (fp) : "./hrtfs/elev35/L35e240a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e240a.dat" right +[ 25, 25 ] = ascii (fp) : "./hrtfs/elev35/L35e235a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e235a.dat" right +[ 25, 26 ] = ascii (fp) : "./hrtfs/elev35/L35e230a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e230a.dat" right +[ 25, 27 ] = ascii (fp) : "./hrtfs/elev35/L35e225a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e225a.dat" right +[ 25, 28 ] = ascii (fp) : "./hrtfs/elev35/L35e220a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e220a.dat" right +[ 25, 29 ] = ascii (fp) : "./hrtfs/elev35/L35e215a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e215a.dat" right +[ 25, 30 ] = ascii (fp) : "./hrtfs/elev35/L35e210a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e210a.dat" right +[ 25, 31 ] = ascii (fp) : "./hrtfs/elev35/L35e205a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e205a.dat" right +[ 25, 32 ] = ascii (fp) : "./hrtfs/elev35/L35e200a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e200a.dat" right +[ 25, 33 ] = ascii (fp) : "./hrtfs/elev35/L35e195a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e195a.dat" right +[ 25, 34 ] = ascii (fp) : "./hrtfs/elev35/L35e190a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e190a.dat" right +[ 25, 35 ] = ascii (fp) : "./hrtfs/elev35/L35e185a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e185a.dat" right +[ 25, 36 ] = ascii (fp) : "./hrtfs/elev35/L35e180a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e180a.dat" right +[ 25, 37 ] = ascii (fp) : "./hrtfs/elev35/L35e175a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e175a.dat" right +[ 25, 38 ] = ascii (fp) : "./hrtfs/elev35/L35e170a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e170a.dat" right +[ 25, 39 ] = ascii (fp) : "./hrtfs/elev35/L35e165a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e165a.dat" right +[ 25, 40 ] = ascii (fp) : "./hrtfs/elev35/L35e160a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e160a.dat" right +[ 25, 41 ] = ascii (fp) : "./hrtfs/elev35/L35e155a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e155a.dat" right +[ 25, 42 ] = ascii (fp) : "./hrtfs/elev35/L35e150a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e150a.dat" right +[ 25, 43 ] = ascii (fp) : "./hrtfs/elev35/L35e145a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e145a.dat" right +[ 25, 44 ] = ascii (fp) : "./hrtfs/elev35/L35e140a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e140a.dat" right +[ 25, 45 ] = ascii (fp) : "./hrtfs/elev35/L35e135a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e135a.dat" right +[ 25, 46 ] = ascii (fp) : "./hrtfs/elev35/L35e130a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e130a.dat" right +[ 25, 47 ] = ascii (fp) : "./hrtfs/elev35/L35e125a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e125a.dat" right +[ 25, 48 ] = ascii (fp) : "./hrtfs/elev35/L35e120a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e120a.dat" right +[ 25, 49 ] = ascii (fp) : "./hrtfs/elev35/L35e115a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e115a.dat" right +[ 25, 50 ] = ascii (fp) : "./hrtfs/elev35/L35e110a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e110a.dat" right +[ 25, 51 ] = ascii (fp) : "./hrtfs/elev35/L35e105a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e105a.dat" right +[ 25, 52 ] = ascii (fp) : "./hrtfs/elev35/L35e100a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e100a.dat" right +[ 25, 53 ] = ascii (fp) : "./hrtfs/elev35/L35e095a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e095a.dat" right +[ 25, 54 ] = ascii (fp) : "./hrtfs/elev35/L35e090a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e090a.dat" right +[ 25, 55 ] = ascii (fp) : "./hrtfs/elev35/L35e085a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e085a.dat" right +[ 25, 56 ] = ascii (fp) : "./hrtfs/elev35/L35e080a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e080a.dat" right +[ 25, 57 ] = ascii (fp) : "./hrtfs/elev35/L35e075a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e075a.dat" right +[ 25, 58 ] = ascii (fp) : "./hrtfs/elev35/L35e070a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e070a.dat" right +[ 25, 59 ] = ascii (fp) : "./hrtfs/elev35/L35e065a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e065a.dat" right +[ 25, 60 ] = ascii (fp) : "./hrtfs/elev35/L35e060a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e060a.dat" right +[ 25, 61 ] = ascii (fp) : "./hrtfs/elev35/L35e055a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e055a.dat" right +[ 25, 62 ] = ascii (fp) : "./hrtfs/elev35/L35e050a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e050a.dat" right +[ 25, 63 ] = ascii (fp) : "./hrtfs/elev35/L35e045a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e045a.dat" right +[ 25, 64 ] = ascii (fp) : "./hrtfs/elev35/L35e040a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e040a.dat" right +[ 25, 65 ] = ascii (fp) : "./hrtfs/elev35/L35e035a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e035a.dat" right +[ 25, 66 ] = ascii (fp) : "./hrtfs/elev35/L35e030a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e030a.dat" right +[ 25, 67 ] = ascii (fp) : "./hrtfs/elev35/L35e025a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e025a.dat" right +[ 25, 68 ] = ascii (fp) : "./hrtfs/elev35/L35e020a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e020a.dat" right +[ 25, 69 ] = ascii (fp) : "./hrtfs/elev35/L35e015a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e015a.dat" right +[ 25, 70 ] = ascii (fp) : "./hrtfs/elev35/L35e010a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e010a.dat" right +[ 25, 71 ] = ascii (fp) : "./hrtfs/elev35/L35e005a.dat" left + + ascii (fp) : "./hrtfs/elev35/R35e005a.dat" right -[ 26, 0 ] = ascii (fp) : "./hrtfs/elev40/L40e000a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e000a.dat right -[ 26, 1 ] = ascii (fp) : "./hrtfs/elev40/L40e355a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e355a.dat right -[ 26, 2 ] = ascii (fp) : "./hrtfs/elev40/L40e350a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e350a.dat right -[ 26, 3 ] = ascii (fp) : "./hrtfs/elev40/L40e345a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e345a.dat right -[ 26, 4 ] = ascii (fp) : "./hrtfs/elev40/L40e340a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e340a.dat right -[ 26, 5 ] = ascii (fp) : "./hrtfs/elev40/L40e335a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e335a.dat right -[ 26, 6 ] = ascii (fp) : "./hrtfs/elev40/L40e330a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e330a.dat right -[ 26, 7 ] = ascii (fp) : "./hrtfs/elev40/L40e325a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e325a.dat right -[ 26, 8 ] = ascii (fp) : "./hrtfs/elev40/L40e320a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e320a.dat right -[ 26, 9 ] = ascii (fp) : "./hrtfs/elev40/L40e315a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e315a.dat right -[ 26, 10 ] = ascii (fp) : "./hrtfs/elev40/L40e310a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e310a.dat right -[ 26, 11 ] = ascii (fp) : "./hrtfs/elev40/L40e305a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e305a.dat right -[ 26, 12 ] = ascii (fp) : "./hrtfs/elev40/L40e300a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e300a.dat right -[ 26, 13 ] = ascii (fp) : "./hrtfs/elev40/L40e295a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e295a.dat right -[ 26, 14 ] = ascii (fp) : "./hrtfs/elev40/L40e290a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e290a.dat right -[ 26, 15 ] = ascii (fp) : "./hrtfs/elev40/L40e285a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e285a.dat right -[ 26, 16 ] = ascii (fp) : "./hrtfs/elev40/L40e280a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e280a.dat right -[ 26, 17 ] = ascii (fp) : "./hrtfs/elev40/L40e275a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e275a.dat right -[ 26, 18 ] = ascii (fp) : "./hrtfs/elev40/L40e270a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e270a.dat right -[ 26, 19 ] = ascii (fp) : "./hrtfs/elev40/L40e265a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e265a.dat right -[ 26, 20 ] = ascii (fp) : "./hrtfs/elev40/L40e260a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e260a.dat right -[ 26, 21 ] = ascii (fp) : "./hrtfs/elev40/L40e255a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e255a.dat right -[ 26, 22 ] = ascii (fp) : "./hrtfs/elev40/L40e250a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e250a.dat right -[ 26, 23 ] = ascii (fp) : "./hrtfs/elev40/L40e245a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e245a.dat right -[ 26, 24 ] = ascii (fp) : "./hrtfs/elev40/L40e240a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e240a.dat right -[ 26, 25 ] = ascii (fp) : "./hrtfs/elev40/L40e235a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e235a.dat right -[ 26, 26 ] = ascii (fp) : "./hrtfs/elev40/L40e230a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e230a.dat right -[ 26, 27 ] = ascii (fp) : "./hrtfs/elev40/L40e225a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e225a.dat right -[ 26, 28 ] = ascii (fp) : "./hrtfs/elev40/L40e220a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e220a.dat right -[ 26, 29 ] = ascii (fp) : "./hrtfs/elev40/L40e215a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e215a.dat right -[ 26, 30 ] = ascii (fp) : "./hrtfs/elev40/L40e210a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e210a.dat right -[ 26, 31 ] = ascii (fp) : "./hrtfs/elev40/L40e205a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e205a.dat right -[ 26, 32 ] = ascii (fp) : "./hrtfs/elev40/L40e200a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e200a.dat right -[ 26, 33 ] = ascii (fp) : "./hrtfs/elev40/L40e195a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e195a.dat right -[ 26, 34 ] = ascii (fp) : "./hrtfs/elev40/L40e190a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e190a.dat right -[ 26, 35 ] = ascii (fp) : "./hrtfs/elev40/L40e185a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e185a.dat right -[ 26, 36 ] = ascii (fp) : "./hrtfs/elev40/L40e180a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e180a.dat right -[ 26, 37 ] = ascii (fp) : "./hrtfs/elev40/L40e175a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e175a.dat right -[ 26, 38 ] = ascii (fp) : "./hrtfs/elev40/L40e170a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e170a.dat right -[ 26, 39 ] = ascii (fp) : "./hrtfs/elev40/L40e165a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e165a.dat right -[ 26, 40 ] = ascii (fp) : "./hrtfs/elev40/L40e160a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e160a.dat right -[ 26, 41 ] = ascii (fp) : "./hrtfs/elev40/L40e155a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e155a.dat right -[ 26, 42 ] = ascii (fp) : "./hrtfs/elev40/L40e150a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e150a.dat right -[ 26, 43 ] = ascii (fp) : "./hrtfs/elev40/L40e145a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e145a.dat right -[ 26, 44 ] = ascii (fp) : "./hrtfs/elev40/L40e140a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e140a.dat right -[ 26, 45 ] = ascii (fp) : "./hrtfs/elev40/L40e135a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e135a.dat right -[ 26, 46 ] = ascii (fp) : "./hrtfs/elev40/L40e130a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e130a.dat right -[ 26, 47 ] = ascii (fp) : "./hrtfs/elev40/L40e125a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e125a.dat right -[ 26, 48 ] = ascii (fp) : "./hrtfs/elev40/L40e120a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e120a.dat right -[ 26, 49 ] = ascii (fp) : "./hrtfs/elev40/L40e115a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e115a.dat right -[ 26, 50 ] = ascii (fp) : "./hrtfs/elev40/L40e110a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e110a.dat right -[ 26, 51 ] = ascii (fp) : "./hrtfs/elev40/L40e105a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e105a.dat right -[ 26, 52 ] = ascii (fp) : "./hrtfs/elev40/L40e100a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e100a.dat right -[ 26, 53 ] = ascii (fp) : "./hrtfs/elev40/L40e095a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e095a.dat right -[ 26, 54 ] = ascii (fp) : "./hrtfs/elev40/L40e090a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e090a.dat right -[ 26, 55 ] = ascii (fp) : "./hrtfs/elev40/L40e085a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e085a.dat right -[ 26, 56 ] = ascii (fp) : "./hrtfs/elev40/L40e080a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e080a.dat right -[ 26, 57 ] = ascii (fp) : "./hrtfs/elev40/L40e075a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e075a.dat right -[ 26, 58 ] = ascii (fp) : "./hrtfs/elev40/L40e070a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e070a.dat right -[ 26, 59 ] = ascii (fp) : "./hrtfs/elev40/L40e065a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e065a.dat right -[ 26, 60 ] = ascii (fp) : "./hrtfs/elev40/L40e060a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e060a.dat right -[ 26, 61 ] = ascii (fp) : "./hrtfs/elev40/L40e055a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e055a.dat right -[ 26, 62 ] = ascii (fp) : "./hrtfs/elev40/L40e050a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e050a.dat right -[ 26, 63 ] = ascii (fp) : "./hrtfs/elev40/L40e045a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e045a.dat right -[ 26, 64 ] = ascii (fp) : "./hrtfs/elev40/L40e040a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e040a.dat right -[ 26, 65 ] = ascii (fp) : "./hrtfs/elev40/L40e035a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e035a.dat right -[ 26, 66 ] = ascii (fp) : "./hrtfs/elev40/L40e030a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e030a.dat right -[ 26, 67 ] = ascii (fp) : "./hrtfs/elev40/L40e025a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e025a.dat right -[ 26, 68 ] = ascii (fp) : "./hrtfs/elev40/L40e020a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e020a.dat right -[ 26, 69 ] = ascii (fp) : "./hrtfs/elev40/L40e015a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e015a.dat right -[ 26, 70 ] = ascii (fp) : "./hrtfs/elev40/L40e010a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e010a.dat right -[ 26, 71 ] = ascii (fp) : "./hrtfs/elev40/L40e005a.dat left - + ascii (fp) : "./hrtfs/elev40/R40e005a.dat right +[ 26, 0 ] = ascii (fp) : "./hrtfs/elev40/L40e000a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e000a.dat" right +[ 26, 1 ] = ascii (fp) : "./hrtfs/elev40/L40e355a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e355a.dat" right +[ 26, 2 ] = ascii (fp) : "./hrtfs/elev40/L40e350a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e350a.dat" right +[ 26, 3 ] = ascii (fp) : "./hrtfs/elev40/L40e345a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e345a.dat" right +[ 26, 4 ] = ascii (fp) : "./hrtfs/elev40/L40e340a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e340a.dat" right +[ 26, 5 ] = ascii (fp) : "./hrtfs/elev40/L40e335a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e335a.dat" right +[ 26, 6 ] = ascii (fp) : "./hrtfs/elev40/L40e330a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e330a.dat" right +[ 26, 7 ] = ascii (fp) : "./hrtfs/elev40/L40e325a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e325a.dat" right +[ 26, 8 ] = ascii (fp) : "./hrtfs/elev40/L40e320a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e320a.dat" right +[ 26, 9 ] = ascii (fp) : "./hrtfs/elev40/L40e315a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e315a.dat" right +[ 26, 10 ] = ascii (fp) : "./hrtfs/elev40/L40e310a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e310a.dat" right +[ 26, 11 ] = ascii (fp) : "./hrtfs/elev40/L40e305a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e305a.dat" right +[ 26, 12 ] = ascii (fp) : "./hrtfs/elev40/L40e300a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e300a.dat" right +[ 26, 13 ] = ascii (fp) : "./hrtfs/elev40/L40e295a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e295a.dat" right +[ 26, 14 ] = ascii (fp) : "./hrtfs/elev40/L40e290a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e290a.dat" right +[ 26, 15 ] = ascii (fp) : "./hrtfs/elev40/L40e285a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e285a.dat" right +[ 26, 16 ] = ascii (fp) : "./hrtfs/elev40/L40e280a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e280a.dat" right +[ 26, 17 ] = ascii (fp) : "./hrtfs/elev40/L40e275a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e275a.dat" right +[ 26, 18 ] = ascii (fp) : "./hrtfs/elev40/L40e270a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e270a.dat" right +[ 26, 19 ] = ascii (fp) : "./hrtfs/elev40/L40e265a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e265a.dat" right +[ 26, 20 ] = ascii (fp) : "./hrtfs/elev40/L40e260a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e260a.dat" right +[ 26, 21 ] = ascii (fp) : "./hrtfs/elev40/L40e255a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e255a.dat" right +[ 26, 22 ] = ascii (fp) : "./hrtfs/elev40/L40e250a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e250a.dat" right +[ 26, 23 ] = ascii (fp) : "./hrtfs/elev40/L40e245a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e245a.dat" right +[ 26, 24 ] = ascii (fp) : "./hrtfs/elev40/L40e240a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e240a.dat" right +[ 26, 25 ] = ascii (fp) : "./hrtfs/elev40/L40e235a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e235a.dat" right +[ 26, 26 ] = ascii (fp) : "./hrtfs/elev40/L40e230a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e230a.dat" right +[ 26, 27 ] = ascii (fp) : "./hrtfs/elev40/L40e225a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e225a.dat" right +[ 26, 28 ] = ascii (fp) : "./hrtfs/elev40/L40e220a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e220a.dat" right +[ 26, 29 ] = ascii (fp) : "./hrtfs/elev40/L40e215a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e215a.dat" right +[ 26, 30 ] = ascii (fp) : "./hrtfs/elev40/L40e210a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e210a.dat" right +[ 26, 31 ] = ascii (fp) : "./hrtfs/elev40/L40e205a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e205a.dat" right +[ 26, 32 ] = ascii (fp) : "./hrtfs/elev40/L40e200a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e200a.dat" right +[ 26, 33 ] = ascii (fp) : "./hrtfs/elev40/L40e195a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e195a.dat" right +[ 26, 34 ] = ascii (fp) : "./hrtfs/elev40/L40e190a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e190a.dat" right +[ 26, 35 ] = ascii (fp) : "./hrtfs/elev40/L40e185a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e185a.dat" right +[ 26, 36 ] = ascii (fp) : "./hrtfs/elev40/L40e180a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e180a.dat" right +[ 26, 37 ] = ascii (fp) : "./hrtfs/elev40/L40e175a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e175a.dat" right +[ 26, 38 ] = ascii (fp) : "./hrtfs/elev40/L40e170a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e170a.dat" right +[ 26, 39 ] = ascii (fp) : "./hrtfs/elev40/L40e165a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e165a.dat" right +[ 26, 40 ] = ascii (fp) : "./hrtfs/elev40/L40e160a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e160a.dat" right +[ 26, 41 ] = ascii (fp) : "./hrtfs/elev40/L40e155a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e155a.dat" right +[ 26, 42 ] = ascii (fp) : "./hrtfs/elev40/L40e150a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e150a.dat" right +[ 26, 43 ] = ascii (fp) : "./hrtfs/elev40/L40e145a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e145a.dat" right +[ 26, 44 ] = ascii (fp) : "./hrtfs/elev40/L40e140a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e140a.dat" right +[ 26, 45 ] = ascii (fp) : "./hrtfs/elev40/L40e135a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e135a.dat" right +[ 26, 46 ] = ascii (fp) : "./hrtfs/elev40/L40e130a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e130a.dat" right +[ 26, 47 ] = ascii (fp) : "./hrtfs/elev40/L40e125a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e125a.dat" right +[ 26, 48 ] = ascii (fp) : "./hrtfs/elev40/L40e120a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e120a.dat" right +[ 26, 49 ] = ascii (fp) : "./hrtfs/elev40/L40e115a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e115a.dat" right +[ 26, 50 ] = ascii (fp) : "./hrtfs/elev40/L40e110a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e110a.dat" right +[ 26, 51 ] = ascii (fp) : "./hrtfs/elev40/L40e105a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e105a.dat" right +[ 26, 52 ] = ascii (fp) : "./hrtfs/elev40/L40e100a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e100a.dat" right +[ 26, 53 ] = ascii (fp) : "./hrtfs/elev40/L40e095a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e095a.dat" right +[ 26, 54 ] = ascii (fp) : "./hrtfs/elev40/L40e090a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e090a.dat" right +[ 26, 55 ] = ascii (fp) : "./hrtfs/elev40/L40e085a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e085a.dat" right +[ 26, 56 ] = ascii (fp) : "./hrtfs/elev40/L40e080a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e080a.dat" right +[ 26, 57 ] = ascii (fp) : "./hrtfs/elev40/L40e075a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e075a.dat" right +[ 26, 58 ] = ascii (fp) : "./hrtfs/elev40/L40e070a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e070a.dat" right +[ 26, 59 ] = ascii (fp) : "./hrtfs/elev40/L40e065a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e065a.dat" right +[ 26, 60 ] = ascii (fp) : "./hrtfs/elev40/L40e060a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e060a.dat" right +[ 26, 61 ] = ascii (fp) : "./hrtfs/elev40/L40e055a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e055a.dat" right +[ 26, 62 ] = ascii (fp) : "./hrtfs/elev40/L40e050a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e050a.dat" right +[ 26, 63 ] = ascii (fp) : "./hrtfs/elev40/L40e045a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e045a.dat" right +[ 26, 64 ] = ascii (fp) : "./hrtfs/elev40/L40e040a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e040a.dat" right +[ 26, 65 ] = ascii (fp) : "./hrtfs/elev40/L40e035a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e035a.dat" right +[ 26, 66 ] = ascii (fp) : "./hrtfs/elev40/L40e030a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e030a.dat" right +[ 26, 67 ] = ascii (fp) : "./hrtfs/elev40/L40e025a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e025a.dat" right +[ 26, 68 ] = ascii (fp) : "./hrtfs/elev40/L40e020a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e020a.dat" right +[ 26, 69 ] = ascii (fp) : "./hrtfs/elev40/L40e015a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e015a.dat" right +[ 26, 70 ] = ascii (fp) : "./hrtfs/elev40/L40e010a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e010a.dat" right +[ 26, 71 ] = ascii (fp) : "./hrtfs/elev40/L40e005a.dat" left + + ascii (fp) : "./hrtfs/elev40/R40e005a.dat" right -[ 27, 0 ] = ascii (fp) : "./hrtfs/elev45/L45e000a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e000a.dat right -[ 27, 1 ] = ascii (fp) : "./hrtfs/elev45/L45e355a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e355a.dat right -[ 27, 2 ] = ascii (fp) : "./hrtfs/elev45/L45e350a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e350a.dat right -[ 27, 3 ] = ascii (fp) : "./hrtfs/elev45/L45e345a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e345a.dat right -[ 27, 4 ] = ascii (fp) : "./hrtfs/elev45/L45e340a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e340a.dat right -[ 27, 5 ] = ascii (fp) : "./hrtfs/elev45/L45e335a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e335a.dat right -[ 27, 6 ] = ascii (fp) : "./hrtfs/elev45/L45e330a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e330a.dat right -[ 27, 7 ] = ascii (fp) : "./hrtfs/elev45/L45e325a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e325a.dat right -[ 27, 8 ] = ascii (fp) : "./hrtfs/elev45/L45e320a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e320a.dat right -[ 27, 9 ] = ascii (fp) : "./hrtfs/elev45/L45e315a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e315a.dat right -[ 27, 10 ] = ascii (fp) : "./hrtfs/elev45/L45e310a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e310a.dat right -[ 27, 11 ] = ascii (fp) : "./hrtfs/elev45/L45e305a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e305a.dat right -[ 27, 12 ] = ascii (fp) : "./hrtfs/elev45/L45e300a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e300a.dat right -[ 27, 13 ] = ascii (fp) : "./hrtfs/elev45/L45e295a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e295a.dat right -[ 27, 14 ] = ascii (fp) : "./hrtfs/elev45/L45e290a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e290a.dat right -[ 27, 15 ] = ascii (fp) : "./hrtfs/elev45/L45e285a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e285a.dat right -[ 27, 16 ] = ascii (fp) : "./hrtfs/elev45/L45e280a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e280a.dat right -[ 27, 17 ] = ascii (fp) : "./hrtfs/elev45/L45e275a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e275a.dat right -[ 27, 18 ] = ascii (fp) : "./hrtfs/elev45/L45e270a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e270a.dat right -[ 27, 19 ] = ascii (fp) : "./hrtfs/elev45/L45e265a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e265a.dat right -[ 27, 20 ] = ascii (fp) : "./hrtfs/elev45/L45e260a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e260a.dat right -[ 27, 21 ] = ascii (fp) : "./hrtfs/elev45/L45e255a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e255a.dat right -[ 27, 22 ] = ascii (fp) : "./hrtfs/elev45/L45e250a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e250a.dat right -[ 27, 23 ] = ascii (fp) : "./hrtfs/elev45/L45e245a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e245a.dat right -[ 27, 24 ] = ascii (fp) : "./hrtfs/elev45/L45e240a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e240a.dat right -[ 27, 25 ] = ascii (fp) : "./hrtfs/elev45/L45e235a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e235a.dat right -[ 27, 26 ] = ascii (fp) : "./hrtfs/elev45/L45e230a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e230a.dat right -[ 27, 27 ] = ascii (fp) : "./hrtfs/elev45/L45e225a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e225a.dat right -[ 27, 28 ] = ascii (fp) : "./hrtfs/elev45/L45e220a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e220a.dat right -[ 27, 29 ] = ascii (fp) : "./hrtfs/elev45/L45e215a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e215a.dat right -[ 27, 30 ] = ascii (fp) : "./hrtfs/elev45/L45e210a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e210a.dat right -[ 27, 31 ] = ascii (fp) : "./hrtfs/elev45/L45e205a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e205a.dat right -[ 27, 32 ] = ascii (fp) : "./hrtfs/elev45/L45e200a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e200a.dat right -[ 27, 33 ] = ascii (fp) : "./hrtfs/elev45/L45e195a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e195a.dat right -[ 27, 34 ] = ascii (fp) : "./hrtfs/elev45/L45e190a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e190a.dat right -[ 27, 35 ] = ascii (fp) : "./hrtfs/elev45/L45e185a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e185a.dat right -[ 27, 36 ] = ascii (fp) : "./hrtfs/elev45/L45e180a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e180a.dat right -[ 27, 37 ] = ascii (fp) : "./hrtfs/elev45/L45e175a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e175a.dat right -[ 27, 38 ] = ascii (fp) : "./hrtfs/elev45/L45e170a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e170a.dat right -[ 27, 39 ] = ascii (fp) : "./hrtfs/elev45/L45e165a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e165a.dat right -[ 27, 40 ] = ascii (fp) : "./hrtfs/elev45/L45e160a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e160a.dat right -[ 27, 41 ] = ascii (fp) : "./hrtfs/elev45/L45e155a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e155a.dat right -[ 27, 42 ] = ascii (fp) : "./hrtfs/elev45/L45e150a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e150a.dat right -[ 27, 43 ] = ascii (fp) : "./hrtfs/elev45/L45e145a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e145a.dat right -[ 27, 44 ] = ascii (fp) : "./hrtfs/elev45/L45e140a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e140a.dat right -[ 27, 45 ] = ascii (fp) : "./hrtfs/elev45/L45e135a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e135a.dat right -[ 27, 46 ] = ascii (fp) : "./hrtfs/elev45/L45e130a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e130a.dat right -[ 27, 47 ] = ascii (fp) : "./hrtfs/elev45/L45e125a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e125a.dat right -[ 27, 48 ] = ascii (fp) : "./hrtfs/elev45/L45e120a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e120a.dat right -[ 27, 49 ] = ascii (fp) : "./hrtfs/elev45/L45e115a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e115a.dat right -[ 27, 50 ] = ascii (fp) : "./hrtfs/elev45/L45e110a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e110a.dat right -[ 27, 51 ] = ascii (fp) : "./hrtfs/elev45/L45e105a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e105a.dat right -[ 27, 52 ] = ascii (fp) : "./hrtfs/elev45/L45e100a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e100a.dat right -[ 27, 53 ] = ascii (fp) : "./hrtfs/elev45/L45e095a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e095a.dat right -[ 27, 54 ] = ascii (fp) : "./hrtfs/elev45/L45e090a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e090a.dat right -[ 27, 55 ] = ascii (fp) : "./hrtfs/elev45/L45e085a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e085a.dat right -[ 27, 56 ] = ascii (fp) : "./hrtfs/elev45/L45e080a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e080a.dat right -[ 27, 57 ] = ascii (fp) : "./hrtfs/elev45/L45e075a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e075a.dat right -[ 27, 58 ] = ascii (fp) : "./hrtfs/elev45/L45e070a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e070a.dat right -[ 27, 59 ] = ascii (fp) : "./hrtfs/elev45/L45e065a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e065a.dat right -[ 27, 60 ] = ascii (fp) : "./hrtfs/elev45/L45e060a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e060a.dat right -[ 27, 61 ] = ascii (fp) : "./hrtfs/elev45/L45e055a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e055a.dat right -[ 27, 62 ] = ascii (fp) : "./hrtfs/elev45/L45e050a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e050a.dat right -[ 27, 63 ] = ascii (fp) : "./hrtfs/elev45/L45e045a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e045a.dat right -[ 27, 64 ] = ascii (fp) : "./hrtfs/elev45/L45e040a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e040a.dat right -[ 27, 65 ] = ascii (fp) : "./hrtfs/elev45/L45e035a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e035a.dat right -[ 27, 66 ] = ascii (fp) : "./hrtfs/elev45/L45e030a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e030a.dat right -[ 27, 67 ] = ascii (fp) : "./hrtfs/elev45/L45e025a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e025a.dat right -[ 27, 68 ] = ascii (fp) : "./hrtfs/elev45/L45e020a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e020a.dat right -[ 27, 69 ] = ascii (fp) : "./hrtfs/elev45/L45e015a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e015a.dat right -[ 27, 70 ] = ascii (fp) : "./hrtfs/elev45/L45e010a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e010a.dat right -[ 27, 71 ] = ascii (fp) : "./hrtfs/elev45/L45e005a.dat left - + ascii (fp) : "./hrtfs/elev45/R45e005a.dat right +[ 27, 0 ] = ascii (fp) : "./hrtfs/elev45/L45e000a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e000a.dat" right +[ 27, 1 ] = ascii (fp) : "./hrtfs/elev45/L45e355a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e355a.dat" right +[ 27, 2 ] = ascii (fp) : "./hrtfs/elev45/L45e350a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e350a.dat" right +[ 27, 3 ] = ascii (fp) : "./hrtfs/elev45/L45e345a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e345a.dat" right +[ 27, 4 ] = ascii (fp) : "./hrtfs/elev45/L45e340a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e340a.dat" right +[ 27, 5 ] = ascii (fp) : "./hrtfs/elev45/L45e335a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e335a.dat" right +[ 27, 6 ] = ascii (fp) : "./hrtfs/elev45/L45e330a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e330a.dat" right +[ 27, 7 ] = ascii (fp) : "./hrtfs/elev45/L45e325a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e325a.dat" right +[ 27, 8 ] = ascii (fp) : "./hrtfs/elev45/L45e320a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e320a.dat" right +[ 27, 9 ] = ascii (fp) : "./hrtfs/elev45/L45e315a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e315a.dat" right +[ 27, 10 ] = ascii (fp) : "./hrtfs/elev45/L45e310a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e310a.dat" right +[ 27, 11 ] = ascii (fp) : "./hrtfs/elev45/L45e305a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e305a.dat" right +[ 27, 12 ] = ascii (fp) : "./hrtfs/elev45/L45e300a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e300a.dat" right +[ 27, 13 ] = ascii (fp) : "./hrtfs/elev45/L45e295a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e295a.dat" right +[ 27, 14 ] = ascii (fp) : "./hrtfs/elev45/L45e290a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e290a.dat" right +[ 27, 15 ] = ascii (fp) : "./hrtfs/elev45/L45e285a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e285a.dat" right +[ 27, 16 ] = ascii (fp) : "./hrtfs/elev45/L45e280a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e280a.dat" right +[ 27, 17 ] = ascii (fp) : "./hrtfs/elev45/L45e275a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e275a.dat" right +[ 27, 18 ] = ascii (fp) : "./hrtfs/elev45/L45e270a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e270a.dat" right +[ 27, 19 ] = ascii (fp) : "./hrtfs/elev45/L45e265a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e265a.dat" right +[ 27, 20 ] = ascii (fp) : "./hrtfs/elev45/L45e260a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e260a.dat" right +[ 27, 21 ] = ascii (fp) : "./hrtfs/elev45/L45e255a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e255a.dat" right +[ 27, 22 ] = ascii (fp) : "./hrtfs/elev45/L45e250a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e250a.dat" right +[ 27, 23 ] = ascii (fp) : "./hrtfs/elev45/L45e245a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e245a.dat" right +[ 27, 24 ] = ascii (fp) : "./hrtfs/elev45/L45e240a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e240a.dat" right +[ 27, 25 ] = ascii (fp) : "./hrtfs/elev45/L45e235a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e235a.dat" right +[ 27, 26 ] = ascii (fp) : "./hrtfs/elev45/L45e230a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e230a.dat" right +[ 27, 27 ] = ascii (fp) : "./hrtfs/elev45/L45e225a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e225a.dat" right +[ 27, 28 ] = ascii (fp) : "./hrtfs/elev45/L45e220a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e220a.dat" right +[ 27, 29 ] = ascii (fp) : "./hrtfs/elev45/L45e215a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e215a.dat" right +[ 27, 30 ] = ascii (fp) : "./hrtfs/elev45/L45e210a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e210a.dat" right +[ 27, 31 ] = ascii (fp) : "./hrtfs/elev45/L45e205a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e205a.dat" right +[ 27, 32 ] = ascii (fp) : "./hrtfs/elev45/L45e200a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e200a.dat" right +[ 27, 33 ] = ascii (fp) : "./hrtfs/elev45/L45e195a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e195a.dat" right +[ 27, 34 ] = ascii (fp) : "./hrtfs/elev45/L45e190a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e190a.dat" right +[ 27, 35 ] = ascii (fp) : "./hrtfs/elev45/L45e185a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e185a.dat" right +[ 27, 36 ] = ascii (fp) : "./hrtfs/elev45/L45e180a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e180a.dat" right +[ 27, 37 ] = ascii (fp) : "./hrtfs/elev45/L45e175a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e175a.dat" right +[ 27, 38 ] = ascii (fp) : "./hrtfs/elev45/L45e170a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e170a.dat" right +[ 27, 39 ] = ascii (fp) : "./hrtfs/elev45/L45e165a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e165a.dat" right +[ 27, 40 ] = ascii (fp) : "./hrtfs/elev45/L45e160a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e160a.dat" right +[ 27, 41 ] = ascii (fp) : "./hrtfs/elev45/L45e155a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e155a.dat" right +[ 27, 42 ] = ascii (fp) : "./hrtfs/elev45/L45e150a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e150a.dat" right +[ 27, 43 ] = ascii (fp) : "./hrtfs/elev45/L45e145a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e145a.dat" right +[ 27, 44 ] = ascii (fp) : "./hrtfs/elev45/L45e140a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e140a.dat" right +[ 27, 45 ] = ascii (fp) : "./hrtfs/elev45/L45e135a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e135a.dat" right +[ 27, 46 ] = ascii (fp) : "./hrtfs/elev45/L45e130a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e130a.dat" right +[ 27, 47 ] = ascii (fp) : "./hrtfs/elev45/L45e125a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e125a.dat" right +[ 27, 48 ] = ascii (fp) : "./hrtfs/elev45/L45e120a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e120a.dat" right +[ 27, 49 ] = ascii (fp) : "./hrtfs/elev45/L45e115a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e115a.dat" right +[ 27, 50 ] = ascii (fp) : "./hrtfs/elev45/L45e110a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e110a.dat" right +[ 27, 51 ] = ascii (fp) : "./hrtfs/elev45/L45e105a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e105a.dat" right +[ 27, 52 ] = ascii (fp) : "./hrtfs/elev45/L45e100a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e100a.dat" right +[ 27, 53 ] = ascii (fp) : "./hrtfs/elev45/L45e095a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e095a.dat" right +[ 27, 54 ] = ascii (fp) : "./hrtfs/elev45/L45e090a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e090a.dat" right +[ 27, 55 ] = ascii (fp) : "./hrtfs/elev45/L45e085a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e085a.dat" right +[ 27, 56 ] = ascii (fp) : "./hrtfs/elev45/L45e080a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e080a.dat" right +[ 27, 57 ] = ascii (fp) : "./hrtfs/elev45/L45e075a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e075a.dat" right +[ 27, 58 ] = ascii (fp) : "./hrtfs/elev45/L45e070a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e070a.dat" right +[ 27, 59 ] = ascii (fp) : "./hrtfs/elev45/L45e065a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e065a.dat" right +[ 27, 60 ] = ascii (fp) : "./hrtfs/elev45/L45e060a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e060a.dat" right +[ 27, 61 ] = ascii (fp) : "./hrtfs/elev45/L45e055a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e055a.dat" right +[ 27, 62 ] = ascii (fp) : "./hrtfs/elev45/L45e050a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e050a.dat" right +[ 27, 63 ] = ascii (fp) : "./hrtfs/elev45/L45e045a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e045a.dat" right +[ 27, 64 ] = ascii (fp) : "./hrtfs/elev45/L45e040a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e040a.dat" right +[ 27, 65 ] = ascii (fp) : "./hrtfs/elev45/L45e035a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e035a.dat" right +[ 27, 66 ] = ascii (fp) : "./hrtfs/elev45/L45e030a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e030a.dat" right +[ 27, 67 ] = ascii (fp) : "./hrtfs/elev45/L45e025a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e025a.dat" right +[ 27, 68 ] = ascii (fp) : "./hrtfs/elev45/L45e020a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e020a.dat" right +[ 27, 69 ] = ascii (fp) : "./hrtfs/elev45/L45e015a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e015a.dat" right +[ 27, 70 ] = ascii (fp) : "./hrtfs/elev45/L45e010a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e010a.dat" right +[ 27, 71 ] = ascii (fp) : "./hrtfs/elev45/L45e005a.dat" left + + ascii (fp) : "./hrtfs/elev45/R45e005a.dat" right -[ 28, 0 ] = ascii (fp) : "./hrtfs/elev50/L50e000a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e000a.dat right -[ 28, 1 ] = ascii (fp) : "./hrtfs/elev50/L50e355a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e355a.dat right -[ 28, 2 ] = ascii (fp) : "./hrtfs/elev50/L50e350a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e350a.dat right -[ 28, 3 ] = ascii (fp) : "./hrtfs/elev50/L50e345a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e345a.dat right -[ 28, 4 ] = ascii (fp) : "./hrtfs/elev50/L50e340a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e340a.dat right -[ 28, 5 ] = ascii (fp) : "./hrtfs/elev50/L50e335a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e335a.dat right -[ 28, 6 ] = ascii (fp) : "./hrtfs/elev50/L50e330a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e330a.dat right -[ 28, 7 ] = ascii (fp) : "./hrtfs/elev50/L50e325a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e325a.dat right -[ 28, 8 ] = ascii (fp) : "./hrtfs/elev50/L50e320a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e320a.dat right -[ 28, 9 ] = ascii (fp) : "./hrtfs/elev50/L50e315a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e315a.dat right -[ 28, 10 ] = ascii (fp) : "./hrtfs/elev50/L50e310a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e310a.dat right -[ 28, 11 ] = ascii (fp) : "./hrtfs/elev50/L50e305a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e305a.dat right -[ 28, 12 ] = ascii (fp) : "./hrtfs/elev50/L50e300a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e300a.dat right -[ 28, 13 ] = ascii (fp) : "./hrtfs/elev50/L50e295a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e295a.dat right -[ 28, 14 ] = ascii (fp) : "./hrtfs/elev50/L50e290a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e290a.dat right -[ 28, 15 ] = ascii (fp) : "./hrtfs/elev50/L50e285a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e285a.dat right -[ 28, 16 ] = ascii (fp) : "./hrtfs/elev50/L50e280a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e280a.dat right -[ 28, 17 ] = ascii (fp) : "./hrtfs/elev50/L50e275a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e275a.dat right -[ 28, 18 ] = ascii (fp) : "./hrtfs/elev50/L50e270a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e270a.dat right -[ 28, 19 ] = ascii (fp) : "./hrtfs/elev50/L50e265a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e265a.dat right -[ 28, 20 ] = ascii (fp) : "./hrtfs/elev50/L50e260a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e260a.dat right -[ 28, 21 ] = ascii (fp) : "./hrtfs/elev50/L50e255a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e255a.dat right -[ 28, 22 ] = ascii (fp) : "./hrtfs/elev50/L50e250a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e250a.dat right -[ 28, 23 ] = ascii (fp) : "./hrtfs/elev50/L50e245a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e245a.dat right -[ 28, 24 ] = ascii (fp) : "./hrtfs/elev50/L50e240a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e240a.dat right -[ 28, 25 ] = ascii (fp) : "./hrtfs/elev50/L50e235a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e235a.dat right -[ 28, 26 ] = ascii (fp) : "./hrtfs/elev50/L50e230a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e230a.dat right -[ 28, 27 ] = ascii (fp) : "./hrtfs/elev50/L50e225a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e225a.dat right -[ 28, 28 ] = ascii (fp) : "./hrtfs/elev50/L50e220a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e220a.dat right -[ 28, 29 ] = ascii (fp) : "./hrtfs/elev50/L50e215a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e215a.dat right -[ 28, 30 ] = ascii (fp) : "./hrtfs/elev50/L50e210a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e210a.dat right -[ 28, 31 ] = ascii (fp) : "./hrtfs/elev50/L50e205a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e205a.dat right -[ 28, 32 ] = ascii (fp) : "./hrtfs/elev50/L50e200a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e200a.dat right -[ 28, 33 ] = ascii (fp) : "./hrtfs/elev50/L50e195a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e195a.dat right -[ 28, 34 ] = ascii (fp) : "./hrtfs/elev50/L50e190a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e190a.dat right -[ 28, 35 ] = ascii (fp) : "./hrtfs/elev50/L50e185a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e185a.dat right -[ 28, 36 ] = ascii (fp) : "./hrtfs/elev50/L50e180a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e180a.dat right -[ 28, 37 ] = ascii (fp) : "./hrtfs/elev50/L50e175a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e175a.dat right -[ 28, 38 ] = ascii (fp) : "./hrtfs/elev50/L50e170a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e170a.dat right -[ 28, 39 ] = ascii (fp) : "./hrtfs/elev50/L50e165a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e165a.dat right -[ 28, 40 ] = ascii (fp) : "./hrtfs/elev50/L50e160a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e160a.dat right -[ 28, 41 ] = ascii (fp) : "./hrtfs/elev50/L50e155a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e155a.dat right -[ 28, 42 ] = ascii (fp) : "./hrtfs/elev50/L50e150a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e150a.dat right -[ 28, 43 ] = ascii (fp) : "./hrtfs/elev50/L50e145a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e145a.dat right -[ 28, 44 ] = ascii (fp) : "./hrtfs/elev50/L50e140a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e140a.dat right -[ 28, 45 ] = ascii (fp) : "./hrtfs/elev50/L50e135a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e135a.dat right -[ 28, 46 ] = ascii (fp) : "./hrtfs/elev50/L50e130a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e130a.dat right -[ 28, 47 ] = ascii (fp) : "./hrtfs/elev50/L50e125a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e125a.dat right -[ 28, 48 ] = ascii (fp) : "./hrtfs/elev50/L50e120a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e120a.dat right -[ 28, 49 ] = ascii (fp) : "./hrtfs/elev50/L50e115a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e115a.dat right -[ 28, 50 ] = ascii (fp) : "./hrtfs/elev50/L50e110a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e110a.dat right -[ 28, 51 ] = ascii (fp) : "./hrtfs/elev50/L50e105a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e105a.dat right -[ 28, 52 ] = ascii (fp) : "./hrtfs/elev50/L50e100a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e100a.dat right -[ 28, 53 ] = ascii (fp) : "./hrtfs/elev50/L50e095a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e095a.dat right -[ 28, 54 ] = ascii (fp) : "./hrtfs/elev50/L50e090a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e090a.dat right -[ 28, 55 ] = ascii (fp) : "./hrtfs/elev50/L50e085a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e085a.dat right -[ 28, 56 ] = ascii (fp) : "./hrtfs/elev50/L50e080a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e080a.dat right -[ 28, 57 ] = ascii (fp) : "./hrtfs/elev50/L50e075a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e075a.dat right -[ 28, 58 ] = ascii (fp) : "./hrtfs/elev50/L50e070a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e070a.dat right -[ 28, 59 ] = ascii (fp) : "./hrtfs/elev50/L50e065a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e065a.dat right -[ 28, 60 ] = ascii (fp) : "./hrtfs/elev50/L50e060a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e060a.dat right -[ 28, 61 ] = ascii (fp) : "./hrtfs/elev50/L50e055a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e055a.dat right -[ 28, 62 ] = ascii (fp) : "./hrtfs/elev50/L50e050a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e050a.dat right -[ 28, 63 ] = ascii (fp) : "./hrtfs/elev50/L50e045a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e045a.dat right -[ 28, 64 ] = ascii (fp) : "./hrtfs/elev50/L50e040a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e040a.dat right -[ 28, 65 ] = ascii (fp) : "./hrtfs/elev50/L50e035a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e035a.dat right -[ 28, 66 ] = ascii (fp) : "./hrtfs/elev50/L50e030a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e030a.dat right -[ 28, 67 ] = ascii (fp) : "./hrtfs/elev50/L50e025a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e025a.dat right -[ 28, 68 ] = ascii (fp) : "./hrtfs/elev50/L50e020a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e020a.dat right -[ 28, 69 ] = ascii (fp) : "./hrtfs/elev50/L50e015a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e015a.dat right -[ 28, 70 ] = ascii (fp) : "./hrtfs/elev50/L50e010a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e010a.dat right -[ 28, 71 ] = ascii (fp) : "./hrtfs/elev50/L50e005a.dat left - + ascii (fp) : "./hrtfs/elev50/R50e005a.dat right +[ 28, 0 ] = ascii (fp) : "./hrtfs/elev50/L50e000a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e000a.dat" right +[ 28, 1 ] = ascii (fp) : "./hrtfs/elev50/L50e355a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e355a.dat" right +[ 28, 2 ] = ascii (fp) : "./hrtfs/elev50/L50e350a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e350a.dat" right +[ 28, 3 ] = ascii (fp) : "./hrtfs/elev50/L50e345a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e345a.dat" right +[ 28, 4 ] = ascii (fp) : "./hrtfs/elev50/L50e340a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e340a.dat" right +[ 28, 5 ] = ascii (fp) : "./hrtfs/elev50/L50e335a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e335a.dat" right +[ 28, 6 ] = ascii (fp) : "./hrtfs/elev50/L50e330a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e330a.dat" right +[ 28, 7 ] = ascii (fp) : "./hrtfs/elev50/L50e325a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e325a.dat" right +[ 28, 8 ] = ascii (fp) : "./hrtfs/elev50/L50e320a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e320a.dat" right +[ 28, 9 ] = ascii (fp) : "./hrtfs/elev50/L50e315a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e315a.dat" right +[ 28, 10 ] = ascii (fp) : "./hrtfs/elev50/L50e310a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e310a.dat" right +[ 28, 11 ] = ascii (fp) : "./hrtfs/elev50/L50e305a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e305a.dat" right +[ 28, 12 ] = ascii (fp) : "./hrtfs/elev50/L50e300a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e300a.dat" right +[ 28, 13 ] = ascii (fp) : "./hrtfs/elev50/L50e295a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e295a.dat" right +[ 28, 14 ] = ascii (fp) : "./hrtfs/elev50/L50e290a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e290a.dat" right +[ 28, 15 ] = ascii (fp) : "./hrtfs/elev50/L50e285a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e285a.dat" right +[ 28, 16 ] = ascii (fp) : "./hrtfs/elev50/L50e280a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e280a.dat" right +[ 28, 17 ] = ascii (fp) : "./hrtfs/elev50/L50e275a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e275a.dat" right +[ 28, 18 ] = ascii (fp) : "./hrtfs/elev50/L50e270a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e270a.dat" right +[ 28, 19 ] = ascii (fp) : "./hrtfs/elev50/L50e265a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e265a.dat" right +[ 28, 20 ] = ascii (fp) : "./hrtfs/elev50/L50e260a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e260a.dat" right +[ 28, 21 ] = ascii (fp) : "./hrtfs/elev50/L50e255a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e255a.dat" right +[ 28, 22 ] = ascii (fp) : "./hrtfs/elev50/L50e250a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e250a.dat" right +[ 28, 23 ] = ascii (fp) : "./hrtfs/elev50/L50e245a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e245a.dat" right +[ 28, 24 ] = ascii (fp) : "./hrtfs/elev50/L50e240a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e240a.dat" right +[ 28, 25 ] = ascii (fp) : "./hrtfs/elev50/L50e235a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e235a.dat" right +[ 28, 26 ] = ascii (fp) : "./hrtfs/elev50/L50e230a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e230a.dat" right +[ 28, 27 ] = ascii (fp) : "./hrtfs/elev50/L50e225a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e225a.dat" right +[ 28, 28 ] = ascii (fp) : "./hrtfs/elev50/L50e220a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e220a.dat" right +[ 28, 29 ] = ascii (fp) : "./hrtfs/elev50/L50e215a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e215a.dat" right +[ 28, 30 ] = ascii (fp) : "./hrtfs/elev50/L50e210a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e210a.dat" right +[ 28, 31 ] = ascii (fp) : "./hrtfs/elev50/L50e205a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e205a.dat" right +[ 28, 32 ] = ascii (fp) : "./hrtfs/elev50/L50e200a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e200a.dat" right +[ 28, 33 ] = ascii (fp) : "./hrtfs/elev50/L50e195a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e195a.dat" right +[ 28, 34 ] = ascii (fp) : "./hrtfs/elev50/L50e190a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e190a.dat" right +[ 28, 35 ] = ascii (fp) : "./hrtfs/elev50/L50e185a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e185a.dat" right +[ 28, 36 ] = ascii (fp) : "./hrtfs/elev50/L50e180a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e180a.dat" right +[ 28, 37 ] = ascii (fp) : "./hrtfs/elev50/L50e175a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e175a.dat" right +[ 28, 38 ] = ascii (fp) : "./hrtfs/elev50/L50e170a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e170a.dat" right +[ 28, 39 ] = ascii (fp) : "./hrtfs/elev50/L50e165a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e165a.dat" right +[ 28, 40 ] = ascii (fp) : "./hrtfs/elev50/L50e160a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e160a.dat" right +[ 28, 41 ] = ascii (fp) : "./hrtfs/elev50/L50e155a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e155a.dat" right +[ 28, 42 ] = ascii (fp) : "./hrtfs/elev50/L50e150a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e150a.dat" right +[ 28, 43 ] = ascii (fp) : "./hrtfs/elev50/L50e145a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e145a.dat" right +[ 28, 44 ] = ascii (fp) : "./hrtfs/elev50/L50e140a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e140a.dat" right +[ 28, 45 ] = ascii (fp) : "./hrtfs/elev50/L50e135a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e135a.dat" right +[ 28, 46 ] = ascii (fp) : "./hrtfs/elev50/L50e130a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e130a.dat" right +[ 28, 47 ] = ascii (fp) : "./hrtfs/elev50/L50e125a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e125a.dat" right +[ 28, 48 ] = ascii (fp) : "./hrtfs/elev50/L50e120a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e120a.dat" right +[ 28, 49 ] = ascii (fp) : "./hrtfs/elev50/L50e115a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e115a.dat" right +[ 28, 50 ] = ascii (fp) : "./hrtfs/elev50/L50e110a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e110a.dat" right +[ 28, 51 ] = ascii (fp) : "./hrtfs/elev50/L50e105a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e105a.dat" right +[ 28, 52 ] = ascii (fp) : "./hrtfs/elev50/L50e100a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e100a.dat" right +[ 28, 53 ] = ascii (fp) : "./hrtfs/elev50/L50e095a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e095a.dat" right +[ 28, 54 ] = ascii (fp) : "./hrtfs/elev50/L50e090a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e090a.dat" right +[ 28, 55 ] = ascii (fp) : "./hrtfs/elev50/L50e085a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e085a.dat" right +[ 28, 56 ] = ascii (fp) : "./hrtfs/elev50/L50e080a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e080a.dat" right +[ 28, 57 ] = ascii (fp) : "./hrtfs/elev50/L50e075a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e075a.dat" right +[ 28, 58 ] = ascii (fp) : "./hrtfs/elev50/L50e070a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e070a.dat" right +[ 28, 59 ] = ascii (fp) : "./hrtfs/elev50/L50e065a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e065a.dat" right +[ 28, 60 ] = ascii (fp) : "./hrtfs/elev50/L50e060a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e060a.dat" right +[ 28, 61 ] = ascii (fp) : "./hrtfs/elev50/L50e055a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e055a.dat" right +[ 28, 62 ] = ascii (fp) : "./hrtfs/elev50/L50e050a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e050a.dat" right +[ 28, 63 ] = ascii (fp) : "./hrtfs/elev50/L50e045a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e045a.dat" right +[ 28, 64 ] = ascii (fp) : "./hrtfs/elev50/L50e040a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e040a.dat" right +[ 28, 65 ] = ascii (fp) : "./hrtfs/elev50/L50e035a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e035a.dat" right +[ 28, 66 ] = ascii (fp) : "./hrtfs/elev50/L50e030a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e030a.dat" right +[ 28, 67 ] = ascii (fp) : "./hrtfs/elev50/L50e025a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e025a.dat" right +[ 28, 68 ] = ascii (fp) : "./hrtfs/elev50/L50e020a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e020a.dat" right +[ 28, 69 ] = ascii (fp) : "./hrtfs/elev50/L50e015a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e015a.dat" right +[ 28, 70 ] = ascii (fp) : "./hrtfs/elev50/L50e010a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e010a.dat" right +[ 28, 71 ] = ascii (fp) : "./hrtfs/elev50/L50e005a.dat" left + + ascii (fp) : "./hrtfs/elev50/R50e005a.dat" right -[ 29, 0 ] = ascii (fp) : "./hrtfs/elev55/L55e000a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e000a.dat right -[ 29, 1 ] = ascii (fp) : "./hrtfs/elev55/L55e355a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e355a.dat right -[ 29, 2 ] = ascii (fp) : "./hrtfs/elev55/L55e350a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e350a.dat right -[ 29, 3 ] = ascii (fp) : "./hrtfs/elev55/L55e345a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e345a.dat right -[ 29, 4 ] = ascii (fp) : "./hrtfs/elev55/L55e340a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e340a.dat right -[ 29, 5 ] = ascii (fp) : "./hrtfs/elev55/L55e335a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e335a.dat right -[ 29, 6 ] = ascii (fp) : "./hrtfs/elev55/L55e330a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e330a.dat right -[ 29, 7 ] = ascii (fp) : "./hrtfs/elev55/L55e325a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e325a.dat right -[ 29, 8 ] = ascii (fp) : "./hrtfs/elev55/L55e320a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e320a.dat right -[ 29, 9 ] = ascii (fp) : "./hrtfs/elev55/L55e315a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e315a.dat right -[ 29, 10 ] = ascii (fp) : "./hrtfs/elev55/L55e310a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e310a.dat right -[ 29, 11 ] = ascii (fp) : "./hrtfs/elev55/L55e305a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e305a.dat right -[ 29, 12 ] = ascii (fp) : "./hrtfs/elev55/L55e300a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e300a.dat right -[ 29, 13 ] = ascii (fp) : "./hrtfs/elev55/L55e295a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e295a.dat right -[ 29, 14 ] = ascii (fp) : "./hrtfs/elev55/L55e290a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e290a.dat right -[ 29, 15 ] = ascii (fp) : "./hrtfs/elev55/L55e285a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e285a.dat right -[ 29, 16 ] = ascii (fp) : "./hrtfs/elev55/L55e280a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e280a.dat right -[ 29, 17 ] = ascii (fp) : "./hrtfs/elev55/L55e275a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e275a.dat right -[ 29, 18 ] = ascii (fp) : "./hrtfs/elev55/L55e270a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e270a.dat right -[ 29, 19 ] = ascii (fp) : "./hrtfs/elev55/L55e265a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e265a.dat right -[ 29, 20 ] = ascii (fp) : "./hrtfs/elev55/L55e260a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e260a.dat right -[ 29, 21 ] = ascii (fp) : "./hrtfs/elev55/L55e255a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e255a.dat right -[ 29, 22 ] = ascii (fp) : "./hrtfs/elev55/L55e250a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e250a.dat right -[ 29, 23 ] = ascii (fp) : "./hrtfs/elev55/L55e245a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e245a.dat right -[ 29, 24 ] = ascii (fp) : "./hrtfs/elev55/L55e240a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e240a.dat right -[ 29, 25 ] = ascii (fp) : "./hrtfs/elev55/L55e235a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e235a.dat right -[ 29, 26 ] = ascii (fp) : "./hrtfs/elev55/L55e230a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e230a.dat right -[ 29, 27 ] = ascii (fp) : "./hrtfs/elev55/L55e225a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e225a.dat right -[ 29, 28 ] = ascii (fp) : "./hrtfs/elev55/L55e220a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e220a.dat right -[ 29, 29 ] = ascii (fp) : "./hrtfs/elev55/L55e215a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e215a.dat right -[ 29, 30 ] = ascii (fp) : "./hrtfs/elev55/L55e210a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e210a.dat right -[ 29, 31 ] = ascii (fp) : "./hrtfs/elev55/L55e205a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e205a.dat right -[ 29, 32 ] = ascii (fp) : "./hrtfs/elev55/L55e200a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e200a.dat right -[ 29, 33 ] = ascii (fp) : "./hrtfs/elev55/L55e195a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e195a.dat right -[ 29, 34 ] = ascii (fp) : "./hrtfs/elev55/L55e190a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e190a.dat right -[ 29, 35 ] = ascii (fp) : "./hrtfs/elev55/L55e185a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e185a.dat right -[ 29, 36 ] = ascii (fp) : "./hrtfs/elev55/L55e180a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e180a.dat right -[ 29, 37 ] = ascii (fp) : "./hrtfs/elev55/L55e175a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e175a.dat right -[ 29, 38 ] = ascii (fp) : "./hrtfs/elev55/L55e170a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e170a.dat right -[ 29, 39 ] = ascii (fp) : "./hrtfs/elev55/L55e165a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e165a.dat right -[ 29, 40 ] = ascii (fp) : "./hrtfs/elev55/L55e160a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e160a.dat right -[ 29, 41 ] = ascii (fp) : "./hrtfs/elev55/L55e155a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e155a.dat right -[ 29, 42 ] = ascii (fp) : "./hrtfs/elev55/L55e150a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e150a.dat right -[ 29, 43 ] = ascii (fp) : "./hrtfs/elev55/L55e145a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e145a.dat right -[ 29, 44 ] = ascii (fp) : "./hrtfs/elev55/L55e140a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e140a.dat right -[ 29, 45 ] = ascii (fp) : "./hrtfs/elev55/L55e135a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e135a.dat right -[ 29, 46 ] = ascii (fp) : "./hrtfs/elev55/L55e130a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e130a.dat right -[ 29, 47 ] = ascii (fp) : "./hrtfs/elev55/L55e125a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e125a.dat right -[ 29, 48 ] = ascii (fp) : "./hrtfs/elev55/L55e120a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e120a.dat right -[ 29, 49 ] = ascii (fp) : "./hrtfs/elev55/L55e115a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e115a.dat right -[ 29, 50 ] = ascii (fp) : "./hrtfs/elev55/L55e110a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e110a.dat right -[ 29, 51 ] = ascii (fp) : "./hrtfs/elev55/L55e105a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e105a.dat right -[ 29, 52 ] = ascii (fp) : "./hrtfs/elev55/L55e100a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e100a.dat right -[ 29, 53 ] = ascii (fp) : "./hrtfs/elev55/L55e095a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e095a.dat right -[ 29, 54 ] = ascii (fp) : "./hrtfs/elev55/L55e090a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e090a.dat right -[ 29, 55 ] = ascii (fp) : "./hrtfs/elev55/L55e085a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e085a.dat right -[ 29, 56 ] = ascii (fp) : "./hrtfs/elev55/L55e080a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e080a.dat right -[ 29, 57 ] = ascii (fp) : "./hrtfs/elev55/L55e075a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e075a.dat right -[ 29, 58 ] = ascii (fp) : "./hrtfs/elev55/L55e070a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e070a.dat right -[ 29, 59 ] = ascii (fp) : "./hrtfs/elev55/L55e065a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e065a.dat right -[ 29, 60 ] = ascii (fp) : "./hrtfs/elev55/L55e060a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e060a.dat right -[ 29, 61 ] = ascii (fp) : "./hrtfs/elev55/L55e055a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e055a.dat right -[ 29, 62 ] = ascii (fp) : "./hrtfs/elev55/L55e050a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e050a.dat right -[ 29, 63 ] = ascii (fp) : "./hrtfs/elev55/L55e045a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e045a.dat right -[ 29, 64 ] = ascii (fp) : "./hrtfs/elev55/L55e040a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e040a.dat right -[ 29, 65 ] = ascii (fp) : "./hrtfs/elev55/L55e035a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e035a.dat right -[ 29, 66 ] = ascii (fp) : "./hrtfs/elev55/L55e030a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e030a.dat right -[ 29, 67 ] = ascii (fp) : "./hrtfs/elev55/L55e025a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e025a.dat right -[ 29, 68 ] = ascii (fp) : "./hrtfs/elev55/L55e020a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e020a.dat right -[ 29, 69 ] = ascii (fp) : "./hrtfs/elev55/L55e015a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e015a.dat right -[ 29, 70 ] = ascii (fp) : "./hrtfs/elev55/L55e010a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e010a.dat right -[ 29, 71 ] = ascii (fp) : "./hrtfs/elev55/L55e005a.dat left - + ascii (fp) : "./hrtfs/elev55/R55e005a.dat right +[ 29, 0 ] = ascii (fp) : "./hrtfs/elev55/L55e000a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e000a.dat" right +[ 29, 1 ] = ascii (fp) : "./hrtfs/elev55/L55e355a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e355a.dat" right +[ 29, 2 ] = ascii (fp) : "./hrtfs/elev55/L55e350a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e350a.dat" right +[ 29, 3 ] = ascii (fp) : "./hrtfs/elev55/L55e345a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e345a.dat" right +[ 29, 4 ] = ascii (fp) : "./hrtfs/elev55/L55e340a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e340a.dat" right +[ 29, 5 ] = ascii (fp) : "./hrtfs/elev55/L55e335a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e335a.dat" right +[ 29, 6 ] = ascii (fp) : "./hrtfs/elev55/L55e330a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e330a.dat" right +[ 29, 7 ] = ascii (fp) : "./hrtfs/elev55/L55e325a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e325a.dat" right +[ 29, 8 ] = ascii (fp) : "./hrtfs/elev55/L55e320a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e320a.dat" right +[ 29, 9 ] = ascii (fp) : "./hrtfs/elev55/L55e315a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e315a.dat" right +[ 29, 10 ] = ascii (fp) : "./hrtfs/elev55/L55e310a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e310a.dat" right +[ 29, 11 ] = ascii (fp) : "./hrtfs/elev55/L55e305a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e305a.dat" right +[ 29, 12 ] = ascii (fp) : "./hrtfs/elev55/L55e300a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e300a.dat" right +[ 29, 13 ] = ascii (fp) : "./hrtfs/elev55/L55e295a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e295a.dat" right +[ 29, 14 ] = ascii (fp) : "./hrtfs/elev55/L55e290a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e290a.dat" right +[ 29, 15 ] = ascii (fp) : "./hrtfs/elev55/L55e285a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e285a.dat" right +[ 29, 16 ] = ascii (fp) : "./hrtfs/elev55/L55e280a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e280a.dat" right +[ 29, 17 ] = ascii (fp) : "./hrtfs/elev55/L55e275a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e275a.dat" right +[ 29, 18 ] = ascii (fp) : "./hrtfs/elev55/L55e270a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e270a.dat" right +[ 29, 19 ] = ascii (fp) : "./hrtfs/elev55/L55e265a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e265a.dat" right +[ 29, 20 ] = ascii (fp) : "./hrtfs/elev55/L55e260a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e260a.dat" right +[ 29, 21 ] = ascii (fp) : "./hrtfs/elev55/L55e255a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e255a.dat" right +[ 29, 22 ] = ascii (fp) : "./hrtfs/elev55/L55e250a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e250a.dat" right +[ 29, 23 ] = ascii (fp) : "./hrtfs/elev55/L55e245a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e245a.dat" right +[ 29, 24 ] = ascii (fp) : "./hrtfs/elev55/L55e240a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e240a.dat" right +[ 29, 25 ] = ascii (fp) : "./hrtfs/elev55/L55e235a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e235a.dat" right +[ 29, 26 ] = ascii (fp) : "./hrtfs/elev55/L55e230a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e230a.dat" right +[ 29, 27 ] = ascii (fp) : "./hrtfs/elev55/L55e225a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e225a.dat" right +[ 29, 28 ] = ascii (fp) : "./hrtfs/elev55/L55e220a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e220a.dat" right +[ 29, 29 ] = ascii (fp) : "./hrtfs/elev55/L55e215a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e215a.dat" right +[ 29, 30 ] = ascii (fp) : "./hrtfs/elev55/L55e210a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e210a.dat" right +[ 29, 31 ] = ascii (fp) : "./hrtfs/elev55/L55e205a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e205a.dat" right +[ 29, 32 ] = ascii (fp) : "./hrtfs/elev55/L55e200a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e200a.dat" right +[ 29, 33 ] = ascii (fp) : "./hrtfs/elev55/L55e195a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e195a.dat" right +[ 29, 34 ] = ascii (fp) : "./hrtfs/elev55/L55e190a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e190a.dat" right +[ 29, 35 ] = ascii (fp) : "./hrtfs/elev55/L55e185a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e185a.dat" right +[ 29, 36 ] = ascii (fp) : "./hrtfs/elev55/L55e180a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e180a.dat" right +[ 29, 37 ] = ascii (fp) : "./hrtfs/elev55/L55e175a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e175a.dat" right +[ 29, 38 ] = ascii (fp) : "./hrtfs/elev55/L55e170a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e170a.dat" right +[ 29, 39 ] = ascii (fp) : "./hrtfs/elev55/L55e165a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e165a.dat" right +[ 29, 40 ] = ascii (fp) : "./hrtfs/elev55/L55e160a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e160a.dat" right +[ 29, 41 ] = ascii (fp) : "./hrtfs/elev55/L55e155a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e155a.dat" right +[ 29, 42 ] = ascii (fp) : "./hrtfs/elev55/L55e150a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e150a.dat" right +[ 29, 43 ] = ascii (fp) : "./hrtfs/elev55/L55e145a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e145a.dat" right +[ 29, 44 ] = ascii (fp) : "./hrtfs/elev55/L55e140a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e140a.dat" right +[ 29, 45 ] = ascii (fp) : "./hrtfs/elev55/L55e135a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e135a.dat" right +[ 29, 46 ] = ascii (fp) : "./hrtfs/elev55/L55e130a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e130a.dat" right +[ 29, 47 ] = ascii (fp) : "./hrtfs/elev55/L55e125a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e125a.dat" right +[ 29, 48 ] = ascii (fp) : "./hrtfs/elev55/L55e120a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e120a.dat" right +[ 29, 49 ] = ascii (fp) : "./hrtfs/elev55/L55e115a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e115a.dat" right +[ 29, 50 ] = ascii (fp) : "./hrtfs/elev55/L55e110a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e110a.dat" right +[ 29, 51 ] = ascii (fp) : "./hrtfs/elev55/L55e105a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e105a.dat" right +[ 29, 52 ] = ascii (fp) : "./hrtfs/elev55/L55e100a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e100a.dat" right +[ 29, 53 ] = ascii (fp) : "./hrtfs/elev55/L55e095a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e095a.dat" right +[ 29, 54 ] = ascii (fp) : "./hrtfs/elev55/L55e090a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e090a.dat" right +[ 29, 55 ] = ascii (fp) : "./hrtfs/elev55/L55e085a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e085a.dat" right +[ 29, 56 ] = ascii (fp) : "./hrtfs/elev55/L55e080a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e080a.dat" right +[ 29, 57 ] = ascii (fp) : "./hrtfs/elev55/L55e075a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e075a.dat" right +[ 29, 58 ] = ascii (fp) : "./hrtfs/elev55/L55e070a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e070a.dat" right +[ 29, 59 ] = ascii (fp) : "./hrtfs/elev55/L55e065a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e065a.dat" right +[ 29, 60 ] = ascii (fp) : "./hrtfs/elev55/L55e060a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e060a.dat" right +[ 29, 61 ] = ascii (fp) : "./hrtfs/elev55/L55e055a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e055a.dat" right +[ 29, 62 ] = ascii (fp) : "./hrtfs/elev55/L55e050a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e050a.dat" right +[ 29, 63 ] = ascii (fp) : "./hrtfs/elev55/L55e045a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e045a.dat" right +[ 29, 64 ] = ascii (fp) : "./hrtfs/elev55/L55e040a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e040a.dat" right +[ 29, 65 ] = ascii (fp) : "./hrtfs/elev55/L55e035a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e035a.dat" right +[ 29, 66 ] = ascii (fp) : "./hrtfs/elev55/L55e030a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e030a.dat" right +[ 29, 67 ] = ascii (fp) : "./hrtfs/elev55/L55e025a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e025a.dat" right +[ 29, 68 ] = ascii (fp) : "./hrtfs/elev55/L55e020a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e020a.dat" right +[ 29, 69 ] = ascii (fp) : "./hrtfs/elev55/L55e015a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e015a.dat" right +[ 29, 70 ] = ascii (fp) : "./hrtfs/elev55/L55e010a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e010a.dat" right +[ 29, 71 ] = ascii (fp) : "./hrtfs/elev55/L55e005a.dat" left + + ascii (fp) : "./hrtfs/elev55/R55e005a.dat" right -[ 30, 0 ] = ascii (fp) : "./hrtfs/elev60/L60e000a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e000a.dat right -[ 30, 1 ] = ascii (fp) : "./hrtfs/elev60/L60e355a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e355a.dat right -[ 30, 2 ] = ascii (fp) : "./hrtfs/elev60/L60e350a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e350a.dat right -[ 30, 3 ] = ascii (fp) : "./hrtfs/elev60/L60e345a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e345a.dat right -[ 30, 4 ] = ascii (fp) : "./hrtfs/elev60/L60e340a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e340a.dat right -[ 30, 5 ] = ascii (fp) : "./hrtfs/elev60/L60e335a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e335a.dat right -[ 30, 6 ] = ascii (fp) : "./hrtfs/elev60/L60e330a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e330a.dat right -[ 30, 7 ] = ascii (fp) : "./hrtfs/elev60/L60e325a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e325a.dat right -[ 30, 8 ] = ascii (fp) : "./hrtfs/elev60/L60e320a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e320a.dat right -[ 30, 9 ] = ascii (fp) : "./hrtfs/elev60/L60e315a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e315a.dat right -[ 30, 10 ] = ascii (fp) : "./hrtfs/elev60/L60e310a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e310a.dat right -[ 30, 11 ] = ascii (fp) : "./hrtfs/elev60/L60e305a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e305a.dat right -[ 30, 12 ] = ascii (fp) : "./hrtfs/elev60/L60e300a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e300a.dat right -[ 30, 13 ] = ascii (fp) : "./hrtfs/elev60/L60e295a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e295a.dat right -[ 30, 14 ] = ascii (fp) : "./hrtfs/elev60/L60e290a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e290a.dat right -[ 30, 15 ] = ascii (fp) : "./hrtfs/elev60/L60e285a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e285a.dat right -[ 30, 16 ] = ascii (fp) : "./hrtfs/elev60/L60e280a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e280a.dat right -[ 30, 17 ] = ascii (fp) : "./hrtfs/elev60/L60e275a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e275a.dat right -[ 30, 18 ] = ascii (fp) : "./hrtfs/elev60/L60e270a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e270a.dat right -[ 30, 19 ] = ascii (fp) : "./hrtfs/elev60/L60e265a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e265a.dat right -[ 30, 20 ] = ascii (fp) : "./hrtfs/elev60/L60e260a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e260a.dat right -[ 30, 21 ] = ascii (fp) : "./hrtfs/elev60/L60e255a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e255a.dat right -[ 30, 22 ] = ascii (fp) : "./hrtfs/elev60/L60e250a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e250a.dat right -[ 30, 23 ] = ascii (fp) : "./hrtfs/elev60/L60e245a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e245a.dat right -[ 30, 24 ] = ascii (fp) : "./hrtfs/elev60/L60e240a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e240a.dat right -[ 30, 25 ] = ascii (fp) : "./hrtfs/elev60/L60e235a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e235a.dat right -[ 30, 26 ] = ascii (fp) : "./hrtfs/elev60/L60e230a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e230a.dat right -[ 30, 27 ] = ascii (fp) : "./hrtfs/elev60/L60e225a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e225a.dat right -[ 30, 28 ] = ascii (fp) : "./hrtfs/elev60/L60e220a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e220a.dat right -[ 30, 29 ] = ascii (fp) : "./hrtfs/elev60/L60e215a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e215a.dat right -[ 30, 30 ] = ascii (fp) : "./hrtfs/elev60/L60e210a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e210a.dat right -[ 30, 31 ] = ascii (fp) : "./hrtfs/elev60/L60e205a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e205a.dat right -[ 30, 32 ] = ascii (fp) : "./hrtfs/elev60/L60e200a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e200a.dat right -[ 30, 33 ] = ascii (fp) : "./hrtfs/elev60/L60e195a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e195a.dat right -[ 30, 34 ] = ascii (fp) : "./hrtfs/elev60/L60e190a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e190a.dat right -[ 30, 35 ] = ascii (fp) : "./hrtfs/elev60/L60e185a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e185a.dat right -[ 30, 36 ] = ascii (fp) : "./hrtfs/elev60/L60e180a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e180a.dat right -[ 30, 37 ] = ascii (fp) : "./hrtfs/elev60/L60e175a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e175a.dat right -[ 30, 38 ] = ascii (fp) : "./hrtfs/elev60/L60e170a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e170a.dat right -[ 30, 39 ] = ascii (fp) : "./hrtfs/elev60/L60e165a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e165a.dat right -[ 30, 40 ] = ascii (fp) : "./hrtfs/elev60/L60e160a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e160a.dat right -[ 30, 41 ] = ascii (fp) : "./hrtfs/elev60/L60e155a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e155a.dat right -[ 30, 42 ] = ascii (fp) : "./hrtfs/elev60/L60e150a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e150a.dat right -[ 30, 43 ] = ascii (fp) : "./hrtfs/elev60/L60e145a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e145a.dat right -[ 30, 44 ] = ascii (fp) : "./hrtfs/elev60/L60e140a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e140a.dat right -[ 30, 45 ] = ascii (fp) : "./hrtfs/elev60/L60e135a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e135a.dat right -[ 30, 46 ] = ascii (fp) : "./hrtfs/elev60/L60e130a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e130a.dat right -[ 30, 47 ] = ascii (fp) : "./hrtfs/elev60/L60e125a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e125a.dat right -[ 30, 48 ] = ascii (fp) : "./hrtfs/elev60/L60e120a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e120a.dat right -[ 30, 49 ] = ascii (fp) : "./hrtfs/elev60/L60e115a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e115a.dat right -[ 30, 50 ] = ascii (fp) : "./hrtfs/elev60/L60e110a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e110a.dat right -[ 30, 51 ] = ascii (fp) : "./hrtfs/elev60/L60e105a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e105a.dat right -[ 30, 52 ] = ascii (fp) : "./hrtfs/elev60/L60e100a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e100a.dat right -[ 30, 53 ] = ascii (fp) : "./hrtfs/elev60/L60e095a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e095a.dat right -[ 30, 54 ] = ascii (fp) : "./hrtfs/elev60/L60e090a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e090a.dat right -[ 30, 55 ] = ascii (fp) : "./hrtfs/elev60/L60e085a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e085a.dat right -[ 30, 56 ] = ascii (fp) : "./hrtfs/elev60/L60e080a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e080a.dat right -[ 30, 57 ] = ascii (fp) : "./hrtfs/elev60/L60e075a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e075a.dat right -[ 30, 58 ] = ascii (fp) : "./hrtfs/elev60/L60e070a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e070a.dat right -[ 30, 59 ] = ascii (fp) : "./hrtfs/elev60/L60e065a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e065a.dat right -[ 30, 60 ] = ascii (fp) : "./hrtfs/elev60/L60e060a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e060a.dat right -[ 30, 61 ] = ascii (fp) : "./hrtfs/elev60/L60e055a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e055a.dat right -[ 30, 62 ] = ascii (fp) : "./hrtfs/elev60/L60e050a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e050a.dat right -[ 30, 63 ] = ascii (fp) : "./hrtfs/elev60/L60e045a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e045a.dat right -[ 30, 64 ] = ascii (fp) : "./hrtfs/elev60/L60e040a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e040a.dat right -[ 30, 65 ] = ascii (fp) : "./hrtfs/elev60/L60e035a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e035a.dat right -[ 30, 66 ] = ascii (fp) : "./hrtfs/elev60/L60e030a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e030a.dat right -[ 30, 67 ] = ascii (fp) : "./hrtfs/elev60/L60e025a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e025a.dat right -[ 30, 68 ] = ascii (fp) : "./hrtfs/elev60/L60e020a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e020a.dat right -[ 30, 69 ] = ascii (fp) : "./hrtfs/elev60/L60e015a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e015a.dat right -[ 30, 70 ] = ascii (fp) : "./hrtfs/elev60/L60e010a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e010a.dat right -[ 30, 71 ] = ascii (fp) : "./hrtfs/elev60/L60e005a.dat left - + ascii (fp) : "./hrtfs/elev60/R60e005a.dat right +[ 30, 0 ] = ascii (fp) : "./hrtfs/elev60/L60e000a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e000a.dat" right +[ 30, 1 ] = ascii (fp) : "./hrtfs/elev60/L60e355a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e355a.dat" right +[ 30, 2 ] = ascii (fp) : "./hrtfs/elev60/L60e350a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e350a.dat" right +[ 30, 3 ] = ascii (fp) : "./hrtfs/elev60/L60e345a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e345a.dat" right +[ 30, 4 ] = ascii (fp) : "./hrtfs/elev60/L60e340a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e340a.dat" right +[ 30, 5 ] = ascii (fp) : "./hrtfs/elev60/L60e335a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e335a.dat" right +[ 30, 6 ] = ascii (fp) : "./hrtfs/elev60/L60e330a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e330a.dat" right +[ 30, 7 ] = ascii (fp) : "./hrtfs/elev60/L60e325a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e325a.dat" right +[ 30, 8 ] = ascii (fp) : "./hrtfs/elev60/L60e320a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e320a.dat" right +[ 30, 9 ] = ascii (fp) : "./hrtfs/elev60/L60e315a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e315a.dat" right +[ 30, 10 ] = ascii (fp) : "./hrtfs/elev60/L60e310a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e310a.dat" right +[ 30, 11 ] = ascii (fp) : "./hrtfs/elev60/L60e305a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e305a.dat" right +[ 30, 12 ] = ascii (fp) : "./hrtfs/elev60/L60e300a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e300a.dat" right +[ 30, 13 ] = ascii (fp) : "./hrtfs/elev60/L60e295a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e295a.dat" right +[ 30, 14 ] = ascii (fp) : "./hrtfs/elev60/L60e290a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e290a.dat" right +[ 30, 15 ] = ascii (fp) : "./hrtfs/elev60/L60e285a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e285a.dat" right +[ 30, 16 ] = ascii (fp) : "./hrtfs/elev60/L60e280a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e280a.dat" right +[ 30, 17 ] = ascii (fp) : "./hrtfs/elev60/L60e275a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e275a.dat" right +[ 30, 18 ] = ascii (fp) : "./hrtfs/elev60/L60e270a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e270a.dat" right +[ 30, 19 ] = ascii (fp) : "./hrtfs/elev60/L60e265a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e265a.dat" right +[ 30, 20 ] = ascii (fp) : "./hrtfs/elev60/L60e260a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e260a.dat" right +[ 30, 21 ] = ascii (fp) : "./hrtfs/elev60/L60e255a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e255a.dat" right +[ 30, 22 ] = ascii (fp) : "./hrtfs/elev60/L60e250a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e250a.dat" right +[ 30, 23 ] = ascii (fp) : "./hrtfs/elev60/L60e245a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e245a.dat" right +[ 30, 24 ] = ascii (fp) : "./hrtfs/elev60/L60e240a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e240a.dat" right +[ 30, 25 ] = ascii (fp) : "./hrtfs/elev60/L60e235a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e235a.dat" right +[ 30, 26 ] = ascii (fp) : "./hrtfs/elev60/L60e230a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e230a.dat" right +[ 30, 27 ] = ascii (fp) : "./hrtfs/elev60/L60e225a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e225a.dat" right +[ 30, 28 ] = ascii (fp) : "./hrtfs/elev60/L60e220a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e220a.dat" right +[ 30, 29 ] = ascii (fp) : "./hrtfs/elev60/L60e215a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e215a.dat" right +[ 30, 30 ] = ascii (fp) : "./hrtfs/elev60/L60e210a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e210a.dat" right +[ 30, 31 ] = ascii (fp) : "./hrtfs/elev60/L60e205a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e205a.dat" right +[ 30, 32 ] = ascii (fp) : "./hrtfs/elev60/L60e200a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e200a.dat" right +[ 30, 33 ] = ascii (fp) : "./hrtfs/elev60/L60e195a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e195a.dat" right +[ 30, 34 ] = ascii (fp) : "./hrtfs/elev60/L60e190a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e190a.dat" right +[ 30, 35 ] = ascii (fp) : "./hrtfs/elev60/L60e185a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e185a.dat" right +[ 30, 36 ] = ascii (fp) : "./hrtfs/elev60/L60e180a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e180a.dat" right +[ 30, 37 ] = ascii (fp) : "./hrtfs/elev60/L60e175a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e175a.dat" right +[ 30, 38 ] = ascii (fp) : "./hrtfs/elev60/L60e170a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e170a.dat" right +[ 30, 39 ] = ascii (fp) : "./hrtfs/elev60/L60e165a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e165a.dat" right +[ 30, 40 ] = ascii (fp) : "./hrtfs/elev60/L60e160a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e160a.dat" right +[ 30, 41 ] = ascii (fp) : "./hrtfs/elev60/L60e155a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e155a.dat" right +[ 30, 42 ] = ascii (fp) : "./hrtfs/elev60/L60e150a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e150a.dat" right +[ 30, 43 ] = ascii (fp) : "./hrtfs/elev60/L60e145a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e145a.dat" right +[ 30, 44 ] = ascii (fp) : "./hrtfs/elev60/L60e140a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e140a.dat" right +[ 30, 45 ] = ascii (fp) : "./hrtfs/elev60/L60e135a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e135a.dat" right +[ 30, 46 ] = ascii (fp) : "./hrtfs/elev60/L60e130a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e130a.dat" right +[ 30, 47 ] = ascii (fp) : "./hrtfs/elev60/L60e125a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e125a.dat" right +[ 30, 48 ] = ascii (fp) : "./hrtfs/elev60/L60e120a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e120a.dat" right +[ 30, 49 ] = ascii (fp) : "./hrtfs/elev60/L60e115a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e115a.dat" right +[ 30, 50 ] = ascii (fp) : "./hrtfs/elev60/L60e110a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e110a.dat" right +[ 30, 51 ] = ascii (fp) : "./hrtfs/elev60/L60e105a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e105a.dat" right +[ 30, 52 ] = ascii (fp) : "./hrtfs/elev60/L60e100a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e100a.dat" right +[ 30, 53 ] = ascii (fp) : "./hrtfs/elev60/L60e095a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e095a.dat" right +[ 30, 54 ] = ascii (fp) : "./hrtfs/elev60/L60e090a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e090a.dat" right +[ 30, 55 ] = ascii (fp) : "./hrtfs/elev60/L60e085a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e085a.dat" right +[ 30, 56 ] = ascii (fp) : "./hrtfs/elev60/L60e080a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e080a.dat" right +[ 30, 57 ] = ascii (fp) : "./hrtfs/elev60/L60e075a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e075a.dat" right +[ 30, 58 ] = ascii (fp) : "./hrtfs/elev60/L60e070a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e070a.dat" right +[ 30, 59 ] = ascii (fp) : "./hrtfs/elev60/L60e065a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e065a.dat" right +[ 30, 60 ] = ascii (fp) : "./hrtfs/elev60/L60e060a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e060a.dat" right +[ 30, 61 ] = ascii (fp) : "./hrtfs/elev60/L60e055a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e055a.dat" right +[ 30, 62 ] = ascii (fp) : "./hrtfs/elev60/L60e050a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e050a.dat" right +[ 30, 63 ] = ascii (fp) : "./hrtfs/elev60/L60e045a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e045a.dat" right +[ 30, 64 ] = ascii (fp) : "./hrtfs/elev60/L60e040a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e040a.dat" right +[ 30, 65 ] = ascii (fp) : "./hrtfs/elev60/L60e035a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e035a.dat" right +[ 30, 66 ] = ascii (fp) : "./hrtfs/elev60/L60e030a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e030a.dat" right +[ 30, 67 ] = ascii (fp) : "./hrtfs/elev60/L60e025a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e025a.dat" right +[ 30, 68 ] = ascii (fp) : "./hrtfs/elev60/L60e020a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e020a.dat" right +[ 30, 69 ] = ascii (fp) : "./hrtfs/elev60/L60e015a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e015a.dat" right +[ 30, 70 ] = ascii (fp) : "./hrtfs/elev60/L60e010a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e010a.dat" right +[ 30, 71 ] = ascii (fp) : "./hrtfs/elev60/L60e005a.dat" left + + ascii (fp) : "./hrtfs/elev60/R60e005a.dat" right -[ 31, 0 ] = ascii (fp) : "./hrtfs/elev65/L65e000a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e000a.dat right -[ 31, 1 ] = ascii (fp) : "./hrtfs/elev65/L65e355a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e355a.dat right -[ 31, 2 ] = ascii (fp) : "./hrtfs/elev65/L65e350a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e350a.dat right -[ 31, 3 ] = ascii (fp) : "./hrtfs/elev65/L65e345a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e345a.dat right -[ 31, 4 ] = ascii (fp) : "./hrtfs/elev65/L65e340a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e340a.dat right -[ 31, 5 ] = ascii (fp) : "./hrtfs/elev65/L65e335a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e335a.dat right -[ 31, 6 ] = ascii (fp) : "./hrtfs/elev65/L65e330a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e330a.dat right -[ 31, 7 ] = ascii (fp) : "./hrtfs/elev65/L65e325a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e325a.dat right -[ 31, 8 ] = ascii (fp) : "./hrtfs/elev65/L65e320a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e320a.dat right -[ 31, 9 ] = ascii (fp) : "./hrtfs/elev65/L65e315a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e315a.dat right -[ 31, 10 ] = ascii (fp) : "./hrtfs/elev65/L65e310a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e310a.dat right -[ 31, 11 ] = ascii (fp) : "./hrtfs/elev65/L65e305a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e305a.dat right -[ 31, 12 ] = ascii (fp) : "./hrtfs/elev65/L65e300a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e300a.dat right -[ 31, 13 ] = ascii (fp) : "./hrtfs/elev65/L65e295a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e295a.dat right -[ 31, 14 ] = ascii (fp) : "./hrtfs/elev65/L65e290a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e290a.dat right -[ 31, 15 ] = ascii (fp) : "./hrtfs/elev65/L65e285a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e285a.dat right -[ 31, 16 ] = ascii (fp) : "./hrtfs/elev65/L65e280a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e280a.dat right -[ 31, 17 ] = ascii (fp) : "./hrtfs/elev65/L65e275a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e275a.dat right -[ 31, 18 ] = ascii (fp) : "./hrtfs/elev65/L65e270a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e270a.dat right -[ 31, 19 ] = ascii (fp) : "./hrtfs/elev65/L65e265a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e265a.dat right -[ 31, 20 ] = ascii (fp) : "./hrtfs/elev65/L65e260a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e260a.dat right -[ 31, 21 ] = ascii (fp) : "./hrtfs/elev65/L65e255a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e255a.dat right -[ 31, 22 ] = ascii (fp) : "./hrtfs/elev65/L65e250a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e250a.dat right -[ 31, 23 ] = ascii (fp) : "./hrtfs/elev65/L65e245a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e245a.dat right -[ 31, 24 ] = ascii (fp) : "./hrtfs/elev65/L65e240a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e240a.dat right -[ 31, 25 ] = ascii (fp) : "./hrtfs/elev65/L65e235a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e235a.dat right -[ 31, 26 ] = ascii (fp) : "./hrtfs/elev65/L65e230a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e230a.dat right -[ 31, 27 ] = ascii (fp) : "./hrtfs/elev65/L65e225a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e225a.dat right -[ 31, 28 ] = ascii (fp) : "./hrtfs/elev65/L65e220a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e220a.dat right -[ 31, 29 ] = ascii (fp) : "./hrtfs/elev65/L65e215a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e215a.dat right -[ 31, 30 ] = ascii (fp) : "./hrtfs/elev65/L65e210a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e210a.dat right -[ 31, 31 ] = ascii (fp) : "./hrtfs/elev65/L65e205a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e205a.dat right -[ 31, 32 ] = ascii (fp) : "./hrtfs/elev65/L65e200a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e200a.dat right -[ 31, 33 ] = ascii (fp) : "./hrtfs/elev65/L65e195a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e195a.dat right -[ 31, 34 ] = ascii (fp) : "./hrtfs/elev65/L65e190a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e190a.dat right -[ 31, 35 ] = ascii (fp) : "./hrtfs/elev65/L65e185a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e185a.dat right -[ 31, 36 ] = ascii (fp) : "./hrtfs/elev65/L65e180a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e180a.dat right -[ 31, 37 ] = ascii (fp) : "./hrtfs/elev65/L65e175a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e175a.dat right -[ 31, 38 ] = ascii (fp) : "./hrtfs/elev65/L65e170a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e170a.dat right -[ 31, 39 ] = ascii (fp) : "./hrtfs/elev65/L65e165a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e165a.dat right -[ 31, 40 ] = ascii (fp) : "./hrtfs/elev65/L65e160a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e160a.dat right -[ 31, 41 ] = ascii (fp) : "./hrtfs/elev65/L65e155a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e155a.dat right -[ 31, 42 ] = ascii (fp) : "./hrtfs/elev65/L65e150a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e150a.dat right -[ 31, 43 ] = ascii (fp) : "./hrtfs/elev65/L65e145a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e145a.dat right -[ 31, 44 ] = ascii (fp) : "./hrtfs/elev65/L65e140a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e140a.dat right -[ 31, 45 ] = ascii (fp) : "./hrtfs/elev65/L65e135a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e135a.dat right -[ 31, 46 ] = ascii (fp) : "./hrtfs/elev65/L65e130a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e130a.dat right -[ 31, 47 ] = ascii (fp) : "./hrtfs/elev65/L65e125a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e125a.dat right -[ 31, 48 ] = ascii (fp) : "./hrtfs/elev65/L65e120a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e120a.dat right -[ 31, 49 ] = ascii (fp) : "./hrtfs/elev65/L65e115a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e115a.dat right -[ 31, 50 ] = ascii (fp) : "./hrtfs/elev65/L65e110a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e110a.dat right -[ 31, 51 ] = ascii (fp) : "./hrtfs/elev65/L65e105a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e105a.dat right -[ 31, 52 ] = ascii (fp) : "./hrtfs/elev65/L65e100a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e100a.dat right -[ 31, 53 ] = ascii (fp) : "./hrtfs/elev65/L65e095a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e095a.dat right -[ 31, 54 ] = ascii (fp) : "./hrtfs/elev65/L65e090a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e090a.dat right -[ 31, 55 ] = ascii (fp) : "./hrtfs/elev65/L65e085a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e085a.dat right -[ 31, 56 ] = ascii (fp) : "./hrtfs/elev65/L65e080a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e080a.dat right -[ 31, 57 ] = ascii (fp) : "./hrtfs/elev65/L65e075a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e075a.dat right -[ 31, 58 ] = ascii (fp) : "./hrtfs/elev65/L65e070a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e070a.dat right -[ 31, 59 ] = ascii (fp) : "./hrtfs/elev65/L65e065a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e065a.dat right -[ 31, 60 ] = ascii (fp) : "./hrtfs/elev65/L65e060a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e060a.dat right -[ 31, 61 ] = ascii (fp) : "./hrtfs/elev65/L65e055a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e055a.dat right -[ 31, 62 ] = ascii (fp) : "./hrtfs/elev65/L65e050a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e050a.dat right -[ 31, 63 ] = ascii (fp) : "./hrtfs/elev65/L65e045a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e045a.dat right -[ 31, 64 ] = ascii (fp) : "./hrtfs/elev65/L65e040a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e040a.dat right -[ 31, 65 ] = ascii (fp) : "./hrtfs/elev65/L65e035a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e035a.dat right -[ 31, 66 ] = ascii (fp) : "./hrtfs/elev65/L65e030a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e030a.dat right -[ 31, 67 ] = ascii (fp) : "./hrtfs/elev65/L65e025a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e025a.dat right -[ 31, 68 ] = ascii (fp) : "./hrtfs/elev65/L65e020a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e020a.dat right -[ 31, 69 ] = ascii (fp) : "./hrtfs/elev65/L65e015a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e015a.dat right -[ 31, 70 ] = ascii (fp) : "./hrtfs/elev65/L65e010a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e010a.dat right -[ 31, 71 ] = ascii (fp) : "./hrtfs/elev65/L65e005a.dat left - + ascii (fp) : "./hrtfs/elev65/R65e005a.dat right +[ 31, 0 ] = ascii (fp) : "./hrtfs/elev65/L65e000a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e000a.dat" right +[ 31, 1 ] = ascii (fp) : "./hrtfs/elev65/L65e355a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e355a.dat" right +[ 31, 2 ] = ascii (fp) : "./hrtfs/elev65/L65e350a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e350a.dat" right +[ 31, 3 ] = ascii (fp) : "./hrtfs/elev65/L65e345a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e345a.dat" right +[ 31, 4 ] = ascii (fp) : "./hrtfs/elev65/L65e340a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e340a.dat" right +[ 31, 5 ] = ascii (fp) : "./hrtfs/elev65/L65e335a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e335a.dat" right +[ 31, 6 ] = ascii (fp) : "./hrtfs/elev65/L65e330a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e330a.dat" right +[ 31, 7 ] = ascii (fp) : "./hrtfs/elev65/L65e325a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e325a.dat" right +[ 31, 8 ] = ascii (fp) : "./hrtfs/elev65/L65e320a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e320a.dat" right +[ 31, 9 ] = ascii (fp) : "./hrtfs/elev65/L65e315a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e315a.dat" right +[ 31, 10 ] = ascii (fp) : "./hrtfs/elev65/L65e310a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e310a.dat" right +[ 31, 11 ] = ascii (fp) : "./hrtfs/elev65/L65e305a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e305a.dat" right +[ 31, 12 ] = ascii (fp) : "./hrtfs/elev65/L65e300a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e300a.dat" right +[ 31, 13 ] = ascii (fp) : "./hrtfs/elev65/L65e295a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e295a.dat" right +[ 31, 14 ] = ascii (fp) : "./hrtfs/elev65/L65e290a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e290a.dat" right +[ 31, 15 ] = ascii (fp) : "./hrtfs/elev65/L65e285a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e285a.dat" right +[ 31, 16 ] = ascii (fp) : "./hrtfs/elev65/L65e280a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e280a.dat" right +[ 31, 17 ] = ascii (fp) : "./hrtfs/elev65/L65e275a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e275a.dat" right +[ 31, 18 ] = ascii (fp) : "./hrtfs/elev65/L65e270a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e270a.dat" right +[ 31, 19 ] = ascii (fp) : "./hrtfs/elev65/L65e265a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e265a.dat" right +[ 31, 20 ] = ascii (fp) : "./hrtfs/elev65/L65e260a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e260a.dat" right +[ 31, 21 ] = ascii (fp) : "./hrtfs/elev65/L65e255a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e255a.dat" right +[ 31, 22 ] = ascii (fp) : "./hrtfs/elev65/L65e250a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e250a.dat" right +[ 31, 23 ] = ascii (fp) : "./hrtfs/elev65/L65e245a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e245a.dat" right +[ 31, 24 ] = ascii (fp) : "./hrtfs/elev65/L65e240a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e240a.dat" right +[ 31, 25 ] = ascii (fp) : "./hrtfs/elev65/L65e235a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e235a.dat" right +[ 31, 26 ] = ascii (fp) : "./hrtfs/elev65/L65e230a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e230a.dat" right +[ 31, 27 ] = ascii (fp) : "./hrtfs/elev65/L65e225a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e225a.dat" right +[ 31, 28 ] = ascii (fp) : "./hrtfs/elev65/L65e220a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e220a.dat" right +[ 31, 29 ] = ascii (fp) : "./hrtfs/elev65/L65e215a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e215a.dat" right +[ 31, 30 ] = ascii (fp) : "./hrtfs/elev65/L65e210a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e210a.dat" right +[ 31, 31 ] = ascii (fp) : "./hrtfs/elev65/L65e205a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e205a.dat" right +[ 31, 32 ] = ascii (fp) : "./hrtfs/elev65/L65e200a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e200a.dat" right +[ 31, 33 ] = ascii (fp) : "./hrtfs/elev65/L65e195a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e195a.dat" right +[ 31, 34 ] = ascii (fp) : "./hrtfs/elev65/L65e190a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e190a.dat" right +[ 31, 35 ] = ascii (fp) : "./hrtfs/elev65/L65e185a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e185a.dat" right +[ 31, 36 ] = ascii (fp) : "./hrtfs/elev65/L65e180a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e180a.dat" right +[ 31, 37 ] = ascii (fp) : "./hrtfs/elev65/L65e175a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e175a.dat" right +[ 31, 38 ] = ascii (fp) : "./hrtfs/elev65/L65e170a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e170a.dat" right +[ 31, 39 ] = ascii (fp) : "./hrtfs/elev65/L65e165a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e165a.dat" right +[ 31, 40 ] = ascii (fp) : "./hrtfs/elev65/L65e160a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e160a.dat" right +[ 31, 41 ] = ascii (fp) : "./hrtfs/elev65/L65e155a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e155a.dat" right +[ 31, 42 ] = ascii (fp) : "./hrtfs/elev65/L65e150a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e150a.dat" right +[ 31, 43 ] = ascii (fp) : "./hrtfs/elev65/L65e145a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e145a.dat" right +[ 31, 44 ] = ascii (fp) : "./hrtfs/elev65/L65e140a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e140a.dat" right +[ 31, 45 ] = ascii (fp) : "./hrtfs/elev65/L65e135a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e135a.dat" right +[ 31, 46 ] = ascii (fp) : "./hrtfs/elev65/L65e130a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e130a.dat" right +[ 31, 47 ] = ascii (fp) : "./hrtfs/elev65/L65e125a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e125a.dat" right +[ 31, 48 ] = ascii (fp) : "./hrtfs/elev65/L65e120a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e120a.dat" right +[ 31, 49 ] = ascii (fp) : "./hrtfs/elev65/L65e115a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e115a.dat" right +[ 31, 50 ] = ascii (fp) : "./hrtfs/elev65/L65e110a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e110a.dat" right +[ 31, 51 ] = ascii (fp) : "./hrtfs/elev65/L65e105a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e105a.dat" right +[ 31, 52 ] = ascii (fp) : "./hrtfs/elev65/L65e100a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e100a.dat" right +[ 31, 53 ] = ascii (fp) : "./hrtfs/elev65/L65e095a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e095a.dat" right +[ 31, 54 ] = ascii (fp) : "./hrtfs/elev65/L65e090a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e090a.dat" right +[ 31, 55 ] = ascii (fp) : "./hrtfs/elev65/L65e085a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e085a.dat" right +[ 31, 56 ] = ascii (fp) : "./hrtfs/elev65/L65e080a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e080a.dat" right +[ 31, 57 ] = ascii (fp) : "./hrtfs/elev65/L65e075a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e075a.dat" right +[ 31, 58 ] = ascii (fp) : "./hrtfs/elev65/L65e070a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e070a.dat" right +[ 31, 59 ] = ascii (fp) : "./hrtfs/elev65/L65e065a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e065a.dat" right +[ 31, 60 ] = ascii (fp) : "./hrtfs/elev65/L65e060a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e060a.dat" right +[ 31, 61 ] = ascii (fp) : "./hrtfs/elev65/L65e055a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e055a.dat" right +[ 31, 62 ] = ascii (fp) : "./hrtfs/elev65/L65e050a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e050a.dat" right +[ 31, 63 ] = ascii (fp) : "./hrtfs/elev65/L65e045a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e045a.dat" right +[ 31, 64 ] = ascii (fp) : "./hrtfs/elev65/L65e040a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e040a.dat" right +[ 31, 65 ] = ascii (fp) : "./hrtfs/elev65/L65e035a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e035a.dat" right +[ 31, 66 ] = ascii (fp) : "./hrtfs/elev65/L65e030a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e030a.dat" right +[ 31, 67 ] = ascii (fp) : "./hrtfs/elev65/L65e025a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e025a.dat" right +[ 31, 68 ] = ascii (fp) : "./hrtfs/elev65/L65e020a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e020a.dat" right +[ 31, 69 ] = ascii (fp) : "./hrtfs/elev65/L65e015a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e015a.dat" right +[ 31, 70 ] = ascii (fp) : "./hrtfs/elev65/L65e010a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e010a.dat" right +[ 31, 71 ] = ascii (fp) : "./hrtfs/elev65/L65e005a.dat" left + + ascii (fp) : "./hrtfs/elev65/R65e005a.dat" right -[ 32, 0 ] = ascii (fp) : "./hrtfs/elev70/L70e000a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e000a.dat right -[ 32, 1 ] = ascii (fp) : "./hrtfs/elev70/L70e355a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e355a.dat right -[ 32, 2 ] = ascii (fp) : "./hrtfs/elev70/L70e350a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e350a.dat right -[ 32, 3 ] = ascii (fp) : "./hrtfs/elev70/L70e345a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e345a.dat right -[ 32, 4 ] = ascii (fp) : "./hrtfs/elev70/L70e340a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e340a.dat right -[ 32, 5 ] = ascii (fp) : "./hrtfs/elev70/L70e335a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e335a.dat right -[ 32, 6 ] = ascii (fp) : "./hrtfs/elev70/L70e330a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e330a.dat right -[ 32, 7 ] = ascii (fp) : "./hrtfs/elev70/L70e325a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e325a.dat right -[ 32, 8 ] = ascii (fp) : "./hrtfs/elev70/L70e320a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e320a.dat right -[ 32, 9 ] = ascii (fp) : "./hrtfs/elev70/L70e315a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e315a.dat right -[ 32, 10 ] = ascii (fp) : "./hrtfs/elev70/L70e310a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e310a.dat right -[ 32, 11 ] = ascii (fp) : "./hrtfs/elev70/L70e305a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e305a.dat right -[ 32, 12 ] = ascii (fp) : "./hrtfs/elev70/L70e300a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e300a.dat right -[ 32, 13 ] = ascii (fp) : "./hrtfs/elev70/L70e295a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e295a.dat right -[ 32, 14 ] = ascii (fp) : "./hrtfs/elev70/L70e290a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e290a.dat right -[ 32, 15 ] = ascii (fp) : "./hrtfs/elev70/L70e285a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e285a.dat right -[ 32, 16 ] = ascii (fp) : "./hrtfs/elev70/L70e280a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e280a.dat right -[ 32, 17 ] = ascii (fp) : "./hrtfs/elev70/L70e275a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e275a.dat right -[ 32, 18 ] = ascii (fp) : "./hrtfs/elev70/L70e270a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e270a.dat right -[ 32, 19 ] = ascii (fp) : "./hrtfs/elev70/L70e265a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e265a.dat right -[ 32, 20 ] = ascii (fp) : "./hrtfs/elev70/L70e260a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e260a.dat right -[ 32, 21 ] = ascii (fp) : "./hrtfs/elev70/L70e255a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e255a.dat right -[ 32, 22 ] = ascii (fp) : "./hrtfs/elev70/L70e250a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e250a.dat right -[ 32, 23 ] = ascii (fp) : "./hrtfs/elev70/L70e245a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e245a.dat right -[ 32, 24 ] = ascii (fp) : "./hrtfs/elev70/L70e240a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e240a.dat right -[ 32, 25 ] = ascii (fp) : "./hrtfs/elev70/L70e235a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e235a.dat right -[ 32, 26 ] = ascii (fp) : "./hrtfs/elev70/L70e230a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e230a.dat right -[ 32, 27 ] = ascii (fp) : "./hrtfs/elev70/L70e225a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e225a.dat right -[ 32, 28 ] = ascii (fp) : "./hrtfs/elev70/L70e220a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e220a.dat right -[ 32, 29 ] = ascii (fp) : "./hrtfs/elev70/L70e215a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e215a.dat right -[ 32, 30 ] = ascii (fp) : "./hrtfs/elev70/L70e210a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e210a.dat right -[ 32, 31 ] = ascii (fp) : "./hrtfs/elev70/L70e205a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e205a.dat right -[ 32, 32 ] = ascii (fp) : "./hrtfs/elev70/L70e200a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e200a.dat right -[ 32, 33 ] = ascii (fp) : "./hrtfs/elev70/L70e195a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e195a.dat right -[ 32, 34 ] = ascii (fp) : "./hrtfs/elev70/L70e190a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e190a.dat right -[ 32, 35 ] = ascii (fp) : "./hrtfs/elev70/L70e185a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e185a.dat right -[ 32, 36 ] = ascii (fp) : "./hrtfs/elev70/L70e180a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e180a.dat right -[ 32, 37 ] = ascii (fp) : "./hrtfs/elev70/L70e175a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e175a.dat right -[ 32, 38 ] = ascii (fp) : "./hrtfs/elev70/L70e170a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e170a.dat right -[ 32, 39 ] = ascii (fp) : "./hrtfs/elev70/L70e165a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e165a.dat right -[ 32, 40 ] = ascii (fp) : "./hrtfs/elev70/L70e160a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e160a.dat right -[ 32, 41 ] = ascii (fp) : "./hrtfs/elev70/L70e155a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e155a.dat right -[ 32, 42 ] = ascii (fp) : "./hrtfs/elev70/L70e150a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e150a.dat right -[ 32, 43 ] = ascii (fp) : "./hrtfs/elev70/L70e145a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e145a.dat right -[ 32, 44 ] = ascii (fp) : "./hrtfs/elev70/L70e140a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e140a.dat right -[ 32, 45 ] = ascii (fp) : "./hrtfs/elev70/L70e135a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e135a.dat right -[ 32, 46 ] = ascii (fp) : "./hrtfs/elev70/L70e130a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e130a.dat right -[ 32, 47 ] = ascii (fp) : "./hrtfs/elev70/L70e125a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e125a.dat right -[ 32, 48 ] = ascii (fp) : "./hrtfs/elev70/L70e120a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e120a.dat right -[ 32, 49 ] = ascii (fp) : "./hrtfs/elev70/L70e115a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e115a.dat right -[ 32, 50 ] = ascii (fp) : "./hrtfs/elev70/L70e110a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e110a.dat right -[ 32, 51 ] = ascii (fp) : "./hrtfs/elev70/L70e105a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e105a.dat right -[ 32, 52 ] = ascii (fp) : "./hrtfs/elev70/L70e100a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e100a.dat right -[ 32, 53 ] = ascii (fp) : "./hrtfs/elev70/L70e095a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e095a.dat right -[ 32, 54 ] = ascii (fp) : "./hrtfs/elev70/L70e090a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e090a.dat right -[ 32, 55 ] = ascii (fp) : "./hrtfs/elev70/L70e085a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e085a.dat right -[ 32, 56 ] = ascii (fp) : "./hrtfs/elev70/L70e080a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e080a.dat right -[ 32, 57 ] = ascii (fp) : "./hrtfs/elev70/L70e075a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e075a.dat right -[ 32, 58 ] = ascii (fp) : "./hrtfs/elev70/L70e070a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e070a.dat right -[ 32, 59 ] = ascii (fp) : "./hrtfs/elev70/L70e065a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e065a.dat right -[ 32, 60 ] = ascii (fp) : "./hrtfs/elev70/L70e060a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e060a.dat right -[ 32, 61 ] = ascii (fp) : "./hrtfs/elev70/L70e055a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e055a.dat right -[ 32, 62 ] = ascii (fp) : "./hrtfs/elev70/L70e050a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e050a.dat right -[ 32, 63 ] = ascii (fp) : "./hrtfs/elev70/L70e045a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e045a.dat right -[ 32, 64 ] = ascii (fp) : "./hrtfs/elev70/L70e040a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e040a.dat right -[ 32, 65 ] = ascii (fp) : "./hrtfs/elev70/L70e035a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e035a.dat right -[ 32, 66 ] = ascii (fp) : "./hrtfs/elev70/L70e030a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e030a.dat right -[ 32, 67 ] = ascii (fp) : "./hrtfs/elev70/L70e025a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e025a.dat right -[ 32, 68 ] = ascii (fp) : "./hrtfs/elev70/L70e020a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e020a.dat right -[ 32, 69 ] = ascii (fp) : "./hrtfs/elev70/L70e015a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e015a.dat right -[ 32, 70 ] = ascii (fp) : "./hrtfs/elev70/L70e010a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e010a.dat right -[ 32, 71 ] = ascii (fp) : "./hrtfs/elev70/L70e005a.dat left - + ascii (fp) : "./hrtfs/elev70/R70e005a.dat right +[ 32, 0 ] = ascii (fp) : "./hrtfs/elev70/L70e000a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e000a.dat" right +[ 32, 1 ] = ascii (fp) : "./hrtfs/elev70/L70e355a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e355a.dat" right +[ 32, 2 ] = ascii (fp) : "./hrtfs/elev70/L70e350a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e350a.dat" right +[ 32, 3 ] = ascii (fp) : "./hrtfs/elev70/L70e345a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e345a.dat" right +[ 32, 4 ] = ascii (fp) : "./hrtfs/elev70/L70e340a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e340a.dat" right +[ 32, 5 ] = ascii (fp) : "./hrtfs/elev70/L70e335a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e335a.dat" right +[ 32, 6 ] = ascii (fp) : "./hrtfs/elev70/L70e330a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e330a.dat" right +[ 32, 7 ] = ascii (fp) : "./hrtfs/elev70/L70e325a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e325a.dat" right +[ 32, 8 ] = ascii (fp) : "./hrtfs/elev70/L70e320a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e320a.dat" right +[ 32, 9 ] = ascii (fp) : "./hrtfs/elev70/L70e315a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e315a.dat" right +[ 32, 10 ] = ascii (fp) : "./hrtfs/elev70/L70e310a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e310a.dat" right +[ 32, 11 ] = ascii (fp) : "./hrtfs/elev70/L70e305a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e305a.dat" right +[ 32, 12 ] = ascii (fp) : "./hrtfs/elev70/L70e300a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e300a.dat" right +[ 32, 13 ] = ascii (fp) : "./hrtfs/elev70/L70e295a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e295a.dat" right +[ 32, 14 ] = ascii (fp) : "./hrtfs/elev70/L70e290a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e290a.dat" right +[ 32, 15 ] = ascii (fp) : "./hrtfs/elev70/L70e285a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e285a.dat" right +[ 32, 16 ] = ascii (fp) : "./hrtfs/elev70/L70e280a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e280a.dat" right +[ 32, 17 ] = ascii (fp) : "./hrtfs/elev70/L70e275a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e275a.dat" right +[ 32, 18 ] = ascii (fp) : "./hrtfs/elev70/L70e270a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e270a.dat" right +[ 32, 19 ] = ascii (fp) : "./hrtfs/elev70/L70e265a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e265a.dat" right +[ 32, 20 ] = ascii (fp) : "./hrtfs/elev70/L70e260a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e260a.dat" right +[ 32, 21 ] = ascii (fp) : "./hrtfs/elev70/L70e255a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e255a.dat" right +[ 32, 22 ] = ascii (fp) : "./hrtfs/elev70/L70e250a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e250a.dat" right +[ 32, 23 ] = ascii (fp) : "./hrtfs/elev70/L70e245a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e245a.dat" right +[ 32, 24 ] = ascii (fp) : "./hrtfs/elev70/L70e240a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e240a.dat" right +[ 32, 25 ] = ascii (fp) : "./hrtfs/elev70/L70e235a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e235a.dat" right +[ 32, 26 ] = ascii (fp) : "./hrtfs/elev70/L70e230a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e230a.dat" right +[ 32, 27 ] = ascii (fp) : "./hrtfs/elev70/L70e225a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e225a.dat" right +[ 32, 28 ] = ascii (fp) : "./hrtfs/elev70/L70e220a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e220a.dat" right +[ 32, 29 ] = ascii (fp) : "./hrtfs/elev70/L70e215a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e215a.dat" right +[ 32, 30 ] = ascii (fp) : "./hrtfs/elev70/L70e210a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e210a.dat" right +[ 32, 31 ] = ascii (fp) : "./hrtfs/elev70/L70e205a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e205a.dat" right +[ 32, 32 ] = ascii (fp) : "./hrtfs/elev70/L70e200a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e200a.dat" right +[ 32, 33 ] = ascii (fp) : "./hrtfs/elev70/L70e195a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e195a.dat" right +[ 32, 34 ] = ascii (fp) : "./hrtfs/elev70/L70e190a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e190a.dat" right +[ 32, 35 ] = ascii (fp) : "./hrtfs/elev70/L70e185a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e185a.dat" right +[ 32, 36 ] = ascii (fp) : "./hrtfs/elev70/L70e180a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e180a.dat" right +[ 32, 37 ] = ascii (fp) : "./hrtfs/elev70/L70e175a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e175a.dat" right +[ 32, 38 ] = ascii (fp) : "./hrtfs/elev70/L70e170a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e170a.dat" right +[ 32, 39 ] = ascii (fp) : "./hrtfs/elev70/L70e165a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e165a.dat" right +[ 32, 40 ] = ascii (fp) : "./hrtfs/elev70/L70e160a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e160a.dat" right +[ 32, 41 ] = ascii (fp) : "./hrtfs/elev70/L70e155a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e155a.dat" right +[ 32, 42 ] = ascii (fp) : "./hrtfs/elev70/L70e150a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e150a.dat" right +[ 32, 43 ] = ascii (fp) : "./hrtfs/elev70/L70e145a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e145a.dat" right +[ 32, 44 ] = ascii (fp) : "./hrtfs/elev70/L70e140a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e140a.dat" right +[ 32, 45 ] = ascii (fp) : "./hrtfs/elev70/L70e135a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e135a.dat" right +[ 32, 46 ] = ascii (fp) : "./hrtfs/elev70/L70e130a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e130a.dat" right +[ 32, 47 ] = ascii (fp) : "./hrtfs/elev70/L70e125a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e125a.dat" right +[ 32, 48 ] = ascii (fp) : "./hrtfs/elev70/L70e120a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e120a.dat" right +[ 32, 49 ] = ascii (fp) : "./hrtfs/elev70/L70e115a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e115a.dat" right +[ 32, 50 ] = ascii (fp) : "./hrtfs/elev70/L70e110a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e110a.dat" right +[ 32, 51 ] = ascii (fp) : "./hrtfs/elev70/L70e105a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e105a.dat" right +[ 32, 52 ] = ascii (fp) : "./hrtfs/elev70/L70e100a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e100a.dat" right +[ 32, 53 ] = ascii (fp) : "./hrtfs/elev70/L70e095a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e095a.dat" right +[ 32, 54 ] = ascii (fp) : "./hrtfs/elev70/L70e090a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e090a.dat" right +[ 32, 55 ] = ascii (fp) : "./hrtfs/elev70/L70e085a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e085a.dat" right +[ 32, 56 ] = ascii (fp) : "./hrtfs/elev70/L70e080a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e080a.dat" right +[ 32, 57 ] = ascii (fp) : "./hrtfs/elev70/L70e075a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e075a.dat" right +[ 32, 58 ] = ascii (fp) : "./hrtfs/elev70/L70e070a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e070a.dat" right +[ 32, 59 ] = ascii (fp) : "./hrtfs/elev70/L70e065a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e065a.dat" right +[ 32, 60 ] = ascii (fp) : "./hrtfs/elev70/L70e060a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e060a.dat" right +[ 32, 61 ] = ascii (fp) : "./hrtfs/elev70/L70e055a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e055a.dat" right +[ 32, 62 ] = ascii (fp) : "./hrtfs/elev70/L70e050a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e050a.dat" right +[ 32, 63 ] = ascii (fp) : "./hrtfs/elev70/L70e045a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e045a.dat" right +[ 32, 64 ] = ascii (fp) : "./hrtfs/elev70/L70e040a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e040a.dat" right +[ 32, 65 ] = ascii (fp) : "./hrtfs/elev70/L70e035a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e035a.dat" right +[ 32, 66 ] = ascii (fp) : "./hrtfs/elev70/L70e030a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e030a.dat" right +[ 32, 67 ] = ascii (fp) : "./hrtfs/elev70/L70e025a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e025a.dat" right +[ 32, 68 ] = ascii (fp) : "./hrtfs/elev70/L70e020a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e020a.dat" right +[ 32, 69 ] = ascii (fp) : "./hrtfs/elev70/L70e015a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e015a.dat" right +[ 32, 70 ] = ascii (fp) : "./hrtfs/elev70/L70e010a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e010a.dat" right +[ 32, 71 ] = ascii (fp) : "./hrtfs/elev70/L70e005a.dat" left + + ascii (fp) : "./hrtfs/elev70/R70e005a.dat" right -[ 33, 0 ] = ascii (fp) : "./hrtfs/elev75/L75e000a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e000a.dat right -[ 33, 1 ] = ascii (fp) : "./hrtfs/elev75/L75e355a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e355a.dat right -[ 33, 2 ] = ascii (fp) : "./hrtfs/elev75/L75e350a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e350a.dat right -[ 33, 3 ] = ascii (fp) : "./hrtfs/elev75/L75e345a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e345a.dat right -[ 33, 4 ] = ascii (fp) : "./hrtfs/elev75/L75e340a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e340a.dat right -[ 33, 5 ] = ascii (fp) : "./hrtfs/elev75/L75e335a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e335a.dat right -[ 33, 6 ] = ascii (fp) : "./hrtfs/elev75/L75e330a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e330a.dat right -[ 33, 7 ] = ascii (fp) : "./hrtfs/elev75/L75e325a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e325a.dat right -[ 33, 8 ] = ascii (fp) : "./hrtfs/elev75/L75e320a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e320a.dat right -[ 33, 9 ] = ascii (fp) : "./hrtfs/elev75/L75e315a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e315a.dat right -[ 33, 10 ] = ascii (fp) : "./hrtfs/elev75/L75e310a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e310a.dat right -[ 33, 11 ] = ascii (fp) : "./hrtfs/elev75/L75e305a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e305a.dat right -[ 33, 12 ] = ascii (fp) : "./hrtfs/elev75/L75e300a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e300a.dat right -[ 33, 13 ] = ascii (fp) : "./hrtfs/elev75/L75e295a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e295a.dat right -[ 33, 14 ] = ascii (fp) : "./hrtfs/elev75/L75e290a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e290a.dat right -[ 33, 15 ] = ascii (fp) : "./hrtfs/elev75/L75e285a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e285a.dat right -[ 33, 16 ] = ascii (fp) : "./hrtfs/elev75/L75e280a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e280a.dat right -[ 33, 17 ] = ascii (fp) : "./hrtfs/elev75/L75e275a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e275a.dat right -[ 33, 18 ] = ascii (fp) : "./hrtfs/elev75/L75e270a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e270a.dat right -[ 33, 19 ] = ascii (fp) : "./hrtfs/elev75/L75e265a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e265a.dat right -[ 33, 20 ] = ascii (fp) : "./hrtfs/elev75/L75e260a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e260a.dat right -[ 33, 21 ] = ascii (fp) : "./hrtfs/elev75/L75e255a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e255a.dat right -[ 33, 22 ] = ascii (fp) : "./hrtfs/elev75/L75e250a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e250a.dat right -[ 33, 23 ] = ascii (fp) : "./hrtfs/elev75/L75e245a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e245a.dat right -[ 33, 24 ] = ascii (fp) : "./hrtfs/elev75/L75e240a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e240a.dat right -[ 33, 25 ] = ascii (fp) : "./hrtfs/elev75/L75e235a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e235a.dat right -[ 33, 26 ] = ascii (fp) : "./hrtfs/elev75/L75e230a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e230a.dat right -[ 33, 27 ] = ascii (fp) : "./hrtfs/elev75/L75e225a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e225a.dat right -[ 33, 28 ] = ascii (fp) : "./hrtfs/elev75/L75e220a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e220a.dat right -[ 33, 29 ] = ascii (fp) : "./hrtfs/elev75/L75e215a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e215a.dat right -[ 33, 30 ] = ascii (fp) : "./hrtfs/elev75/L75e210a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e210a.dat right -[ 33, 31 ] = ascii (fp) : "./hrtfs/elev75/L75e205a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e205a.dat right -[ 33, 32 ] = ascii (fp) : "./hrtfs/elev75/L75e200a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e200a.dat right -[ 33, 33 ] = ascii (fp) : "./hrtfs/elev75/L75e195a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e195a.dat right -[ 33, 34 ] = ascii (fp) : "./hrtfs/elev75/L75e190a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e190a.dat right -[ 33, 35 ] = ascii (fp) : "./hrtfs/elev75/L75e185a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e185a.dat right -[ 33, 36 ] = ascii (fp) : "./hrtfs/elev75/L75e180a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e180a.dat right -[ 33, 37 ] = ascii (fp) : "./hrtfs/elev75/L75e175a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e175a.dat right -[ 33, 38 ] = ascii (fp) : "./hrtfs/elev75/L75e170a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e170a.dat right -[ 33, 39 ] = ascii (fp) : "./hrtfs/elev75/L75e165a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e165a.dat right -[ 33, 40 ] = ascii (fp) : "./hrtfs/elev75/L75e160a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e160a.dat right -[ 33, 41 ] = ascii (fp) : "./hrtfs/elev75/L75e155a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e155a.dat right -[ 33, 42 ] = ascii (fp) : "./hrtfs/elev75/L75e150a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e150a.dat right -[ 33, 43 ] = ascii (fp) : "./hrtfs/elev75/L75e145a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e145a.dat right -[ 33, 44 ] = ascii (fp) : "./hrtfs/elev75/L75e140a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e140a.dat right -[ 33, 45 ] = ascii (fp) : "./hrtfs/elev75/L75e135a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e135a.dat right -[ 33, 46 ] = ascii (fp) : "./hrtfs/elev75/L75e130a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e130a.dat right -[ 33, 47 ] = ascii (fp) : "./hrtfs/elev75/L75e125a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e125a.dat right -[ 33, 48 ] = ascii (fp) : "./hrtfs/elev75/L75e120a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e120a.dat right -[ 33, 49 ] = ascii (fp) : "./hrtfs/elev75/L75e115a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e115a.dat right -[ 33, 50 ] = ascii (fp) : "./hrtfs/elev75/L75e110a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e110a.dat right -[ 33, 51 ] = ascii (fp) : "./hrtfs/elev75/L75e105a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e105a.dat right -[ 33, 52 ] = ascii (fp) : "./hrtfs/elev75/L75e100a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e100a.dat right -[ 33, 53 ] = ascii (fp) : "./hrtfs/elev75/L75e095a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e095a.dat right -[ 33, 54 ] = ascii (fp) : "./hrtfs/elev75/L75e090a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e090a.dat right -[ 33, 55 ] = ascii (fp) : "./hrtfs/elev75/L75e085a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e085a.dat right -[ 33, 56 ] = ascii (fp) : "./hrtfs/elev75/L75e080a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e080a.dat right -[ 33, 57 ] = ascii (fp) : "./hrtfs/elev75/L75e075a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e075a.dat right -[ 33, 58 ] = ascii (fp) : "./hrtfs/elev75/L75e070a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e070a.dat right -[ 33, 59 ] = ascii (fp) : "./hrtfs/elev75/L75e065a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e065a.dat right -[ 33, 60 ] = ascii (fp) : "./hrtfs/elev75/L75e060a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e060a.dat right -[ 33, 61 ] = ascii (fp) : "./hrtfs/elev75/L75e055a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e055a.dat right -[ 33, 62 ] = ascii (fp) : "./hrtfs/elev75/L75e050a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e050a.dat right -[ 33, 63 ] = ascii (fp) : "./hrtfs/elev75/L75e045a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e045a.dat right -[ 33, 64 ] = ascii (fp) : "./hrtfs/elev75/L75e040a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e040a.dat right -[ 33, 65 ] = ascii (fp) : "./hrtfs/elev75/L75e035a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e035a.dat right -[ 33, 66 ] = ascii (fp) : "./hrtfs/elev75/L75e030a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e030a.dat right -[ 33, 67 ] = ascii (fp) : "./hrtfs/elev75/L75e025a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e025a.dat right -[ 33, 68 ] = ascii (fp) : "./hrtfs/elev75/L75e020a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e020a.dat right -[ 33, 69 ] = ascii (fp) : "./hrtfs/elev75/L75e015a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e015a.dat right -[ 33, 70 ] = ascii (fp) : "./hrtfs/elev75/L75e010a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e010a.dat right -[ 33, 71 ] = ascii (fp) : "./hrtfs/elev75/L75e005a.dat left - + ascii (fp) : "./hrtfs/elev75/R75e005a.dat right +[ 33, 0 ] = ascii (fp) : "./hrtfs/elev75/L75e000a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e000a.dat" right +[ 33, 1 ] = ascii (fp) : "./hrtfs/elev75/L75e355a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e355a.dat" right +[ 33, 2 ] = ascii (fp) : "./hrtfs/elev75/L75e350a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e350a.dat" right +[ 33, 3 ] = ascii (fp) : "./hrtfs/elev75/L75e345a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e345a.dat" right +[ 33, 4 ] = ascii (fp) : "./hrtfs/elev75/L75e340a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e340a.dat" right +[ 33, 5 ] = ascii (fp) : "./hrtfs/elev75/L75e335a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e335a.dat" right +[ 33, 6 ] = ascii (fp) : "./hrtfs/elev75/L75e330a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e330a.dat" right +[ 33, 7 ] = ascii (fp) : "./hrtfs/elev75/L75e325a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e325a.dat" right +[ 33, 8 ] = ascii (fp) : "./hrtfs/elev75/L75e320a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e320a.dat" right +[ 33, 9 ] = ascii (fp) : "./hrtfs/elev75/L75e315a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e315a.dat" right +[ 33, 10 ] = ascii (fp) : "./hrtfs/elev75/L75e310a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e310a.dat" right +[ 33, 11 ] = ascii (fp) : "./hrtfs/elev75/L75e305a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e305a.dat" right +[ 33, 12 ] = ascii (fp) : "./hrtfs/elev75/L75e300a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e300a.dat" right +[ 33, 13 ] = ascii (fp) : "./hrtfs/elev75/L75e295a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e295a.dat" right +[ 33, 14 ] = ascii (fp) : "./hrtfs/elev75/L75e290a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e290a.dat" right +[ 33, 15 ] = ascii (fp) : "./hrtfs/elev75/L75e285a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e285a.dat" right +[ 33, 16 ] = ascii (fp) : "./hrtfs/elev75/L75e280a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e280a.dat" right +[ 33, 17 ] = ascii (fp) : "./hrtfs/elev75/L75e275a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e275a.dat" right +[ 33, 18 ] = ascii (fp) : "./hrtfs/elev75/L75e270a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e270a.dat" right +[ 33, 19 ] = ascii (fp) : "./hrtfs/elev75/L75e265a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e265a.dat" right +[ 33, 20 ] = ascii (fp) : "./hrtfs/elev75/L75e260a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e260a.dat" right +[ 33, 21 ] = ascii (fp) : "./hrtfs/elev75/L75e255a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e255a.dat" right +[ 33, 22 ] = ascii (fp) : "./hrtfs/elev75/L75e250a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e250a.dat" right +[ 33, 23 ] = ascii (fp) : "./hrtfs/elev75/L75e245a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e245a.dat" right +[ 33, 24 ] = ascii (fp) : "./hrtfs/elev75/L75e240a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e240a.dat" right +[ 33, 25 ] = ascii (fp) : "./hrtfs/elev75/L75e235a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e235a.dat" right +[ 33, 26 ] = ascii (fp) : "./hrtfs/elev75/L75e230a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e230a.dat" right +[ 33, 27 ] = ascii (fp) : "./hrtfs/elev75/L75e225a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e225a.dat" right +[ 33, 28 ] = ascii (fp) : "./hrtfs/elev75/L75e220a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e220a.dat" right +[ 33, 29 ] = ascii (fp) : "./hrtfs/elev75/L75e215a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e215a.dat" right +[ 33, 30 ] = ascii (fp) : "./hrtfs/elev75/L75e210a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e210a.dat" right +[ 33, 31 ] = ascii (fp) : "./hrtfs/elev75/L75e205a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e205a.dat" right +[ 33, 32 ] = ascii (fp) : "./hrtfs/elev75/L75e200a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e200a.dat" right +[ 33, 33 ] = ascii (fp) : "./hrtfs/elev75/L75e195a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e195a.dat" right +[ 33, 34 ] = ascii (fp) : "./hrtfs/elev75/L75e190a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e190a.dat" right +[ 33, 35 ] = ascii (fp) : "./hrtfs/elev75/L75e185a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e185a.dat" right +[ 33, 36 ] = ascii (fp) : "./hrtfs/elev75/L75e180a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e180a.dat" right +[ 33, 37 ] = ascii (fp) : "./hrtfs/elev75/L75e175a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e175a.dat" right +[ 33, 38 ] = ascii (fp) : "./hrtfs/elev75/L75e170a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e170a.dat" right +[ 33, 39 ] = ascii (fp) : "./hrtfs/elev75/L75e165a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e165a.dat" right +[ 33, 40 ] = ascii (fp) : "./hrtfs/elev75/L75e160a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e160a.dat" right +[ 33, 41 ] = ascii (fp) : "./hrtfs/elev75/L75e155a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e155a.dat" right +[ 33, 42 ] = ascii (fp) : "./hrtfs/elev75/L75e150a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e150a.dat" right +[ 33, 43 ] = ascii (fp) : "./hrtfs/elev75/L75e145a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e145a.dat" right +[ 33, 44 ] = ascii (fp) : "./hrtfs/elev75/L75e140a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e140a.dat" right +[ 33, 45 ] = ascii (fp) : "./hrtfs/elev75/L75e135a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e135a.dat" right +[ 33, 46 ] = ascii (fp) : "./hrtfs/elev75/L75e130a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e130a.dat" right +[ 33, 47 ] = ascii (fp) : "./hrtfs/elev75/L75e125a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e125a.dat" right +[ 33, 48 ] = ascii (fp) : "./hrtfs/elev75/L75e120a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e120a.dat" right +[ 33, 49 ] = ascii (fp) : "./hrtfs/elev75/L75e115a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e115a.dat" right +[ 33, 50 ] = ascii (fp) : "./hrtfs/elev75/L75e110a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e110a.dat" right +[ 33, 51 ] = ascii (fp) : "./hrtfs/elev75/L75e105a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e105a.dat" right +[ 33, 52 ] = ascii (fp) : "./hrtfs/elev75/L75e100a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e100a.dat" right +[ 33, 53 ] = ascii (fp) : "./hrtfs/elev75/L75e095a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e095a.dat" right +[ 33, 54 ] = ascii (fp) : "./hrtfs/elev75/L75e090a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e090a.dat" right +[ 33, 55 ] = ascii (fp) : "./hrtfs/elev75/L75e085a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e085a.dat" right +[ 33, 56 ] = ascii (fp) : "./hrtfs/elev75/L75e080a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e080a.dat" right +[ 33, 57 ] = ascii (fp) : "./hrtfs/elev75/L75e075a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e075a.dat" right +[ 33, 58 ] = ascii (fp) : "./hrtfs/elev75/L75e070a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e070a.dat" right +[ 33, 59 ] = ascii (fp) : "./hrtfs/elev75/L75e065a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e065a.dat" right +[ 33, 60 ] = ascii (fp) : "./hrtfs/elev75/L75e060a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e060a.dat" right +[ 33, 61 ] = ascii (fp) : "./hrtfs/elev75/L75e055a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e055a.dat" right +[ 33, 62 ] = ascii (fp) : "./hrtfs/elev75/L75e050a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e050a.dat" right +[ 33, 63 ] = ascii (fp) : "./hrtfs/elev75/L75e045a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e045a.dat" right +[ 33, 64 ] = ascii (fp) : "./hrtfs/elev75/L75e040a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e040a.dat" right +[ 33, 65 ] = ascii (fp) : "./hrtfs/elev75/L75e035a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e035a.dat" right +[ 33, 66 ] = ascii (fp) : "./hrtfs/elev75/L75e030a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e030a.dat" right +[ 33, 67 ] = ascii (fp) : "./hrtfs/elev75/L75e025a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e025a.dat" right +[ 33, 68 ] = ascii (fp) : "./hrtfs/elev75/L75e020a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e020a.dat" right +[ 33, 69 ] = ascii (fp) : "./hrtfs/elev75/L75e015a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e015a.dat" right +[ 33, 70 ] = ascii (fp) : "./hrtfs/elev75/L75e010a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e010a.dat" right +[ 33, 71 ] = ascii (fp) : "./hrtfs/elev75/L75e005a.dat" left + + ascii (fp) : "./hrtfs/elev75/R75e005a.dat" right -[ 34, 0 ] = ascii (fp) : "./hrtfs/elev80/L80e000a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e000a.dat right -[ 34, 1 ] = ascii (fp) : "./hrtfs/elev80/L80e355a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e355a.dat right -[ 34, 2 ] = ascii (fp) : "./hrtfs/elev80/L80e350a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e350a.dat right -[ 34, 3 ] = ascii (fp) : "./hrtfs/elev80/L80e345a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e345a.dat right -[ 34, 4 ] = ascii (fp) : "./hrtfs/elev80/L80e340a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e340a.dat right -[ 34, 5 ] = ascii (fp) : "./hrtfs/elev80/L80e335a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e335a.dat right -[ 34, 6 ] = ascii (fp) : "./hrtfs/elev80/L80e330a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e330a.dat right -[ 34, 7 ] = ascii (fp) : "./hrtfs/elev80/L80e325a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e325a.dat right -[ 34, 8 ] = ascii (fp) : "./hrtfs/elev80/L80e320a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e320a.dat right -[ 34, 9 ] = ascii (fp) : "./hrtfs/elev80/L80e315a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e315a.dat right -[ 34, 10 ] = ascii (fp) : "./hrtfs/elev80/L80e310a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e310a.dat right -[ 34, 11 ] = ascii (fp) : "./hrtfs/elev80/L80e305a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e305a.dat right -[ 34, 12 ] = ascii (fp) : "./hrtfs/elev80/L80e300a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e300a.dat right -[ 34, 13 ] = ascii (fp) : "./hrtfs/elev80/L80e295a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e295a.dat right -[ 34, 14 ] = ascii (fp) : "./hrtfs/elev80/L80e290a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e290a.dat right -[ 34, 15 ] = ascii (fp) : "./hrtfs/elev80/L80e285a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e285a.dat right -[ 34, 16 ] = ascii (fp) : "./hrtfs/elev80/L80e280a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e280a.dat right -[ 34, 17 ] = ascii (fp) : "./hrtfs/elev80/L80e275a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e275a.dat right -[ 34, 18 ] = ascii (fp) : "./hrtfs/elev80/L80e270a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e270a.dat right -[ 34, 19 ] = ascii (fp) : "./hrtfs/elev80/L80e265a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e265a.dat right -[ 34, 20 ] = ascii (fp) : "./hrtfs/elev80/L80e260a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e260a.dat right -[ 34, 21 ] = ascii (fp) : "./hrtfs/elev80/L80e255a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e255a.dat right -[ 34, 22 ] = ascii (fp) : "./hrtfs/elev80/L80e250a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e250a.dat right -[ 34, 23 ] = ascii (fp) : "./hrtfs/elev80/L80e245a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e245a.dat right -[ 34, 24 ] = ascii (fp) : "./hrtfs/elev80/L80e240a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e240a.dat right -[ 34, 25 ] = ascii (fp) : "./hrtfs/elev80/L80e235a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e235a.dat right -[ 34, 26 ] = ascii (fp) : "./hrtfs/elev80/L80e230a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e230a.dat right -[ 34, 27 ] = ascii (fp) : "./hrtfs/elev80/L80e225a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e225a.dat right -[ 34, 28 ] = ascii (fp) : "./hrtfs/elev80/L80e220a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e220a.dat right -[ 34, 29 ] = ascii (fp) : "./hrtfs/elev80/L80e215a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e215a.dat right -[ 34, 30 ] = ascii (fp) : "./hrtfs/elev80/L80e210a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e210a.dat right -[ 34, 31 ] = ascii (fp) : "./hrtfs/elev80/L80e205a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e205a.dat right -[ 34, 32 ] = ascii (fp) : "./hrtfs/elev80/L80e200a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e200a.dat right -[ 34, 33 ] = ascii (fp) : "./hrtfs/elev80/L80e195a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e195a.dat right -[ 34, 34 ] = ascii (fp) : "./hrtfs/elev80/L80e190a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e190a.dat right -[ 34, 35 ] = ascii (fp) : "./hrtfs/elev80/L80e185a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e185a.dat right -[ 34, 36 ] = ascii (fp) : "./hrtfs/elev80/L80e180a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e180a.dat right -[ 34, 37 ] = ascii (fp) : "./hrtfs/elev80/L80e175a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e175a.dat right -[ 34, 38 ] = ascii (fp) : "./hrtfs/elev80/L80e170a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e170a.dat right -[ 34, 39 ] = ascii (fp) : "./hrtfs/elev80/L80e165a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e165a.dat right -[ 34, 40 ] = ascii (fp) : "./hrtfs/elev80/L80e160a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e160a.dat right -[ 34, 41 ] = ascii (fp) : "./hrtfs/elev80/L80e155a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e155a.dat right -[ 34, 42 ] = ascii (fp) : "./hrtfs/elev80/L80e150a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e150a.dat right -[ 34, 43 ] = ascii (fp) : "./hrtfs/elev80/L80e145a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e145a.dat right -[ 34, 44 ] = ascii (fp) : "./hrtfs/elev80/L80e140a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e140a.dat right -[ 34, 45 ] = ascii (fp) : "./hrtfs/elev80/L80e135a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e135a.dat right -[ 34, 46 ] = ascii (fp) : "./hrtfs/elev80/L80e130a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e130a.dat right -[ 34, 47 ] = ascii (fp) : "./hrtfs/elev80/L80e125a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e125a.dat right -[ 34, 48 ] = ascii (fp) : "./hrtfs/elev80/L80e120a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e120a.dat right -[ 34, 49 ] = ascii (fp) : "./hrtfs/elev80/L80e115a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e115a.dat right -[ 34, 50 ] = ascii (fp) : "./hrtfs/elev80/L80e110a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e110a.dat right -[ 34, 51 ] = ascii (fp) : "./hrtfs/elev80/L80e105a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e105a.dat right -[ 34, 52 ] = ascii (fp) : "./hrtfs/elev80/L80e100a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e100a.dat right -[ 34, 53 ] = ascii (fp) : "./hrtfs/elev80/L80e095a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e095a.dat right -[ 34, 54 ] = ascii (fp) : "./hrtfs/elev80/L80e090a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e090a.dat right -[ 34, 55 ] = ascii (fp) : "./hrtfs/elev80/L80e085a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e085a.dat right -[ 34, 56 ] = ascii (fp) : "./hrtfs/elev80/L80e080a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e080a.dat right -[ 34, 57 ] = ascii (fp) : "./hrtfs/elev80/L80e075a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e075a.dat right -[ 34, 58 ] = ascii (fp) : "./hrtfs/elev80/L80e070a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e070a.dat right -[ 34, 59 ] = ascii (fp) : "./hrtfs/elev80/L80e065a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e065a.dat right -[ 34, 60 ] = ascii (fp) : "./hrtfs/elev80/L80e060a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e060a.dat right -[ 34, 61 ] = ascii (fp) : "./hrtfs/elev80/L80e055a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e055a.dat right -[ 34, 62 ] = ascii (fp) : "./hrtfs/elev80/L80e050a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e050a.dat right -[ 34, 63 ] = ascii (fp) : "./hrtfs/elev80/L80e045a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e045a.dat right -[ 34, 64 ] = ascii (fp) : "./hrtfs/elev80/L80e040a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e040a.dat right -[ 34, 65 ] = ascii (fp) : "./hrtfs/elev80/L80e035a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e035a.dat right -[ 34, 66 ] = ascii (fp) : "./hrtfs/elev80/L80e030a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e030a.dat right -[ 34, 67 ] = ascii (fp) : "./hrtfs/elev80/L80e025a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e025a.dat right -[ 34, 68 ] = ascii (fp) : "./hrtfs/elev80/L80e020a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e020a.dat right -[ 34, 69 ] = ascii (fp) : "./hrtfs/elev80/L80e015a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e015a.dat right -[ 34, 70 ] = ascii (fp) : "./hrtfs/elev80/L80e010a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e010a.dat right -[ 34, 71 ] = ascii (fp) : "./hrtfs/elev80/L80e005a.dat left - + ascii (fp) : "./hrtfs/elev80/R80e005a.dat right +[ 34, 0 ] = ascii (fp) : "./hrtfs/elev80/L80e000a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e000a.dat" right +[ 34, 1 ] = ascii (fp) : "./hrtfs/elev80/L80e355a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e355a.dat" right +[ 34, 2 ] = ascii (fp) : "./hrtfs/elev80/L80e350a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e350a.dat" right +[ 34, 3 ] = ascii (fp) : "./hrtfs/elev80/L80e345a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e345a.dat" right +[ 34, 4 ] = ascii (fp) : "./hrtfs/elev80/L80e340a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e340a.dat" right +[ 34, 5 ] = ascii (fp) : "./hrtfs/elev80/L80e335a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e335a.dat" right +[ 34, 6 ] = ascii (fp) : "./hrtfs/elev80/L80e330a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e330a.dat" right +[ 34, 7 ] = ascii (fp) : "./hrtfs/elev80/L80e325a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e325a.dat" right +[ 34, 8 ] = ascii (fp) : "./hrtfs/elev80/L80e320a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e320a.dat" right +[ 34, 9 ] = ascii (fp) : "./hrtfs/elev80/L80e315a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e315a.dat" right +[ 34, 10 ] = ascii (fp) : "./hrtfs/elev80/L80e310a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e310a.dat" right +[ 34, 11 ] = ascii (fp) : "./hrtfs/elev80/L80e305a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e305a.dat" right +[ 34, 12 ] = ascii (fp) : "./hrtfs/elev80/L80e300a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e300a.dat" right +[ 34, 13 ] = ascii (fp) : "./hrtfs/elev80/L80e295a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e295a.dat" right +[ 34, 14 ] = ascii (fp) : "./hrtfs/elev80/L80e290a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e290a.dat" right +[ 34, 15 ] = ascii (fp) : "./hrtfs/elev80/L80e285a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e285a.dat" right +[ 34, 16 ] = ascii (fp) : "./hrtfs/elev80/L80e280a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e280a.dat" right +[ 34, 17 ] = ascii (fp) : "./hrtfs/elev80/L80e275a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e275a.dat" right +[ 34, 18 ] = ascii (fp) : "./hrtfs/elev80/L80e270a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e270a.dat" right +[ 34, 19 ] = ascii (fp) : "./hrtfs/elev80/L80e265a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e265a.dat" right +[ 34, 20 ] = ascii (fp) : "./hrtfs/elev80/L80e260a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e260a.dat" right +[ 34, 21 ] = ascii (fp) : "./hrtfs/elev80/L80e255a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e255a.dat" right +[ 34, 22 ] = ascii (fp) : "./hrtfs/elev80/L80e250a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e250a.dat" right +[ 34, 23 ] = ascii (fp) : "./hrtfs/elev80/L80e245a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e245a.dat" right +[ 34, 24 ] = ascii (fp) : "./hrtfs/elev80/L80e240a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e240a.dat" right +[ 34, 25 ] = ascii (fp) : "./hrtfs/elev80/L80e235a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e235a.dat" right +[ 34, 26 ] = ascii (fp) : "./hrtfs/elev80/L80e230a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e230a.dat" right +[ 34, 27 ] = ascii (fp) : "./hrtfs/elev80/L80e225a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e225a.dat" right +[ 34, 28 ] = ascii (fp) : "./hrtfs/elev80/L80e220a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e220a.dat" right +[ 34, 29 ] = ascii (fp) : "./hrtfs/elev80/L80e215a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e215a.dat" right +[ 34, 30 ] = ascii (fp) : "./hrtfs/elev80/L80e210a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e210a.dat" right +[ 34, 31 ] = ascii (fp) : "./hrtfs/elev80/L80e205a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e205a.dat" right +[ 34, 32 ] = ascii (fp) : "./hrtfs/elev80/L80e200a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e200a.dat" right +[ 34, 33 ] = ascii (fp) : "./hrtfs/elev80/L80e195a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e195a.dat" right +[ 34, 34 ] = ascii (fp) : "./hrtfs/elev80/L80e190a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e190a.dat" right +[ 34, 35 ] = ascii (fp) : "./hrtfs/elev80/L80e185a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e185a.dat" right +[ 34, 36 ] = ascii (fp) : "./hrtfs/elev80/L80e180a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e180a.dat" right +[ 34, 37 ] = ascii (fp) : "./hrtfs/elev80/L80e175a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e175a.dat" right +[ 34, 38 ] = ascii (fp) : "./hrtfs/elev80/L80e170a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e170a.dat" right +[ 34, 39 ] = ascii (fp) : "./hrtfs/elev80/L80e165a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e165a.dat" right +[ 34, 40 ] = ascii (fp) : "./hrtfs/elev80/L80e160a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e160a.dat" right +[ 34, 41 ] = ascii (fp) : "./hrtfs/elev80/L80e155a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e155a.dat" right +[ 34, 42 ] = ascii (fp) : "./hrtfs/elev80/L80e150a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e150a.dat" right +[ 34, 43 ] = ascii (fp) : "./hrtfs/elev80/L80e145a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e145a.dat" right +[ 34, 44 ] = ascii (fp) : "./hrtfs/elev80/L80e140a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e140a.dat" right +[ 34, 45 ] = ascii (fp) : "./hrtfs/elev80/L80e135a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e135a.dat" right +[ 34, 46 ] = ascii (fp) : "./hrtfs/elev80/L80e130a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e130a.dat" right +[ 34, 47 ] = ascii (fp) : "./hrtfs/elev80/L80e125a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e125a.dat" right +[ 34, 48 ] = ascii (fp) : "./hrtfs/elev80/L80e120a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e120a.dat" right +[ 34, 49 ] = ascii (fp) : "./hrtfs/elev80/L80e115a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e115a.dat" right +[ 34, 50 ] = ascii (fp) : "./hrtfs/elev80/L80e110a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e110a.dat" right +[ 34, 51 ] = ascii (fp) : "./hrtfs/elev80/L80e105a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e105a.dat" right +[ 34, 52 ] = ascii (fp) : "./hrtfs/elev80/L80e100a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e100a.dat" right +[ 34, 53 ] = ascii (fp) : "./hrtfs/elev80/L80e095a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e095a.dat" right +[ 34, 54 ] = ascii (fp) : "./hrtfs/elev80/L80e090a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e090a.dat" right +[ 34, 55 ] = ascii (fp) : "./hrtfs/elev80/L80e085a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e085a.dat" right +[ 34, 56 ] = ascii (fp) : "./hrtfs/elev80/L80e080a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e080a.dat" right +[ 34, 57 ] = ascii (fp) : "./hrtfs/elev80/L80e075a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e075a.dat" right +[ 34, 58 ] = ascii (fp) : "./hrtfs/elev80/L80e070a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e070a.dat" right +[ 34, 59 ] = ascii (fp) : "./hrtfs/elev80/L80e065a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e065a.dat" right +[ 34, 60 ] = ascii (fp) : "./hrtfs/elev80/L80e060a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e060a.dat" right +[ 34, 61 ] = ascii (fp) : "./hrtfs/elev80/L80e055a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e055a.dat" right +[ 34, 62 ] = ascii (fp) : "./hrtfs/elev80/L80e050a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e050a.dat" right +[ 34, 63 ] = ascii (fp) : "./hrtfs/elev80/L80e045a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e045a.dat" right +[ 34, 64 ] = ascii (fp) : "./hrtfs/elev80/L80e040a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e040a.dat" right +[ 34, 65 ] = ascii (fp) : "./hrtfs/elev80/L80e035a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e035a.dat" right +[ 34, 66 ] = ascii (fp) : "./hrtfs/elev80/L80e030a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e030a.dat" right +[ 34, 67 ] = ascii (fp) : "./hrtfs/elev80/L80e025a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e025a.dat" right +[ 34, 68 ] = ascii (fp) : "./hrtfs/elev80/L80e020a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e020a.dat" right +[ 34, 69 ] = ascii (fp) : "./hrtfs/elev80/L80e015a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e015a.dat" right +[ 34, 70 ] = ascii (fp) : "./hrtfs/elev80/L80e010a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e010a.dat" right +[ 34, 71 ] = ascii (fp) : "./hrtfs/elev80/L80e005a.dat" left + + ascii (fp) : "./hrtfs/elev80/R80e005a.dat" right -[ 35, 0 ] = ascii (fp) : "./hrtfs/elev85/L85e000a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e000a.dat right -[ 35, 1 ] = ascii (fp) : "./hrtfs/elev85/L85e355a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e355a.dat right -[ 35, 2 ] = ascii (fp) : "./hrtfs/elev85/L85e350a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e350a.dat right -[ 35, 3 ] = ascii (fp) : "./hrtfs/elev85/L85e345a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e345a.dat right -[ 35, 4 ] = ascii (fp) : "./hrtfs/elev85/L85e340a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e340a.dat right -[ 35, 5 ] = ascii (fp) : "./hrtfs/elev85/L85e335a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e335a.dat right -[ 35, 6 ] = ascii (fp) : "./hrtfs/elev85/L85e330a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e330a.dat right -[ 35, 7 ] = ascii (fp) : "./hrtfs/elev85/L85e325a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e325a.dat right -[ 35, 8 ] = ascii (fp) : "./hrtfs/elev85/L85e320a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e320a.dat right -[ 35, 9 ] = ascii (fp) : "./hrtfs/elev85/L85e315a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e315a.dat right -[ 35, 10 ] = ascii (fp) : "./hrtfs/elev85/L85e310a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e310a.dat right -[ 35, 11 ] = ascii (fp) : "./hrtfs/elev85/L85e305a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e305a.dat right -[ 35, 12 ] = ascii (fp) : "./hrtfs/elev85/L85e300a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e300a.dat right -[ 35, 13 ] = ascii (fp) : "./hrtfs/elev85/L85e295a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e295a.dat right -[ 35, 14 ] = ascii (fp) : "./hrtfs/elev85/L85e290a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e290a.dat right -[ 35, 15 ] = ascii (fp) : "./hrtfs/elev85/L85e285a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e285a.dat right -[ 35, 16 ] = ascii (fp) : "./hrtfs/elev85/L85e280a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e280a.dat right -[ 35, 17 ] = ascii (fp) : "./hrtfs/elev85/L85e275a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e275a.dat right -[ 35, 18 ] = ascii (fp) : "./hrtfs/elev85/L85e270a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e270a.dat right -[ 35, 19 ] = ascii (fp) : "./hrtfs/elev85/L85e265a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e265a.dat right -[ 35, 20 ] = ascii (fp) : "./hrtfs/elev85/L85e260a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e260a.dat right -[ 35, 21 ] = ascii (fp) : "./hrtfs/elev85/L85e255a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e255a.dat right -[ 35, 22 ] = ascii (fp) : "./hrtfs/elev85/L85e250a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e250a.dat right -[ 35, 23 ] = ascii (fp) : "./hrtfs/elev85/L85e245a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e245a.dat right -[ 35, 24 ] = ascii (fp) : "./hrtfs/elev85/L85e240a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e240a.dat right -[ 35, 25 ] = ascii (fp) : "./hrtfs/elev85/L85e235a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e235a.dat right -[ 35, 26 ] = ascii (fp) : "./hrtfs/elev85/L85e230a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e230a.dat right -[ 35, 27 ] = ascii (fp) : "./hrtfs/elev85/L85e225a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e225a.dat right -[ 35, 28 ] = ascii (fp) : "./hrtfs/elev85/L85e220a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e220a.dat right -[ 35, 29 ] = ascii (fp) : "./hrtfs/elev85/L85e215a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e215a.dat right -[ 35, 30 ] = ascii (fp) : "./hrtfs/elev85/L85e210a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e210a.dat right -[ 35, 31 ] = ascii (fp) : "./hrtfs/elev85/L85e205a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e205a.dat right -[ 35, 32 ] = ascii (fp) : "./hrtfs/elev85/L85e200a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e200a.dat right -[ 35, 33 ] = ascii (fp) : "./hrtfs/elev85/L85e195a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e195a.dat right -[ 35, 34 ] = ascii (fp) : "./hrtfs/elev85/L85e190a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e190a.dat right -[ 35, 35 ] = ascii (fp) : "./hrtfs/elev85/L85e185a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e185a.dat right -[ 35, 36 ] = ascii (fp) : "./hrtfs/elev85/L85e180a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e180a.dat right -[ 35, 37 ] = ascii (fp) : "./hrtfs/elev85/L85e175a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e175a.dat right -[ 35, 38 ] = ascii (fp) : "./hrtfs/elev85/L85e170a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e170a.dat right -[ 35, 39 ] = ascii (fp) : "./hrtfs/elev85/L85e165a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e165a.dat right -[ 35, 40 ] = ascii (fp) : "./hrtfs/elev85/L85e160a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e160a.dat right -[ 35, 41 ] = ascii (fp) : "./hrtfs/elev85/L85e155a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e155a.dat right -[ 35, 42 ] = ascii (fp) : "./hrtfs/elev85/L85e150a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e150a.dat right -[ 35, 43 ] = ascii (fp) : "./hrtfs/elev85/L85e145a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e145a.dat right -[ 35, 44 ] = ascii (fp) : "./hrtfs/elev85/L85e140a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e140a.dat right -[ 35, 45 ] = ascii (fp) : "./hrtfs/elev85/L85e135a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e135a.dat right -[ 35, 46 ] = ascii (fp) : "./hrtfs/elev85/L85e130a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e130a.dat right -[ 35, 47 ] = ascii (fp) : "./hrtfs/elev85/L85e125a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e125a.dat right -[ 35, 48 ] = ascii (fp) : "./hrtfs/elev85/L85e120a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e120a.dat right -[ 35, 49 ] = ascii (fp) : "./hrtfs/elev85/L85e115a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e115a.dat right -[ 35, 50 ] = ascii (fp) : "./hrtfs/elev85/L85e110a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e110a.dat right -[ 35, 51 ] = ascii (fp) : "./hrtfs/elev85/L85e105a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e105a.dat right -[ 35, 52 ] = ascii (fp) : "./hrtfs/elev85/L85e100a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e100a.dat right -[ 35, 53 ] = ascii (fp) : "./hrtfs/elev85/L85e095a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e095a.dat right -[ 35, 54 ] = ascii (fp) : "./hrtfs/elev85/L85e090a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e090a.dat right -[ 35, 55 ] = ascii (fp) : "./hrtfs/elev85/L85e085a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e085a.dat right -[ 35, 56 ] = ascii (fp) : "./hrtfs/elev85/L85e080a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e080a.dat right -[ 35, 57 ] = ascii (fp) : "./hrtfs/elev85/L85e075a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e075a.dat right -[ 35, 58 ] = ascii (fp) : "./hrtfs/elev85/L85e070a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e070a.dat right -[ 35, 59 ] = ascii (fp) : "./hrtfs/elev85/L85e065a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e065a.dat right -[ 35, 60 ] = ascii (fp) : "./hrtfs/elev85/L85e060a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e060a.dat right -[ 35, 61 ] = ascii (fp) : "./hrtfs/elev85/L85e055a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e055a.dat right -[ 35, 62 ] = ascii (fp) : "./hrtfs/elev85/L85e050a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e050a.dat right -[ 35, 63 ] = ascii (fp) : "./hrtfs/elev85/L85e045a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e045a.dat right -[ 35, 64 ] = ascii (fp) : "./hrtfs/elev85/L85e040a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e040a.dat right -[ 35, 65 ] = ascii (fp) : "./hrtfs/elev85/L85e035a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e035a.dat right -[ 35, 66 ] = ascii (fp) : "./hrtfs/elev85/L85e030a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e030a.dat right -[ 35, 67 ] = ascii (fp) : "./hrtfs/elev85/L85e025a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e025a.dat right -[ 35, 68 ] = ascii (fp) : "./hrtfs/elev85/L85e020a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e020a.dat right -[ 35, 69 ] = ascii (fp) : "./hrtfs/elev85/L85e015a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e015a.dat right -[ 35, 70 ] = ascii (fp) : "./hrtfs/elev85/L85e010a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e010a.dat right -[ 35, 71 ] = ascii (fp) : "./hrtfs/elev85/L85e005a.dat left - + ascii (fp) : "./hrtfs/elev85/R85e005a.dat right +[ 35, 0 ] = ascii (fp) : "./hrtfs/elev85/L85e000a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e000a.dat" right +[ 35, 1 ] = ascii (fp) : "./hrtfs/elev85/L85e355a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e355a.dat" right +[ 35, 2 ] = ascii (fp) : "./hrtfs/elev85/L85e350a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e350a.dat" right +[ 35, 3 ] = ascii (fp) : "./hrtfs/elev85/L85e345a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e345a.dat" right +[ 35, 4 ] = ascii (fp) : "./hrtfs/elev85/L85e340a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e340a.dat" right +[ 35, 5 ] = ascii (fp) : "./hrtfs/elev85/L85e335a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e335a.dat" right +[ 35, 6 ] = ascii (fp) : "./hrtfs/elev85/L85e330a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e330a.dat" right +[ 35, 7 ] = ascii (fp) : "./hrtfs/elev85/L85e325a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e325a.dat" right +[ 35, 8 ] = ascii (fp) : "./hrtfs/elev85/L85e320a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e320a.dat" right +[ 35, 9 ] = ascii (fp) : "./hrtfs/elev85/L85e315a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e315a.dat" right +[ 35, 10 ] = ascii (fp) : "./hrtfs/elev85/L85e310a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e310a.dat" right +[ 35, 11 ] = ascii (fp) : "./hrtfs/elev85/L85e305a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e305a.dat" right +[ 35, 12 ] = ascii (fp) : "./hrtfs/elev85/L85e300a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e300a.dat" right +[ 35, 13 ] = ascii (fp) : "./hrtfs/elev85/L85e295a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e295a.dat" right +[ 35, 14 ] = ascii (fp) : "./hrtfs/elev85/L85e290a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e290a.dat" right +[ 35, 15 ] = ascii (fp) : "./hrtfs/elev85/L85e285a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e285a.dat" right +[ 35, 16 ] = ascii (fp) : "./hrtfs/elev85/L85e280a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e280a.dat" right +[ 35, 17 ] = ascii (fp) : "./hrtfs/elev85/L85e275a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e275a.dat" right +[ 35, 18 ] = ascii (fp) : "./hrtfs/elev85/L85e270a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e270a.dat" right +[ 35, 19 ] = ascii (fp) : "./hrtfs/elev85/L85e265a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e265a.dat" right +[ 35, 20 ] = ascii (fp) : "./hrtfs/elev85/L85e260a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e260a.dat" right +[ 35, 21 ] = ascii (fp) : "./hrtfs/elev85/L85e255a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e255a.dat" right +[ 35, 22 ] = ascii (fp) : "./hrtfs/elev85/L85e250a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e250a.dat" right +[ 35, 23 ] = ascii (fp) : "./hrtfs/elev85/L85e245a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e245a.dat" right +[ 35, 24 ] = ascii (fp) : "./hrtfs/elev85/L85e240a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e240a.dat" right +[ 35, 25 ] = ascii (fp) : "./hrtfs/elev85/L85e235a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e235a.dat" right +[ 35, 26 ] = ascii (fp) : "./hrtfs/elev85/L85e230a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e230a.dat" right +[ 35, 27 ] = ascii (fp) : "./hrtfs/elev85/L85e225a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e225a.dat" right +[ 35, 28 ] = ascii (fp) : "./hrtfs/elev85/L85e220a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e220a.dat" right +[ 35, 29 ] = ascii (fp) : "./hrtfs/elev85/L85e215a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e215a.dat" right +[ 35, 30 ] = ascii (fp) : "./hrtfs/elev85/L85e210a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e210a.dat" right +[ 35, 31 ] = ascii (fp) : "./hrtfs/elev85/L85e205a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e205a.dat" right +[ 35, 32 ] = ascii (fp) : "./hrtfs/elev85/L85e200a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e200a.dat" right +[ 35, 33 ] = ascii (fp) : "./hrtfs/elev85/L85e195a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e195a.dat" right +[ 35, 34 ] = ascii (fp) : "./hrtfs/elev85/L85e190a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e190a.dat" right +[ 35, 35 ] = ascii (fp) : "./hrtfs/elev85/L85e185a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e185a.dat" right +[ 35, 36 ] = ascii (fp) : "./hrtfs/elev85/L85e180a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e180a.dat" right +[ 35, 37 ] = ascii (fp) : "./hrtfs/elev85/L85e175a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e175a.dat" right +[ 35, 38 ] = ascii (fp) : "./hrtfs/elev85/L85e170a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e170a.dat" right +[ 35, 39 ] = ascii (fp) : "./hrtfs/elev85/L85e165a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e165a.dat" right +[ 35, 40 ] = ascii (fp) : "./hrtfs/elev85/L85e160a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e160a.dat" right +[ 35, 41 ] = ascii (fp) : "./hrtfs/elev85/L85e155a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e155a.dat" right +[ 35, 42 ] = ascii (fp) : "./hrtfs/elev85/L85e150a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e150a.dat" right +[ 35, 43 ] = ascii (fp) : "./hrtfs/elev85/L85e145a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e145a.dat" right +[ 35, 44 ] = ascii (fp) : "./hrtfs/elev85/L85e140a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e140a.dat" right +[ 35, 45 ] = ascii (fp) : "./hrtfs/elev85/L85e135a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e135a.dat" right +[ 35, 46 ] = ascii (fp) : "./hrtfs/elev85/L85e130a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e130a.dat" right +[ 35, 47 ] = ascii (fp) : "./hrtfs/elev85/L85e125a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e125a.dat" right +[ 35, 48 ] = ascii (fp) : "./hrtfs/elev85/L85e120a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e120a.dat" right +[ 35, 49 ] = ascii (fp) : "./hrtfs/elev85/L85e115a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e115a.dat" right +[ 35, 50 ] = ascii (fp) : "./hrtfs/elev85/L85e110a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e110a.dat" right +[ 35, 51 ] = ascii (fp) : "./hrtfs/elev85/L85e105a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e105a.dat" right +[ 35, 52 ] = ascii (fp) : "./hrtfs/elev85/L85e100a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e100a.dat" right +[ 35, 53 ] = ascii (fp) : "./hrtfs/elev85/L85e095a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e095a.dat" right +[ 35, 54 ] = ascii (fp) : "./hrtfs/elev85/L85e090a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e090a.dat" right +[ 35, 55 ] = ascii (fp) : "./hrtfs/elev85/L85e085a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e085a.dat" right +[ 35, 56 ] = ascii (fp) : "./hrtfs/elev85/L85e080a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e080a.dat" right +[ 35, 57 ] = ascii (fp) : "./hrtfs/elev85/L85e075a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e075a.dat" right +[ 35, 58 ] = ascii (fp) : "./hrtfs/elev85/L85e070a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e070a.dat" right +[ 35, 59 ] = ascii (fp) : "./hrtfs/elev85/L85e065a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e065a.dat" right +[ 35, 60 ] = ascii (fp) : "./hrtfs/elev85/L85e060a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e060a.dat" right +[ 35, 61 ] = ascii (fp) : "./hrtfs/elev85/L85e055a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e055a.dat" right +[ 35, 62 ] = ascii (fp) : "./hrtfs/elev85/L85e050a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e050a.dat" right +[ 35, 63 ] = ascii (fp) : "./hrtfs/elev85/L85e045a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e045a.dat" right +[ 35, 64 ] = ascii (fp) : "./hrtfs/elev85/L85e040a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e040a.dat" right +[ 35, 65 ] = ascii (fp) : "./hrtfs/elev85/L85e035a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e035a.dat" right +[ 35, 66 ] = ascii (fp) : "./hrtfs/elev85/L85e030a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e030a.dat" right +[ 35, 67 ] = ascii (fp) : "./hrtfs/elev85/L85e025a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e025a.dat" right +[ 35, 68 ] = ascii (fp) : "./hrtfs/elev85/L85e020a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e020a.dat" right +[ 35, 69 ] = ascii (fp) : "./hrtfs/elev85/L85e015a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e015a.dat" right +[ 35, 70 ] = ascii (fp) : "./hrtfs/elev85/L85e010a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e010a.dat" right +[ 35, 71 ] = ascii (fp) : "./hrtfs/elev85/L85e005a.dat" left + + ascii (fp) : "./hrtfs/elev85/R85e005a.dat" right -[ 36, 0 ] = ascii (fp) : "./hrtfs/elev90/L90e000a.dat left - + ascii (fp) : "./hrtfs/elev90/R90e000a.dat right +[ 36, 0 ] = ascii (fp) : "./hrtfs/elev90/L90e000a.dat" left + + ascii (fp) : "./hrtfs/elev90/R90e000a.dat" right diff --git a/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt b/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt index c6a460759..cb896382c 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt +++ b/Engine/lib/openal-soft/utils/alsoft-config/CMakeLists.txt @@ -12,9 +12,10 @@ if(Qt5Widgets_FOUND) verstr.cpp verstr.h ${UIS} ${RSCS} ${TRS} ${MOCS}) - target_link_libraries(alsoft-config Qt5::Widgets) + target_link_libraries(alsoft-config PUBLIC Qt5::Widgets PRIVATE alcommon) target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}" "${OpenAL_BINARY_DIR}") + target_compile_definitions(alsoft-config PRIVATE QT_NO_KEYWORDS) set_target_properties(alsoft-config PROPERTIES ${DEFAULT_TARGET_PROPS} RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR}) if(TARGET build_version) diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp index bee7022f3..672c3d87d 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.cpp @@ -3,8 +3,9 @@ #include "mainwindow.h" -#include +#include #include +#include #include #include @@ -19,148 +20,149 @@ #include #endif +#include "almalloc.h" +#include "alspan.h" + namespace { -const struct { +struct BackendNamePair { + /* NOLINTBEGIN(*-avoid-c-arrays) */ char backend_name[16]; char full_string[32]; -} backendList[] = { -#ifdef HAVE_JACK - { "jack", "JACK" }, -#endif + /* NOLINTEND(*-avoid-c-arrays) */ +}; +constexpr std::array backendList{ #ifdef HAVE_PIPEWIRE - { "pipewire", "PipeWire" }, + BackendNamePair{ "pipewire", "PipeWire" }, #endif #ifdef HAVE_PULSEAUDIO - { "pulse", "PulseAudio" }, + BackendNamePair{ "pulse", "PulseAudio" }, #endif #ifdef HAVE_ALSA - { "alsa", "ALSA" }, + BackendNamePair{ "alsa", "ALSA" }, +#endif +#ifdef HAVE_JACK + BackendNamePair{ "jack", "JACK" }, #endif #ifdef HAVE_COREAUDIO - { "core", "CoreAudio" }, + BackendNamePair{ "core", "CoreAudio" }, #endif #ifdef HAVE_OSS - { "oss", "OSS" }, + BackendNamePair{ "oss", "OSS" }, #endif #ifdef HAVE_SOLARIS - { "solaris", "Solaris" }, + BackendNamePair{ "solaris", "Solaris" }, #endif #ifdef HAVE_SNDIO - { "sndio", "SoundIO" }, -#endif -#ifdef HAVE_QSA - { "qsa", "QSA" }, + BackendNamePair{ "sndio", "SndIO" }, #endif #ifdef HAVE_WASAPI - { "wasapi", "WASAPI" }, + BackendNamePair{ "wasapi", "WASAPI" }, #endif #ifdef HAVE_DSOUND - { "dsound", "DirectSound" }, + BackendNamePair{ "dsound", "DirectSound" }, #endif #ifdef HAVE_WINMM - { "winmm", "Windows Multimedia" }, + BackendNamePair{ "winmm", "Windows Multimedia" }, #endif #ifdef HAVE_PORTAUDIO - { "port", "PortAudio" }, + BackendNamePair{ "port", "PortAudio" }, #endif #ifdef HAVE_OPENSL - { "opensl", "OpenSL" }, + BackendNamePair{ "opensl", "OpenSL" }, #endif - { "null", "Null Output" }, + BackendNamePair{ "null", "Null Output" }, #ifdef HAVE_WAVE - { "wave", "Wave Writer" }, + BackendNamePair{ "wave", "Wave Writer" }, #endif - { "", "" } }; -const struct NameValuePair { +struct NameValuePair { + /* NOLINTBEGIN(*-avoid-c-arrays) */ const char name[64]; const char value[16]; -} speakerModeList[] = { - { "Autodetect", "" }, - { "Mono", "mono" }, - { "Stereo", "stereo" }, - { "Quadraphonic", "quad" }, - { "5.1 Surround", "surround51" }, - { "6.1 Surround", "surround61" }, - { "7.1 Surround", "surround71" }, - { "3D7.1 Surround", "surround3d71" }, + /* NOLINTEND(*-avoid-c-arrays) */ +}; +constexpr std::array speakerModeList{ + NameValuePair{ "Autodetect", "" }, + NameValuePair{ "Mono", "mono" }, + NameValuePair{ "Stereo", "stereo" }, + NameValuePair{ "Quadraphonic", "quad" }, + NameValuePair{ "5.1 Surround", "surround51" }, + NameValuePair{ "6.1 Surround", "surround61" }, + NameValuePair{ "7.1 Surround", "surround71" }, + NameValuePair{ "3D7.1 Surround", "surround3d71" }, - { "Ambisonic, 1st Order", "ambi1" }, - { "Ambisonic, 2nd Order", "ambi2" }, - { "Ambisonic, 3rd Order", "ambi3" }, - - { "", "" } -}, sampleTypeList[] = { - { "Autodetect", "" }, - { "8-bit int", "int8" }, - { "8-bit uint", "uint8" }, - { "16-bit int", "int16" }, - { "16-bit uint", "uint16" }, - { "32-bit int", "int32" }, - { "32-bit uint", "uint32" }, - { "32-bit float", "float32" }, - - { "", "" } -}, resamplerList[] = { - { "Point", "point" }, - { "Linear", "linear" }, - { "Cubic Spline", "cubic" }, - { "Default (Cubic Spline)", "" }, - { "11th order Sinc (fast)", "fast_bsinc12" }, - { "11th order Sinc", "bsinc12" }, - { "23rd order Sinc (fast)", "fast_bsinc24" }, - { "23rd order Sinc", "bsinc24" }, - - { "", "" } -}, stereoModeList[] = { - { "Autodetect", "" }, - { "Speakers", "speakers" }, - { "Headphones", "headphones" }, - - { "", "" } -}, stereoEncList[] = { - { "Default", "" }, - { "Basic", "panpot" }, - { "UHJ", "uhj" }, - { "Binaural", "hrtf" }, - - { "", "" } -}, ambiFormatList[] = { - { "Default", "" }, - { "AmbiX (ACN, SN3D)", "ambix" }, - { "Furse-Malham", "fuma" }, - { "ACN, N3D", "acn+n3d" }, - { "ACN, FuMa", "acn+fuma" }, - - { "", "" } -}, hrtfModeList[] = { - { "1st Order Ambisonic", "ambi1" }, - { "2nd Order Ambisonic", "ambi2" }, - { "3rd Order Ambisonic", "ambi3" }, - { "Default (Full)", "" }, - { "Full", "full" }, - - { "", "" } + NameValuePair{ "Ambisonic, 1st Order", "ambi1" }, + NameValuePair{ "Ambisonic, 2nd Order", "ambi2" }, + NameValuePair{ "Ambisonic, 3rd Order", "ambi3" }, +}; +constexpr std::array sampleTypeList{ + NameValuePair{ "Autodetect", "" }, + NameValuePair{ "8-bit int", "int8" }, + NameValuePair{ "8-bit uint", "uint8" }, + NameValuePair{ "16-bit int", "int16" }, + NameValuePair{ "16-bit uint", "uint16" }, + NameValuePair{ "32-bit int", "int32" }, + NameValuePair{ "32-bit uint", "uint32" }, + NameValuePair{ "32-bit float", "float32" }, +}; +constexpr std::array resamplerList{ + NameValuePair{ "Point", "point" }, + NameValuePair{ "Linear", "linear" }, + NameValuePair{ "Cubic Spline", "spline" }, + NameValuePair{ "4-point Gaussian", "gaussian" }, + NameValuePair{ "Default (4-point Gaussian)", "" }, + NameValuePair{ "11th order Sinc (fast)", "fast_bsinc12" }, + NameValuePair{ "11th order Sinc", "bsinc12" }, + NameValuePair{ "23rd order Sinc (fast)", "fast_bsinc24" }, + NameValuePair{ "23rd order Sinc", "bsinc24" }, +}; +constexpr std::array stereoModeList{ + NameValuePair{ "Autodetect", "" }, + NameValuePair{ "Speakers", "speakers" }, + NameValuePair{ "Headphones", "headphones" }, +}; +constexpr std::array stereoEncList{ + NameValuePair{ "Default", "" }, + NameValuePair{ "Basic", "panpot" }, + NameValuePair{ "UHJ", "uhj" }, + NameValuePair{ "Binaural", "hrtf" }, +}; +constexpr std::array ambiFormatList{ + NameValuePair{ "Default", "" }, + NameValuePair{ "AmbiX (ACN, SN3D)", "ambix" }, + NameValuePair{ "Furse-Malham", "fuma" }, + NameValuePair{ "ACN, N3D", "acn+n3d" }, + NameValuePair{ "ACN, FuMa", "acn+fuma" }, +}; +constexpr std::array hrtfModeList{ + NameValuePair{ "1st Order Ambisonic", "ambi1" }, + NameValuePair{ "2nd Order Ambisonic", "ambi2" }, + NameValuePair{ "3rd Order Ambisonic", "ambi3" }, + NameValuePair{ "Default (Full)", "" }, + NameValuePair{ "Full", "full" }, }; QString getDefaultConfigName() { #ifdef Q_OS_WIN32 - static const char fname[] = "alsoft.ini"; + const char *fname{"alsoft.ini"}; auto get_appdata_path = []() noexcept -> QString { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) - return QString::fromWCharArray(buffer); - return QString(); + QString ret; + WCHAR *buffer{}; + if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, + nullptr, &buffer)}; SUCCEEDED(hr)) + ret = QString::fromWCharArray(buffer); + CoTaskMemFree(buffer); + return ret; }; QString base = get_appdata_path(); #else - static const char fname[] = "alsoft.conf"; - QByteArray base = qgetenv("XDG_CONFIG_HOME"); + const char *fname{"alsoft.conf"}; + QString base = qgetenv("XDG_CONFIG_HOME"); if(base.isEmpty()) { base = qgetenv("HOME"); @@ -178,14 +180,17 @@ QString getBaseDataPath() #ifdef Q_OS_WIN32 auto get_appdata_path = []() noexcept -> QString { - WCHAR buffer[MAX_PATH]; - if(SHGetSpecialFolderPathW(nullptr, buffer, CSIDL_APPDATA, FALSE) != FALSE) - return QString::fromWCharArray(buffer); - return QString(); + QString ret; + WCHAR *buffer{}; + if(const HRESULT hr{SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DONT_UNEXPAND, + nullptr, &buffer)}; SUCCEEDED(hr)) + ret = QString::fromWCharArray(buffer); + CoTaskMemFree(buffer); + return ret; }; QString base = get_appdata_path(); #else - QByteArray base = qgetenv("XDG_DATA_HOME"); + QString base = qgetenv("XDG_DATA_HOME"); if(base.isEmpty()) { base = qgetenv("HOME"); @@ -226,24 +231,22 @@ QStringList getAllDataPaths(const QString &append) return list; } -template -QString getValueFromName(const NameValuePair (&list)[N], const QString &str) +QString getValueFromName(const al::span list, const QString &str) { - for(size_t i = 0;i < N-1;i++) + for(size_t i{0};i < list.size();++i) { - if(str == list[i].name) - return list[i].value; + if(str == std::data(list[i].name)) + return std::data(list[i].value); } return QString{}; } -template -QString getNameFromValue(const NameValuePair (&list)[N], const QString &str) +QString getNameFromValue(const al::span list, const QString &str) { - for(size_t i = 0;i < N-1;i++) + for(size_t i{0};i < list.size();++i) { - if(str == list[i].value) - return list[i].name; + if(str == std::data(list[i].value)) + return std::data(list[i].name); } return QString{}; } @@ -262,52 +265,37 @@ QString getCheckValue(const QCheckBox *checkbox) { const Qt::CheckState state{checkbox->checkState()}; if(state == Qt::Checked) - return QString{"true"}; + return QStringLiteral("true"); if(state == Qt::Unchecked) - return QString{"false"}; + return QStringLiteral("false"); return QString{}; } } -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow), - mPeriodSizeValidator(nullptr), - mPeriodCountValidator(nullptr), - mSourceCountValidator(nullptr), - mEffectSlotValidator(nullptr), - mSourceSendValidator(nullptr), - mSampleRateValidator(nullptr), - mJackBufferValidator(nullptr), - mNeedsSave(false) +MainWindow::MainWindow(QWidget *parent) : QMainWindow{parent} + , ui{std::make_unique()} { ui->setupUi(this); - for(int i = 0;speakerModeList[i].name[0];i++) - ui->channelConfigCombo->addItem(speakerModeList[i].name); + for(auto &item : speakerModeList) + ui->channelConfigCombo->addItem(std::data(item.name)); ui->channelConfigCombo->adjustSize(); - for(int i = 0;sampleTypeList[i].name[0];i++) - ui->sampleFormatCombo->addItem(sampleTypeList[i].name); + for(auto &item : sampleTypeList) + ui->sampleFormatCombo->addItem(std::data(item.name)); ui->sampleFormatCombo->adjustSize(); - for(int i = 0;stereoModeList[i].name[0];i++) - ui->stereoModeCombo->addItem(stereoModeList[i].name); + for(auto &item : stereoModeList) + ui->stereoModeCombo->addItem(std::data(item.name)); ui->stereoModeCombo->adjustSize(); - for(int i = 0;stereoEncList[i].name[0];i++) - ui->stereoEncodingComboBox->addItem(stereoEncList[i].name); + for(auto &item : stereoEncList) + ui->stereoEncodingComboBox->addItem(std::data(item.name)); ui->stereoEncodingComboBox->adjustSize(); - for(int i = 0;ambiFormatList[i].name[0];i++) - ui->ambiFormatComboBox->addItem(ambiFormatList[i].name); + for(auto &item : ambiFormatList) + ui->ambiFormatComboBox->addItem(std::data(item.name)); ui->ambiFormatComboBox->adjustSize(); - int count; - for(count = 0;resamplerList[count].name[0];count++) { - } - ui->resamplerSlider->setRange(0, count-1); - - for(count = 0;hrtfModeList[count].name[0];count++) { - } - ui->hrtfmodeSlider->setRange(0, count-1); + ui->resamplerSlider->setRange(0, resamplerList.size()-1); + ui->hrtfmodeSlider->setRange(0, hrtfModeList.size()-1); #if !defined(HAVE_NEON) && !defined(HAVE_SSE) ui->cpuExtDisabledLabel->move(ui->cpuExtDisabledLabel->x(), ui->cpuExtDisabledLabel->y() - 60); @@ -355,22 +343,22 @@ MainWindow::MainWindow(QWidget *parent) : ui->enableEaxCheck->setVisible(false); #endif - mPeriodSizeValidator = new QIntValidator{64, 8192, this}; - ui->periodSizeEdit->setValidator(mPeriodSizeValidator); - mPeriodCountValidator = new QIntValidator{2, 16, this}; - ui->periodCountEdit->setValidator(mPeriodCountValidator); + mPeriodSizeValidator = std::make_unique(64, 8192, this); + ui->periodSizeEdit->setValidator(mPeriodSizeValidator.get()); + mPeriodCountValidator = std::make_unique(2, 16, this); + ui->periodCountEdit->setValidator(mPeriodCountValidator.get()); - mSourceCountValidator = new QIntValidator{0, 4096, this}; - ui->srcCountLineEdit->setValidator(mSourceCountValidator); - mEffectSlotValidator = new QIntValidator{0, 64, this}; - ui->effectSlotLineEdit->setValidator(mEffectSlotValidator); - mSourceSendValidator = new QIntValidator{0, 16, this}; - ui->srcSendLineEdit->setValidator(mSourceSendValidator); - mSampleRateValidator = new QIntValidator{8000, 192000, this}; - ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator); + mSourceCountValidator = std::make_unique(0, 4096, this); + ui->srcCountLineEdit->setValidator(mSourceCountValidator.get()); + mEffectSlotValidator = std::make_unique(0, 64, this); + ui->effectSlotLineEdit->setValidator(mEffectSlotValidator.get()); + mSourceSendValidator = std::make_unique(0, 16, this); + ui->srcSendLineEdit->setValidator(mSourceSendValidator.get()); + mSampleRateValidator = std::make_unique(8000, 192000, this); + ui->sampleRateCombo->lineEdit()->setValidator(mSampleRateValidator.get()); - mJackBufferValidator = new QIntValidator{0, 8192, this}; - ui->jackBufferSizeLine->setValidator(mJackBufferValidator); + mJackBufferValidator = std::make_unique(0, 8192, this); + ui->jackBufferSizeLine->setValidator(mJackBufferValidator.get()); connect(ui->actionLoad, &QAction::triggered, this, &MainWindow::loadConfigFromFile); connect(ui->actionSave_As, &QAction::triggered, this, &MainWindow::saveConfigAsFile); @@ -495,28 +483,18 @@ MainWindow::MainWindow(QWidget *parent) : for(int i = 1;i < ui->backendListWidget->count();i++) ui->backendListWidget->setRowHidden(i, true); - for(int i = 0;backendList[i].backend_name[0];i++) + for(size_t i{0};i < backendList.size();++i) { QList items = ui->backendListWidget->findItems( - backendList[i].full_string, Qt::MatchFixedString); - foreach(QListWidgetItem *item, items) + std::data(backendList[i].full_string), Qt::MatchFixedString); + Q_FOREACH(QListWidgetItem *item, items) item->setHidden(false); } loadConfig(getDefaultConfigName()); } -MainWindow::~MainWindow() -{ - delete ui; - delete mPeriodSizeValidator; - delete mPeriodCountValidator; - delete mSourceCountValidator; - delete mEffectSlotValidator; - delete mSourceSendValidator; - delete mSampleRateValidator; - delete mJackBufferValidator; -} +MainWindow::~MainWindow() = default; void MainWindow::closeEvent(QCloseEvent *event) { @@ -560,9 +538,9 @@ QStringList MainWindow::collectHrtfs() { QDir dir(ui->hrtfFileList->item(i)->text()); QStringList fnames = dir.entryList(QDir::Files | QDir::Readable, QDir::Name); - foreach(const QString &fname, fnames) + Q_FOREACH(const QString &fname, fnames) { - if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) + if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) @@ -583,21 +561,21 @@ QStringList MainWindow::collectHrtfs() break; } ++i; - } while(1); + } while(true); } } } if(ui->defaultHrtfPathsCheckBox->isChecked()) { - QStringList paths = getAllDataPaths("/openal/hrtf"); - foreach(const QString &name, paths) + QStringList paths = getAllDataPaths(QStringLiteral("/openal/hrtf")); + Q_FOREACH(const QString &name, paths) { QDir dir{name}; QStringList fnames{dir.entryList(QDir::Files | QDir::Readable, QDir::Name)}; - foreach(const QString &fname, fnames) + Q_FOREACH(const QString &fname, fnames) { - if(!fname.endsWith(".mhr", Qt::CaseInsensitive)) + if(!fname.endsWith(QStringLiteral(".mhr"), Qt::CaseInsensitive)) continue; QString fullname{dir.absoluteFilePath(fname)}; if(processed.contains(fullname)) @@ -618,13 +596,13 @@ QStringList MainWindow::collectHrtfs() break; } ++i; - } while(1); + } while(true); } } } #ifdef ALSOFT_EMBED_HRTF_DATA - ret.push_back("Built-In HRTF"); + ret.push_back(QStringLiteral("Built-In HRTF")); #endif } return ret; @@ -642,7 +620,7 @@ void MainWindow::loadConfig(const QString &fname) { QSettings settings{fname, QSettings::IniFormat}; - QString sampletype = settings.value("sample-type").toString(); + QString sampletype{settings.value(QStringLiteral("sample-type")).toString()}; ui->sampleFormatCombo->setCurrentIndex(0); if(sampletype.isEmpty() == false) { @@ -654,12 +632,12 @@ void MainWindow::loadConfig(const QString &fname) } } - QString channelconfig{settings.value("channels").toString()}; + QString channelconfig{settings.value(QStringLiteral("channels")).toString()}; ui->channelConfigCombo->setCurrentIndex(0); if(channelconfig.isEmpty() == false) { - if(channelconfig == "surround51rear") - channelconfig = "surround51"; + if(channelconfig == QStringLiteral("surround51rear")) + channelconfig = QStringLiteral("surround51"); QString str{getNameFromValue(speakerModeList, channelconfig)}; if(!str.isEmpty()) { @@ -668,7 +646,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString srate{settings.value("frequency").toString()}; + QString srate{settings.value(QStringLiteral("frequency")).toString()}; if(srate.isEmpty()) ui->sampleRateCombo->setCurrentIndex(0); else @@ -678,34 +656,35 @@ void MainWindow::loadConfig(const QString &fname) } ui->srcCountLineEdit->clear(); - ui->srcCountLineEdit->insert(settings.value("sources").toString()); + ui->srcCountLineEdit->insert(settings.value(QStringLiteral("sources")).toString()); ui->effectSlotLineEdit->clear(); - ui->effectSlotLineEdit->insert(settings.value("slots").toString()); + ui->effectSlotLineEdit->insert(settings.value(QStringLiteral("slots")).toString()); ui->srcSendLineEdit->clear(); - ui->srcSendLineEdit->insert(settings.value("sends").toString()); + ui->srcSendLineEdit->insert(settings.value(QStringLiteral("sends")).toString()); - QString resampler = settings.value("resampler").toString().trimmed(); + QString resampler = settings.value(QStringLiteral("resampler")).toString().trimmed(); ui->resamplerSlider->setValue(2); - ui->resamplerLabel->setText(resamplerList[2].name); - /* The "sinc4" and "sinc8" resamplers are no longer supported. Use "cubic" - * as a fallback. + ui->resamplerLabel->setText(std::data(resamplerList[2].name)); + /* "Cubic" is an alias for the 4-point gaussian resampler. The "sinc4" and + * "sinc8" resamplers are unsupported, use "gaussian" as a fallback. */ - if(resampler == "sinc4" || resampler == "sinc8") - resampler = "cubic"; + if(resampler == QLatin1String{"cubic"} || resampler == QLatin1String{"sinc4"} + || resampler == QLatin1String{"sinc8"}) + resampler = QStringLiteral("gaussian"); /* The "bsinc" resampler name is an alias for "bsinc12". */ - else if(resampler == "bsinc") - resampler = "bsinc12"; + else if(resampler == QLatin1String{"bsinc"}) + resampler = QStringLiteral("bsinc12"); for(int i = 0;resamplerList[i].name[0];i++) { - if(resampler == resamplerList[i].value) + if(resampler == std::data(resamplerList[i].value)) { ui->resamplerSlider->setValue(i); - ui->resamplerLabel->setText(resamplerList[i].name); + ui->resamplerLabel->setText(std::data(resamplerList[i].name)); break; } } - QString stereomode = settings.value("stereo-mode").toString().trimmed(); + QString stereomode{settings.value(QStringLiteral("stereo-mode")).toString().trimmed()}; ui->stereoModeCombo->setCurrentIndex(0); if(stereomode.isEmpty() == false) { @@ -733,10 +712,10 @@ void MainWindow::loadConfig(const QString &fname) updatePeriodCountSlider(); } - ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value("output-limiter"))); - ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value("dither"))); + ui->outputLimiterCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("output-limiter")))); + ui->outputDitherCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("dither")))); - QString stereopan{settings.value("stereo-encoding").toString()}; + QString stereopan{settings.value(QStringLiteral("stereo-encoding")).toString()}; ui->stereoEncodingComboBox->setCurrentIndex(0); if(stereopan.isEmpty() == false) { @@ -748,7 +727,7 @@ void MainWindow::loadConfig(const QString &fname) } } - QString ambiformat{settings.value("ambi-format").toString()}; + QString ambiformat{settings.value(QStringLiteral("ambi-format")).toString()}; ui->ambiFormatComboBox->setCurrentIndex(0); if(ambiformat.isEmpty() == false) { @@ -760,46 +739,46 @@ void MainWindow::loadConfig(const QString &fname) } } - ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value("decoder/hq-mode"))); - ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value("decoder/distance-comp"))); - ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value("decoder/nfc"))); - double speakerdist{settings.value("decoder/speaker-dist", 1.0).toDouble()}; + ui->decoderHQModeCheckBox->setChecked(getCheckState(settings.value(QStringLiteral("decoder/hq-mode")))); + ui->decoderDistCompCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/distance-comp")))); + ui->decoderNFEffectsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("decoder/nfc")))); + double speakerdist{settings.value(QStringLiteral("decoder/speaker-dist"), 1.0).toDouble()}; ui->decoderSpeakerDistSpinBox->setValue(speakerdist); - ui->decoderQuadLineEdit->setText(settings.value("decoder/quad").toString()); - ui->decoder51LineEdit->setText(settings.value("decoder/surround51").toString()); - ui->decoder61LineEdit->setText(settings.value("decoder/surround61").toString()); - ui->decoder71LineEdit->setText(settings.value("decoder/surround71").toString()); - ui->decoder3D71LineEdit->setText(settings.value("decoder/surround3d71").toString()); + ui->decoderQuadLineEdit->setText(settings.value(QStringLiteral("decoder/quad")).toString()); + ui->decoder51LineEdit->setText(settings.value(QStringLiteral("decoder/surround51")).toString()); + ui->decoder61LineEdit->setText(settings.value(QStringLiteral("decoder/surround61")).toString()); + ui->decoder71LineEdit->setText(settings.value(QStringLiteral("decoder/surround71")).toString()); + ui->decoder3D71LineEdit->setText(settings.value(QStringLiteral("decoder/surround3d71")).toString()); - QStringList disabledCpuExts{settings.value("disable-cpu-exts").toStringList()}; + QStringList disabledCpuExts{settings.value(QStringLiteral("disable-cpu-exts")).toStringList()}; if(disabledCpuExts.size() == 1) disabledCpuExts = disabledCpuExts[0].split(QChar(',')); for(QString &name : disabledCpuExts) name = name.trimmed(); - ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains("sse", Qt::CaseInsensitive)); - ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains("sse2", Qt::CaseInsensitive)); - ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains("sse3", Qt::CaseInsensitive)); - ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains("sse4.1", Qt::CaseInsensitive)); - ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains("neon", Qt::CaseInsensitive)); + ui->enableSSECheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse"), Qt::CaseInsensitive)); + ui->enableSSE2CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse2"), Qt::CaseInsensitive)); + ui->enableSSE3CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse3"), Qt::CaseInsensitive)); + ui->enableSSE41CheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("sse4.1"), Qt::CaseInsensitive)); + ui->enableNeonCheckBox->setChecked(!disabledCpuExts.contains(QStringLiteral("neon"), Qt::CaseInsensitive)); - QString hrtfmode{settings.value("hrtf-mode").toString().trimmed()}; + QString hrtfmode{settings.value(QStringLiteral("hrtf-mode")).toString().trimmed()}; ui->hrtfmodeSlider->setValue(2); - ui->hrtfmodeLabel->setText(hrtfModeList[3].name); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[3].name)); /* The "basic" mode name is no longer supported. Use "ambi2" instead. */ - if(hrtfmode == "basic") - hrtfmode = "ambi2"; - for(int i = 0;hrtfModeList[i].name[0];i++) + if(hrtfmode == QLatin1String{"basic"}) + hrtfmode = QStringLiteral("ambi2"); + for(size_t i{0};i < hrtfModeList.size();++i) { - if(hrtfmode == hrtfModeList[i].value) + if(hrtfmode == std::data(hrtfModeList[i].value)) { - ui->hrtfmodeSlider->setValue(i); - ui->hrtfmodeLabel->setText(hrtfModeList[i].name); + ui->hrtfmodeSlider->setValue(static_cast(i)); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[i].name)); break; } } - QStringList hrtf_paths{settings.value("hrtf-paths").toStringList()}; + QStringList hrtf_paths{settings.value(QStringLiteral("hrtf-paths")).toStringList()}; if(hrtf_paths.size() == 1) hrtf_paths = hrtf_paths[0].split(QChar(',')); for(QString &name : hrtf_paths) @@ -817,15 +796,15 @@ void MainWindow::loadConfig(const QString &fname) updateHrtfRemoveButton(); ui->preferredHrtfComboBox->clear(); - ui->preferredHrtfComboBox->addItem("- Any -"); + ui->preferredHrtfComboBox->addItem(QStringLiteral("- Any -")); if(ui->defaultHrtfPathsCheckBox->isChecked()) { QStringList hrtfs{collectHrtfs()}; - foreach(const QString &name, hrtfs) + Q_FOREACH(const QString &name, hrtfs) ui->preferredHrtfComboBox->addItem(name); } - QString defaulthrtf{settings.value("default-hrtf").toString()}; + QString defaulthrtf{settings.value(QStringLiteral("default-hrtf")).toString()}; ui->preferredHrtfComboBox->setCurrentIndex(0); if(defaulthrtf.isEmpty() == false) { @@ -843,7 +822,7 @@ void MainWindow::loadConfig(const QString &fname) ui->enabledBackendList->clear(); ui->disabledBackendList->clear(); - QStringList drivers{settings.value("drivers").toStringList()}; + QStringList drivers{settings.value(QStringLiteral("drivers")).toStringList()}; if(drivers.empty()) ui->backendCheckBox->setChecked(true); else @@ -856,35 +835,37 @@ void MainWindow::loadConfig(const QString &fname) /* Convert "mmdevapi" references to "wasapi" for backwards * compatibility. */ - if(name == "-mmdevapi") - name = "-wasapi"; - else if(name == "mmdevapi") - name = "wasapi"; + if(name == QLatin1String{"-mmdevapi"}) + name = QStringLiteral("-wasapi"); + else if(name == QLatin1String{"mmdevapi"}) + name = QStringLiteral("wasapi"); } - bool lastWasEmpty = false; - foreach(const QString &backend, drivers) + bool lastWasEmpty{false}; + Q_FOREACH(const QString &backend, drivers) { lastWasEmpty = backend.isEmpty(); if(lastWasEmpty) continue; if(!backend.startsWith(QChar('-'))) - for(int j = 0;backendList[j].backend_name[0];j++) + { + for(size_t j{0};j < backendList.size();++j) { - if(backend == backendList[j].backend_name) + if(backend == std::data(backendList[j].backend_name)) { - ui->enabledBackendList->addItem(backendList[j].full_string); + ui->enabledBackendList->addItem(std::data(backendList[j].full_string)); break; } } + } else if(backend.size() > 1) { QStringRef backendref{backend.rightRef(backend.size()-1)}; - for(int j = 0;backendList[j].backend_name[0];j++) + for(size_t j{0};j < backendList.size();++j) { - if(backendref == backendList[j].backend_name) + if(backendref == std::data(backendList[j].backend_name)) { - ui->disabledBackendList->addItem(backendList[j].full_string); + ui->disabledBackendList->addItem(std::data(backendList[j].full_string)); break; } } @@ -893,7 +874,7 @@ void MainWindow::loadConfig(const QString &fname) ui->backendCheckBox->setChecked(lastWasEmpty); } - QString defaultreverb{settings.value("default-reverb").toString().toLower()}; + QString defaultreverb{settings.value(QStringLiteral("default-reverb")).toString().toLower()}; ui->defaultReverbComboBox->setCurrentIndex(0); if(defaultreverb.isEmpty() == false) { @@ -907,56 +888,56 @@ void MainWindow::loadConfig(const QString &fname) } } - QStringList excludefx{settings.value("excludefx").toStringList()}; + QStringList excludefx{settings.value(QStringLiteral("excludefx")).toStringList()}; if(excludefx.size() == 1) excludefx = excludefx[0].split(QChar(',')); for(QString &name : excludefx) name = name.trimmed(); - ui->enableEaxReverbCheck->setChecked(!excludefx.contains("eaxreverb", Qt::CaseInsensitive)); - ui->enableStdReverbCheck->setChecked(!excludefx.contains("reverb", Qt::CaseInsensitive)); - ui->enableAutowahCheck->setChecked(!excludefx.contains("autowah", Qt::CaseInsensitive)); - ui->enableChorusCheck->setChecked(!excludefx.contains("chorus", Qt::CaseInsensitive)); - ui->enableCompressorCheck->setChecked(!excludefx.contains("compressor", Qt::CaseInsensitive)); - ui->enableDistortionCheck->setChecked(!excludefx.contains("distortion", Qt::CaseInsensitive)); - ui->enableEchoCheck->setChecked(!excludefx.contains("echo", Qt::CaseInsensitive)); - ui->enableEqualizerCheck->setChecked(!excludefx.contains("equalizer", Qt::CaseInsensitive)); - ui->enableFlangerCheck->setChecked(!excludefx.contains("flanger", Qt::CaseInsensitive)); - ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains("fshifter", Qt::CaseInsensitive)); - ui->enableModulatorCheck->setChecked(!excludefx.contains("modulator", Qt::CaseInsensitive)); - ui->enableDedicatedCheck->setChecked(!excludefx.contains("dedicated", Qt::CaseInsensitive)); - ui->enablePitchShifterCheck->setChecked(!excludefx.contains("pshifter", Qt::CaseInsensitive)); - ui->enableVocalMorpherCheck->setChecked(!excludefx.contains("vmorpher", Qt::CaseInsensitive)); + ui->enableEaxReverbCheck->setChecked(!excludefx.contains(QStringLiteral("eaxreverb"), Qt::CaseInsensitive)); + ui->enableStdReverbCheck->setChecked(!excludefx.contains(QStringLiteral("reverb"), Qt::CaseInsensitive)); + ui->enableAutowahCheck->setChecked(!excludefx.contains(QStringLiteral("autowah"), Qt::CaseInsensitive)); + ui->enableChorusCheck->setChecked(!excludefx.contains(QStringLiteral("chorus"), Qt::CaseInsensitive)); + ui->enableCompressorCheck->setChecked(!excludefx.contains(QStringLiteral("compressor"), Qt::CaseInsensitive)); + ui->enableDistortionCheck->setChecked(!excludefx.contains(QStringLiteral("distortion"), Qt::CaseInsensitive)); + ui->enableEchoCheck->setChecked(!excludefx.contains(QStringLiteral("echo"), Qt::CaseInsensitive)); + ui->enableEqualizerCheck->setChecked(!excludefx.contains(QStringLiteral("equalizer"), Qt::CaseInsensitive)); + ui->enableFlangerCheck->setChecked(!excludefx.contains(QStringLiteral("flanger"), Qt::CaseInsensitive)); + ui->enableFrequencyShifterCheck->setChecked(!excludefx.contains(QStringLiteral("fshifter"), Qt::CaseInsensitive)); + ui->enableModulatorCheck->setChecked(!excludefx.contains(QStringLiteral("modulator"), Qt::CaseInsensitive)); + ui->enableDedicatedCheck->setChecked(!excludefx.contains(QStringLiteral("dedicated"), Qt::CaseInsensitive)); + ui->enablePitchShifterCheck->setChecked(!excludefx.contains(QStringLiteral("pshifter"), Qt::CaseInsensitive)); + ui->enableVocalMorpherCheck->setChecked(!excludefx.contains(QStringLiteral("vmorpher"), Qt::CaseInsensitive)); if(ui->enableEaxCheck->isEnabled()) - ui->enableEaxCheck->setChecked(getCheckState(settings.value("eax/enable")) != Qt::Unchecked); + ui->enableEaxCheck->setChecked(getCheckState(settings.value(QStringLiteral("eax/enable"))) != Qt::Unchecked); - ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value("pulse/spawn-server"))); - ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value("pulse/allow-moves"))); - ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value("pulse/fix-rate"))); - ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value("pulse/adjust-latency"))); + ui->pulseAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/spawn-server")))); + ui->pulseAllowMovesCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/allow-moves")))); + ui->pulseFixRateCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/fix-rate")))); + ui->pulseAdjLatencyCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pulse/adjust-latency")))); - ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value("pipewire/assume-audio"))); - ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value("pipewire/rt-mix"))); + ui->pwireAssumeAudioCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/assume-audio")))); + ui->pwireRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("pipewire/rt-mix")))); - ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value("wasapi/allow-resampler"))); + ui->wasapiResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("wasapi/allow-resampler")))); - ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value("jack/spawn-server"))); - ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value("jack/connect-ports"))); - ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value("jack/rt-mix"))); - ui->jackBufferSizeLine->setText(settings.value("jack/buffer-size", QString()).toString()); + ui->jackAutospawnCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/spawn-server")))); + ui->jackConnectPortsCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/connect-ports")))); + ui->jackRtMixCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("jack/rt-mix")))); + ui->jackBufferSizeLine->setText(settings.value(QStringLiteral("jack/buffer-size"), QString()).toString()); updateJackBufferSizeSlider(); - ui->alsaDefaultDeviceLine->setText(settings.value("alsa/device", QString()).toString()); - ui->alsaDefaultCaptureLine->setText(settings.value("alsa/capture", QString()).toString()); - ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value("alsa/allow-resampler"))); - ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value("alsa/mmap"))); + ui->alsaDefaultDeviceLine->setText(settings.value(QStringLiteral("alsa/device"), QString()).toString()); + ui->alsaDefaultCaptureLine->setText(settings.value(QStringLiteral("alsa/capture"), QString()).toString()); + ui->alsaResamplerCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/allow-resampler")))); + ui->alsaMmapCheckBox->setCheckState(getCheckState(settings.value(QStringLiteral("alsa/mmap")))); - ui->ossDefaultDeviceLine->setText(settings.value("oss/device", QString()).toString()); - ui->ossDefaultCaptureLine->setText(settings.value("oss/capture", QString()).toString()); + ui->ossDefaultDeviceLine->setText(settings.value(QStringLiteral("oss/device"), QString()).toString()); + ui->ossDefaultCaptureLine->setText(settings.value(QStringLiteral("oss/capture"), QString()).toString()); - ui->solarisDefaultDeviceLine->setText(settings.value("solaris/device", QString()).toString()); + ui->solarisDefaultDeviceLine->setText(settings.value(QStringLiteral("solaris/device"), QString()).toString()); - ui->waveOutputLine->setText(settings.value("wave/file", QString()).toString()); - ui->waveBFormatCheckBox->setChecked(settings.value("wave/bformat", false).toBool()); + ui->waveOutputLine->setText(settings.value(QStringLiteral("wave/file"), QString()).toString()); + ui->waveBFormatCheckBox->setChecked(settings.value(QStringLiteral("wave/bformat"), false).toBool()); ui->applyButton->setEnabled(false); ui->closeCancelButton->setText(tr("Close")); @@ -990,72 +971,72 @@ void MainWindow::saveConfig(const QString &fname) const /* HACK: Compound any stringlist values into a comma-separated string. */ QStringList allkeys{settings.allKeys()}; - foreach(const QString &key, allkeys) + Q_FOREACH(const QString &key, allkeys) { QStringList vals{settings.value(key).toStringList()}; if(vals.size() > 1) settings.setValue(key, vals.join(QChar(','))); } - settings.setValue("sample-type", getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); - settings.setValue("channels", getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); + settings.setValue(QStringLiteral("sample-type"), getValueFromName(sampleTypeList, ui->sampleFormatCombo->currentText())); + settings.setValue(QStringLiteral("channels"), getValueFromName(speakerModeList, ui->channelConfigCombo->currentText())); uint rate{ui->sampleRateCombo->currentText().toUInt()}; if(rate <= 0) - settings.setValue("frequency", QString{}); + settings.setValue(QStringLiteral("frequency"), QString{}); else - settings.setValue("frequency", rate); + settings.setValue(QStringLiteral("frequency"), rate); - settings.setValue("period_size", ui->periodSizeEdit->text()); - settings.setValue("periods", ui->periodCountEdit->text()); + settings.setValue(QStringLiteral("period_size"), ui->periodSizeEdit->text()); + settings.setValue(QStringLiteral("periods"), ui->periodCountEdit->text()); - settings.setValue("sources", ui->srcCountLineEdit->text()); - settings.setValue("slots", ui->effectSlotLineEdit->text()); + settings.setValue(QStringLiteral("sources"), ui->srcCountLineEdit->text()); + settings.setValue(QStringLiteral("slots"), ui->effectSlotLineEdit->text()); - settings.setValue("resampler", resamplerList[ui->resamplerSlider->value()].value); + settings.setValue(QStringLiteral("resampler"), std::data(resamplerList[ui->resamplerSlider->value()].value)); - settings.setValue("stereo-mode", getValueFromName(stereoModeList, ui->stereoModeCombo->currentText())); - settings.setValue("stereo-encoding", getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText())); - settings.setValue("ambi-format", getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText())); + settings.setValue(QStringLiteral("stereo-mode"), getValueFromName(stereoModeList, ui->stereoModeCombo->currentText())); + settings.setValue(QStringLiteral("stereo-encoding"), getValueFromName(stereoEncList, ui->stereoEncodingComboBox->currentText())); + settings.setValue(QStringLiteral("ambi-format"), getValueFromName(ambiFormatList, ui->ambiFormatComboBox->currentText())); - settings.setValue("output-limiter", getCheckValue(ui->outputLimiterCheckBox)); - settings.setValue("dither", getCheckValue(ui->outputDitherCheckBox)); + settings.setValue(QStringLiteral("output-limiter"), getCheckValue(ui->outputLimiterCheckBox)); + settings.setValue(QStringLiteral("dither"), getCheckValue(ui->outputDitherCheckBox)); - settings.setValue("decoder/hq-mode", getCheckValue(ui->decoderHQModeCheckBox)); - settings.setValue("decoder/distance-comp", getCheckValue(ui->decoderDistCompCheckBox)); - settings.setValue("decoder/nfc", getCheckValue(ui->decoderNFEffectsCheckBox)); + settings.setValue(QStringLiteral("decoder/hq-mode"), getCheckValue(ui->decoderHQModeCheckBox)); + settings.setValue(QStringLiteral("decoder/distance-comp"), getCheckValue(ui->decoderDistCompCheckBox)); + settings.setValue(QStringLiteral("decoder/nfc"), getCheckValue(ui->decoderNFEffectsCheckBox)); double speakerdist{ui->decoderSpeakerDistSpinBox->value()}; - settings.setValue("decoder/speaker-dist", + settings.setValue(QStringLiteral("decoder/speaker-dist"), (speakerdist != 1.0) ? QString::number(speakerdist) : QString{} ); - settings.setValue("decoder/quad", ui->decoderQuadLineEdit->text()); - settings.setValue("decoder/surround51", ui->decoder51LineEdit->text()); - settings.setValue("decoder/surround61", ui->decoder61LineEdit->text()); - settings.setValue("decoder/surround71", ui->decoder71LineEdit->text()); - settings.setValue("decoder/surround3d71", ui->decoder3D71LineEdit->text()); + settings.setValue(QStringLiteral("decoder/quad"), ui->decoderQuadLineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround51"), ui->decoder51LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround61"), ui->decoder61LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround71"), ui->decoder71LineEdit->text()); + settings.setValue(QStringLiteral("decoder/surround3d71"), ui->decoder3D71LineEdit->text()); QStringList strlist; if(!ui->enableSSECheckBox->isChecked()) - strlist.append("sse"); + strlist.append(QStringLiteral("sse")); if(!ui->enableSSE2CheckBox->isChecked()) - strlist.append("sse2"); + strlist.append(QStringLiteral("sse2")); if(!ui->enableSSE3CheckBox->isChecked()) - strlist.append("sse3"); + strlist.append(QStringLiteral("sse3")); if(!ui->enableSSE41CheckBox->isChecked()) - strlist.append("sse4.1"); + strlist.append(QStringLiteral("sse4.1")); if(!ui->enableNeonCheckBox->isChecked()) - strlist.append("neon"); - settings.setValue("disable-cpu-exts", strlist.join(QChar(','))); + strlist.append(QStringLiteral("neon")); + settings.setValue(QStringLiteral("disable-cpu-exts"), strlist.join(QChar(','))); - settings.setValue("hrtf-mode", hrtfModeList[ui->hrtfmodeSlider->value()].value); + settings.setValue(QStringLiteral("hrtf-mode"), std::data(hrtfModeList[ui->hrtfmodeSlider->value()].value)); if(ui->preferredHrtfComboBox->currentIndex() == 0) - settings.setValue("default-hrtf", QString{}); + settings.setValue(QStringLiteral("default-hrtf"), QString{}); else { QString str{ui->preferredHrtfComboBox->currentText()}; - settings.setValue("default-hrtf", str); + settings.setValue(QStringLiteral("default-hrtf"), str); } strlist.clear(); @@ -1064,17 +1045,17 @@ void MainWindow::saveConfig(const QString &fname) const strlist.append(ui->hrtfFileList->item(i)->text()); if(!strlist.empty() && ui->defaultHrtfPathsCheckBox->isChecked()) strlist.append(QString{}); - settings.setValue("hrtf-paths", strlist.join(QChar{','})); + settings.setValue(QStringLiteral("hrtf-paths"), strlist.join(QChar{','})); strlist.clear(); for(int i = 0;i < ui->enabledBackendList->count();i++) { QString label{ui->enabledBackendList->item(i)->text()}; - for(int j = 0;backendList[j].backend_name[0];j++) + for(size_t j{0};j < backendList.size();++j) { - if(label == backendList[j].full_string) + if(label == std::data(backendList[j].full_string)) { - strlist.append(backendList[j].backend_name); + strlist.append(std::data(backendList[j].backend_name)); break; } } @@ -1082,102 +1063,102 @@ void MainWindow::saveConfig(const QString &fname) const for(int i = 0;i < ui->disabledBackendList->count();i++) { QString label{ui->disabledBackendList->item(i)->text()}; - for(int j = 0;backendList[j].backend_name[0];j++) + for(size_t j{0};j < backendList.size();++j) { - if(label == backendList[j].full_string) + if(label == std::data(backendList[j].full_string)) { - strlist.append(QChar{'-'}+QString{backendList[j].backend_name}); + strlist.append(QChar{'-'}+QString{std::data(backendList[j].backend_name)}); break; } } } if(strlist.empty() && !ui->backendCheckBox->isChecked()) - strlist.append("-all"); + strlist.append(QStringLiteral("-all")); else if(ui->backendCheckBox->isChecked()) strlist.append(QString{}); - settings.setValue("drivers", strlist.join(QChar(','))); + settings.setValue(QStringLiteral("drivers"), strlist.join(QChar(','))); // TODO: Remove check when we can properly match global values. if(ui->defaultReverbComboBox->currentIndex() == 0) - settings.setValue("default-reverb", QString{}); + settings.setValue(QStringLiteral("default-reverb"), QString{}); else { QString str{ui->defaultReverbComboBox->currentText().toLower()}; - settings.setValue("default-reverb", str); + settings.setValue(QStringLiteral("default-reverb"), str); } strlist.clear(); if(!ui->enableEaxReverbCheck->isChecked()) - strlist.append("eaxreverb"); + strlist.append(QStringLiteral("eaxreverb")); if(!ui->enableStdReverbCheck->isChecked()) - strlist.append("reverb"); + strlist.append(QStringLiteral("reverb")); if(!ui->enableAutowahCheck->isChecked()) - strlist.append("autowah"); + strlist.append(QStringLiteral("autowah")); if(!ui->enableChorusCheck->isChecked()) - strlist.append("chorus"); + strlist.append(QStringLiteral("chorus")); if(!ui->enableDistortionCheck->isChecked()) - strlist.append("distortion"); + strlist.append(QStringLiteral("distortion")); if(!ui->enableCompressorCheck->isChecked()) - strlist.append("compressor"); + strlist.append(QStringLiteral("compressor")); if(!ui->enableEchoCheck->isChecked()) - strlist.append("echo"); + strlist.append(QStringLiteral("echo")); if(!ui->enableEqualizerCheck->isChecked()) - strlist.append("equalizer"); + strlist.append(QStringLiteral("equalizer")); if(!ui->enableFlangerCheck->isChecked()) - strlist.append("flanger"); + strlist.append(QStringLiteral("flanger")); if(!ui->enableFrequencyShifterCheck->isChecked()) - strlist.append("fshifter"); + strlist.append(QStringLiteral("fshifter")); if(!ui->enableModulatorCheck->isChecked()) - strlist.append("modulator"); + strlist.append(QStringLiteral("modulator")); if(!ui->enableDedicatedCheck->isChecked()) - strlist.append("dedicated"); + strlist.append(QStringLiteral("dedicated")); if(!ui->enablePitchShifterCheck->isChecked()) - strlist.append("pshifter"); + strlist.append(QStringLiteral("pshifter")); if(!ui->enableVocalMorpherCheck->isChecked()) - strlist.append("vmorpher"); - settings.setValue("excludefx", strlist.join(QChar{','})); - settings.setValue("eax/enable", + strlist.append(QStringLiteral("vmorpher")); + settings.setValue(QStringLiteral("excludefx"), strlist.join(QChar{','})); + settings.setValue(QStringLiteral("eax/enable"), (!ui->enableEaxCheck->isEnabled() || ui->enableEaxCheck->isChecked()) - ? QString{/*"true"*/} : QString{"false"}); + ? QString{/*"true"*/} : QStringLiteral("false")); - settings.setValue("pipewire/assume-audio", getCheckValue(ui->pwireAssumeAudioCheckBox)); - settings.setValue("pipewire/rt-mix", getCheckValue(ui->pwireRtMixCheckBox)); + settings.setValue(QStringLiteral("pipewire/assume-audio"), getCheckValue(ui->pwireAssumeAudioCheckBox)); + settings.setValue(QStringLiteral("pipewire/rt-mix"), getCheckValue(ui->pwireRtMixCheckBox)); - settings.setValue("wasapi/allow-resampler", getCheckValue(ui->wasapiResamplerCheckBox)); + settings.setValue(QStringLiteral("wasapi/allow-resampler"), getCheckValue(ui->wasapiResamplerCheckBox)); - settings.setValue("pulse/spawn-server", getCheckValue(ui->pulseAutospawnCheckBox)); - settings.setValue("pulse/allow-moves", getCheckValue(ui->pulseAllowMovesCheckBox)); - settings.setValue("pulse/fix-rate", getCheckValue(ui->pulseFixRateCheckBox)); - settings.setValue("pulse/adjust-latency", getCheckValue(ui->pulseAdjLatencyCheckBox)); + settings.setValue(QStringLiteral("pulse/spawn-server"), getCheckValue(ui->pulseAutospawnCheckBox)); + settings.setValue(QStringLiteral("pulse/allow-moves"), getCheckValue(ui->pulseAllowMovesCheckBox)); + settings.setValue(QStringLiteral("pulse/fix-rate"), getCheckValue(ui->pulseFixRateCheckBox)); + settings.setValue(QStringLiteral("pulse/adjust-latency"), getCheckValue(ui->pulseAdjLatencyCheckBox)); - settings.setValue("jack/spawn-server", getCheckValue(ui->jackAutospawnCheckBox)); - settings.setValue("jack/connect-ports", getCheckValue(ui->jackConnectPortsCheckBox)); - settings.setValue("jack/rt-mix", getCheckValue(ui->jackRtMixCheckBox)); - settings.setValue("jack/buffer-size", ui->jackBufferSizeLine->text()); + settings.setValue(QStringLiteral("jack/spawn-server"), getCheckValue(ui->jackAutospawnCheckBox)); + settings.setValue(QStringLiteral("jack/connect-ports"), getCheckValue(ui->jackConnectPortsCheckBox)); + settings.setValue(QStringLiteral("jack/rt-mix"), getCheckValue(ui->jackRtMixCheckBox)); + settings.setValue(QStringLiteral("jack/buffer-size"), ui->jackBufferSizeLine->text()); - settings.setValue("alsa/device", ui->alsaDefaultDeviceLine->text()); - settings.setValue("alsa/capture", ui->alsaDefaultCaptureLine->text()); - settings.setValue("alsa/allow-resampler", getCheckValue(ui->alsaResamplerCheckBox)); - settings.setValue("alsa/mmap", getCheckValue(ui->alsaMmapCheckBox)); + settings.setValue(QStringLiteral("alsa/device"), ui->alsaDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("alsa/capture"), ui->alsaDefaultCaptureLine->text()); + settings.setValue(QStringLiteral("alsa/allow-resampler"), getCheckValue(ui->alsaResamplerCheckBox)); + settings.setValue(QStringLiteral("alsa/mmap"), getCheckValue(ui->alsaMmapCheckBox)); - settings.setValue("oss/device", ui->ossDefaultDeviceLine->text()); - settings.setValue("oss/capture", ui->ossDefaultCaptureLine->text()); + settings.setValue(QStringLiteral("oss/device"), ui->ossDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("oss/capture"), ui->ossDefaultCaptureLine->text()); - settings.setValue("solaris/device", ui->solarisDefaultDeviceLine->text()); + settings.setValue(QStringLiteral("solaris/device"), ui->solarisDefaultDeviceLine->text()); - settings.setValue("wave/file", ui->waveOutputLine->text()); - settings.setValue("wave/bformat", - ui->waveBFormatCheckBox->isChecked() ? QString{"true"} : QString{/*"false"*/} + settings.setValue(QStringLiteral("wave/file"), ui->waveOutputLine->text()); + settings.setValue(QStringLiteral("wave/bformat"), + ui->waveBFormatCheckBox->isChecked() ? QStringLiteral("true") : QString{/*"false"*/} ); /* Remove empty keys * FIXME: Should only remove keys whose value matches the globally-specified value. */ allkeys = settings.allKeys(); - foreach(const QString &key, allkeys) + Q_FOREACH(const QString &key, allkeys) { QString str{settings.value(key).toString()}; - if(str == QString{}) + if(str.isEmpty()) settings.remove(key); } } @@ -1194,7 +1175,7 @@ void MainWindow::enableApplyButton() void MainWindow::updateResamplerLabel(int num) { - ui->resamplerLabel->setText(resamplerList[num].name); + ui->resamplerLabel->setText(std::data(resamplerList[num].name)); enableApplyButton(); } @@ -1294,7 +1275,7 @@ void MainWindow::updateJackBufferSizeSlider() void MainWindow::updateHrtfModeLabel(int num) { - ui->hrtfmodeLabel->setText(hrtfModeList[num].name); + ui->hrtfmodeLabel->setText(std::data(hrtfModeList[static_cast(num)].name)); enableApplyButton(); } @@ -1302,7 +1283,7 @@ void MainWindow::updateHrtfModeLabel(int num) void MainWindow::addHrtfFile() { QString path{QFileDialog::getExistingDirectory(this, tr("Select HRTF Path"))}; - if(path.isEmpty() == false && !getAllDataPaths("/openal/hrtf").contains(path)) + if(path.isEmpty() == false && !getAllDataPaths(QStringLiteral("/openal/hrtf")).contains(path)) { ui->hrtfFileList->addItem(path); enableApplyButton(); @@ -1311,11 +1292,10 @@ void MainWindow::addHrtfFile() void MainWindow::removeHrtfFile() { - QList selected{ui->hrtfFileList->selectedItems()}; + QList> selected{ui->hrtfFileList->selectedItems()}; if(!selected.isEmpty()) { - foreach(QListWidgetItem *item, selected) - delete item; + std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } } @@ -1336,9 +1316,9 @@ void MainWindow::showEnabledBackendMenu(QPoint pt) if(ui->enabledBackendList->selectedItems().empty()) removeAction->setEnabled(false); ctxmenu.addSeparator(); - for(size_t i = 0;backendList[i].backend_name[0];i++) + for(size_t i{0};i < backendList.size();++i) { - QString backend{backendList[i].full_string}; + QString backend{std::data(backendList[i].full_string)}; QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(!ui->enabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || @@ -1349,9 +1329,8 @@ void MainWindow::showEnabledBackendMenu(QPoint pt) QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { - QList selected{ui->enabledBackendList->selectedItems()}; - foreach(QListWidgetItem *item, selected) - delete item; + QList> selected{ui->enabledBackendList->selectedItems()}; + std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } else if(gotAction != nullptr) @@ -1374,9 +1353,9 @@ void MainWindow::showDisabledBackendMenu(QPoint pt) if(ui->disabledBackendList->selectedItems().empty()) removeAction->setEnabled(false); ctxmenu.addSeparator(); - for(size_t i = 0;backendList[i].backend_name[0];i++) + for(size_t i{0};i < backendList.size();++i) { - QString backend{backendList[i].full_string}; + QString backend{std::data(backendList[i].full_string)}; QAction *action{ctxmenu.addAction(QString("Add ")+backend)}; actionMap[action] = backend; if(!ui->disabledBackendList->findItems(backend, Qt::MatchFixedString).empty() || @@ -1387,9 +1366,8 @@ void MainWindow::showDisabledBackendMenu(QPoint pt) QAction *gotAction{ctxmenu.exec(pt)}; if(gotAction == removeAction) { - QList selected{ui->disabledBackendList->selectedItems()}; - foreach(QListWidgetItem *item, selected) - delete item; + QList> selected{ui->disabledBackendList->selectedItems()}; + std::for_each(selected.begin(), selected.end(), std::default_delete{}); enableApplyButton(); } else if(gotAction != nullptr) diff --git a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h index f7af8eace..9596793eb 100644 --- a/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h +++ b/Engine/lib/openal-soft/utils/alsoft-config/mainwindow.h @@ -1,6 +1,8 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H +#include + #include #include @@ -8,15 +10,10 @@ namespace Ui { class MainWindow; } -class MainWindow : public QMainWindow -{ +class MainWindow : public QMainWindow { Q_OBJECT -public: - explicit MainWindow(QWidget *parent = 0); - ~MainWindow(); - -private slots: +private Q_SLOTS: void cancelCloseAction(); void saveCurrentConfig(); @@ -32,7 +29,7 @@ private slots: void updatePeriodSizeEdit(int size); void updatePeriodSizeSlider(); - void updatePeriodCountEdit(int size); + void updatePeriodCountEdit(int count); void updatePeriodCountSlider(); void selectQuadDecoderFile(); @@ -60,22 +57,26 @@ private slots: void selectWaveOutput(); +public: + explicit MainWindow(QWidget *parent=nullptr); + ~MainWindow() override; + private: - Ui::MainWindow *ui; + std::unique_ptr mPeriodSizeValidator; + std::unique_ptr mPeriodCountValidator; + std::unique_ptr mSourceCountValidator; + std::unique_ptr mEffectSlotValidator; + std::unique_ptr mSourceSendValidator; + std::unique_ptr mSampleRateValidator; + std::unique_ptr mJackBufferValidator; - QValidator *mPeriodSizeValidator; - QValidator *mPeriodCountValidator; - QValidator *mSourceCountValidator; - QValidator *mEffectSlotValidator; - QValidator *mSourceSendValidator; - QValidator *mSampleRateValidator; - QValidator *mJackBufferValidator; + std::unique_ptr ui; - bool mNeedsSave; + bool mNeedsSave{}; - void closeEvent(QCloseEvent *event); + void closeEvent(QCloseEvent *event) override; - void selectDecoderFile(QLineEdit *line, const char *name); + void selectDecoderFile(QLineEdit *line, const char *caption); QStringList collectHrtfs(); diff --git a/Engine/lib/openal-soft/utils/getopt.c b/Engine/lib/openal-soft/utils/getopt.c deleted file mode 100644 index ab1a246e3..000000000 --- a/Engine/lib/openal-soft/utils/getopt.c +++ /dev/null @@ -1,137 +0,0 @@ -/* $NetBSD: getopt.c,v 1.26 2003/08/07 16:43:40 agc Exp $ */ - -/* - * Copyright (c) 1987, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 4. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#if defined(LIBC_SCCS) && !defined(lint) -static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; -#endif /* LIBC_SCCS and not lint */ -#include -#include -#include -#include "getopt.h" - -int opterr = 1, /* if error message should be printed */ - optind = 1, /* index into parent argv vector */ - optopt, /* character checked for validity */ - optreset; /* reset getopt */ -char *optarg; /* argument associated with option */ - -#define BADCH (int)'?' -#define BADARG (int)':' -#define EMSG "" - -/* - * Get program name in Windows - */ -const char * _getprogname(void); - -/* - * getopt -- - * Parse argc/argv argument vector. - */ -int -getopt(int nargc, char * const nargv[], const char *ostr) -{ - static char *place = EMSG; /* option letter processing */ - char *oli; /* option letter list index */ - - if (optreset || *place == 0) { /* update scanning pointer */ - optreset = 0; - place = nargv[optind]; - if (optind >= nargc || *place++ != '-') { - /* Argument is absent or is not an option */ - place = EMSG; - return (-1); - } - optopt = *place++; - if (optopt == '-' && *place == 0) { - /* "--" => end of options */ - ++optind; - place = EMSG; - return (-1); - } - if (optopt == 0) { - /* Solitary '-', treat as a '-' option - if the program (eg su) is looking for it. */ - place = EMSG; - if (strchr(ostr, '-') == NULL) - return (-1); - optopt = '-'; - } - } else - optopt = *place++; - - /* See if option letter is one the caller wanted... */ - if (optopt == ':' || (oli = strchr(ostr, optopt)) == NULL) { - if (*place == 0) - ++optind; - if (opterr && *ostr != ':') - (void)fprintf(stderr, - "%s: illegal option -- %c\n", _getprogname(), - optopt); - return (BADCH); - } - - /* Does this option need an argument? */ - if (oli[1] != ':') { - /* don't need argument */ - optarg = NULL; - if (*place == 0) - ++optind; - } else { - /* Option-argument is either the rest of this argument or the - entire next argument. */ - if (*place) - optarg = place; - else if (nargc > ++optind) - optarg = nargv[optind]; - else { - /* option-argument absent */ - place = EMSG; - if (*ostr == ':') - return (BADARG); - if (opterr) - (void)fprintf(stderr, - "%s: option requires an argument -- %c\n", - _getprogname(), optopt); - return (BADCH); - } - place = EMSG; - ++optind; - } - return (optopt); /* return option letter */ -} - -const char * _getprogname() { - char *pgmptr = NULL; - _get_pgmptr(&pgmptr); - return strrchr(pgmptr,'\\')+1; -} - diff --git a/Engine/lib/openal-soft/utils/getopt.h b/Engine/lib/openal-soft/utils/getopt.h deleted file mode 100644 index 0218c42dd..000000000 --- a/Engine/lib/openal-soft/utils/getopt.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef GETOPT_H -#define GETOPT_H - -#ifndef _WIN32 - -#include - -#else /* _WIN32 */ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -extern char *optarg; -extern int optind, opterr, optopt, optreset; - -int getopt(int nargc, char * const nargv[], const char *ostr); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* !_WIN32 */ - -#endif /* !GETOPT_H */ - diff --git a/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp b/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp index e8092363a..5938cfed8 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loaddef.cpp @@ -30,14 +30,19 @@ #include #include #include +#include +#include #include #include #include -#include +#include +#include +#include #include -#include "alfstream.h" -#include "aloptional.h" +#include "albit.h" +#include "almalloc.h" +#include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "makemhr.h" @@ -45,21 +50,23 @@ #include "mysofa.h" +namespace { + // Constants for accessing the token reader's ring buffer. -#define TR_RING_BITS (16) -#define TR_RING_SIZE (1 << TR_RING_BITS) -#define TR_RING_MASK (TR_RING_SIZE - 1) +constexpr uint TRRingBits{16}; +constexpr uint TRRingSize{1 << TRRingBits}; +constexpr uint TRRingMask{TRRingSize - 1}; // The token reader's load interval in bytes. -#define TR_LOAD_SIZE (TR_RING_SIZE >> 2) +constexpr uint TRLoadSize{TRRingSize >> 2}; // Token reader state for parsing the data set definition. struct TokenReaderT { std::istream &mIStream; - const char *mName{}; + std::string mName{}; uint mLine{}; uint mColumn{}; - char mRing[TR_RING_SIZE]{}; + std::array mRing{}; std::streamsize mIn{}; std::streamsize mOut{}; @@ -70,44 +77,48 @@ struct TokenReaderT { // The maximum identifier length used when processing the data set // definition. -#define MAX_IDENT_LEN (16) +constexpr uint MaxIdentLen{16}; // The limits for the listener's head 'radius' in the data set definition. -#define MIN_RADIUS (0.05) -#define MAX_RADIUS (0.15) +constexpr double MinRadius{0.05}; +constexpr double MaxRadius{0.15}; // The maximum number of channels that can be addressed for a WAVE file // source listed in the data set definition. -#define MAX_WAVE_CHANNELS (65535) +constexpr uint MaxWaveChannels{65535}; // The limits to the byte size for a binary source listed in the definition // file. -#define MIN_BIN_SIZE (2) -#define MAX_BIN_SIZE (4) - -// The minimum number of significant bits for binary sources listed in the -// data set definition. The maximum is calculated from the byte size. -#define MIN_BIN_BITS (16) +enum : uint { + MinBinSize = 2, + MaxBinSize = 4 +}; // The limits to the number of significant bits for an ASCII source listed in // the data set definition. -#define MIN_ASCII_BITS (16) -#define MAX_ASCII_BITS (32) +enum : uint { + MinASCIIBits = 16, + MaxASCIIBits = 32 +}; // The four-character-codes for RIFF/RIFX WAVE file chunks. -#define FOURCC_RIFF (0x46464952) // 'RIFF' -#define FOURCC_RIFX (0x58464952) // 'RIFX' -#define FOURCC_WAVE (0x45564157) // 'WAVE' -#define FOURCC_FMT (0x20746D66) // 'fmt ' -#define FOURCC_DATA (0x61746164) // 'data' -#define FOURCC_LIST (0x5453494C) // 'LIST' -#define FOURCC_WAVL (0x6C766177) // 'wavl' -#define FOURCC_SLNT (0x746E6C73) // 'slnt' +enum : uint { + FOURCC_RIFF = 0x46464952, // 'RIFF' + FOURCC_RIFX = 0x58464952, // 'RIFX' + FOURCC_WAVE = 0x45564157, // 'WAVE' + FOURCC_FMT = 0x20746D66, // 'fmt ' + FOURCC_DATA = 0x61746164, // 'data' + FOURCC_LIST = 0x5453494C, // 'LIST' + FOURCC_WAVL = 0x6C766177, // 'wavl' + FOURCC_SLNT = 0x746E6C73, // 'slnt' +}; // The supported wave formats. -#define WAVE_FORMAT_PCM (0x0001) -#define WAVE_FORMAT_IEEE_FLOAT (0x0003) -#define WAVE_FORMAT_EXTENSIBLE (0xFFFE) +enum : uint { + WAVE_FORMAT_PCM = 0x0001, + WAVE_FORMAT_IEEE_FLOAT = 0x0003, + WAVE_FORMAT_EXTENSIBLE = 0xFFFE, +}; enum ByteOrderT { @@ -145,7 +156,7 @@ struct SourceRefT { double mRadius; uint mSkip; uint mOffset; - char mPath[MAX_PATH_LEN+1]; + std::array mPath; }; @@ -156,71 +167,67 @@ struct SourceRefT { // Setup the reader on the given file. The filename can be NULL if no error // output is desired. -static void TrSetup(const char *startbytes, std::streamsize startbytecount, const char *filename, +void TrSetup(const al::span startbytes, const std::string_view filename, TokenReaderT *tr) { - const char *name = nullptr; + std::string_view namepart; - if(filename) + if(!filename.empty()) { - const char *slash = strrchr(filename, '/'); - if(slash) - { - const char *bslash = strrchr(slash+1, '\\'); - if(bslash) name = bslash+1; - else name = slash+1; - } - else - { - const char *bslash = strrchr(filename, '\\'); - if(bslash) name = bslash+1; - else name = filename; - } + const auto fslashpos = filename.rfind('/'); + const auto bslashpos = filename.rfind('\\'); + const auto slashpos = (bslashpos >= filename.size()) ? fslashpos : + (fslashpos >= filename.size()) ? bslashpos : + std::max(fslashpos, bslashpos); + if(slashpos < filename.size()) + namepart = filename.substr(slashpos+1); } - tr->mName = name; + tr->mName = namepart; tr->mLine = 1; tr->mColumn = 1; tr->mIn = 0; tr->mOut = 0; - if(startbytecount > 0) + if(!startbytes.empty()) { - std::copy_n(startbytes, startbytecount, std::begin(tr->mRing)); - tr->mIn += startbytecount; + assert(startbytes.size() <= tr->mRing.size()); + std::copy(startbytes.cbegin(), startbytes.cend(), tr->mRing.begin()); + tr->mIn += std::streamsize(startbytes.size()); } } // Prime the reader's ring buffer, and return a result indicating that there // is text to process. -static int TrLoad(TokenReaderT *tr) +auto TrLoad(TokenReaderT *tr) -> int { std::istream &istream = tr->mIStream; - std::streamsize toLoad{TR_RING_SIZE - static_cast(tr->mIn - tr->mOut)}; - if(toLoad >= TR_LOAD_SIZE && istream.good()) + std::streamsize toLoad{TRRingSize - static_cast(tr->mIn - tr->mOut)}; + if(toLoad >= TRLoadSize && istream.good()) { - // Load TR_LOAD_SIZE (or less if at the end of the file) per read. - toLoad = TR_LOAD_SIZE; - std::streamsize in{tr->mIn&TR_RING_MASK}; - std::streamsize count{TR_RING_SIZE - in}; + // Load TRLoadSize (or less if at the end of the file) per read. + toLoad = TRLoadSize; + + const auto in = tr->mIn&TRRingMask; + std::streamsize count{TRRingSize - in}; if(count < toLoad) { - istream.read(&tr->mRing[in], count); + istream.read(al::to_address(tr->mRing.begin() + in), count); tr->mIn += istream.gcount(); - istream.read(&tr->mRing[0], toLoad-count); + istream.read(tr->mRing.data(), toLoad-count); tr->mIn += istream.gcount(); } else { - istream.read(&tr->mRing[in], toLoad); + istream.read(al::to_address(tr->mRing.begin() + in), toLoad); tr->mIn += istream.gcount(); } - if(tr->mOut >= TR_RING_SIZE) + if(tr->mOut >= TRRingSize) { - tr->mOut -= TR_RING_SIZE; - tr->mIn -= TR_RING_SIZE; + tr->mOut -= TRRingSize; + tr->mIn -= TRRingSize; } } if(tr->mIn > tr->mOut) @@ -229,42 +236,44 @@ static int TrLoad(TokenReaderT *tr) } // Error display routine. Only displays when the base name is not NULL. -static void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr) +void TrErrorVA(const TokenReaderT *tr, uint line, uint column, const char *format, va_list argPtr) { - if(!tr->mName) + if(tr->mName.empty()) return; - fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName, line, column); + fprintf(stderr, "\nError (%s:%u:%u): ", tr->mName.c_str(), line, column); vfprintf(stderr, format, argPtr); } // Used to display an error at a saved line/column. -static void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...) +void TrErrorAt(const TokenReaderT *tr, uint line, uint column, const char *format, ...) { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ va_list argPtr; - va_start(argPtr, format); TrErrorVA(tr, line, column, format, argPtr); va_end(argPtr); + /* NOLINTEND(*-array-to-pointer-decay) */ } // Used to display an error at the current line/column. -static void TrError(const TokenReaderT *tr, const char *format, ...) +void TrError(const TokenReaderT *tr, const char *format, ...) { + /* NOLINTBEGIN(*-array-to-pointer-decay) */ va_list argPtr; - va_start(argPtr, format); TrErrorVA(tr, tr->mLine, tr->mColumn, format, argPtr); va_end(argPtr); + /* NOLINTEND(*-array-to-pointer-decay) */ } // Skips to the next line. -static void TrSkipLine(TokenReaderT *tr) +void TrSkipLine(TokenReaderT *tr) { char ch; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\n') { @@ -277,11 +286,11 @@ static void TrSkipLine(TokenReaderT *tr) } // Skips to the next token. -static int TrSkipWhitespace(TokenReaderT *tr) +auto TrSkipWhitespace(TokenReaderT *tr) -> int { while(TrLoad(tr)) { - char ch{tr->mRing[tr->mOut&TR_RING_MASK]}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(isspace(ch)) { tr->mOut++; @@ -302,7 +311,7 @@ static int TrSkipWhitespace(TokenReaderT *tr) } // Get the line and/or column of the next token (or the end of input). -static void TrIndication(TokenReaderT *tr, uint *line, uint *column) +void TrIndication(TokenReaderT *tr, uint *line, uint *column) { TrSkipWhitespace(tr); if(line) *line = tr->mLine; @@ -311,35 +320,31 @@ static void TrIndication(TokenReaderT *tr, uint *line, uint *column) // Checks to see if a token is (likely to be) an identifier. It does not // display any errors and will not proceed to the next token. -static int TrIsIdent(TokenReaderT *tr) +auto TrIsIdent(TokenReaderT *tr) -> int { if(!TrSkipWhitespace(tr)) return 0; - char ch{tr->mRing[tr->mOut&TR_RING_MASK]}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; return ch == '_' || isalpha(ch); } // Checks to see if a token is the given operator. It does not display any // errors and will not proceed to the next token. -static int TrIsOperator(TokenReaderT *tr, const char *op) +auto TrIsOperator(TokenReaderT *tr, const std::string_view op) -> int { - std::streamsize out; - size_t len; - char ch; - if(!TrSkipWhitespace(tr)) return 0; - out = tr->mOut; - len = 0; - while(op[len] != '\0' && out < tr->mIn) + auto out = tr->mOut; + size_t len{0}; + while(len < op.size() && out < tr->mIn) { - ch = tr->mRing[out&TR_RING_MASK]; - if(ch != op[len]) break; - len++; - out++; + if(tr->mRing[out&TRRingMask] != op[len]) + break; + ++len; + ++out; } - if(op[len] == '\0') + if(len == op.size()) return 1; return 0; } @@ -350,30 +355,29 @@ static int TrIsOperator(TokenReaderT *tr, const char *op) */ // Reads and validates an identifier token. -static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) +auto TrReadIdent(TokenReaderT *tr, const al::span ident) -> int { - uint col, len; - char ch; - - col = tr->mColumn; + assert(!ident.empty()); + const size_t maxLen{ident.size()-1}; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '_' || isalpha(ch)) { - len = 0; + size_t len{0}; do { if(len < maxLen) ident[len] = ch; - len++; + ++len; tr->mOut++; if(!TrLoad(tr)) break; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; } while(ch == '_' || isdigit(ch) || isalpha(ch)); - tr->mColumn += len; + tr->mColumn += static_cast(len); if(len < maxLen) { ident[len] = '\0'; @@ -388,27 +392,25 @@ static int TrReadIdent(TokenReaderT *tr, const uint maxLen, char *ident) } // Reads and validates (including bounds) an integer token. -static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) +auto TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int *value) -> int { - uint col, digis, len; - char ch, temp[64+1]; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + uint len{0}; + std::array temp{}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '+' || ch == '-') { temp[len] = ch; len++; tr->mOut++; } - digis = 0; + uint digis{0}; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -425,7 +427,7 @@ static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int return 0; } temp[len] = '\0'; - *value = static_cast(strtol(temp, nullptr, 10)); + *value = static_cast(strtol(temp.data(), nullptr, 10)); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from %d to %d.\n", loBound, hiBound); @@ -439,17 +441,15 @@ static int TrReadInt(TokenReaderT *tr, const int loBound, const int hiBound, int } // Reads and validates (including bounds) a float token. -static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) +auto TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBound, double *value) -> int { - uint col, digis, len; - char ch, temp[64+1]; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + std::array temp{}; + uint len{0}; + char ch{tr->mRing[tr->mOut&TRRingMask]}; if(ch == '+' || ch == '-') { temp[len] = ch; @@ -457,10 +457,10 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo tr->mOut++; } - digis = 0; + uint digis{0}; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -477,7 +477,7 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo } while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -503,7 +503,7 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo } while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; if(!isdigit(ch)) break; if(len < 64) temp[len] = ch; @@ -521,7 +521,7 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo return 0; } temp[len] = '\0'; - *value = strtod(temp, nullptr); + *value = strtod(temp.data(), nullptr); if(*value < loBound || *value > hiBound) { TrErrorAt(tr, tr->mLine, col, "Expected a value from %f to %f.\n", loBound, hiBound); @@ -538,23 +538,22 @@ static int TrReadFloat(TokenReaderT *tr, const double loBound, const double hiBo } // Reads and validates a string token. -static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) +auto TrReadString(TokenReaderT *tr, const al::span text) -> int { - uint col, len; - char ch; + assert(!text.empty()); + const size_t maxLen{text.size()-1}; - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - ch = tr->mRing[tr->mOut&TR_RING_MASK]; - if(ch == '\"') + if(char ch{tr->mRing[tr->mOut&TRRingMask]}; ch == '\"') { tr->mOut++; - len = 0; + size_t len{0}; while(TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; + ch = tr->mRing[tr->mOut&TRRingMask]; tr->mOut++; if(ch == '\"') break; @@ -569,11 +568,11 @@ static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) } if(ch != '\"') { - tr->mColumn += 1 + len; + tr->mColumn += static_cast(1 + len); TrErrorAt(tr, tr->mLine, col, "Unterminated string at end of input.\n"); return 0; } - tr->mColumn += 2 + len; + tr->mColumn += static_cast(2 + len); if(len > maxLen) { TrErrorAt(tr, tr->mLine, col, "String is too long.\n"); @@ -588,25 +587,22 @@ static int TrReadString(TokenReaderT *tr, const uint maxLen, char *text) } // Reads and validates the given operator. -static int TrReadOperator(TokenReaderT *tr, const char *op) +auto TrReadOperator(TokenReaderT *tr, const std::string_view op) -> int { - uint col, len; - char ch; - - col = tr->mColumn; + uint col{tr->mColumn}; if(TrSkipWhitespace(tr)) { col = tr->mColumn; - len = 0; - while(op[len] != '\0' && TrLoad(tr)) + size_t len{0}; + while(len < op.size() && TrLoad(tr)) { - ch = tr->mRing[tr->mOut&TR_RING_MASK]; - if(ch != op[len]) break; - len++; - tr->mOut++; + if(tr->mRing[tr->mOut&TRRingMask] != op[len]) + break; + ++len; + tr->mOut += 1; } - tr->mColumn += len; - if(op[len] == '\0') + tr->mColumn += static_cast(len); + if(len == op.size()) return 1; } TrErrorAt(tr, tr->mLine, col, "Expected '%s' operator.\n", op); @@ -620,10 +616,11 @@ static int TrReadOperator(TokenReaderT *tr, const char *op) // Read a binary value of the specified byte order and byte size from a file, // storing it as a 32-bit unsigned integer. -static int ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, const uint bytes, uint32_t *out) +auto ReadBin4(std::istream &istream, const char *filename, const ByteOrderT order, + const uint bytes, uint32_t *out) -> int { - uint8_t in[4]; - istream.read(reinterpret_cast(in), static_cast(bytes)); + std::array in{}; + istream.read(reinterpret_cast(in.data()), static_cast(bytes)); if(istream.gcount() != bytes) { fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); @@ -649,31 +646,29 @@ static int ReadBin4(std::istream &istream, const char *filename, const ByteOrder // Read a binary value of the specified byte order from a file, storing it as // a 64-bit unsigned integer. -static int ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) +auto ReadBin8(std::istream &istream, const char *filename, const ByteOrderT order, uint64_t *out) -> int { - uint8_t in[8]; - uint64_t accum; - uint i; - - istream.read(reinterpret_cast(in), 8); + std::array in{}; + istream.read(reinterpret_cast(in.data()), 8); if(istream.gcount() != 8) { fprintf(stderr, "\nError: Bad read from file '%s'.\n", filename); return 0; } - accum = 0; + + uint64_t accum{}; switch(order) { - case BO_LITTLE: - for(i = 0;i < 8;i++) - accum = (accum<<8) | in[8 - i - 1]; - break; - case BO_BIG: - for(i = 0;i < 8;i++) - accum = (accum<<8) | in[i]; - break; - default: - break; + case BO_LITTLE: + for(uint i{0};i < 8;++i) + accum = (accum<<8) | in[8 - i - 1]; + break; + case BO_BIG: + for(uint i{0};i < 8;++i) + accum = (accum<<8) | in[i]; + break; + default: + break; } *out = accum; return 1; @@ -685,43 +680,35 @@ static int ReadBin8(std::istream &istream, const char *filename, const ByteOrder * whether they are padded toward the MSB (negative) or LSB (positive). * Floating-point types are not normalized. */ -static int ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, - const ElementTypeT type, const uint bytes, const int bits, double *out) +auto ReadBinAsDouble(std::istream &istream, const char *filename, const ByteOrderT order, + const ElementTypeT type, const uint bytes, const int bits, double *out) -> int { - union { - uint32_t ui; - int32_t i; - float f; - } v4; - union { - uint64_t ui; - double f; - } v8; - *out = 0.0; if(bytes > 4) { - if(!ReadBin8(istream, filename, order, &v8.ui)) + uint64_t val{}; + if(!ReadBin8(istream, filename, order, &val)) return 0; if(type == ET_FP) - *out = v8.f; + *out = al::bit_cast(val); } else { - if(!ReadBin4(istream, filename, order, bytes, &v4.ui)) + uint32_t val{}; + if(!ReadBin4(istream, filename, order, bytes, &val)) return 0; if(type == ET_FP) - *out = v4.f; + *out = al::bit_cast(val); else { if(bits > 0) - v4.ui >>= (8*bytes) - (static_cast(bits)); + val >>= (8*bytes) - (static_cast(bits)); else - v4.ui &= (0xFFFFFFFF >> (32+bits)); + val &= (0xFFFFFFFF >> (32+bits)); - if(v4.ui&static_cast(1<<(std::abs(bits)-1))) - v4.ui |= (0xFFFFFFFF << std::abs(bits)); - *out = v4.i / static_cast(1<<(std::abs(bits)-1)); + if(val&static_cast(1<<(std::abs(bits)-1))) + val |= (0xFFFFFFFF << std::abs(bits)); + *out = static_cast(val) / static_cast(1<<(std::abs(bits)-1)); } } return 1; @@ -732,7 +719,8 @@ static int ReadBinAsDouble(std::istream &istream, const char *filename, const By * result. The sign of the bits should always be positive. This also skips * up to one separator character before the element itself. */ -static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, const uint bits, double *out) +auto ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const ElementTypeT type, + const uint bits, double *out) -> int { if(TrIsOperator(tr, ",")) TrReadOperator(tr, ","); @@ -767,8 +755,8 @@ static int ReadAsciiAsDouble(TokenReaderT *tr, const char *filename, const Eleme // Read the RIFF/RIFX WAVE format chunk from a file, validating it against // the source parameters and data set metrics. -static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, - SourceRefT *src) +auto ReadWaveFormat(std::istream &istream, const ByteOrderT order, const uint hrirRate, + SourceRefT *src) -> int { uint32_t fourCC, chunkSize; uint32_t format, channels, rate, dummy, block, size, bits; @@ -777,20 +765,20 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u do { if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) return 0; } while(fourCC != FOURCC_FMT); - if(!ReadBin4(istream, src->mPath, order, 2, &format) - || !ReadBin4(istream, src->mPath, order, 2, &channels) - || !ReadBin4(istream, src->mPath, order, 4, &rate) - || !ReadBin4(istream, src->mPath, order, 4, &dummy) - || !ReadBin4(istream, src->mPath, order, 2, &block)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &format) + || !ReadBin4(istream, src->mPath.data(), order, 2, &channels) + || !ReadBin4(istream, src->mPath.data(), order, 4, &rate) + || !ReadBin4(istream, src->mPath.data(), order, 4, &dummy) + || !ReadBin4(istream, src->mPath.data(), order, 2, &block)) return 0; block /= channels; if(chunkSize > 14) { - if(!ReadBin4(istream, src->mPath, order, 2, &size)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &size)) return 0; size /= 8; if(block > size) @@ -801,12 +789,12 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u if(format == WAVE_FORMAT_EXTENSIBLE) { istream.seekg(2, std::ios::cur); - if(!ReadBin4(istream, src->mPath, order, 2, &bits)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &bits)) return 0; if(bits == 0) bits = 8 * size; istream.seekg(4, std::ios::cur); - if(!ReadBin4(istream, src->mPath, order, 2, &format)) + if(!ReadBin4(istream, src->mPath.data(), order, 2, &format)) return 0; istream.seekg(static_cast(chunkSize - 26), std::ios::cur); } @@ -820,29 +808,32 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u } if(format != WAVE_FORMAT_PCM && format != WAVE_FORMAT_IEEE_FLOAT) { - fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Unsupported WAVE format in file '%s'.\n", src->mPath.data()); return 0; } if(src->mChannel >= channels) { - fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Missing source channel in WAVE file '%s'.\n", src->mPath.data()); return 0; } if(rate != hrirRate) { - fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Mismatched source sample rate in WAVE file '%s'.\n", + src->mPath.data()); return 0; } if(format == WAVE_FORMAT_PCM) { if(size < 2 || size > 4) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", + src->mPath.data()); return 0; } if(bits < 16 || bits > (8*size)) { - fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad significant bits in WAVE file '%s'.\n", + src->mPath.data()); return 0; } src->mType = ET_INT; @@ -851,7 +842,8 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u { if(size != 4 && size != 8) { - fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Unsupported sample size in WAVE file '%s'.\n", + src->mPath.data()); return 0; } src->mType = ET_FP; @@ -863,21 +855,19 @@ static int ReadWaveFormat(std::istream &istream, const ByteOrderT order, const u } // Read a RIFF/RIFX WAVE data chunk, converting all elements to doubles. -static int ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, - const uint n, double *hrir) +auto ReadWaveData(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { - int pre, post, skip; - uint i; - - pre = static_cast(src->mSize * src->mChannel); - post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); - skip = 0; - for(i = 0;i < n;i++) + auto pre = static_cast(src->mSize * src->mChannel); + auto post = static_cast(src->mSize * (src->mSkip - src->mChannel - 1)); + auto skip = int{0}; + for(size_t i{0};i < hrir.size();++i) { skip += pre; if(skip > 0) istream.seekg(skip, std::ios::cur); - if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) + if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + &hrir[i])) return 0; skip = post; } @@ -888,8 +878,8 @@ static int ReadWaveData(std::istream &istream, const SourceRefT *src, const Byte // Read the RIFF/RIFX WAVE list or data chunk, converting all elements to // doubles. -static int ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, - const uint n, double *hrir) +auto ReadWaveList(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { uint32_t fourCC, chunkSize, listSize, count; uint block, skip, offset, i; @@ -897,27 +887,28 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte for(;;) { - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) return 0; if(fourCC == FOURCC_DATA) { block = src->mSize * src->mSkip; count = chunkSize / block; - if(count < (src->mOffset + n)) + if(count < (src->mOffset + hrir.size())) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); return 0; } - istream.seekg(static_cast(src->mOffset * block), std::ios::cur); - if(!ReadWaveData(istream, src, order, n, &hrir[0])) + using off_type = std::istream::off_type; + istream.seekg(off_type(src->mOffset) * off_type(block), std::ios::cur); + if(!ReadWaveData(istream, src, order, hrir)) return 0; return 1; } - else if(fourCC == FOURCC_LIST) + if(fourCC == FOURCC_LIST) { - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) return 0; chunkSize -= 4; if(fourCC == FOURCC_WAVL) @@ -931,10 +922,10 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte skip = src->mOffset; offset = 0; lastSample = 0.0; - while(offset < n && listSize > 8) + while(offset < hrir.size() && listSize > 8) { - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, order, 4, &chunkSize)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), order, 4, &chunkSize)) return 0; listSize -= 8 + chunkSize; if(fourCC == FOURCC_DATA) @@ -942,13 +933,14 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte count = chunkSize / block; if(count > skip) { - istream.seekg(static_cast(skip * block), std::ios::cur); + using off_type = std::istream::off_type; + istream.seekg(off_type(skip) * off_type(block), std::ios::cur); chunkSize -= skip * block; count -= skip; skip = 0; - if(count > (n - offset)) - count = n - offset; - if(!ReadWaveData(istream, src, order, count, &hrir[offset])) + if(count > (hrir.size() - offset)) + count = static_cast(hrir.size() - offset); + if(!ReadWaveData(istream, src, order, hrir.subspan(offset, count))) return 0; chunkSize -= count * block; offset += count; @@ -962,15 +954,15 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte } else if(fourCC == FOURCC_SLNT) { - if(!ReadBin4(istream, src->mPath, order, 4, &count)) + if(!ReadBin4(istream, src->mPath.data(), order, 4, &count)) return 0; chunkSize -= 4; if(count > skip) { count -= skip; skip = 0; - if(count > (n - offset)) - count = n - offset; + if(count > (hrir.size() - offset)) + count = static_cast(hrir.size() - offset); for(i = 0; i < count; i ++) hrir[offset + i] = lastSample; offset += count; @@ -984,9 +976,9 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte if(chunkSize > 0) istream.seekg(static_cast(chunkSize), std::ios::cur); } - if(offset < n) + if(offset < hrir.size()) { - fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Bad read from file '%s'.\n", src->mPath.data()); return 0; } return 1; @@ -994,26 +986,28 @@ static int ReadWaveList(std::istream &istream, const SourceRefT *src, const Byte // Load a source HRIR from an ASCII text file containing a list of elements // separated by whitespace or common list operators (',', ';', ':', '|'). -static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, - const uint n, double *hrir) +auto LoadAsciiSource(std::istream &istream, const SourceRefT *src, const al::span hrir) -> int { TokenReaderT tr{istream}; - uint i, j; - double dummy; - TrSetup(nullptr, 0, nullptr, &tr); - for(i = 0;i < src->mOffset;i++) + TrSetup({}, {}, &tr); + for(uint i{0};i < src->mOffset;++i) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) + double dummy{}; + if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), + &dummy)) return 0; } - for(i = 0;i < n;i++) + for(size_t i{0};i < hrir.size();++i) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &hrir[i])) + if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, static_cast(src->mBits), + &hrir[i])) return 0; - for(j = 0;j < src->mSkip;j++) + for(uint j{0};j < src->mSkip;++j) { - if(!ReadAsciiAsDouble(&tr, src->mPath, src->mType, static_cast(src->mBits), &dummy)) + double dummy{}; + if(!ReadAsciiAsDouble(&tr, src->mPath.data(), src->mType, + static_cast(src->mBits), &dummy)) return 0; } } @@ -1021,13 +1015,14 @@ static int LoadAsciiSource(std::istream &istream, const SourceRefT *src, } // Load a source HRIR from a binary file. -static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, - const uint n, double *hrir) +auto LoadBinarySource(std::istream &istream, const SourceRefT *src, const ByteOrderT order, + const al::span hrir) -> int { istream.seekg(static_cast(src->mOffset), std::ios::beg); - for(uint i{0};i < n;i++) + for(size_t i{0};i < hrir.size();++i) { - if(!ReadBinAsDouble(istream, src->mPath, order, src->mType, src->mSize, src->mBits, &hrir[i])) + if(!ReadBinAsDouble(istream, src->mPath.data(), order, src->mType, src->mSize, src->mBits, + &hrir[i])) return 0; if(src->mSkip > 0) istream.seekg(static_cast(src->mSkip), std::ios::cur); @@ -1036,14 +1031,14 @@ static int LoadBinarySource(std::istream &istream, const SourceRefT *src, const } // Load a source HRIR from a RIFF/RIFX WAVE file. -static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, - const uint n, double *hrir) +auto LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hrirRate, + const al::span hrir) -> int { uint32_t fourCC, dummy; ByteOrderT order; - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC) - || !ReadBin4(istream, src->mPath, BO_LITTLE, 4, &dummy)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC) + || !ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &dummy)) return 0; if(fourCC == FOURCC_RIFF) order = BO_LITTLE; @@ -1051,34 +1046,54 @@ static int LoadWaveSource(std::istream &istream, SourceRefT *src, const uint hri order = BO_BIG; else { - fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: No RIFF/RIFX chunk in file '%s'.\n", src->mPath.data()); return 0; } - if(!ReadBin4(istream, src->mPath, BO_LITTLE, 4, &fourCC)) + if(!ReadBin4(istream, src->mPath.data(), BO_LITTLE, 4, &fourCC)) return 0; if(fourCC != FOURCC_WAVE) { - fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Not a RIFF/RIFX WAVE file '%s'.\n", src->mPath.data()); return 0; } if(!ReadWaveFormat(istream, order, hrirRate, src)) return 0; - if(!ReadWaveList(istream, src, order, n, hrir)) + if(!ReadWaveList(istream, src, order, hrir)) return 0; return 1; } +struct SofaEasyDeleter { + void operator()(gsl::owner sofa) + { + if(sofa->neighborhood) mysofa_neighborhood_free(sofa->neighborhood); + if(sofa->lookup) mysofa_lookup_free(sofa->lookup); + if(sofa->hrtf) mysofa_free(sofa->hrtf); + delete sofa; + } +}; +using SofaEasyPtr = std::unique_ptr; + +struct SofaCacheEntry { + std::string mName; + uint mSampleRate{}; + SofaEasyPtr mSofa; +}; +std::vector gSofaCache; // Load a Spatially Oriented Format for Accoustics (SOFA) file. -static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) +auto LoadSofaFile(SourceRefT *src, const uint hrirRate, const uint n) -> MYSOFA_EASY* { - struct MYSOFA_EASY *sofa{mysofa_cache_lookup(src->mPath, static_cast(hrirRate))}; - if(sofa) return sofa; + const std::string_view srcname{src->mPath.data()}; + auto iter = std::find_if(gSofaCache.begin(), gSofaCache.end(), + [srcname,hrirRate](SofaCacheEntry &entry) -> bool + { return entry.mName == srcname && entry.mSampleRate == hrirRate; }); + if(iter != gSofaCache.end()) return iter->mSofa.get(); - sofa = static_cast(calloc(1, sizeof(*sofa))); - if(sofa == nullptr) + SofaEasyPtr sofa{new(std::nothrow) MYSOFA_EASY{}}; + if(!sofa) { fprintf(stderr, "\nError: Out of memory.\n"); return nullptr; @@ -1087,136 +1102,127 @@ static MYSOFA_EASY* LoadSofaFile(SourceRefT *src, const uint hrirRate, const uin sofa->neighborhood = nullptr; int err; - sofa->hrtf = mysofa_load(src->mPath, &err); + sofa->hrtf = mysofa_load(src->mPath.data(), &err); if(!sofa->hrtf) { - mysofa_close(sofa); - fprintf(stderr, "\nError: Could not load source file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Could not load source file '%s' (error: %d).\n", + src->mPath.data(), err); return nullptr; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofa->hrtf); if(err != MYSOFA_OK) - fprintf(stderr, "\nWarning: Supposedly malformed source file '%s'.\n", src->mPath); + fprintf(stderr, "\nWarning: Supposedly malformed source file '%s' (error: %d).\n", + src->mPath.data(), err); if((src->mOffset + n) > sofa->hrtf->N) { - mysofa_close(sofa); - fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Not enough samples in SOFA file '%s'.\n", src->mPath.data()); return nullptr; } if(src->mChannel >= sofa->hrtf->R) { - mysofa_close(sofa); - fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Missing source receiver in SOFA file '%s'.\n",src->mPath.data()); return nullptr; } mysofa_tocartesian(sofa->hrtf); sofa->lookup = mysofa_lookup_init(sofa->hrtf); if(sofa->lookup == nullptr) { - mysofa_close(sofa); fprintf(stderr, "\nError: Out of memory.\n"); return nullptr; } - return mysofa_cache_store(sofa, src->mPath, static_cast(hrirRate)); + gSofaCache.emplace_back(SofaCacheEntry{std::string{srcname}, hrirRate, std::move(sofa)}); + return gSofaCache.back().mSofa.get(); } // Copies the HRIR data from a particular SOFA measurement. -static void ExtractSofaHrir(const MYSOFA_EASY *sofa, const uint index, const uint channel, const uint offset, const uint n, double *hrir) +void ExtractSofaHrir(const MYSOFA_HRTF *hrtf, const size_t index, const size_t channel, + const size_t offset, const al::span hrir) { - for(uint i{0u};i < n;i++) - hrir[i] = sofa->hrtf->DataIR.values[(index*sofa->hrtf->R + channel)*sofa->hrtf->N + offset + i]; + const auto irValues = al::span{hrtf->DataIR.values, hrtf->DataIR.elements} + .subspan((index*hrtf->R + channel)*hrtf->N + offset); + std::copy_n(irValues.cbegin(), hrir.size(), hrir.begin()); } // Load a source HRIR from a Spatially Oriented Format for Accoustics (SOFA) // file. -static int LoadSofaSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) +auto LoadSofaSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - struct MYSOFA_EASY *sofa; - float target[3]; - int nearest; - float *coords; + MYSOFA_EASY *sofa{LoadSofaFile(src, hrirRate, static_cast(hrir.size()))}; + if(sofa == nullptr) return 0; - sofa = LoadSofaFile(src, hrirRate, n); - if(sofa == nullptr) - return 0; - - /* NOTE: At some point it may be benficial or necessary to consider the + /* NOTE: At some point it may be beneficial or necessary to consider the various coordinate systems, listener/source orientations, and - direciontal vectors defined in the SOFA file. + directional vectors defined in the SOFA file. */ - target[0] = static_cast(src->mAzimuth); - target[1] = static_cast(src->mElevation); - target[2] = static_cast(src->mRadius); - mysofa_s2c(target); + std::array target{ + static_cast(src->mAzimuth), + static_cast(src->mElevation), + static_cast(src->mRadius) + }; + mysofa_s2c(target.data()); - nearest = mysofa_lookup(sofa->lookup, target); + int nearest{mysofa_lookup(sofa->lookup, target.data())}; if(nearest < 0) { - fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Lookup failed in source file '%s'.\n", src->mPath.data()); return 0; } - coords = &sofa->hrtf->SourcePosition.values[3 * nearest]; - if(std::abs(coords[0] - target[0]) > 0.001 || std::abs(coords[1] - target[1]) > 0.001 || std::abs(coords[2] - target[2]) > 0.001) + al::span coords = al::span{sofa->hrtf->SourcePosition.values, sofa->hrtf->M*3_uz} + .subspan(static_cast(nearest)*3_uz).first<3>(); + if(std::abs(coords[0] - target[0]) > 0.001 || std::abs(coords[1] - target[1]) > 0.001 + || std::abs(coords[2] - target[2]) > 0.001) { - fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", src->mRadius, src->mElevation, src->mAzimuth, src->mPath); + fprintf(stderr, "\nError: No impulse response at coordinates (%.3fr, %.1fev, %.1faz) in file '%s'.\n", + src->mRadius, src->mElevation, src->mAzimuth, src->mPath.data()); target[0] = coords[0]; target[1] = coords[1]; target[2] = coords[2]; - mysofa_c2s(target); - fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], target[1], target[0]); + mysofa_c2s(target.data()); + fprintf(stderr, " Nearest candidate at (%.3fr, %.1fev, %.1faz).\n", target[2], + target[1], target[0]); return 0; } - ExtractSofaHrir(sofa, static_cast(nearest), src->mChannel, src->mOffset, n, hrir); + ExtractSofaHrir(sofa->hrtf, static_cast(nearest), src->mChannel, src->mOffset, hrir); return 1; } // Load a source HRIR from a supported file type. -static int LoadSource(SourceRefT *src, const uint hrirRate, const uint n, double *hrir) +auto LoadSource(SourceRefT *src, const uint hrirRate, const al::span hrir) -> int { - std::unique_ptr istream; + std::unique_ptr istream; if(src->mFormat != SF_SOFA) { if(src->mFormat == SF_ASCII) - istream.reset(new al::ifstream{src->mPath}); + istream = std::make_unique(std::filesystem::u8path(src->mPath.data())); else - istream.reset(new al::ifstream{src->mPath, std::ios::binary}); + istream = std::make_unique(std::filesystem::u8path(src->mPath.data()), + std::ios::binary); if(!istream->good()) { - fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath); + fprintf(stderr, "\nError: Could not open source file '%s'.\n", src->mPath.data()); return 0; } } - int result{0}; + switch(src->mFormat) { - case SF_ASCII: - result = LoadAsciiSource(*istream, src, n, hrir); - break; - case SF_BIN_LE: - result = LoadBinarySource(*istream, src, BO_LITTLE, n, hrir); - break; - case SF_BIN_BE: - result = LoadBinarySource(*istream, src, BO_BIG, n, hrir); - break; - case SF_WAVE: - result = LoadWaveSource(*istream, src, hrirRate, n, hrir); - break; - case SF_SOFA: - result = LoadSofaSource(src, hrirRate, n, hrir); - break; - case SF_NONE: - break; + case SF_ASCII: return LoadAsciiSource(*istream, src, hrir); + case SF_BIN_LE: return LoadBinarySource(*istream, src, BO_LITTLE, hrir); + case SF_BIN_BE: return LoadBinarySource(*istream, src, BO_BIG, hrir); + case SF_WAVE: return LoadWaveSource(*istream, src, hrirRate, hrir); + case SF_SOFA: return LoadSofaSource(src, hrirRate, hrir); + case SF_NONE: break; } - return result; + return 0; } // Match the channel type from a given identifier. -static ChannelTypeT MatchChannelType(const char *ident) +auto MatchChannelType(const char *ident) -> ChannelTypeT { if(al::strcasecmp(ident, "mono") == 0) return CT_MONO; @@ -1227,18 +1233,19 @@ static ChannelTypeT MatchChannelType(const char *ident) // Process the data set definition to read and validate the data set metrics. -static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData) +auto ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint truncSize, + const ChannelModeT chanMode, HrirDataT *hData) -> int { int hasRate = 0, hasType = 0, hasPoints = 0, hasRadius = 0; int hasDistance = 0, hasAzimuths = 0; - char ident[MAX_IDENT_LEN+1]; + std::array ident{}; uint line, col; double fpVal; uint points; int intVal; - double distances[MAX_FD_COUNT]; + std::array distances{}; uint fdCount = 0; - uint evCounts[MAX_FD_COUNT]; + std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); @@ -1246,9 +1253,9 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc while(TrIsIdent(tr)) { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - if(al::strcasecmp(ident, "rate") == 0) + if(al::strcasecmp(ident.data(), "rate") == 0) { if(hasRate) { @@ -1262,9 +1269,9 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrRate = static_cast(intVal); hasRate = 1; } - else if(al::strcasecmp(ident, "type") == 0) + else if(al::strcasecmp(ident.data(), "type") == 0) { - char type[MAX_IDENT_LEN+1]; + std::array type{}; if(hasType) { @@ -1274,22 +1281,22 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc if(!TrReadOperator(tr, "=")) return 0; - if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + if(!TrReadIdent(tr, type)) return 0; - hData->mChannelType = MatchChannelType(type); + hData->mChannelType = MatchChannelType(type.data()); if(hData->mChannelType == CT_NONE) { TrErrorAt(tr, line, col, "Expected a channel type.\n"); return 0; } - else if(hData->mChannelType == CT_STEREO) + if(hData->mChannelType == CT_STEREO) { if(chanMode == CM_ForceMono) hData->mChannelType = CT_MONO; } hasType = 1; } - else if(al::strcasecmp(ident, "points") == 0) + else if(al::strcasecmp(ident.data(), "points") == 0) { if(hasPoints) { @@ -1319,7 +1326,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc hData->mIrSize = points; hasPoints = 1; } - else if(al::strcasecmp(ident, "radius") == 0) + else if(al::strcasecmp(ident.data(), "radius") == 0) { if(hasRadius) { @@ -1328,12 +1335,12 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } if(!TrReadOperator(tr, "=")) return 0; - if(!TrReadFloat(tr, MIN_RADIUS, MAX_RADIUS, &fpVal)) + if(!TrReadFloat(tr, MinRadius, MaxRadius, &fpVal)) return 0; hData->mRadius = fpVal; hasRadius = 1; } - else if(al::strcasecmp(ident, "distance") == 0) + else if(al::strcasecmp(ident.data(), "distance") == 0) { uint count = 0; @@ -1372,7 +1379,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc fdCount = count; hasDistance = 1; } - else if(al::strcasecmp(ident, "azimuths") == 0) + else if(al::strcasecmp(ident.data(), "azimuths") == 0) { uint count = 0; @@ -1451,8 +1458,8 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } if(hData->mChannelType == CT_NONE) hData->mChannelType = CT_MONO; - const auto azs = al::as_span(azCounts).first(); - if(!PrepareHrirData({distances, fdCount}, evCounts, azs, hData)) + const auto azs = al::span{azCounts}.first(); + if(!PrepareHrirData(al::span{distances}.first(fdCount), evCounts, azs, hData)) { fprintf(stderr, "Error: Out of memory.\n"); exit(-1); @@ -1461,7 +1468,7 @@ static int ProcessMetrics(TokenReaderT *tr, const uint fftSize, const uint trunc } // Parse an index triplet from the data set definition. -static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai) +auto ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, uint *ei, uint *ai)->int { int intVal; @@ -1489,7 +1496,7 @@ static int ReadIndexTriplet(TokenReaderT *tr, const HrirDataT *hData, uint *fi, } // Match the source format from a given identifier. -static SourceFormatT MatchSourceFormat(const char *ident) +auto MatchSourceFormat(const char *ident) -> SourceFormatT { if(al::strcasecmp(ident, "ascii") == 0) return SF_ASCII; @@ -1505,7 +1512,7 @@ static SourceFormatT MatchSourceFormat(const char *ident) } // Match the source element type from a given identifier. -static ElementTypeT MatchElementType(const char *ident) +auto MatchElementType(const char *ident) -> ElementTypeT { if(al::strcasecmp(ident, "int") == 0) return ET_INT; @@ -1515,17 +1522,17 @@ static ElementTypeT MatchElementType(const char *ident) } // Parse and validate a source reference from the data set definition. -static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) +auto ReadSourceRef(TokenReaderT *tr, SourceRefT *src) -> int { - char ident[MAX_IDENT_LEN+1]; + std::array ident{}; uint line, col; double fpVal; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - src->mFormat = MatchSourceFormat(ident); + src->mFormat = MatchSourceFormat(ident.data()); if(src->mFormat == SF_NONE) { TrErrorAt(tr, line, col, "Expected a source format.\n"); @@ -1550,7 +1557,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) src->mAzimuth = fpVal; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal)) + if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return 0; src->mType = ET_NONE; src->mSize = 0; @@ -1560,7 +1567,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) } else if(src->mFormat == SF_WAVE) { - if(!TrReadInt(tr, 0, MAX_WAVE_CHANNELS, &intVal)) + if(!TrReadInt(tr, 0, MaxWaveChannels, &intVal)) return 0; src->mType = ET_NONE; src->mSize = 0; @@ -1571,9 +1578,9 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) else { TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - src->mType = MatchElementType(ident); + src->mType = MatchElementType(ident.data()); if(src->mType == ET_NONE) { TrErrorAt(tr, line, col, "Expected a source element type.\n"); @@ -1585,7 +1592,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) return 0; if(src->mType == ET_INT) { - if(!TrReadInt(tr, MIN_BIN_SIZE, MAX_BIN_SIZE, &intVal)) + if(!TrReadInt(tr, MinBinSize, MaxBinSize, &intVal)) return 0; src->mSize = static_cast(intVal); if(!TrIsOperator(tr, ",")) @@ -1596,9 +1603,9 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) TrIndication(tr, &line, &col); if(!TrReadInt(tr, -2147483647-1, 2147483647, &intVal)) return 0; - if(std::abs(intVal) < MIN_BIN_BITS || static_cast(std::abs(intVal)) > (8*src->mSize)) + if(std::abs(intVal) < int{MinBinSize}*8 || static_cast(std::abs(intVal)) > (8*src->mSize)) { - TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MIN_BIN_BITS, 8*src->mSize); + TrErrorAt(tr, line, col, "Expected a value of (+/-) %d to %d.\n", MinBinSize*8, 8*src->mSize); return 0; } src->mBits = intVal; @@ -1622,7 +1629,7 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) { if(!TrReadOperator(tr, ",")) return 0; - if(!TrReadInt(tr, MIN_ASCII_BITS, MAX_ASCII_BITS, &intVal)) + if(!TrReadInt(tr, MinASCIIBits, MaxASCIIBits, &intVal)) return 0; src->mSize = 0; src->mBits = intVal; @@ -1656,22 +1663,22 @@ static int ReadSourceRef(TokenReaderT *tr, SourceRefT *src) src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, MAX_PATH_LEN, src->mPath)) + if(!TrReadString(tr, src->mPath)) return 0; return 1; } // Parse and validate a SOFA source reference from the data set definition. -static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) +auto ReadSofaRef(TokenReaderT *tr, SourceRefT *src) -> int { - char ident[MAX_IDENT_LEN+1]; + std::array ident{}; uint line, col; int intVal; TrIndication(tr, &line, &col); - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + if(!TrReadIdent(tr, ident)) return 0; - src->mFormat = MatchSourceFormat(ident); + src->mFormat = MatchSourceFormat(ident.data()); if(src->mFormat != SF_SOFA) { TrErrorAt(tr, line, col, "Expected the SOFA source format.\n"); @@ -1695,13 +1702,13 @@ static int ReadSofaRef(TokenReaderT *tr, SourceRefT *src) src->mOffset = 0; if(!TrReadOperator(tr, ":")) return 0; - if(!TrReadString(tr, MAX_PATH_LEN, src->mPath)) + if(!TrReadString(tr, src->mPath)) return 0; return 1; } // Match the target ear (index) from a given identifier. -static int MatchTargetEar(const char *ident) +auto MatchTargetEar(const char *ident) -> int { if(al::strcasecmp(ident, "left") == 0) return 0; @@ -1712,13 +1719,13 @@ static int MatchTargetEar(const char *ident) // Calculate the onset time of an HRIR and average it with any existing // timing for its field, elevation, azimuth, and ear. -static constexpr int OnsetRateMultiple{10}; -static double AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, - const uint n, const double *hrir, const double f, const double onset) +constexpr int OnsetRateMultiple{10}; +auto AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, const uint rate, + const al::span hrir, const double f, const double onset) -> double { - rs.process(n, hrir, static_cast(upsampled.size()), upsampled.data()); + rs.process(hrir, upsampled); - auto abs_lt = [](const double &lhs, const double &rhs) -> bool + auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return Lerp(onset, static_cast(std::distance(upsampled.cbegin(), iter))/(10*rate), f); @@ -1726,36 +1733,35 @@ static double AverageHrirOnset(PPhaseResampler &rs, al::span upsampled, // Calculate the magnitude response of an HRIR and average it with any // existing responses for its field, elevation, azimuth, and ear. -static void AverageHrirMagnitude(const uint points, const uint n, const double *hrir, const double f, double *mag) +void AverageHrirMagnitude(const uint fftSize, const al::span hrir, const double f, + const al::span mag) { - uint m = 1 + (n / 2), i; - std::vector h(n); - std::vector r(n); + const uint m{1 + (fftSize/2)}; + std::vector h(fftSize); + std::vector r(m); - for(i = 0;i < points;i++) - h[i] = hrir[i]; - for(;i < n;i++) - h[i] = 0.0; - FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), r.data()); - for(i = 0;i < m;i++) + auto hiter = std::copy(hrir.cbegin(), hrir.cend(), h.begin()); + std::fill(hiter, h.end(), 0.0); + forward_fft(h); + MagnitudeResponse(h, r); + for(uint i{0};i < m;++i) mag[i] = Lerp(mag[i], r[i], f); } // Process the list of sources in the data set definition. -static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) +auto ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate) -> int { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize); - double *hrirs = hData->mHrirsBase.data(); - auto hrir = std::make_unique(hData->mIrSize); + hData->mHrirsBase.resize(size_t{channels} * hData->mIrCount * hData->mIrSize); + const auto hrirs = al::span{hData->mHrirsBase}; + auto hrir = std::vector(hData->mIrSize); uint line, col, fi, ei, ai; - std::vector onsetSamples(OnsetRateMultiple * hData->mIrPoints); + std::vector onsetSamples(size_t{OnsetRateMultiple} * hData->mIrPoints); PPhaseResampler onsetResampler; onsetResampler.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); - al::optional resampler; + std::optional resampler; if(outRate && outRate != hData->mIrRate) resampler.emplace().init(hData->mIrRate, outRate); const double rateScale{outRate ? static_cast(outRate) / hData->mIrRate : 1.0}; @@ -1768,57 +1774,50 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate int count{0}; while(TrIsOperator(tr, "[")) { - double factor[2]{ 1.0, 1.0 }; + std::array factor{1.0, 1.0}; TrIndication(tr, &line, &col); TrReadOperator(tr, "["); if(TrIsOperator(tr, "*")) { - SourceRefT src; - struct MYSOFA_EASY *sofa; - uint si; - TrReadOperator(tr, "*"); if(!TrReadOperator(tr, "]") || !TrReadOperator(tr, "=")) return 0; TrIndication(tr, &line, &col); + SourceRefT src{}; if(!ReadSofaRef(tr, &src)) return 0; if(hData->mChannelType == CT_STEREO) { - char type[MAX_IDENT_LEN+1]; - ChannelTypeT channelType; + std::array type{}; - if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + if(!TrReadIdent(tr, type)) return 0; - channelType = MatchChannelType(type); - + const ChannelTypeT channelType{MatchChannelType(type.data())}; switch(channelType) { - case CT_NONE: - TrErrorAt(tr, line, col, "Expected a channel type.\n"); - return 0; - case CT_MONO: - src.mChannel = 0; - break; - case CT_STEREO: - src.mChannel = 1; - break; + case CT_NONE: + TrErrorAt(tr, line, col, "Expected a channel type.\n"); + return 0; + case CT_MONO: + src.mChannel = 0; + break; + case CT_STEREO: + src.mChannel = 1; + break; } } else { - char type[MAX_IDENT_LEN+1]; - ChannelTypeT channelType; - - if(!TrReadIdent(tr, MAX_IDENT_LEN, type)) + std::array type{}; + if(!TrReadIdent(tr, type)) return 0; - channelType = MatchChannelType(type); + ChannelTypeT channelType{MatchChannelType(type.data())}; if(channelType != CT_MONO) { TrErrorAt(tr, line, col, "Expected a mono channel type.\n"); @@ -1827,20 +1826,19 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate src.mChannel = 0; } - sofa = LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints); + MYSOFA_EASY *sofa{LoadSofaFile(&src, hData->mIrRate, hData->mIrPoints)}; if(!sofa) return 0; - for(si = 0;si < sofa->hrtf->M;si++) + const auto srcPosValues = al::span{sofa->hrtf->SourcePosition.values, + sofa->hrtf->M*3_uz}; + for(uint si{0};si < sofa->hrtf->M;++si) { printf("\rLoading sources... %d of %d", si+1, sofa->hrtf->M); fflush(stdout); - float aer[3] = { - sofa->hrtf->SourcePosition.values[3*si], - sofa->hrtf->SourcePosition.values[3*si + 1], - sofa->hrtf->SourcePosition.values[3*si + 2] - }; - mysofa_c2s(aer); + std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], + srcPosValues[3_uz*si + 2]}; + mysofa_c2s(aer.data()); if(std::fabs(aer[1]) >= 89.999f) aer[0] = 0.0f; @@ -1870,30 +1868,33 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate continue; HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Redefinition of source [ %d, %d, %d ].\n", fi, ei, ai); return 0; } - ExtractSofaHrir(sofa, si, 0, src.mOffset, hData->mIrPoints, hrir.get()); - azd->mIrs[0] = &hrirs[hData->mIrSize * azd->mIndex]; + const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); + ExtractSofaHrir(sofa->hrtf, si, 0, src.mOffset, hrirPoints); + azd->mIrs[0] = hrirs.subspan(size_t{hData->mIrSize}*azd->mIndex, hData->mIrSize); azd->mDelays[0] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, - hData->mIrPoints, hrir.get(), 1.0, azd->mDelays[0]); + hrirPoints, 1.0, azd->mDelays[0]); if(resampler) - resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, hrir.get()); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0, azd->mIrs[0]); + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, + azd->mIrs[0]); if(src.mChannel == 1) { - ExtractSofaHrir(sofa, si, 1, src.mOffset, hData->mIrPoints, hrir.get()); - azd->mIrs[1] = &hrirs[hData->mIrSize * (hData->mIrCount + azd->mIndex)]; + ExtractSofaHrir(sofa->hrtf, si, 1, src.mOffset, hrirPoints); + azd->mIrs[1] = hrirs.subspan( + (size_t{hData->mIrCount}+azd->mIndex) * hData->mIrSize, hData->mIrSize); azd->mDelays[1] = AverageHrirOnset(onsetResampler, onsetSamples, - hData->mIrRate, hData->mIrPoints, hrir.get(), 1.0, azd->mDelays[1]); + hData->mIrRate, hrirPoints, 1.0, azd->mDelays[1]); if(resampler) - resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, - hrir.get()); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0, azd->mIrs[1]); + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.first(irPoints), 1.0, + azd->mIrs[1]); } // TODO: Since some SOFA files contain minimum phase HRIRs, @@ -1910,7 +1911,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate return 0; HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Redefinition of source.\n"); return 0; @@ -1918,10 +1919,9 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate if(!TrReadOperator(tr, "=")) return 0; - for(;;) + while(true) { - SourceRefT src; - + SourceRefT src{}; if(!ReadSourceRef(tr, &src)) return 0; @@ -1932,29 +1932,30 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate printf("\rLoading sources... %d file%s", count, (count==1)?"":"s"); fflush(stdout); - if(!LoadSource(&src, hData->mIrRate, hData->mIrPoints, hrir.get())) + if(!LoadSource(&src, hData->mIrRate, al::span{hrir}.first(hData->mIrPoints))) return 0; uint ti{0}; if(hData->mChannelType == CT_STEREO) { - char ident[MAX_IDENT_LEN+1]; - - if(!TrReadIdent(tr, MAX_IDENT_LEN, ident)) + std::array ident{}; + if(!TrReadIdent(tr, ident)) return 0; - ti = static_cast(MatchTargetEar(ident)); + ti = static_cast(MatchTargetEar(ident.data())); if(static_cast(ti) < 0) { TrErrorAt(tr, line, col, "Expected a target ear.\n"); return 0; } } - azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)]; + const auto hrirPoints = al::span{hrir}.first(hData->mIrPoints); + azd->mIrs[ti] = hrirs.subspan((ti*size_t{hData->mIrCount}+azd->mIndex)*hData->mIrSize, + hData->mIrSize); azd->mDelays[ti] = AverageHrirOnset(onsetResampler, onsetSamples, hData->mIrRate, - hData->mIrPoints, hrir.get(), 1.0 / factor[ti], azd->mDelays[ti]); + hrirPoints, 1.0/factor[ti], azd->mDelays[ti]); if(resampler) - resampler->process(hData->mIrPoints, hrir.get(), hData->mIrSize, hrir.get()); - AverageHrirMagnitude(irPoints, hData->mFftSize, hrir.get(), 1.0 / factor[ti], + resampler->process(hrirPoints, hrir); + AverageHrirMagnitude(hData->mFftSize, al::span{hrir}.subspan(irPoints), 1.0/factor[ti], azd->mIrs[ti]); factor[ti] += 1.0; if(!TrIsOperator(tr, "+")) @@ -1963,12 +1964,12 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } if(hData->mChannelType == CT_STEREO) { - if(azd->mIrs[0] == nullptr) + if(azd->mIrs[0].empty()) { TrErrorAt(tr, line, col, "Missing left ear source reference(s).\n"); return 0; } - else if(azd->mIrs[1] == nullptr) + if(azd->mIrs[1].empty()) { TrErrorAt(tr, line, col, "Missing right ear source reference(s).\n"); return 0; @@ -1976,7 +1977,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate } } printf("\n"); - hrir = nullptr; + hrir.clear(); if(resampler) { hData->mIrRate = outRate; @@ -1990,7 +1991,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + if(!azd->mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) @@ -2008,7 +2009,7 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] == nullptr) + if(azd->mIrs[0].empty()) { TrError(tr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); return 0; @@ -2025,31 +2026,33 @@ static int ProcessSources(TokenReaderT *tr, HrirDataT *hData, const uint outRate for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - - azd->mIrs[ti] = &hrirs[hData->mIrSize * (ti * hData->mIrCount + azd->mIndex)]; + azd->mIrs[ti] = hrirs.subspan( + (ti*size_t{hData->mIrCount} + azd->mIndex) * hData->mIrSize, + hData->mIrSize); } } } } if(!TrLoad(tr)) { - mysofa_cache_release_all(); + gSofaCache.clear(); return 1; } TrError(tr, "Errant data at end of source list.\n"); - mysofa_cache_release_all(); + gSofaCache.clear(); return 0; } +} /* namespace */ -bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const uint outRate, +bool LoadDefInput(std::istream &istream, const al::span startbytes, + const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { TokenReaderT tr{istream}; - TrSetup(startbytes, startbytecount, filename, &tr); + TrSetup(startbytes, filename, &tr); if(!ProcessMetrics(&tr, fftSize, truncSize, chanMode, hData) || !ProcessSources(&tr, hData, outRate)) return false; diff --git a/Engine/lib/openal-soft/utils/makemhr/loaddef.h b/Engine/lib/openal-soft/utils/makemhr/loaddef.h index 63600dcd3..3eafc8254 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loaddef.h +++ b/Engine/lib/openal-soft/utils/makemhr/loaddef.h @@ -2,12 +2,15 @@ #define LOADDEF_H #include +#include + +#include "alspan.h" #include "makemhr.h" -bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount, - const char *filename, const uint fftSize, const uint truncSize, const uint outRate, +bool LoadDefInput(std::istream &istream, const al::span startbytes, + const std::string_view filename, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADDEF_H */ diff --git a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp index dcb0a35ed..903019d86 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/loadsofa.cpp @@ -33,12 +33,15 @@ #include #include #include +#include #include +#include #include #include -#include "aloptional.h" #include "alspan.h" +#include "alstring.h" +#include "alnumeric.h" #include "makemhr.h" #include "polyphase_resampler.h" #include "sofa-support.h" @@ -46,6 +49,9 @@ #include "mysofa.h" +namespace { + +using namespace std::string_view_literals; using uint = unsigned int; /* Attempts to produce a compatible layout. Most data sets tend to be @@ -54,19 +60,19 @@ using uint = unsigned int; * possible. Those sets that contain purely random measurements or use * different major axes will fail. */ -static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) +auto PrepareLayout(const al::span xyzs, HrirDataT *hData) -> bool { fprintf(stdout, "Detecting compatible layout...\n"); - auto fds = GetCompatibleLayout(m, xyzs); + auto fds = GetCompatibleLayout(xyzs); if(fds.size() > MAX_FD_COUNT) { fprintf(stdout, "Incompatible layout (inumerable radii).\n"); return false; } - double distances[MAX_FD_COUNT]{}; - uint evCounts[MAX_FD_COUNT]{}; + std::array distances{}; + std::array evCounts{}; auto azCounts = std::vector>(MAX_FD_COUNT); for(auto &azs : azCounts) azs.fill(0u); @@ -86,12 +92,11 @@ static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData) ++fi; } - fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m); - const auto azs = al::as_span(azCounts).first(); - return PrepareHrirData({distances, fi}, evCounts, azs, hData); + fprintf(stdout, "Using %u of %zu IRs.\n", ir_total, xyzs.size()/3); + const auto azs = al::span{azCounts}.first(); + return PrepareHrirData(al::span{distances}.first(fi), evCounts, azs, hData); } - float GetSampleRate(MYSOFA_HRTF *sofaHrtf) { const char *srate_dim{nullptr}; @@ -100,7 +105,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes}; while(srate_attrs) { - if(std::string{"DIMENSION_LIST"} == srate_attrs->name) + if("DIMENSION_LIST"sv == srate_attrs->name) { if(srate_dim) { @@ -109,7 +114,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) } srate_dim = srate_attrs->value; } - else if(std::string{"Units"} == srate_attrs->name) + else if("Units"sv == srate_attrs->name) { if(srate_units) { @@ -128,7 +133,7 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing sample rate dimensions\n"); return 0.0f; } - if(srate_dim != std::string{"I"}) + if(srate_dim != "I"sv) { fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim); return 0.0f; @@ -138,40 +143,40 @@ float GetSampleRate(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing sample rate unit type\n"); return 0.0f; } - if(srate_units != std::string{"hertz"}) + if(srate_units != "hertz"sv) { fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units); return 0.0f; } /* I dimensions guarantees 1 element, so just extract it. */ - if(srate_array->values[0] < MIN_RATE || srate_array->values[0] > MAX_RATE) + const auto values = al::span{srate_array->values, sofaHrtf->I}; + if(values[0] < float{MIN_RATE} || values[0] > float{MAX_RATE}) { - fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", srate_array->values[0], - MIN_RATE, MAX_RATE); + fprintf(stderr, "Sample rate out of range: %f (expected %u to %u)", values[0], MIN_RATE, + MAX_RATE); return 0.0f; } - return srate_array->values[0]; + return values[0]; } enum class DelayType : uint8_t { None, I_R, /* [1][Channels] */ M_R, /* [HRIRs][Channels] */ - Invalid, }; -DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) +auto PrepareDelay(MYSOFA_HRTF *sofaHrtf) -> std::optional { const char *delay_dim{nullptr}; MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay}; MYSOFA_ATTRIBUTE *delay_attrs{delay_array->attributes}; while(delay_attrs) { - if(std::string{"DIMENSION_LIST"} == delay_attrs->name) + if("DIMENSION_LIST"sv == delay_attrs->name) { if(delay_dim) { fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n"); - return DelayType::Invalid; + return std::nullopt; } delay_dim = delay_attrs->value; } @@ -185,13 +190,13 @@ DelayType PrepareDelay(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing delay dimensions\n"); return DelayType::None; } - if(delay_dim == std::string{"I,R"}) + if(delay_dim == "I,R"sv) return DelayType::I_R; - else if(delay_dim == std::string{"M,R"}) + if(delay_dim == "M,R"sv) return DelayType::M_R; fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim); - return DelayType::Invalid; + return std::nullopt; } bool CheckIrData(MYSOFA_HRTF *sofaHrtf) @@ -201,7 +206,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes}; while(ir_attrs) { - if(std::string{"DIMENSION_LIST"} == ir_attrs->name) + if("DIMENSION_LIST"sv == ir_attrs->name) { if(ir_dim) { @@ -220,7 +225,7 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) fprintf(stderr, "Missing IR dimensions\n"); return false; } - if(ir_dim != std::string{"M,R,N"}) + if(ir_dim != "M,R,N"sv) { fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim); return false; @@ -230,13 +235,13 @@ bool CheckIrData(MYSOFA_HRTF *sofaHrtf) /* Calculate the onset time of a HRIR. */ -static constexpr int OnsetRateMultiple{10}; -static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, - al::span upsampled, const double *hrir) +constexpr int OnsetRateMultiple{10}; +auto CalcHrirOnset(PPhaseResampler &rs, const uint rate, al::span upsampled, + const al::span hrir) -> double { - rs.process(n, hrir, static_cast(upsampled.size()), upsampled.data()); + rs.process(hrir, upsampled); - auto abs_lt = [](const double &lhs, const double &rhs) -> bool + auto abs_lt = [](const double lhs, const double rhs) -> bool { return std::abs(lhs) < std::abs(rhs); }; auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt); return static_cast(std::distance(upsampled.cbegin(), iter)) / @@ -244,16 +249,16 @@ static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n, } /* Calculate the magnitude response of a HRIR. */ -static void CalcHrirMagnitude(const uint points, const uint n, al::span h, double *hrir) +void CalcHrirMagnitude(const uint points, al::span h, const al::span hrir) { - auto iter = std::copy_n(hrir, points, h.begin()); + auto iter = std::copy_n(hrir.cbegin(), points, h.begin()); std::fill(iter, h.end(), complex_d{0.0, 0.0}); - FftForward(n, h.data()); - MagnitudeResponse(n, h.data(), hrir); + forward_fft(h); + MagnitudeResponse(h, hrir.first((h.size()/2) + 1)); } -static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType, +bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayType delayType, const uint outRate) { std::atomic loaded_count{0u}; @@ -261,27 +266,27 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy auto load_proc = [sofaHrtf,hData,delayType,outRate,&loaded_count]() -> bool { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize, 0.0); - double *hrirs = hData->mHrirsBase.data(); + hData->mHrirsBase.resize(channels * size_t{hData->mIrCount} * hData->mIrSize, 0.0); + const auto hrirs = al::span{hData->mHrirsBase}; - std::unique_ptr restmp; - al::optional resampler; + std::vector restmp; + std::optional resampler; if(outRate && outRate != hData->mIrRate) { resampler.emplace().init(hData->mIrRate, outRate); - restmp = std::make_unique(sofaHrtf->N); + restmp.resize(sofaHrtf->N); } + const auto srcPosValues = al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}; + const auto irValues = al::span{sofaHrtf->DataIR.values, + size_t{sofaHrtf->M}*sofaHrtf->R*sofaHrtf->N}; for(uint si{0u};si < sofaHrtf->M;++si) { loaded_count.fetch_add(1u); - float aer[3]{ - sofaHrtf->SourcePosition.values[3*si], - sofaHrtf->SourcePosition.values[3*si + 1], - sofaHrtf->SourcePosition.values[3*si + 2] - }; - mysofa_c2s(aer); + std::array aer{srcPosValues[3_uz*si], srcPosValues[3_uz*si + 1], + srcPosValues[3_uz*si + 2]}; + mysofa_c2s(aer.data()); if(std::abs(aer[1]) >= 89.999f) aer[0] = 0.0f; @@ -307,8 +312,8 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy ai %= static_cast(field->mEvs[ei].mAzs.size()); if(std::abs(af) >= 0.1) continue; - HrirAzT *azd = &field->mEvs[ei].mAzs[ai]; - if(azd->mIrs[0] != nullptr) + HrirAzT &azd = field->mEvs[ei].mAzs[ai]; + if(!azd.mIrs[0].empty()) { fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n", aer[0], aer[1], aer[2]); @@ -317,30 +322,33 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy for(uint ti{0u};ti < channels;++ti) { - azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)]; + azd.mIrs[ti] = hrirs.subspan( + (size_t{hData->mIrCount}*ti + azd.mIndex) * hData->mIrSize, hData->mIrSize); + const auto ir = irValues.subspan((size_t{si}*sofaHrtf->R + ti)*sofaHrtf->N, + sofaHrtf->N); if(!resampler) - std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], - sofaHrtf->N, azd->mIrs[ti]); + std::copy_n(ir.cbegin(), ir.size(), azd.mIrs[ti].begin()); else { - std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N], - sofaHrtf->N, restmp.get()); - resampler->process(sofaHrtf->N, restmp.get(), hData->mIrSize, azd->mIrs[ti]); + std::copy_n(ir.cbegin(), ir.size(), restmp.begin()); + resampler->process(restmp, azd.mIrs[ti]); } } /* Include any per-channel or per-HRIR delays. */ if(delayType == DelayType::I_R) { - const float *delayValues{sofaHrtf->DataDelay.values}; + const auto delayValues = al::span{sofaHrtf->DataDelay.values, + size_t{sofaHrtf->I}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) - azd->mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); + azd.mDelays[ti] = delayValues[ti] / static_cast(hData->mIrRate); } else if(delayType == DelayType::M_R) { - const float *delayValues{sofaHrtf->DataDelay.values}; + const auto delayValues = al::span{sofaHrtf->DataDelay.values, + size_t{sofaHrtf->M}*sofaHrtf->R}; for(uint ti{0u};ti < channels;++ti) - azd->mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / + azd.mDelays[ti] = delayValues[si*sofaHrtf->R + ti] / static_cast(hData->mIrRate); } } @@ -374,7 +382,7 @@ static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData, const DelayTy struct MagCalculator { const uint mFftSize{}; const uint mIrPoints{}; - std::vector mIrs{}; + std::vector> mIrs{}; std::atomic mCurrent{}; std::atomic mDone{}; @@ -382,7 +390,7 @@ struct MagCalculator { { auto htemp = std::vector(mFftSize); - while(1) + while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; @@ -397,7 +405,7 @@ struct MagCalculator { */ } while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed)); - CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]); + CalcHrirMagnitude(mIrPoints, htemp, mIrs[idx]); /* Increment the number of IRs done. */ mDone.fetch_add(1); @@ -405,22 +413,25 @@ struct MagCalculator { } }; -bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, +} // namespace + +bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData) { int err; - MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)}; + MySofaHrtfPtr sofaHrtf{mysofa_load(std::string{filename}.c_str(), &err)}; if(!sofaHrtf) { - fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err)); + fprintf(stdout, "Error: Could not load %.*s: %s\n", al::sizei(filename), filename.data(), + SofaErrorStr(err)); return false; } /* NOTE: Some valid SOFA files are failing this check. */ err = mysofa_check(sofaHrtf.get()); if(err != MYSOFA_OK) - fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename, - SofaErrorStr(err)); + fprintf(stderr, "Warning: Supposedly malformed source file '%.*s' (%s).\n", + al::sizei(filename), filename.data(), SofaErrorStr(err)); mysofa_tocartesian(sofaHrtf.get()); @@ -459,19 +470,19 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz /* Assume a default head radius of 9cm. */ hData->mRadius = 0.09; - hData->mIrRate = static_cast(GetSampleRate(sofaHrtf.get()) + 0.5f); + hData->mIrRate = static_cast(std::lround(GetSampleRate(sofaHrtf.get()))); if(!hData->mIrRate) return false; - DelayType delayType = PrepareDelay(sofaHrtf.get()); - if(delayType == DelayType::Invalid) + const auto delayType = PrepareDelay(sofaHrtf.get()); + if(!delayType) return false; if(!CheckIrData(sofaHrtf.get())) return false; - if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData)) + if(!PrepareLayout(al::span{sofaHrtf->SourcePosition.values, sofaHrtf->M*3_uz}, hData)) return false; - if(!LoadResponses(sofaHrtf.get(), hData, delayType, outRate)) + if(!LoadResponses(sofaHrtf.get(), hData, *delayType, outRate)) return false; sofaHrtf = nullptr; @@ -484,7 +495,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd.mIrs[0] != nullptr) break; + if(!azd.mIrs[0].empty()) break; } if(ai < hData->mFds[fi].mEvs[ei].mAzs.size()) break; @@ -500,7 +511,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - if(azd.mIrs[0] == nullptr) + if(azd.mIrs[0].empty()) { fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai); return false; @@ -512,7 +523,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz size_t hrir_total{0}; const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; - double *hrirs = hData->mHrirsBase.data(); + const auto hrirs = al::span{hData->mHrirsBase}; for(uint fi{0u};fi < hData->mFds.size();fi++) { for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++) @@ -520,8 +531,9 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) { HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai]; - for(uint ti{0u};ti < channels;ti++) - azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)]; + for(size_t ti{0u};ti < channels;ti++) + azd.mIrs[ti] = hrirs.subspan((hData->mIrCount*ti + azd.mIndex)*hData->mIrSize, + hData->mIrSize); } } @@ -533,7 +545,7 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz auto onset_proc = [hData,channels,&hrir_done]() -> bool { /* Temporary buffer used to calculate the IR's onset. */ - auto upsampled = std::vector(OnsetRateMultiple * hData->mIrPoints); + auto upsampled = std::vector(size_t{OnsetRateMultiple} * hData->mIrPoints); /* This resampler is used to help detect the response onset. */ PPhaseResampler rs; rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate); @@ -547,8 +559,8 @@ bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSiz for(uint ti{0};ti < channels;ti++) { hrir_done.fetch_add(1u, std::memory_order_acq_rel); - azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints, - upsampled, azd.mIrs[ti]); + azd.mDelays[ti] += CalcHrirOnset(rs, hData->mIrRate, upsampled, + azd.mIrs[ti].first(hData->mIrPoints)); } } } diff --git a/Engine/lib/openal-soft/utils/makemhr/loadsofa.h b/Engine/lib/openal-soft/utils/makemhr/loadsofa.h index 82dce85a0..90d77a0f8 100644 --- a/Engine/lib/openal-soft/utils/makemhr/loadsofa.h +++ b/Engine/lib/openal-soft/utils/makemhr/loadsofa.h @@ -1,10 +1,12 @@ #ifndef LOADSOFA_H #define LOADSOFA_H +#include + #include "makemhr.h" -bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize, +bool LoadSofaFile(const std::string_view filename, const uint numThreads, const uint fftSize, const uint truncSize, const uint outRate, const ChannelModeT chanMode, HrirDataT *hData); #endif /* LOADSOFA_H */ diff --git a/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp b/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp index ae301dc33..b6bafba93 100644 --- a/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp +++ b/Engine/lib/openal-soft/utils/makemhr/makemhr.cpp @@ -59,7 +59,7 @@ * 1999 */ -#define _UNICODE +#define _UNICODE /* NOLINT(bugprone-reserved-identifier) */ #include "config.h" #include "makemhr.h" @@ -73,23 +73,21 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include -#ifdef HAVE_GETOPT -#include -#else -#include "../getopt.h" -#endif - #include "alcomplex.h" -#include "alfstream.h" +#include "alnumbers.h" +#include "alnumeric.h" #include "alspan.h" #include "alstring.h" #include "loaddef.h" @@ -98,61 +96,61 @@ #include "win_main_utf8.h" -namespace { - -using namespace std::placeholders; - -} // namespace - -#ifndef M_PI -#define M_PI (3.14159265358979323846) -#endif - - HrirDataT::~HrirDataT() = default; -// Head model used for calculating the impulse delays. -enum HeadModelT { - HM_NONE, - HM_DATASET, // Measure the onset from the dataset. - HM_SPHERE // Calculate the onset using a spherical head model. -}; +namespace { +using namespace std::string_view_literals; + +struct FileDeleter { + void operator()(gsl::owner f) { fclose(f); } +}; +using FilePtr = std::unique_ptr; // The epsilon used to maintain signal stability. -#define EPSILON (1e-9) +constexpr double Epsilon{1e-9}; // The limits to the FFT window size override on the command line. -#define MIN_FFTSIZE (65536) -#define MAX_FFTSIZE (131072) +constexpr uint MinFftSize{65536}; +constexpr uint MaxFftSize{131072}; // The limits to the equalization range limit on the command line. -#define MIN_LIMIT (2.0) -#define MAX_LIMIT (120.0) +constexpr double MinLimit{2.0}; +constexpr double MaxLimit{120.0}; // The limits to the truncation window size on the command line. -#define MIN_TRUNCSIZE (16) -#define MAX_TRUNCSIZE (128) +constexpr uint MinTruncSize{16}; +constexpr uint MaxTruncSize{128}; // The limits to the custom head radius on the command line. -#define MIN_CUSTOM_RADIUS (0.05) -#define MAX_CUSTOM_RADIUS (0.15) - -// The defaults for the command line options. -#define DEFAULT_FFTSIZE (65536) -#define DEFAULT_EQUALIZE (1) -#define DEFAULT_SURFACE (1) -#define DEFAULT_LIMIT (24.0) -#define DEFAULT_TRUNCSIZE (64) -#define DEFAULT_HEAD_MODEL (HM_DATASET) -#define DEFAULT_CUSTOM_RADIUS (0.0) +constexpr double MinCustomRadius{0.05}; +constexpr double MaxCustomRadius{0.15}; // The maximum propagation delay value supported by OpenAL Soft. -#define MAX_HRTD (63.0) +constexpr double MaxHrtd{63.0}; // The OpenAL Soft HRTF format marker. It stands for minimum-phase head // response protocol 03. -#define MHR_FORMAT ("MinPHR03") +constexpr auto GetMHRMarker() noexcept { return "MinPHR03"sv; } + + +// Head model used for calculating the impulse delays. +enum HeadModelT { + HM_None, + HM_Dataset, // Measure the onset from the dataset. + HM_Sphere, // Calculate the onset using a spherical head model. + + HM_Default = HM_Dataset +}; + + +// The defaults for the command line options. +constexpr uint DefaultFftSize{65536}; +constexpr bool DefaultEqualize{true}; +constexpr bool DefaultSurface{true}; +constexpr double DefaultLimit{24.0}; +constexpr uint DefaultTruncSize{64}; +constexpr double DefaultCustomRadius{0.0}; /* Channel index enums. Mono uses LeftChannel only. */ enum ChannelIndex : uint { @@ -165,29 +163,28 @@ enum ChannelIndex : uint { * pattern string are replaced with the replacement string. The result is * truncated if necessary. */ -static std::string StrSubst(al::span in, const al::span pat, - const al::span rep) +auto StrSubst(std::string_view in, const std::string_view pat, const std::string_view rep) -> std::string { std::string ret; ret.reserve(in.size() + pat.size()); while(in.size() >= pat.size()) { - if(al::strncasecmp(in.data(), pat.data(), pat.size()) == 0) + if(al::starts_with(in, pat)) { - in = in.subspan(pat.size()); - ret.append(rep.data(), rep.size()); + in = in.substr(pat.size()); + ret += rep; } else { size_t endpos{1}; - while(endpos < in.size() && in[endpos] != pat.front()) + while(endpos < in.size() && std::toupper(in[endpos]) != std::toupper(pat.front())) ++endpos; - ret.append(in.data(), endpos); - in = in.subspan(endpos); + ret += in.substr(0, endpos); + in = in.substr(endpos); } } - ret.append(in.data(), in.size()); + ret += in; return ret; } @@ -198,12 +195,12 @@ static std::string StrSubst(al::span in, const al::span *********************/ // Simple clamp routine. -static double Clamp(const double val, const double lower, const double upper) +double Clamp(const double val, const double lower, const double upper) { return std::min(std::max(val, lower), upper); } -static inline uint dither_rng(uint *seed) +inline uint dither_rng(uint *seed) { *seed = *seed * 96314165 + 907633515; return *seed; @@ -211,69 +208,44 @@ static inline uint dither_rng(uint *seed) // Performs a triangular probability density function dither. The input samples // should be normalized (-1 to +1). -static void TpdfDither(double *RESTRICT out, const double *RESTRICT in, const double scale, - const uint count, const uint step, uint *seed) +void TpdfDither(const al::span out, const al::span in, const double scale, + const size_t channel, const size_t step, uint *seed) { static constexpr double PRNG_SCALE = 1.0 / std::numeric_limits::max(); + assert(channel < step); - for(uint i{0};i < count;i++) + for(size_t i{0};i < in.size();++i) { uint prn0{dither_rng(seed)}; uint prn1{dither_rng(seed)}; - *out = std::round(*(in++)*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); - out += step; + out[i*step + channel] = std::round(in[i]*scale + (prn0*PRNG_SCALE - prn1*PRNG_SCALE)); } } - -/* Calculate the complex helical sequence (or discrete-time analytical signal) - * of the given input using the Hilbert transform. Given the natural logarithm - * of a signal's magnitude response, the imaginary components can be used as - * the angles for minimum-phase reconstruction. - */ -inline static void Hilbert(const uint n, complex_d *inout) -{ complex_hilbert({inout, n}); } - -/* Calculate the magnitude response of the given input. This is used in - * place of phase decomposition, since the phase residuals are discarded for - * minimum phase reconstruction. The mirrored half of the response is also - * discarded. - */ -void MagnitudeResponse(const uint n, const complex_d *in, double *out) -{ - const uint m = 1 + (n / 2); - uint i; - for(i = 0;i < m;i++) - out[i] = std::max(std::abs(in[i]), EPSILON); -} - /* Apply a range limit (in dB) to the given magnitude response. This is used * to adjust the effects of the diffuse-field average on the equalization * process. */ -static void LimitMagnitudeResponse(const uint n, const uint m, const double limit, const double *in, double *out) +void LimitMagnitudeResponse(const uint n, const uint m, const double limit, + const al::span inout) { - double halfLim; - uint i, lower, upper; - double ave; - - halfLim = limit / 2.0; + const double halfLim{limit / 2.0}; // Convert the response to dB. - for(i = 0;i < m;i++) - out[i] = 20.0 * std::log10(in[i]); + for(uint i{0};i < m;++i) + inout[i] = 20.0 * std::log10(inout[i]); // Use six octaves to calculate the average magnitude of the signal. - lower = (static_cast(std::ceil(n / std::pow(2.0, 8.0)))) - 1; - upper = (static_cast(std::floor(n / std::pow(2.0, 2.0)))) - 1; - ave = 0.0; - for(i = lower;i <= upper;i++) - ave += out[i]; + const auto lower = (static_cast(std::ceil(n / std::pow(2.0, 8.0)))) - 1; + const auto upper = (static_cast(std::floor(n / std::pow(2.0, 2.0)))) - 1; + double ave{0.0}; + for(uint i{lower};i <= upper;++i) + ave += inout[i]; ave /= upper - lower + 1; // Keep the response within range of the average magnitude. - for(i = 0;i < m;i++) - out[i] = Clamp(out[i], ave - halfLim, ave + halfLim); + for(uint i{0};i < m;++i) + inout[i] = Clamp(inout[i], ave - halfLim, ave + halfLim); // Convert the response back to linear magnitude. - for(i = 0;i < m;i++) - out[i] = std::pow(10.0, out[i] / 20.0); + for(uint i{0};i < m;++i) + inout[i] = std::pow(10.0, inout[i] / 20.0); } /* Reconstructs the minimum-phase component for the given magnitude response @@ -281,26 +253,24 @@ static void LimitMagnitudeResponse(const uint n, const uint m, const double limi * residuals (which were discarded). The mirrored half of the response is * reconstructed. */ -static void MinimumPhase(const uint n, double *mags, complex_d *out) +void MinimumPhase(const al::span mags, const al::span out) { - const uint m{(n/2) + 1}; + assert(mags.size() == out.size()); + const size_t m{(mags.size()/2) + 1}; - uint i; + size_t i; for(i = 0;i < m;i++) out[i] = std::log(mags[i]); - for(;i < n;i++) + for(;i < mags.size();++i) { - mags[i] = mags[n - i]; - out[i] = out[n - i]; + mags[i] = mags[mags.size() - i]; + out[i] = out[mags.size() - i]; } - Hilbert(n, out); + complex_hilbert(out); // Remove any DC offset the filter has. - mags[0] = EPSILON; - for(i = 0;i < n;i++) - { - auto a = std::exp(complex_d{0.0, out[i].imag()}); - out[i] = a * mags[i]; - } + mags[0] = Epsilon; + for(i = 0;i < mags.size();++i) + out[i] = std::polar(mags[i], out[i].imag()); } @@ -309,15 +279,12 @@ static void MinimumPhase(const uint n, double *mags, complex_d *out) ***************************/ // Write an ASCII string to a file. -static int WriteAscii(const char *out, FILE *fp, const char *filename) +auto WriteAscii(const std::string_view out, std::ostream &ostream, const std::string_view filename) -> int { - size_t len; - - len = strlen(out); - if(fwrite(out, 1, len, fp) != len) + if(!ostream.write(out.data(), std::streamsize(out.size())) || ostream.bad()) { - fclose(fp); - fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad write to file '%.*s'.\n", al::sizei(filename), + filename.data()); return 0; } return 1; @@ -325,105 +292,104 @@ static int WriteAscii(const char *out, FILE *fp, const char *filename) // Write a binary value of the given byte order and byte size to a file, // loading it from a 32-bit unsigned integer. -static int WriteBin4(const uint bytes, const uint32_t in, FILE *fp, const char *filename) +auto WriteBin4(const uint bytes, const uint32_t in, std::ostream &ostream, + const std::string_view filename) -> int { - uint8_t out[4]; - uint i; + std::array out{}; + for(uint i{0};i < bytes;i++) + out[i] = static_cast((in>>(i*8)) & 0x000000FF); - for(i = 0;i < bytes;i++) - out[i] = (in>>(i*8)) & 0x000000FF; - - if(fwrite(out, 1, bytes, fp) != bytes) + if(!ostream.write(out.data(), std::streamsize(bytes)) || ostream.bad()) { - fprintf(stderr, "\nError: Bad write to file '%s'.\n", filename); + fprintf(stderr, "\nError: Bad write to file '%.*s'.\n", al::sizei(filename), + filename.data()); return 0; } return 1; } // Store the OpenAL Soft HRTF data set. -static int StoreMhr(const HrirDataT *hData, const char *filename) +auto StoreMhr(const HrirDataT *hData, const std::string_view filename) -> bool { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; const uint n{hData->mIrPoints}; uint dither_seed{22222}; - uint fi, ei, ai, i; - FILE *fp; - if((fp=fopen(filename, "wb")) == nullptr) + std::ofstream ostream{std::filesystem::u8path(filename)}; + if(!ostream.is_open()) { - fprintf(stderr, "\nError: Could not open MHR file '%s'.\n", filename); - return 0; + fprintf(stderr, "\nError: Could not open MHR file '%.*s'.\n", al::sizei(filename), + filename.data()); + return false; } - if(!WriteAscii(MHR_FORMAT, fp, filename)) - return 0; - if(!WriteBin4(4, hData->mIrRate, fp, filename)) - return 0; - if(!WriteBin4(1, static_cast(hData->mChannelType), fp, filename)) - return 0; - if(!WriteBin4(1, hData->mIrPoints, fp, filename)) - return 0; - if(!WriteBin4(1, static_cast(hData->mFds.size()), fp, filename)) - return 0; - for(fi = static_cast(hData->mFds.size()-1);fi < hData->mFds.size();fi--) + if(!WriteAscii(GetMHRMarker(), ostream, filename)) + return false; + if(!WriteBin4(4, hData->mIrRate, ostream, filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mChannelType), ostream, filename)) + return false; + if(!WriteBin4(1, hData->mIrPoints, ostream, filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mFds.size()), ostream, filename)) + return false; + for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { auto fdist = static_cast(std::round(1000.0 * hData->mFds[fi].mDistance)); - if(!WriteBin4(2, fdist, fp, filename)) - return 0; - if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), fp, filename)) - return 0; - for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) + if(!WriteBin4(2, fdist, ostream, filename)) + return false; + if(!WriteBin4(1, static_cast(hData->mFds[fi].mEvs.size()), ostream, filename)) + return false; + for(size_t ei{0};ei < hData->mFds[fi].mEvs.size();++ei) { const auto &elev = hData->mFds[fi].mEvs[ei]; - if(!WriteBin4(1, static_cast(elev.mAzs.size()), fp, filename)) - return 0; + if(!WriteBin4(1, static_cast(elev.mAzs.size()), ostream, filename)) + return false; } } - for(fi = static_cast(hData->mFds.size()-1);fi < hData->mFds.size();fi--) + for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { - constexpr double scale{8388607.0}; - constexpr uint bps{3u}; + static constexpr double scale{8388607.0}; + static constexpr uint bps{3u}; - for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) + for(const auto &evd : hData->mFds[fi].mEvs) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) + for(const auto &azd : evd.mAzs) { - HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; - double out[2 * MAX_TRUNCSIZE]; + std::array out{}; - TpdfDither(out, azd->mIrs[0], scale, n, channels, &dither_seed); + TpdfDither(out, azd.mIrs[0].first(n), scale, 0, channels, &dither_seed); if(hData->mChannelType == CT_STEREO) - TpdfDither(out+1, azd->mIrs[1], scale, n, channels, &dither_seed); - for(i = 0;i < (channels * n);i++) + TpdfDither(out, azd.mIrs[1].first(n), scale, 1, channels, &dither_seed); + const size_t numsamples{size_t{channels} * n}; + for(size_t i{0};i < numsamples;i++) { const auto v = static_cast(Clamp(out[i], -scale-1.0, scale)); - if(!WriteBin4(bps, static_cast(v), fp, filename)) - return 0; + if(!WriteBin4(bps, static_cast(v), ostream, filename)) + return false; } } } } - for(fi = static_cast(hData->mFds.size()-1);fi < hData->mFds.size();fi--) + for(size_t fi{hData->mFds.size()-1};fi < hData->mFds.size();--fi) { /* Delay storage has 2 bits of extra precision. */ - constexpr double DelayPrecScale{4.0}; - for(ei = 0;ei < hData->mFds[fi].mEvs.size();ei++) + static constexpr double DelayPrecScale{4.0}; + for(const auto &evd : hData->mFds[fi].mEvs) { - for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) + for(const auto &azd : evd.mAzs) { auto v = static_cast(std::round(azd.mDelays[0]*DelayPrecScale)); - if(!WriteBin4(1, v, fp, filename)) return 0; + if(!WriteBin4(1, v, ostream, filename)) return false; if(hData->mChannelType == CT_STEREO) { v = static_cast(std::round(azd.mDelays[1]*DelayPrecScale)); - if(!WriteBin4(1, v, fp, filename)) return 0; + if(!WriteBin4(1, v, ostream, filename)) return false; } } } } - fclose(fp); - return 1; + return true; } @@ -435,23 +401,20 @@ static int StoreMhr(const HrirDataT *hData, const char *filename) * independently normalizing each field in relation to the overall maximum. * This is done to ignore distance attenuation. */ -static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m) +void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, const uint m) { - double maxMags[MAX_FD_COUNT]; - uint fi, ei, ti, i; - + std::array maxMags{}; double maxMag{0.0}; - for(fi = 0;fi < hData->mFds.size();fi++) - { - maxMags[fi] = 0.0; - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t fi{0};fi < hData->mFds.size();++fi) + { + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) maxMags[fi] = std::max(azd.mIrs[ti][i], maxMags[fi]); } } @@ -460,17 +423,17 @@ static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, maxMag = std::max(maxMags[fi], maxMag); } - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { const double magFactor{maxMag / maxMags[fi]}; - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(const auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) azd.mIrs[ti][i] *= magFactor; } } @@ -482,7 +445,7 @@ static void BalanceFieldMagnitudes(const HrirDataT *hData, const uint channels, * on its coverage volume. All volumes are centered at the spherical HRIR * coordinates and measured by extruded solid angle. */ -static void CalculateDfWeights(const HrirDataT *hData, double *weights) +void CalculateDfWeights(const HrirDataT *hData, const al::span weights) { double sum, innerRa, outerRa, evs, ev, upperEv, lowerEv; double solidAngle, solidVolume; @@ -502,17 +465,17 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) outerRa = 10.0f; const double raPowDiff{std::pow(outerRa, 3.0) - std::pow(innerRa, 3.0)}; - evs = M_PI / 2.0 / static_cast(hData->mFds[fi].mEvs.size() - 1); + evs = al::numbers::pi / 2.0 / static_cast(hData->mFds[fi].mEvs.size() - 1); for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) { const auto &elev = hData->mFds[fi].mEvs[ei]; // For each elevation, calculate the upper and lower limits of // the patch band. ev = elev.mElevation; - lowerEv = std::max(-M_PI / 2.0, ev - evs); - upperEv = std::min(M_PI / 2.0, ev + evs); + lowerEv = std::max(-al::numbers::pi / 2.0, ev - evs); + upperEv = std::min(al::numbers::pi / 2.0, ev + evs); // Calculate the surface area of the patch band. - solidAngle = 2.0 * M_PI * (std::sin(upperEv) - std::sin(lowerEv)); + solidAngle = 2.0 * al::numbers::pi * (std::sin(upperEv) - std::sin(lowerEv)); // Then the volume of the extruded patch band. solidVolume = solidAngle * raPowDiff / 3.0; // Each weight is the volume of one extruded patch. @@ -538,16 +501,16 @@ static void CalculateDfWeights(const HrirDataT *hData, double *weights) * coverage of each HRIR. The final average can then be limited by the * specified magnitude range (in positive dB; 0.0 to skip). */ -static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, - const int weighted, const double limit, double *dfa) +void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint channels, const uint m, + const bool weighted, const double limit, const al::span dfa) { std::vector weights(hData->mFds.size() * MAX_EV_COUNT); - uint count, ti, fi, ei, i, ai; + uint count; if(weighted) { // Use coverage weighting to calculate the average. - CalculateDfWeights(hData, weights.data()); + CalculateDfWeights(hData, weights); } else { @@ -556,64 +519,63 @@ static void CalculateDiffuseFieldAverage(const HrirDataT *hData, const uint chan // If coverage weighting is not used, the weights still need to be // averaged by the number of existing HRIRs. count = hData->mIrCount; - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = 0;ei < hData->mFds[fi].mEvStart;ei++) + for(size_t ei{0};ei < hData->mFds[fi].mEvStart;++ei) count -= static_cast(hData->mFds[fi].mEvs[ei].mAzs.size()); } weight = 1.0 / count; - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) weights[(fi * MAX_EV_COUNT) + ei] = weight; } } - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) dfa[(ti * m) + i] = 0.0; - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { - for(ai = 0;ai < hData->mFds[fi].mEvs[ei].mAzs.size();ai++) + for(size_t ai{0};ai < hData->mFds[fi].mEvs[ei].mAzs.size();++ai) { HrirAzT *azd = &hData->mFds[fi].mEvs[ei].mAzs[ai]; // Get the weight for this HRIR's contribution. double weight = weights[(fi * MAX_EV_COUNT) + ei]; // Add this HRIR's weighted power average to the total. - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) dfa[(ti * m) + i] += weight * azd->mIrs[ti][i] * azd->mIrs[ti][i]; } } } // Finish the average calculation and keep it from being too small. - for(i = 0;i < m;i++) - dfa[(ti * m) + i] = std::max(sqrt(dfa[(ti * m) + i]), EPSILON); + for(size_t i{0};i < m;++i) + dfa[(ti * m) + i] = std::max(sqrt(dfa[(ti * m) + i]), Epsilon); // Apply a limit to the magnitude range of the diffuse-field average // if desired. if(limit > 0.0) - LimitMagnitudeResponse(hData->mFftSize, m, limit, &dfa[ti * m], &dfa[ti * m]); + LimitMagnitudeResponse(hData->mFftSize, m, limit, dfa.subspan(ti * m)); } } // Perform diffuse-field equalization on the magnitude responses of the HRIR // set using the given average response. -static void DiffuseFieldEqualize(const uint channels, const uint m, const double *dfa, const HrirDataT *hData) +void DiffuseFieldEqualize(const uint channels, const uint m, const al::span dfa, + const HrirDataT *hData) { - uint ti, fi, ei, i; - - for(fi = 0;fi < hData->mFds.size();fi++) + for(size_t fi{0};fi < hData->mFds.size();++fi) { - for(ei = hData->mFds[fi].mEvStart;ei < hData->mFds[fi].mEvs.size();ei++) + for(size_t ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvs.size();++ei) { for(auto &azd : hData->mFds[fi].mEvs[ei].mAzs) { - for(ti = 0;ti < channels;ti++) + for(size_t ti{0};ti < channels;++ti) { - for(i = 0;i < m;i++) + for(size_t i{0};i < m;++i) azd.mIrs[ti][i] /= dfa[(ti * m) + i]; } } @@ -625,9 +587,10 @@ static void DiffuseFieldEqualize(const uint channels, const uint m, const double * the two HRIRs that bound the coordinate along with a factor for * calculating the continuous HRIR using interpolation. */ -static void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af) +void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, uint *a0, uint *a1, double *af) { - double f{(2.0*M_PI + az) * static_cast(field.mEvs[ei].mAzs.size()) / (2.0*M_PI)}; + double f{(2.0*al::numbers::pi + az) * static_cast(field.mEvs[ei].mAzs.size()) / + (2.0*al::numbers::pi)}; const uint i{static_cast(f) % static_cast(field.mEvs[ei].mAzs.size())}; f -= std::floor(f); @@ -640,7 +603,7 @@ static void CalcAzIndices(const HrirFdT &field, const uint ei, const double az, * This just mirrors some top elevations for the bottom, and blends the * remaining elevations (not an accurate model). */ -static void SynthesizeOnsets(HrirDataT *hData) +void SynthesizeOnsets(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; @@ -677,7 +640,7 @@ static void SynthesizeOnsets(HrirDataT *hData) * the mirrored elevation to find the indices for the polar * opposite position (may need blending). */ - const double az{field.mEvs[ei].mAzs[ai].mAzimuth + M_PI}; + const double az{field.mEvs[ei].mAzs[ai].mAzimuth + al::numbers::pi}; CalcAzIndices(field, topElev, az, &a0, &a1, &af); /* Blend the delays, and again, swap the ears. */ @@ -709,8 +672,8 @@ static void SynthesizeOnsets(HrirDataT *hData) * measurement). */ double az{field.mEvs[ei].mAzs[ai].mAzimuth}; - if(az <= M_PI) az = M_PI - az; - else az = (M_PI*2.0)-az + M_PI; + if(az <= al::numbers::pi) az = al::numbers::pi - az; + else az = (al::numbers::pi*2.0)-az + al::numbers::pi; CalcAzIndices(field, topElev, az, &a0, &a1, &af); field.mEvs[ei].mAzs[ai].mDelays[0] = Lerp( @@ -738,12 +701,12 @@ static void SynthesizeOnsets(HrirDataT *hData) double az{field.mEvs[ei].mAzs[ai].mAzimuth}; CalcAzIndices(field, upperElevReal, az, &a0, &a1, &af0); CalcAzIndices(field, lowerElevFake, az, &a2, &a3, &af1); - double blend[4]{ + std::array blend{{ (1.0-ef) * (1.0-af0), (1.0-ef) * ( af0), ( ef) * (1.0-af1), ( ef) * ( af1) - }; + }}; for(uint ti{0u};ti < channels;ti++) { @@ -764,7 +727,7 @@ static void SynthesizeOnsets(HrirDataT *hData) * applies a low-pass filter to simulate body occlusion. It is a simple, if * inaccurate model. */ -static void SynthesizeHrirs(HrirDataT *hData) +void SynthesizeHrirs(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; auto htemp = std::vector(hData->mFftSize); @@ -788,7 +751,7 @@ static void SynthesizeHrirs(HrirDataT *hData) * and vice-versa, this produces a decent phantom-center response * underneath the head. */ - CalcAzIndices(field, oi, ((ti==0) ? -M_PI : M_PI) / 2.0, &a0, &a1, &af); + CalcAzIndices(field, oi, al::numbers::pi / ((ti==0) ? -2.0 : 2.0), &a0, &a1, &af); for(uint i{0u};i < m;i++) { field.mEvs[0].mAzs[0].mIrs[ti][i] = Lerp(field.mEvs[oi].mAzs[a0].mIrs[ti][i], @@ -800,7 +763,7 @@ static void SynthesizeHrirs(HrirDataT *hData) { const double of{static_cast(ei) / field.mEvStart}; const double b{(1.0 - of) * beta}; - double lp[4]{}; + std::array lp{}; /* Calculate a low-pass filter to simulate body occlusion. */ lp[0] = Lerp(1.0, lp[0], b); @@ -821,7 +784,7 @@ static void SynthesizeHrirs(HrirDataT *hData) */ FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), - [](const complex_d &c) -> double { return std::abs(c); }); + [](const complex_d c) -> double { return std::abs(c); }); for(uint ai{0u};ai < field.mEvs[ei].mAzs.size();ai++) { @@ -845,7 +808,7 @@ static void SynthesizeHrirs(HrirDataT *hData) } } const double b{beta}; - double lp[4]{}; + std::array lp{}; lp[0] = Lerp(1.0, lp[0], b); lp[1] = Lerp(lp[0], lp[1], b); lp[2] = Lerp(lp[1], lp[2], b); @@ -861,7 +824,7 @@ static void SynthesizeHrirs(HrirDataT *hData) } FftForward(static_cast(htemp.size()), htemp.data()); std::transform(htemp.cbegin(), htemp.cbegin()+m, filter.begin(), - [](const complex_d &c) -> double { return std::abs(c); }); + [](const complex_d c) -> double { return std::abs(c); }); for(uint ti{0u};ti < channels;ti++) { @@ -879,11 +842,11 @@ static void SynthesizeHrirs(HrirDataT *hData) * or more threads (sharing the same reconstructor object). */ struct HrirReconstructor { - std::vector mIrs; - std::atomic mCurrent; - std::atomic mDone; - uint mFftSize; - uint mIrPoints; + std::vector> mIrs; + std::atomic mCurrent{}; + std::atomic mDone{}; + uint mFftSize{}; + uint mIrPoints{}; void Worker() { @@ -891,7 +854,7 @@ struct HrirReconstructor { auto mags = std::vector(mFftSize); size_t m{(mFftSize/2) + 1}; - while(1) + while(true) { /* Load the current index to process. */ size_t idx{mCurrent.load()}; @@ -910,8 +873,8 @@ struct HrirReconstructor { * time-domain response. */ for(size_t i{0};i < m;++i) - mags[i] = std::max(mIrs[idx][i], EPSILON); - MinimumPhase(mFftSize, mags.data(), h.data()); + mags[i] = std::max(mIrs[idx][i], Epsilon); + MinimumPhase(mags, h); FftInverse(mFftSize, h.data()); for(uint i{0u};i < mIrPoints;++i) mIrs[idx][i] = h[i].real(); @@ -922,7 +885,7 @@ struct HrirReconstructor { } }; -static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) +void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; @@ -973,17 +936,18 @@ static void ReconstructHrirs(const HrirDataT *hData, const uint numThreads) } // Normalize the HRIR set and slightly attenuate the result. -static void NormalizeHrirs(HrirDataT *hData) +void NormalizeHrirs(HrirDataT *hData) { const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u}; const uint irSize{hData->mIrPoints}; /* Find the maximum amplitude and RMS out of all the IRs. */ struct LevelPair { double amp, rms; }; - auto mesasure_channel = [irSize](const LevelPair levels, const double *ir) + auto mesasure_channel = [irSize](const LevelPair levels, al::span ir) { /* Calculate the peak amplitude and RMS of this IR. */ - auto current = std::accumulate(ir, ir+irSize, LevelPair{0.0, 0.0}, + ir = ir.first(irSize); + auto current = std::accumulate(ir.cbegin(), ir.cend(), LevelPair{0.0, 0.0}, [](const LevelPair cur, const double impulse) { return LevelPair{std::max(std::abs(impulse), cur.amp), cur.rms + impulse*impulse}; @@ -994,7 +958,7 @@ static void NormalizeHrirs(HrirDataT *hData) return LevelPair{std::max(current.amp, levels.amp), std::max(current.rms, levels.rms)}; }; auto measure_azi = [channels,mesasure_channel](const LevelPair levels, const HrirAzT &azi) - { return std::accumulate(azi.mIrs, azi.mIrs+channels, levels, mesasure_channel); }; + { return std::accumulate(azi.mIrs.begin(), azi.mIrs.begin()+channels, levels, mesasure_channel); }; auto measure_elev = [measure_azi](const LevelPair levels, const HrirEvT &elev) { return std::accumulate(elev.mAzs.cbegin(), elev.mAzs.cend(), levels, measure_azi); }; auto measure_field = [measure_elev](const LevelPair levels, const HrirFdT &field) @@ -1018,10 +982,14 @@ static void NormalizeHrirs(HrirDataT *hData) factor = std::min(factor, 0.99/maxlev.amp); /* Now scale all IRs by the given factor. */ - auto proc_channel = [irSize,factor](double *ir) - { std::transform(ir, ir+irSize, ir, [factor](double s){ return s * factor; }); }; + auto proc_channel = [irSize,factor](al::span ir) + { + ir = ir.first(irSize); + std::transform(ir.cbegin(), ir.cend(), ir.begin(), + [factor](double s) { return s * factor; }); + }; auto proc_azi = [channels,proc_channel](HrirAzT &azi) - { std::for_each(azi.mIrs, azi.mIrs+channels, proc_channel); }; + { std::for_each(azi.mIrs.begin(), azi.mIrs.begin()+channels, proc_channel); }; auto proc_elev = [proc_azi](HrirEvT &elev) { std::for_each(elev.mAzs.begin(), elev.mAzs.end(), proc_azi); }; auto proc1_field = [proc_elev](HrirFdT &field) @@ -1031,14 +999,14 @@ static void NormalizeHrirs(HrirDataT *hData) } // Calculate the left-ear time delay using a spherical head model. -static double CalcLTD(const double ev, const double az, const double rad, const double dist) +double CalcLTD(const double ev, const double az, const double rad, const double dist) { double azp, dlp, l, al; azp = std::asin(std::cos(ev) * std::sin(az)); dlp = std::sqrt((dist*dist) + (rad*rad) + (2.0*dist*rad*sin(azp))); l = std::sqrt((dist*dist) - (rad*rad)); - al = (0.5 * M_PI) + azp; + al = (0.5 * al::numbers::pi) + azp; if(dlp > l) dlp = l + (rad * (al - std::acos(rad / dist))); return dlp / 343.3; @@ -1046,13 +1014,13 @@ static double CalcLTD(const double ev, const double az, const double rad, const // Calculate the effective head-related time delays for each minimum-phase // HRIR. This is done per-field since distance delay is ignored. -static void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hData) +void CalculateHrtds(const HeadModelT model, const double radius, HrirDataT *hData) { uint channels = (hData->mChannelType == CT_STEREO) ? 2 : 1; double customRatio{radius / hData->mRadius}; uint ti; - if(model == HM_SPHERE) + if(model == HM_Sphere) { for(auto &field : hData->mFds) { @@ -1106,10 +1074,10 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData } } } - if(maxHrtd > MAX_HRTD) + if(maxHrtd > MaxHrtd) { - fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MAX_HRTD); - const double scale{MAX_HRTD / maxHrtd}; + fprintf(stdout, " Scaling for max delay of %f samples to %f\n...\n", maxHrtd, MaxHrtd); + const double scale{MaxHrtd / maxHrtd}; for(auto &field : hData->mFds) { for(auto &elev : field.mEvs) @@ -1124,6 +1092,8 @@ static void CalculateHrtds(const HeadModelT model, const double radius, HrirData } } +} // namespace + // Allocate and configure dynamic HRIR structures. bool PrepareHrirData(const al::span distances, const al::span evCounts, @@ -1150,22 +1120,23 @@ bool PrepareHrirData(const al::span distances, { hData->mFds[fi].mDistance = distances[fi]; hData->mFds[fi].mEvStart = 0; - hData->mFds[fi].mEvs = {&hData->mEvsBase[evTotal], evCounts[fi]}; + hData->mFds[fi].mEvs = al::span{hData->mEvsBase}.subspan(evTotal, evCounts[fi]); evTotal += evCounts[fi]; for(uint ei{0};ei < evCounts[fi];++ei) { uint azCount = azCounts[fi][ei]; - hData->mFds[fi].mEvs[ei].mElevation = -M_PI / 2.0 + M_PI * ei / (evCounts[fi] - 1); - hData->mFds[fi].mEvs[ei].mAzs = {&hData->mAzsBase[azTotal], azCount}; + hData->mFds[fi].mEvs[ei].mElevation = -al::numbers::pi / 2.0 + al::numbers::pi * ei / + (evCounts[fi] - 1); + hData->mFds[fi].mEvs[ei].mAzs = al::span{hData->mAzsBase}.subspan(azTotal, azCount); for(uint ai{0};ai < azCount;ai++) { - hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * M_PI * ai / azCount; + hData->mFds[fi].mEvs[ei].mAzs[ai].mAzimuth = 2.0 * al::numbers::pi * ai / azCount; hData->mFds[fi].mEvs[ei].mAzs[ai].mIndex = azTotal + ai; hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[0] = 0.0; hData->mFds[fi].mEvs[ei].mAzs[ai].mDelays[1] = 0.0; - hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = nullptr; - hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = nullptr; + hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[0] = {}; + hData->mFds[fi].mEvs[ei].mAzs[ai].mIrs[1] = {}; } azTotal += azCount; } @@ -1174,56 +1145,63 @@ bool PrepareHrirData(const al::span distances, } +namespace { + /* Parse the data set definition and process the source data, storing the * resulting data set as desired. If the input name is NULL it will read * from standard input. */ -static int ProcessDefinition(const char *inName, const uint outRate, const ChannelModeT chanMode, - const bool farfield, const uint numThreads, const uint fftSize, const int equalize, - const int surface, const double limit, const uint truncSize, const HeadModelT model, - const double radius, const char *outName) +bool ProcessDefinition(std::string_view inName, const uint outRate, const ChannelModeT chanMode, + const bool farfield, const uint numThreads, const uint fftSize, const bool equalize, + const bool surface, const double limit, const uint truncSize, const HeadModelT model, + const double radius, const std::string_view outName) { HrirDataT hData; fprintf(stdout, "Using %u thread%s.\n", numThreads, (numThreads==1)?"":"s"); - if(!inName) + if(inName.empty() || inName == "-"sv) { - inName = "stdin"; - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(std::cin, nullptr, 0, inName, fftSize, truncSize, outRate, chanMode, &hData)) - return 0; + inName = "stdin"sv; + fprintf(stdout, "Reading HRIR definition from %.*s...\n", al::sizei(inName), + inName.data()); + if(!LoadDefInput(std::cin, {}, inName, fftSize, truncSize, outRate, chanMode, &hData)) + return false; } else { - std::unique_ptr input{new al::ifstream{inName}}; + auto input = std::make_unique(std::filesystem::u8path(inName)); if(!input->is_open()) { - fprintf(stderr, "Error: Could not open input file '%s'\n", inName); - return 0; + fprintf(stderr, "Error: Could not open input file '%.*s'\n", al::sizei(inName), + inName.data()); + return false; } - char startbytes[4]{}; - input->read(startbytes, sizeof(startbytes)); - std::streamsize startbytecount{input->gcount()}; - if(startbytecount != sizeof(startbytes) || !input->good()) + std::array startbytes{}; + input->read(startbytes.data(), startbytes.size()); + if(input->gcount() != startbytes.size() || !input->good()) { - fprintf(stderr, "Error: Could not read input file '%s'\n", inName); - return 0; + fprintf(stderr, "Error: Could not read input file '%.*s'\n", al::sizei(inName), + inName.data()); + return false; } if(startbytes[0] == '\x89' && startbytes[1] == 'H' && startbytes[2] == 'D' && startbytes[3] == 'F') { input = nullptr; - fprintf(stdout, "Reading HRTF data from %s...\n", inName); + fprintf(stdout, "Reading HRTF data from %.*s...\n", al::sizei(inName), + inName.data()); if(!LoadSofaFile(inName, numThreads, fftSize, truncSize, outRate, chanMode, &hData)) - return 0; + return false; } else { - fprintf(stdout, "Reading HRIR definition from %s...\n", inName); - if(!LoadDefInput(*input, startbytes, startbytecount, inName, fftSize, truncSize, outRate, chanMode, &hData)) - return 0; + fprintf(stdout, "Reading HRIR definition from %.*s...\n", al::sizei(inName), + inName.data()); + if(!LoadDefInput(*input, startbytes, inName, fftSize, truncSize, outRate, chanMode, + &hData)) + return false; } } @@ -1231,7 +1209,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann { uint c{(hData.mChannelType == CT_STEREO) ? 2u : 1u}; uint m{hData.mFftSize/2u + 1u}; - auto dfa = std::vector(c * m); + auto dfa = std::vector(size_t{c} * m); if(hData.mFds.size() > 1) { @@ -1239,9 +1217,9 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann BalanceFieldMagnitudes(&hData, c, m); } fprintf(stdout, "Calculating diffuse-field average...\n"); - CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa.data()); + CalculateDiffuseFieldAverage(&hData, c, m, surface, limit, dfa); fprintf(stdout, "Performing diffuse-field equalization...\n"); - DiffuseFieldEqualize(c, m, dfa.data(), &hData); + DiffuseFieldEqualize(c, m, dfa, &hData); } if(hData.mFds.size() > 1) { @@ -1257,7 +1235,7 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann } } fprintf(stdout, "Synthesizing missing elevations...\n"); - if(model == HM_DATASET) + if(model == HM_Dataset) SynthesizeOnsets(&hData); SynthesizeHrirs(&hData); fprintf(stdout, "Performing minimum phase reconstruction...\n"); @@ -1267,18 +1245,17 @@ static int ProcessDefinition(const char *inName, const uint outRate, const Chann fprintf(stdout, "Normalizing final HRIRs...\n"); NormalizeHrirs(&hData); fprintf(stdout, "Calculating impulse delays...\n"); - CalculateHrtds(model, (radius > DEFAULT_CUSTOM_RADIUS) ? radius : hData.mRadius, &hData); + CalculateHrtds(model, (radius > DefaultCustomRadius) ? radius : hData.mRadius, &hData); const auto rateStr = std::to_string(hData.mIrRate); - const auto expName = StrSubst({outName, strlen(outName)}, {"%r", 2}, - {rateStr.data(), rateStr.size()}); + const auto expName = StrSubst(outName, "%r"sv, rateStr); fprintf(stdout, "Creating MHR data set %s...\n", expName.c_str()); - return StoreMhr(&hData, expName.c_str()); + return StoreMhr(&hData, expName); } -static void PrintHelp(const char *argv0, FILE *ofile) +void PrintHelp(const std::string_view argv0, FILE *ofile) { - fprintf(ofile, "Usage: %s [