Merge branch 'development' of https://github.com/TorqueGameEngines/Torque3D into Enumnanigans

This commit is contained in:
AzaezelX 2024-07-24 17:58:27 -05:00
commit 72c67e196a
394 changed files with 49666 additions and 46898 deletions

View file

@ -108,10 +108,11 @@ mark_as_advanced(SDL_WAYLAND)
mark_as_advanced(SDL_WERROR)
mark_as_advanced(SDL_X11)
mark_as_advanced(SDL_XINPUT)
mark_as_advanced(SDL2_DIR)
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)
@ -189,7 +190,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)
@ -198,64 +199,59 @@ add_subdirectory(glad ${TORQUE_LIB_TARG_DIRECTORY}/glad EXCLUDE_FROM_ALL)
if(TORQUE_SFX_OPENAL)
advanced_option(TORQUE_OGGVORBIS "Enable OGG Vorbis" ON)
advanced_option(ALSOFT_EAX "Enable legacy EAX extensions" ${WIN32})
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" ON)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" ON)
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" OFF)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" OFF)
advanced_option(ALSOFT_UPDATE_BUILD_VERSION "Update git build version info" ON)
mark_as_advanced(ALSOFT_REQUIRE_OBOE)
mark_as_advanced(ALSOFT_REQUIRE_PIPEWIRE)
mark_as_advanced(ALSOFT_REQUIRE_RTKIT)
mark_as_advanced(ALSOFT_REQUIRE_SSE3)
#Hide some unnecessary fields as advanced
mark_as_advanced(ALSOFT_INSTALL_AMBDEC_PRESETS)
mark_as_advanced(ALSOFT_BACKEND_DSOUND)
mark_as_advanced(ALSOFT_BACKEND_MMDEVAPI)
mark_as_advanced(ALSOFT_BACKEND_COREAUDIO)
mark_as_advanced(ALSOFT_BACKEND_DSOUND)
mark_as_advanced(ALSOFT_BACKEND_JACK)
mark_as_advanced(ALSOFT_BACKEND_OBOE)
mark_as_advanced(ALSOFT_BACKEND_OPENSL)
mark_as_advanced(ALSOFT_BACKEND_PIPEWIRE)
mark_as_advanced(ALSOFT_BACKEND_PORTAUDIO)
mark_as_advanced(ALSOFT_BACKEND_PULSEAUDIO)
mark_as_advanced(ALSOFT_BACKEND_SDL2)
mark_as_advanced(ALSOFT_BACKEND_WASAPI)
mark_as_advanced(ALSOFT_BACKEND_WAVE)
mark_as_advanced(ALSOFT_BACKEND_WINMM)
mark_as_advanced(ALSOFT_INSTALL_CONFIG)
mark_as_advanced(ALSOFT_BUILD_ROUTER)
mark_as_advanced(ALSOFT_CPUEXT_NEON)
mark_as_advanced(ALSOFT_CPUEXT_SSE)
mark_as_advanced(ALSOFT_CPUEXT_SSE2)
mark_as_advanced(ALSOFT_CPUEXT_SSE3)
mark_as_advanced(ALSOFT_CPUEXT_SSE4_1)
mark_as_advanced(ALSOFT_DLOPEN)
mark_as_advanced(ALSOFT_EMBED_HRTF_DATA)
mark_as_advanced(ALSOFT_EXAMPLES)
mark_as_advanced(ALSOFT_INSTALL_HRTF_DATA)
mark_as_advanced(ALSOFT_EAX)
advanced_option(ALSOFT_EMBED_HRTF_DATA "Embed the HRTF data files (increases library footprint)" OFF)
advanced_option(ALSOFT_EXAMPLES "Build example programs" OFF)
mark_as_advanced(ALSOFT_INSTALL)
advanced_option(ALSOFT_INSTALL_CONFIG "Install alsoft.conf sample configuration file" OFF)
advanced_option(ALSOFT_INSTALL_HRTF_DATA "Install HRTF data files" OFF)
advanced_option(ALSOFT_INSTALL_AMBDEC_PRESETS "Install AmbDec preset files" OFF)
advanced_option(ALSOFT_INSTALL_EXAMPLES "Install example programs (alplay, alstream, ...)" OFF)
advanced_option(ALSOFT_INSTALL_UTILS "Install utility programs (openal-info, alsoft-config, ...)" OFF)
mark_as_advanced(ALSOFT_UPDATE_BUILD_VERSION)
mark_as_advanced(ALSOFT_NO_CONFIG_UTIL)
mark_as_advanced(ALSOFT_NO_UID_DEFS)
mark_as_advanced(ALSOFT_REQUIRE_ALSA)
mark_as_advanced(ALSOFT_REQUIRE_COREAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_DSOUND)
mark_as_advanced(ALSOFT_REQUIRE_JACK)
mark_as_advanced(ALSOFT_REQUIRE_MMDEVAPI)
mark_as_advanced(ALSOFT_REQUIRE_NEON)
mark_as_advanced(ALSOFT_REQUIRE_OBOE)
mark_as_advanced(ALSOFT_REQUIRE_OPENSL)
mark_as_advanced(ALSOFT_REQUIRE_OSS)
mark_as_advanced(ALSOFT_REQUIRE_PIPEWIRE)
mark_as_advanced(ALSOFT_REQUIRE_PORTAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_PULSEAUDIO)
mark_as_advanced(ALSOFT_REQUIRE_QSA)
mark_as_advanced(ALSOFT_REQUIRE_SNDIO)
mark_as_advanced(ALSOFT_REQUIRE_SOLARIS)
mark_as_advanced(ALSOFT_REQUIRE_SDL2)
mark_as_advanced(ALSOFT_REQUIRE_SSE)
mark_as_advanced(ALSOFT_REQUIRE_SSE2)
mark_as_advanced(ALSOFT_REQUIRE_SSE3)
mark_as_advanced(ALSOFT_REQUIRE_SSE4_1)
mark_as_advanced(ALSOFT_REQUIRE_WASAPI)
mark_as_advanced(ALSOFT_REQUIRE_WINMM)
mark_as_advanced(ALSOFT_SEARCH_INSTALL_DATADIR)
mark_as_advanced(ALSOFT_TESTS)
mark_as_advanced(ALSOFT_UTILS)
mark_as_advanced(ALSOFT_WERROR)
mark_as_advanced(COREAUDIO_FRAMEWORK)
mark_as_advanced(CMAKE_DEBUG_POSTFIX)
mark_as_advanced(FORCE_STATIC_VCRT)
mark_as_advanced(ALSOFT_BACKEND_WASAPI)
mark_as_advanced(ALSOFT_BUILD_ROUTER)
mark_as_advanced(ALSOFT_REQUIRE_SDL2)
mark_as_advanced(ALSOFT_REQUIRE_WASAPI)
mark_as_advanced(ALSOFT_BACKEND_COREAUDIO)
mark_as_advanced(ALSOFT_OSX_FRAMEWORK)
mark_as_advanced(ALSOFT_STATIC_LIBGCC)
mark_as_advanced(ALSOFT_STATIC_STDCXX)
mark_as_advanced(AUDIOTOOLBOX_LIBRARY)
mark_as_advanced(AUDIOUNIT_INCLUDE_DIR)
endif()
advanced_option(INSTALL_DOCS "" OFF)
@ -272,9 +268,33 @@ add_subdirectory(flac ${TORQUE_LIB_TARG_DIRECTORY}/flac EXCLUDE_FROM_ALL)
advanced_option(INSTALL_DOCS "" OFF)
advanced_option(OPUS_INSTALL_PKG_CONFIG_MODULE "" OFF)
advanced_option(OPUS_INSTALL_CMAKE_CONFIG_MODULE "" OFF)
mark_as_advanced(OPUS_ASSERTIONS)
mark_as_advanced(OPUS_BUILD_PROGRAMS)
mark_as_advanced(OPUS_BUILD_SHARED_LIBRARY)
mark_as_advanced(OPUS_BUILD_TESTING)
mark_as_advanced(OPUS_CHECK_ASM)
mark_as_advanced(OPUS_CUSTOM_MODES)
mark_as_advanced(OPUS_DISABLE_INTRINSICS)
mark_as_advanced(OPUS_ENABLE_FLOAT_API)
mark_as_advanced(OPUS_FIXED_POINT)
mark_as_advanced(OPUS_FLOAT_APPROX)
mark_as_advanced(OPUS_FUZZING)
mark_as_advanced(OPUS_HARDENING)
mark_as_advanced(OPUS_STACK_PROTECTOR)
mark_as_advanced(OPUS_USE_ALLOCA)
mark_as_advanced(OPUS_X86_MAY_HAVE_AVX)
mark_as_advanced(OPUS_X86_MAY_HAVE_SSE)
mark_as_advanced(OPUS_X86_MAY_HAVE_SSE2)
mark_as_advanced(OPUS_X86_MAY_HAVE_SSE4_1)
mark_as_advanced(OPUS_X86_PRESUME_AVX)
mark_as_advanced(OPUS_X86_PRESUME_SSE)
mark_as_advanced(OPUS_X86_PRESUME_SSE2)
mark_as_advanced(OPUS_X86_PRESUME_SSE4_1)
add_subdirectory(opus ${TORQUE_LIB_TARG_DIRECTORY}/opus EXCLUDE_FROM_ALL)
add_subdirectory(libtheora ${TORQUE_LIB_TARG_DIRECTORY}/libtheora EXCLUDE_FROM_ALL)
mark_as_advanced(PULSEAUDIO_INCLUDE_DIR)
mark_as_advanced(PULSEAUDIO_LIBRARY)
advanced_option(BUILD_PROGRAMS "" OFF)
advanced_option(BUILD_EXAMPLES "" OFF)
advanced_option(ENABLE_CPACK "" OFF)
@ -287,4 +307,29 @@ add_subdirectory(libsndfile ${TORQUE_LIB_TARG_DIRECTORY}/libsndfile EXCLUDE_FROM
if(TORQUE_TESTING)
add_subdirectory(gtest ${TORQUE_LIB_TARG_DIRECTORY}/gtest EXCLUDE_FROM_ALL)
endif()
#endif()
#endif()
#misc randoms
mark_as_advanced(WINDRES)
mark_as_advanced(AUDIOUNIT_INCLUDE_DIR)
mark_as_advanced(CCACHE_BINARY)
mark_as_advanced(COREAUDIO_FRAMEWORK)
mark_as_advanced(FORCE_STATIC_VCRT)
mark_as_advanced(HAVE_GIT)
mark_as_advanced(LIBRARY_SUFFIX)
mark_as_advanced(USE_STATIC_CRT)
mark_as_advanced(BUILD_CXXLIBS)
mark_as_advanced(BUILD_DOCS)
mark_as_advanced(BUILD_REGTEST)
mark_as_advanced(BUILD_UTILS)
mark_as_advanced(ENABLE_64_BIT_WORDS)
mark_as_advanced(ENABLE_BOW_DOCS)
mark_as_advanced(ENABLE_EXTERNAL_LIBS)
mark_as_advanced(WITH_ASM)
mark_as_advanced(WITH_AVX)
mark_as_advanced(WITH_FORTIFY_SOURCE)
mark_as_advanced(WITH_OGG)
mark_as_advanced(WITH_STACK_PROTECTOR)

View file

@ -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)

View file

@ -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)

View file

@ -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 <stdio.h>
#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; i<tcount_mesh; i++)
#endif
{
NxU32 i1 = indices[0];
NxU32 i2 = indices[1];
NxU32 i3 = indices[2];
const NxF32 *p1 = &vertices_mesh[i1*3];
const NxF32 *p2 = &vertices_mesh[i2*3];
const NxF32 *p3 = &vertices_mesh[i3*3];
NxF32 normal[3];
NxF32 d = fm_computePlane(p3,p2,p1,normal);
NxF32 vertices[6*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];
NxF32 midPoint[3];
midPoint[0] = (p1[0]+p2[0]+p3[0])/3;
midPoint[1] = (p1[1]+p2[1]+p3[1])/3;
midPoint[2] = (p1[2]+p2[2]+p3[2])/3;
fm_lerp(midPoint,p1,&vertices[0],0.9999f);
fm_lerp(midPoint,p2,&vertices[3],0.9999f);
fm_lerp(midPoint,p3,&vertices[6],0.9999f);
NxF32 *_p1 = &vertices[3*3];
NxF32 *_p2 = &vertices[4*3];
NxF32 *_p3 = &vertices[5*3];
NxU32 hitCount = 0;
if ( raycast(&vertices[0],normal, _p1,cast_hull,cast_mesh) ) hitCount++;
if ( raycast(&vertices[3],normal, _p2,cast_hull,cast_mesh) ) hitCount++;
if ( raycast(&vertices[6],normal, _p3,cast_hull,cast_mesh) ) hitCount++;
// form triangle mesh!
if ( hitCount == 3 )
{
NxU32 tcount = 0;
NxU32 tindices[8*3];
addTri(tindices,2,1,0,tcount);
addTri(tindices,3,4,5,tcount);
addTri(tindices,0,3,2,tcount);
addTri(tindices,2,3,5,tcount);
addTri(tindices,1,3,0,tcount);
addTri(tindices,4,3,1,tcount);
addTri(tindices,5,4,1,tcount);
addTri(tindices,2,5,1,tcount);
NxF32 volume = fm_computeMeshVolume(vertices,tcount,tindices);
total_volume+=volume;
#if SHOW_DEBUG
NVSHARE::gRenderDebug->setCurrentColor(0x0000FF,0xFFFFFF);
NVSHARE::gRenderDebug->addToCurrentState(NVSHARE::DebugRenderState::SolidWireShaded);
for (NxU32 i=0; i<tcount; i++)
{
NxU32 i1 = tindices[i*3+0];
NxU32 i2 = tindices[i*3+1];
NxU32 i3 = tindices[i*3+2];
const NxF32 *p1 = &vertices[i1*3];
const NxF32 *p2 = &vertices[i2*3];
const NxF32 *p3 = &vertices[i3*3];
NVSHARE::gRenderDebug->DebugTri(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

View file

@ -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

View file

@ -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 <math.h>
#include <float.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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; i<mVcount; i++)
{
dest[0] = source[0];
dest[1] = source[1];
dest[2] = source[2];
dest+=3;
source+=3;
}
source = mergeHull->mVertices;
for (NxU32 i=0; i<mergeHull->mVcount; 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; i<mVcount; i++)
{
dest[0] = source[0];
dest[1] = source[1];
dest[2] = source[2];
dest+=3;
source+=3;
}
source = mergeHull->mVertices;
for (NxU32 i=0; i<mergeHull->mVcount; 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; i<mHulls.size(); i++)
{
ConvexHull *ch = mHulls[i];
if ( !ch->beenTested() )
{
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; i<icount && !mCancel; i++)
{
NxU32 tcount;
NxU32 *indices = mi->getIsland(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; i<mHulls.size(); i++)
{
ConvexHull *ch = mHulls[i];
if ( ch->mTcount )
{
hullCount++;
}
}
return hullCount;
}
virtual bool getConvexHullResult(NxU32 hullIndex,ConvexHullResult &result)
{
bool ret = false;
wait();
NxU32 index = 0;
for (NxU32 i=0; i<mHulls.size(); i++)
{
ConvexHull *ch = mHulls[i];
if ( ch->mTcount )
{
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; i<hullCount; i++)
{
ConvexHull *mergeHull = mHulls[i];
if ( !mergeHull->beenTested() && 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

View file

@ -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

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <float.h>
#include "NvFloatMath.h"
#define REAL NxF32
#include "NvFloatMath.inl"
#undef REAL
#define REAL NxF64
#include "NvFloatMath.inl"

View file

@ -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 <float.h>
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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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; i<tcount; i++)
{
Triangle &t = mTriangles[i];
NxU32 i1 = *indices++;
NxU32 i2 = *indices++;
NxU32 i3 = *indices++;
t.mEdges[0] = e;
t.mEdges[1] = e+1;
t.mEdges[2] = e+2;
e = addEdge(e,&t,i1,i2);
e = addEdge(e,&t,i2,i3);
e = addEdge(e,&t,i3,i1);
}
// while there are still edges to process...
while ( mTriangleEdges.size() != 0 )
{
EdgeHashMap::Iterator iter = mTriangleEdges.getIterator();
Triangle *t = iter->second->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; i<count; i++)
{
NxU32 otcount;
const NxU32 *oindices = getIsland(i,otcount);
if ( otcount )
{
bool isCoplanar;
if ( mVerticesFloat )
isCoplanar = fm_isMeshCoplanar(otcount, oindices, mVerticesFloat, true);
else
isCoplanar = fm_isMeshCoplanar(otcount, oindices, mVerticesDouble, true);
if ( isCoplanar )
{
Island *isl = mIslands[i];
isl->mCoplanar = 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; i<cp_count; i++)
{
Island *_i = mIslands[i];
isl->merge(*_i);
delete _i;
}
}
else
{
Triangle *t = mTriangles;
for (NxU32 i=0; i<mTcount; i++)
{
t->buildBox(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

View file

@ -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

View file

@ -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<mTcount; i++)
{
NxU32 i1 = mIndices[i*3+0];
NxU32 i2 = mIndices[i*3+1];
NxU32 i3 = mIndices[i*3+2];
const NxF32 *t1 = &mVertices[i1*3];
const NxF32 *t2 = &mVertices[i2*3];
const NxF32 *t3 = &mVertices[i3*3];
NxF32 t;
if ( fm_rayIntersectsTriangle(orig,dir,t1,t2,t3,t) )
{
if ( t < nearest )
{
dest[0] = orig[0]+dir[0]*t;
dest[1] = orig[1]+dir[1]*t;
dest[2] = orig[2]+dir[2]*t;
ret = true;
near_face = i;
nearest = t;
}
}
}
if ( ret )
{
// If the nearest face we hit was back-facing, then reject this cast!
NxU32 i1 = mIndices[near_face*3+0];
NxU32 i2 = mIndices[near_face*3+1];
NxU32 i3 = mIndices[near_face*3+2];
const NxF32 *t1 = &mVertices[i1*3];
const NxF32 *t2 = &mVertices[i2*3];
const NxF32 *t3 = &mVertices[i3*3];
fm_computePlane(t3,t2,t1,hitNormal);
}
return ret;
}
private:
const NxF32 *mVertices;
NxU32 mTcount;
const NxU32 *mIndices;
};
iRayCast *createRayCast(const NxF32 *vertices,NxU32 tcount,const NxU32 *indices)
{
RayCast *rc = MEMALLOC_NEW(RayCast)(vertices,tcount,indices);
return static_cast< iRayCast *>(rc);
}
void releaseRayCast(iRayCast *rc)
{
RayCast *r = static_cast< RayCast *>(rc);
delete r;
}
};

View file

@ -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

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#pragma warning(disable:4702)
#pragma warning(disable:4127) //conditional expression is constant (because _HAS_EXCEPTIONS=0)
#include <vector>
#if defined( __APPLE__ ) || defined( __FreeBSD__)
#include <ext/hash_map>
#elif LINUX
#include <hash_map>
#elif _MSC_VER < 1500
#include <hash_map>
#elif _MSC_VER > 1800
#include <unordered_map>
#endif
#include "NvUserMemAlloc.h"
#include "NvHashMap.h"
#include "NvRemoveTjunctions.h"
#include "NvFloatMath.h"
#ifdef LINUX
#include <climits>
#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; i<tcount; i++)
{
if ( ids ) id = *ids++;
e =init(t,indices,vertices,e,id);
indices+=3;
t++;
}
{
TriangleVector test;
for (EdgeMap::Iterator i = mEdgeMap.getIterator(); !i.done(); ++i)
{
RtEdge *e = (*i).second;
if ( e->mNextEdge == 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; i<mTcount; i++)
{
mIndices.pushBack(t->mI1);
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; i<tcount_out; i++)
{
NxU32 i1 = scan[0];
NxU32 i2 = scan[1];
NxU32 i3 = scan[2];
assert( i1 != i2 && i1 != i3 && i2 != i3 );
scan+=3;
}
}
#endif
return ret;
}
Triangle * locateIntersection(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;
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<mTcount; i++)
{
ret = locateIntersection(scan,t);
if ( ret )
break;
scan++;
}
return ret;
}
Triangle *mInputTriangles;
NxU32 mVcount;
NxU32 mMaxTcount;
NxU32 mTcount;
const float *mVertices;
NxU32Vector mIndices;
NxU32Vector mIds;
TriangleVector mSplit;
NxU32 mEdgeCount;
RtEdge *mEdges;
EdgeMap mEdgeMap;
NxF32 mEpsilon;
};
RemoveTjunctions * createRemoveTjunctions(void)
{
MyRemoveTjunctions *m = new MyRemoveTjunctions;
return static_cast< RemoveTjunctions *>(m);
}
void releaseRemoveTjunctions(RemoveTjunctions *tj)
{
MyRemoveTjunctions *m = static_cast< MyRemoveTjunctions *>(tj);
delete m;
}
}; // end of namespace

View file

@ -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

View file

@ -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 <sys/malloc.h>
#else
#if defined( __FreeBSD__)
#include <stdlib.h>
#else
#include <malloc.h>
#endif
#endif
#include <assert.h>
#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

View file

@ -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; i<source.mTcount; i++)
{
NxU32 i1 = source.mIndices[i*3+0];
NxU32 i2 = source.mIndices[i*3+1];
NxU32 i3 = source.mIndices[i*3+2];
const NxF32 *p1 = &source.mVertices[i1*3];
const NxF32 *p2 = &source.mVertices[i2*3];
const NxF32 *p3 = &source.mVertices[i3*3];
NxF32 source_tri[3*3];
source_tri[0] = p1[0];
source_tri[1] = p1[1];
source_tri[2] = p1[2];
source_tri[3] = p2[0];
source_tri[4] = p2[1];
source_tri[5] = p2[2];
source_tri[6] = p3[0];
source_tri[7] = p3[1];
source_tri[8] = p3[2];
NxF32 front_tri[3*5];
NxF32 back_tri[3*5];
NxU32 fcount,bcount;
fm_planeTriIntersection(planeEquation,source_tri,sizeof(NxF32)*3,0.000001f,front_tri,fcount,back_tri,bcount);
bool newPos;
if ( fcount )
{
NxU32 i1,i2,i3,i4;
i1 = mLeftVertices->getIndex( &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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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 <cassert>
#include "NvThreadConfig.h"
#if defined(WIN32)
#define _WIN32_WINNT 0x400
#include <windows.h>
#pragma comment(lib,"winmm.lib")
// #ifndef _WIN32_WINNT
// #endif
// #include <windows.h>
//#include <winbase.h>
#endif
#if defined(_XBOX)
#include <xtl.h>
#endif
#if defined(__linux__) || defined( __APPLE__ ) || defined( __FreeBSD__)
//#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#define __stdcall
#endif
#if defined( __APPLE__ ) || defined( __FreeBSD__)
#include <sys/time.h>
#endif
#if defined(__APPLE__) || defined(__linux__) || defined( __FreeBSD__)
#include <pthread.h>
#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<MyThread *>(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<ThreadEvent *>(m);
}
void tc_releaseThreadEvent(ThreadEvent *t)
{
MyThreadEvent *m = static_cast< MyThreadEvent *>(t);
delete m;
}
}; // end of namespace

View file

@ -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 <stdint.h>
#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

View file

@ -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

View file

@ -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.

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>
#include "wavefront.h"
#include <vector>
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; i<argc; i++)
{
GetVertex(v[i-1],argv[i] );
}
mCallback->NodeTriangle(&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; i<vcount; i++)
{
if ( v[0] == p[0] && v[1] == p[1] && v[2] == p[2] )
{
if (texCoord == NULL || (t[0] == texCoord[0] && t[1] == texCoord[1]))
{
return i;
}
}
v+=3;
if (t != NULL)
t += 2;
}
}
mVertices.push_back( p[0] );
mVertices.push_back( p[1] );
mVertices.push_back( p[2] );
if (texCoord != NULL)
{
mTexCoords.push_back( texCoord[0] );
mTexCoords.push_back( texCoord[1] );
}
return vcount;
}
virtual void NodeTriangle(const GeometryVertex *v1,const GeometryVertex *v2,const GeometryVertex *v3, bool textured)
{
mIndices.push_back( GetIndex(v1->mPos, 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

View file

@ -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

View file

@ -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})

View file

@ -0,0 +1,17 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include <float.h>
#include "FloatMath.h"
#include <vector>
#define REAL float
#include "FloatMath.inl"
#undef REAL
#define REAL double
#include "FloatMath.inl"

View file

@ -0,0 +1,525 @@
#ifndef FLOAT_MATH_LIB_H
#define FLOAT_MATH_LIB_H
#include <float.h>
#include <stdint.h>
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

File diff suppressed because it is too large Load diff

View file

@ -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}}

View file

@ -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/"

View file

@ -1,6 +1,7 @@
build*/
winbuild
win64build
.vs/
## kdevelop
*.kdev4

View file

@ -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 <cinttypes>
#include <cstdio>
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} $<$<COMPILE_LANGUAGE:CXX>:-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
$<BUILD_INTERFACE:${OpenAL_SOURCE_DIR}/include>
@ -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 $<$<BOOL:${ALSOFT_EAX}>: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}

View file

@ -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.

View file

@ -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")

View file

@ -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
----------------

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,24 @@
#ifndef AL_AUXEFFECTSLOT_H
#define AL_AUXEFFECTSLOT_H
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <utility>
#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 <memory>
#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<EffectState> State;
} Effect;
};
EffectData Effect;
bool mPropsDirty{true};
SlotState mState{SlotState::Initial};
RefCount ref{0u};
std::atomic<ALuint> 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<const EAX40FXSLOTPROPERTIES&>(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<std::array<ALeffectslot,64>*> 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

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,23 @@
#ifndef AL_BUFFER_H
#define AL_BUFFER_H
#include <array>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <string_view>
#include <utility>
#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<al::byte,16> mDataStorage;
al::vector<std::byte,16> 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<ALuint> 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<std::array<ALbuffer,64>*> 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

View file

@ -0,0 +1,618 @@
#include "config.h"
#include "debug.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <deque>
#include <mutex>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <unordered_map>
#include <utility>
#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<typename T, T ...Vals>
constexpr auto make_array_sequence(std::integer_sequence<T, Vals...>)
{ return std::array<T,sizeof...(Vals)>{Vals...}; }
template<typename T, size_t N>
constexpr auto make_array_sequence()
{ return make_array_sequence(std::make_integer_sequence<T,N>{}); }
constexpr auto GetDebugSource(ALenum source) noexcept -> std::optional<DebugSource>
{
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<DebugType>
{
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<DebugSeverity>
{
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 "<invalid source>";
}
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 "<invalid type>";
}
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 "<invalid severity>";
}
} // namespace
void ALCcontext::sendDebugMessage(std::unique_lock<std::mutex> &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<ALsizei>(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<std::mutex> 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<uint>(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<uint8_t,ElemCount>();
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<std::mutex> debuglock{context->mDebugCbLock};
DebugGroup &debug = context->mDebugGroups.back();
if(count > 0)
{
const uint filterbase{(1u<<srcIndices[0]) | (1u<<typeIndices[0])};
for(const uint id : al::span{ids, static_cast<uint>(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<<idx)); });
};
auto apply_type = [apply_severity,typeIndices](const uint filter)
{
std::for_each(typeIndices.cbegin(), typeIndices.cend(),
[apply_severity,filter](const uint idx){ apply_severity(filter | (1<<idx)); });
};
std::for_each(srcIndices.cbegin(), srcIndices.cend(),
[apply_type](const uint idx){ apply_type(1<<idx); });
}
}
catch(al::context_error& e) {
context->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<ALsizei>(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<std::mutex> 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<uint>(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<std::mutex> 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<ALuint>(logBufSize) : 0u};
std::lock_guard<std::mutex> 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<ALsizei>(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<uint>(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<ALuint>(bufSize) : 0u};
auto copy_name = [name,length,labelOut](std::unordered_map<ALuint,std::string> &names)
{
std::string_view objname;
auto iter = names.find(name);
if(iter != names.end())
objname = iter->second;
if(labelOut.empty())
*length = static_cast<ALsizei>(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<ALsizei>(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());
}

View file

@ -0,0 +1,70 @@
#ifndef AL_DEBUG_H
#define AL_DEBUG_H
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
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<uint> mFilters;
std::vector<std::uint64_t> mIdFilters;
template<typename T>
DebugGroup(DebugSource source, uint id, T&& message)
: mId{id}, mSource{source}, mMessage{std::forward<T>(message)}
{ }
DebugGroup(const DebugGroup&) = default;
DebugGroup(DebugGroup&&) = default;
~DebugGroup();
};
#endif /* AL_DEBUG_H */

View file

@ -0,0 +1,127 @@
#ifndef AL_DIRECT_DEFS_H
#define AL_DIRECT_DEFS_H
namespace detail_ {
template<typename T>
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
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<R>(); \
return Name##Direct##Ext(context.get(), n1, n2, n3, n4, n5, n6, n7, n8); \
}
#endif /* AL_DIRECT_DEFS_H */

View file

@ -10,34 +10,39 @@
//
#include <array>
#include <cfloat>
#include <cstdint>
#include <cstring>
#include <array>
#include <tuple>
#ifdef _WIN32
#include <guiddef.h>
#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<std::uint8_t,8> 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<GUID,EAX40_MAX_ACTIVE_FXSLOTS> guidActiveFXSlots;
}; // EAX40ACTIVEFXSLOTS
struct EAX50ACTIVEFXSLOTS {
GUID guidActiveFXSlots[EAX50_MAX_ACTIVE_FXSLOTS];
std::array<GUID,EAX50_MAX_ACTIVE_FXSLOTS> 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

View file

@ -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}
{

View file

@ -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<typename TException, typename TValue>
TValue& get_value() const
[[nodiscard]] auto get_value() const -> TValue&
{
if(mPropertyBufferSize < sizeof(TValue))
fail_too_small();
@ -49,32 +49,32 @@ public:
}
template<typename TValue>
al::span<TValue> get_values(size_t max_count) const
[[nodiscard]] auto get_values(size_t max_count) const -> al::span<TValue>
{
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<TValue*>(mPropertyBuffer), count);
const auto count = std::min(mPropertyBufferSize/sizeof(TValue), max_count);
return {static_cast<TValue*>(mPropertyBuffer), count};
}
template<typename TValue>
al::span<TValue> get_values() const
[[nodiscard]] auto get_values() const -> al::span<TValue>
{
return get_values<TValue>(~size_t{});
return get_values<TValue>(~0_uz);
}
template<typename TException, typename TValue>
void set_value(const TValue& value) const
auto set_value(const TValue& value) const -> void
{
get_value<TException, TValue>() = value;
}
private:
const EaxCallType mCallType;
int mVersion;
EaxFxSlotIndex mFxSlotIndex;
EaxCallPropertySetId mPropertySetId;
int mVersion{};
EaxFxSlotIndex mFxSlotIndex{};
EaxCallPropertySetId mPropertySetId{EaxCallPropertySetId::none};
bool mIsDeferred;
const ALuint mPropertyId;

View file

@ -4,60 +4,56 @@
#include <cassert>
#include <memory>
#include <variant>
#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<std::monostate,
EAXREVERBPROPERTIES,
EAXCHORUSPROPERTIES,
EAXAUTOWAHPROPERTIES,
EAXAGCCOMPRESSORPROPERTIES,
EAXDISTORTIONPROPERTIES,
EAXECHOPROPERTIES,
EAXEQUALIZERPROPERTIES,
EAXFLANGERPROPERTIES,
EAXFREQUENCYSHIFTERPROPERTIES,
EAXRINGMODULATORPROPERTIES,
EAXPITCHSHIFTERPROPERTIES,
EAXVOCALMORPHERPROPERTIES>;
template<typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
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<typename T>
@ -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<EaxAutowahCommitter> {
using EaxCommitter<EaxAutowahCommitter>::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<EaxChorusCommitter> {
using EaxCommitter<EaxChorusCommitter>::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<EaxCompressorCommitter> {
using EaxCommitter<EaxCompressorCommitter>::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<EaxDistortionCommitter> {
using EaxCommitter<EaxDistortionCommitter>::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<EaxEchoCommitter> {
using EaxCommitter<EaxEchoCommitter>::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<EaxEqualizerCommitter> {
using EaxCommitter<EaxEqualizerCommitter>::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<EaxFlangerCommitter> {
using EaxCommitter<EaxFlangerCommitter>::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<EaxFrequencyShifterCommitter> {
using EaxCommitter<EaxFrequencyShifterCommitter>::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<EaxModulatorCommitter> {
using EaxCommitter<EaxModulatorCommitter>::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<EaxPitchShifterCommitter> {
using EaxCommitter<EaxPitchShifterCommitter>::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<EaxVocalMorpherCommitter> {
using EaxCommitter<EaxVocalMorpherCommitter>::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<EaxNullCommitter> {
using EaxCommitter<EaxNullCommitter>::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<typename T>
struct CommitterFromProps { };
template<> struct CommitterFromProps<std::monostate> { using type = EaxNullCommitter; };
template<> struct CommitterFromProps<EAXREVERBPROPERTIES> { using type = EaxReverbCommitter; };
template<> struct CommitterFromProps<EAXCHORUSPROPERTIES> { using type = EaxChorusCommitter; };
template<> struct CommitterFromProps<EAXAGCCOMPRESSORPROPERTIES> { using type = EaxCompressorCommitter; };
template<> struct CommitterFromProps<EAXAUTOWAHPROPERTIES> { using type = EaxAutowahCommitter; };
template<> struct CommitterFromProps<EAXDISTORTIONPROPERTIES> { using type = EaxDistortionCommitter; };
template<> struct CommitterFromProps<EAXECHOPROPERTIES> { using type = EaxEchoCommitter; };
template<> struct CommitterFromProps<EAXEQUALIZERPROPERTIES> { using type = EaxEqualizerCommitter; };
template<> struct CommitterFromProps<EAXFLANGERPROPERTIES> { using type = EaxFlangerCommitter; };
template<> struct CommitterFromProps<EAXFREQUENCYSHIFTERPROPERTIES> { using type = EaxFrequencyShifterCommitter; };
template<> struct CommitterFromProps<EAXRINGMODULATORPROPERTIES> { using type = EaxModulatorCommitter; };
template<> struct CommitterFromProps<EAXPITCHSHIFTERPROPERTIES> { using type = EaxPitchShifterCommitter; };
template<> struct CommitterFromProps<EAXVOCALMORPHERPROPERTIES> { using type = EaxVocalMorpherCommitter; };
template<typename T>
using CommitterFor = typename CommitterFromProps<std::remove_cv_t<std::remove_reference_t<T>>>::type;
class EaxEffect {
public:
@ -238,51 +316,39 @@ public:
State4 state5_{};
template<typename T, typename ...Args>
void call_set_defaults(Args&& ...args)
{ return T::SetDefaults(std::forward<Args>(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<EaxReverbCommitter>(props);
if(altype == AL_EFFECT_CHORUS)
return call_set_defaults<EaxChorusCommitter>(props);
if(altype == AL_EFFECT_AUTOWAH)
return call_set_defaults<EaxAutowahCommitter>(props);
if(altype == AL_EFFECT_COMPRESSOR)
return call_set_defaults<EaxCompressorCommitter>(props);
if(altype == AL_EFFECT_DISTORTION)
return call_set_defaults<EaxDistortionCommitter>(props);
if(altype == AL_EFFECT_ECHO)
return call_set_defaults<EaxEchoCommitter>(props);
if(altype == AL_EFFECT_EQUALIZER)
return call_set_defaults<EaxEqualizerCommitter>(props);
if(altype == AL_EFFECT_FLANGER)
return call_set_defaults<EaxFlangerCommitter>(props);
if(altype == AL_EFFECT_FREQUENCY_SHIFTER)
return call_set_defaults<EaxFrequencyShifterCommitter>(props);
if(altype == AL_EFFECT_RING_MODULATOR)
return call_set_defaults<EaxModulatorCommitter>(props);
if(altype == AL_EFFECT_PITCH_SHIFTER)
return call_set_defaults<EaxPitchShifterCommitter>(props);
if(altype == AL_EFFECT_VOCAL_MORPHER)
return call_set_defaults<EaxVocalMorpherCommitter>(props);
return call_set_defaults<EaxNullCommitter>(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<typename T>
void init()
{
call_set_defaults<EaxReverbCommitter>(state1_.d);
EaxReverbCommitter::SetDefaults(state1_.d);
state1_.i = state1_.d;
call_set_defaults<EaxReverbCommitter>(state2_.d);
EaxReverbCommitter::SetDefaults(state2_.d);
state2_.i = state2_.d;
call_set_defaults<EaxReverbCommitter>(state3_.d);
EaxReverbCommitter::SetDefaults(state3_.d);
state3_.i = state3_.d;
call_set_defaults<T>(state4_.d);
T::SetDefaults(state4_.d);
state4_.i = state4_.d;
call_set_defaults<T>(state5_.d);
T::SetDefaults(state5_.d);
state5_.i = state5_.d;
}
@ -290,9 +356,9 @@ public:
{
switch(eax_version)
{
case 1: call_set_defaults<EaxReverbCommitter>(state1_.d); break;
case 2: call_set_defaults<EaxReverbCommitter>(state2_.d); break;
case 3: call_set_defaults<EaxReverbCommitter>(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<EaxReverbCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Chorus) \
return Callable<EaxChorusCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Autowah) \
return Callable<EaxAutowahCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Compressor) \
return Callable<EaxCompressorCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Distortion) \
return Callable<EaxDistortionCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Echo) \
return Callable<EaxEchoCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Equalizer) \
return Callable<EaxEqualizerCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Flanger) \
return Callable<EaxFlangerCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::FrequencyShifter) \
return Callable<EaxFrequencyShifterCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::Modulator) \
return Callable<EaxModulatorCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::PitchShifter) \
return Callable<EaxPitchShifterCommitter>(__VA_ARGS__); \
if(T == EaxEffectType::VocalMorpher) \
return Callable<EaxVocalMorpherCommitter>(__VA_ARGS__); \
return Callable<EaxNullCommitter>(__VA_ARGS__)
template<typename T, typename ...Args>
static void call_set(Args&& ...args)
{ return T::Set(std::forward<Args>(args)...); }
static void call_set(const EaxCall &call, EaxEffectProps &props)
{ EAXCALL(props.mType, call_set, call, props); }
{
return std::visit([&](auto &arg)
{ return CommitterFor<decltype(arg)>::Set(call, arg); },
props);
}
void set(const EaxCall &call)
{
switch(call.get_version())
{
case 1: call_set<EaxReverbCommitter>(call, state1_.d); break;
case 2: call_set<EaxReverbCommitter>(call, state2_.d); break;
case 3: call_set<EaxReverbCommitter>(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<typename T, typename ...Args>
static void call_get(Args&& ...args)
{ return T::Get(std::forward<Args>(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<decltype(arg)>::Get(call, arg); },
props);
}
void get(const EaxCall &call)
void get(const EaxCall &call) const
{
switch(call.get_version())
{
case 1: call_get<EaxReverbCommitter>(call, state1_.d); break;
case 2: call_get<EaxReverbCommitter>(call, state2_.d); break;
case 3: call_get<EaxReverbCommitter>(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<typename T, typename ...Args>
bool call_commit(Args&& ...args)
{ return T{props_, al_effect_props_}.commit(std::forward<Args>(args)...); }
bool call_commit(const EaxEffectProps &props)
{ EAXCALL(props.mType, call_commit, props); }
{
return std::visit([&](auto &arg)
{ return CommitterFor<decltype(arg)>{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<EaxReverbCommitter>(state1_.d);
ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state1_.d);
break;
case 2:
state2_.i = state2_.d;
ret |= call_commit<EaxReverbCommitter>(state2_.d);
ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state2_.d);
break;
case 3:
state3_.i = state3_.d;
ret |= call_commit<EaxReverbCommitter>(state3_.d);
ret |= EaxReverbCommitter{props_, al_effect_props_}.commit(state3_.d);
break;
case 4:
state4_.i = state4_.d;

View file

@ -6,54 +6,27 @@
#include <string>
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;
}

View file

@ -1,18 +1,23 @@
#ifndef EAX_EXCEPTION_INCLUDED
#define EAX_EXCEPTION_INCLUDED
#include <stdexcept>
#include <string>
#include <string_view>
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 */

View file

@ -3,17 +3,16 @@
#include <cstddef>
#include <optional>
#include "aloptional.h"
#include "api.h"
using EaxFxSlotIndexValue = std::size_t;
class EaxFxSlotIndex : public al::optional<EaxFxSlotIndexValue>
{
class EaxFxSlotIndex : public std::optional<EaxFxSlotIndexValue> {
public:
using al::optional<EaxFxSlotIndexValue>::optional;
using std::optional<EaxFxSlotIndexValue>::optional;
EaxFxSlotIndex& operator=(const EaxFxSlotIndexValue &value) { set(value); return *this; }
EaxFxSlotIndex& operator=(const GUID &guid) { set(guid); return *this; }

View file

@ -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<EaxAlEffectSlotUPtr, EAX_MAX_FXSLOTS>;
@ -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

View file

@ -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";

View file

@ -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 */

View file

@ -5,10 +5,11 @@
#include <cassert>
#include <exception>
#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.");
}
}

View file

@ -4,8 +4,11 @@
#include <algorithm>
#include <cstdint>
#include <string>
#include <string_view>
#include <type_traits>
#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<typename TException, typename TValue>
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 =

View file

@ -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

View file

@ -28,9 +28,13 @@
#include <iterator>
#include <memory>
#include <mutex>
#include <new>
#include <numeric>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
#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 <cassert>
#include "eax/exception.h"
#endif // ALSOFT_EAX
const EffectList gEffectList[16]{
const std::array<EffectList,16> 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<std::array<ALeffect,64>>;
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<NullEffectHandler>(); break;
case AL_EFFECT_EAXREVERB: effect->PropsVariant.emplace<ReverbEffectHandler>(); break;
case AL_EFFECT_REVERB: effect->PropsVariant.emplace<StdReverbEffectHandler>(); break;
case AL_EFFECT_AUTOWAH: effect->PropsVariant.emplace<AutowahEffectHandler>(); break;
case AL_EFFECT_CHORUS: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
case AL_EFFECT_COMPRESSOR: effect->PropsVariant.emplace<CompressorEffectHandler>(); break;
case AL_EFFECT_DISTORTION: effect->PropsVariant.emplace<DistortionEffectHandler>(); break;
case AL_EFFECT_ECHO: effect->PropsVariant.emplace<EchoEffectHandler>(); break;
case AL_EFFECT_EQUALIZER: effect->PropsVariant.emplace<EqualizerEffectHandler>(); break;
case AL_EFFECT_FLANGER: effect->PropsVariant.emplace<ChorusEffectHandler>(); break;
case AL_EFFECT_FREQUENCY_SHIFTER: effect->PropsVariant.emplace<FshifterEffectHandler>(); break;
case AL_EFFECT_RING_MODULATOR: effect->PropsVariant.emplace<ModulatorEffectHandler>(); break;
case AL_EFFECT_PITCH_SHIFTER: effect->PropsVariant.emplace<PshifterEffectHandler>(); break;
case AL_EFFECT_VOCAL_MORPHER: effect->PropsVariant.emplace<VmorpherEffectHandler>(); break;
case AL_EFFECT_DEDICATED_DIALOGUE:
effect->PropsVariant.emplace<DedicatedDialogEffectHandler>();
break;
case AL_EFFECT_DEDICATED_LOW_FREQUENCY_EFFECT:
effect->PropsVariant.emplace<DedicatedLfeEffectHandler>();
break;
case AL_EFFECT_CONVOLUTION_SOFT:
effect->PropsVariant.emplace<ConvolutionEffectHandler>();
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<ALuint>(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<ALeffect*>(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<SubListAllocator::value_type>;
}
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<ALuint>(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<std::mutex> _{device->EffectLock};
if(!EnsureEffects(device, static_cast<ALuint>(n)))
{
context->setError(AL_OUT_OF_MEMORY, "Failed to allocate %d effect%s", n, (n==1)?"":"s");
return;
}
std::lock_guard<std::mutex> 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<ALuint> ids;
ids.reserve(static_cast<ALuint>(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<ALuint>(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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<ALuint>(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<std::mutex> _{device->EffectLock};
if(!effect || LookupEffect(device, effect))
return AL_TRUE;
}
ALCdevice *device{context->mALDevice.get()};
std::lock_guard<std::mutex> 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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParami(std::get<PropType>(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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParamiv(std::get<PropType>(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<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParamf(std::get<PropType>(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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.SetParamfv(std::get<PropType>(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<std::mutex> _{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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParami(std::get<PropType>(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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParamiv(std::get<PropType>(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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParamf(std::get<PropType>(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<std::mutex> _{device->EffectLock};
std::lock_guard<std::mutex> 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<std::remove_reference_t<decltype(arg)>>;
using PropType = typename Type::prop_type;
return arg.GetParamfv(std::get<PropType>(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<std::mutex> 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<ReverbProps>(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);
}

View file

@ -1,11 +1,20 @@
#ifndef AL_EFFECT_H
#define AL_EFFECT_H
#include <array>
#include <bitset>
#include <cstdint>
#include <string_view>
#include <utility>
#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<MAX_EFFECTS> 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<EffectList,16> gEffectList;
using EffectHandlerVariant = std::variant<NullEffectHandler,ReverbEffectHandler,
StdReverbEffectHandler,AutowahEffectHandler,ChorusEffectHandler,CompressorEffectHandler,
DistortionEffectHandler,EchoEffectHandler,EqualizerEffectHandler,FlangerEffectHandler,
FshifterEffectHandler,ModulatorEffectHandler,PshifterEffectHandler,VmorpherEffectHandler,
DedicatedDialogEffectHandler,DedicatedLfeEffectHandler,ConvolutionEffectHandler>;
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<std::array<ALeffect,64>*> 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

View file

@ -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<EAXAUTOWAHPROPERTIES>(&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<float>(props.mAutowah.lResonance));
mAlProps.Autowah.PeakGain = level_mb_to_gain(static_cast<float>(props.mAutowah.lPeakLevel));
mAlProps = [&]{
AutowahProps ret{};
ret.AttackTime = props.flAttackTime;
ret.ReleaseTime = props.flReleaseTime;
ret.Resonance = level_mb_to_gain(static_cast<float>(props.lResonance));
ret.PeakGain = level_mb_to_gain(static_cast<float>(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<Exception>(props.mAutowah); break;
case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.mAutowah.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.mAutowah.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.mAutowah.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(props.mAutowah.lPeakLevel); break;
case EAXAUTOWAH_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXAUTOWAH_ATTACKTIME: call.set_value<Exception>(props.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: call.set_value<Exception>(props.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: call.set_value<Exception>(props.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: call.set_value<Exception>(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<AllValidator>(call, props.mAutowah); break;
case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.mAutowah.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.mAutowah.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.mAutowah.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.mAutowah.lPeakLevel); break;
case EAXAUTOWAH_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXAUTOWAH_ATTACKTIME: defer<AttackTimeValidator>(call, props.flAttackTime); break;
case EAXAUTOWAH_RELEASETIME: defer<ReleaseTimeValidator>(call, props.flReleaseTime); break;
case EAXAUTOWAH_RESONANCE: defer<ResonanceValidator>(call, props.lResonance); break;
case EAXAUTOWAH_PEAKLEVEL: defer<PeakLevelValidator>(call, props.lPeakLevel); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,19 +1,17 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#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 <cassert>
#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<ChorusWaveform> WaveformFromEnum(ALenum type)
constexpr std::optional<ChorusWaveform> 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<int>(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<typename TTraits>
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<EaxProps>();
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<Exception>(all);
break;
case Traits::eax_waveform_param_id():
call.template set_value<Exception>(all.ulWaveform);
break;
case Traits::eax_phase_param_id():
call.template set_value<Exception>(all.lPhase);
break;
case Traits::eax_rate_param_id():
call.template set_value<Exception>(all.flRate);
break;
case Traits::eax_depth_param_id():
call.template set_value<Exception>(all.flDepth);
break;
case Traits::eax_feedback_param_id():
call.template set_value<Exception>(all.flFeedback);
break;
case Traits::eax_delay_param_id():
call.template set_value<Exception>(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<AllValidator>(call, all);
break;
case Traits::eax_waveform_param_id():
Committer::template defer<WaveformValidator>(call, all.ulWaveform);
break;
case Traits::eax_phase_param_id():
Committer::template defer<PhaseValidator>(call, all.lPhase);
break;
case Traits::eax_rate_param_id():
Committer::template defer<RateValidator>(call, all.flRate);
break;
case Traits::eax_depth_param_id():
Committer::template defer<DepthValidator>(call, all.flDepth);
break;
case Traits::eax_feedback_param_id():
Committer::template defer<FeedbackValidator>(call, all.flFeedback);
break;
case Traits::eax_delay_param_id():
Committer::template defer<DelayValidator>(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<EaxProps>(&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<int>(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<int>(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<EaxChorusTraits>;
return Committer::Commit(props, mEaxProps, mAlProps);
return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
}
template<>
void ChorusCommitter::SetDefaults(EaxEffectProps &props)
void EaxChorusCommitter::SetDefaults(EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
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<EaxChorusTraits>;
Committer::Get(call, props);
}
template<>
void ChorusCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxChorusCommitter::Set(const EaxCall &call, EAXCHORUSPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxChorusTraits>;
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<EaxFlangerTraits>;
return Committer::Commit(props, mEaxProps, mAlProps);
return Committer::Commit(props, mEaxProps, mAlProps.emplace<ChorusProps>());
}
template<>
void FlangerCommitter::SetDefaults(EaxEffectProps &props)
void EaxFlangerCommitter::SetDefaults(EaxEffectProps &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
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<EaxFlangerTraits>;
Committer::Get(call, props);
}
template<>
void FlangerCommitter::Set(const EaxCall &call, EaxEffectProps &props)
void EaxFlangerCommitter::Set(const EaxCall &call, EAXFLANGERPROPERTIES &props)
{
using Committer = ChorusFlangerEffect<EaxFlangerTraits>;
Committer::Set(call, props);

View file

@ -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<EAXAGCCOMPRESSORPROPERTIES>(&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<Exception>(props.mCompressor); break;
case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(props.mCompressor.ulOnOff); break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXAGCCOMPRESSOR_ONOFF: call.set_value<Exception>(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<AllValidator>(call, props.mCompressor); break;
case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.mCompressor.ulOnOff); break;
case EAXAGCCOMPRESSOR_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXAGCCOMPRESSOR_ONOFF: defer<OnOffValidator>(call, props.ulOnOff); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,93 +1,117 @@
#include "config.h"
#include "AL/al.h"
#include "alc/inprogext.h"
#include <algorithm>
#include <array>
#include <cmath>
#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<const float> 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<float> 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);
}
}

View file

@ -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); }

View file

@ -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<EAXDISTORTIONPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Distortion.Edge = props.mDistortion.flEdge;
mAlProps.Distortion.Gain = level_mb_to_gain(static_cast<float>(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<float>(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<Exception>(props.mDistortion); break;
case EAXDISTORTION_EDGE: call.set_value<Exception>(props.mDistortion.flEdge); break;
case EAXDISTORTION_GAIN: call.set_value<Exception>(props.mDistortion.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.mDistortion.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.mDistortion.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(props.mDistortion.flEQBandwidth); break;
case EAXDISTORTION_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXDISTORTION_EDGE: call.set_value<Exception>(props.flEdge); break;
case EAXDISTORTION_GAIN: call.set_value<Exception>(props.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: call.set_value<Exception>(props.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: call.set_value<Exception>(props.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: call.set_value<Exception>(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<AllValidator>(call, props.mDistortion); break;
case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.mDistortion.flEdge); break;
case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.mDistortion.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.mDistortion.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.mDistortion.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.mDistortion.flEQBandwidth); break;
case EAXDISTORTION_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXDISTORTION_EDGE: defer<EdgeValidator>(call, props.flEdge); break;
case EAXDISTORTION_GAIN: defer<GainValidator>(call, props.lGain); break;
case EAXDISTORTION_LOWPASSCUTOFF: defer<LowPassCutOffValidator>(call, props.flLowPassCutOff); break;
case EAXDISTORTION_EQCENTER: defer<EqCenterValidator>(call, props.flEQCenter); break;
case EAXDISTORTION_EQBANDWIDTH: defer<EqBandwidthValidator>(call, props.flEQBandwidth); break;
default: fail_unknown_property_id();
}
}

View file

@ -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<EAXECHOPROPERTIES>(&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<Exception>(props.mEcho); break;
case EAXECHO_DELAY: call.set_value<Exception>(props.mEcho.flDelay); break;
case EAXECHO_LRDELAY: call.set_value<Exception>(props.mEcho.flLRDelay); break;
case EAXECHO_DAMPING: call.set_value<Exception>(props.mEcho.flDamping); break;
case EAXECHO_FEEDBACK: call.set_value<Exception>(props.mEcho.flFeedback); break;
case EAXECHO_SPREAD: call.set_value<Exception>(props.mEcho.flSpread); break;
case EAXECHO_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXECHO_DELAY: call.set_value<Exception>(props.flDelay); break;
case EAXECHO_LRDELAY: call.set_value<Exception>(props.flLRDelay); break;
case EAXECHO_DAMPING: call.set_value<Exception>(props.flDamping); break;
case EAXECHO_FEEDBACK: call.set_value<Exception>(props.flFeedback); break;
case EAXECHO_SPREAD: call.set_value<Exception>(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<AllValidator>(call, props.mEcho); break;
case EAXECHO_DELAY: defer<DelayValidator>(call, props.mEcho.flDelay); break;
case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.mEcho.flLRDelay); break;
case EAXECHO_DAMPING: defer<DampingValidator>(call, props.mEcho.flDamping); break;
case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.mEcho.flFeedback); break;
case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.mEcho.flSpread); break;
case EAXECHO_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXECHO_DELAY: defer<DelayValidator>(call, props.flDelay); break;
case EAXECHO_LRDELAY: defer<LrDelayValidator>(call, props.flLRDelay); break;
case EAXECHO_DAMPING: defer<DampingValidator>(call, props.flDamping); break;
case EAXECHO_FEEDBACK: defer<FeedbackValidator>(call, props.flFeedback); break;
case EAXECHO_SPREAD: defer<SpreadValidator>(call, props.flSpread); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,9 +1,3 @@
#include "config.h"
#ifdef ALSOFT_EAX
#include <cassert>
#include "AL/efx.h"
#include "effects.h"
#endif // ALSOFT_EAX

View file

@ -1,52 +1,47 @@
#ifndef AL_EFFECTS_EFFECTS_H
#define AL_EFFECTS_EFFECTS_H
#include <variant>
#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 */

View file

@ -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<EAXEQUALIZERPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Equalizer.LowGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lLowGain));
mAlProps.Equalizer.LowCutoff = props.mEqualizer.flLowCutOff;
mAlProps.Equalizer.Mid1Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid1Gain));
mAlProps.Equalizer.Mid1Center = props.mEqualizer.flMid1Center;
mAlProps.Equalizer.Mid1Width = props.mEqualizer.flMid1Width;
mAlProps.Equalizer.Mid2Gain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lMid2Gain));
mAlProps.Equalizer.Mid2Center = props.mEqualizer.flMid2Center;
mAlProps.Equalizer.Mid2Width = props.mEqualizer.flMid2Width;
mAlProps.Equalizer.HighGain = level_mb_to_gain(static_cast<float>(props.mEqualizer.lHighGain));
mAlProps.Equalizer.HighCutoff = props.mEqualizer.flHighCutOff;
mAlProps = [&]{
EqualizerProps ret{};
ret.LowGain = level_mb_to_gain(static_cast<float>(props.lLowGain));
ret.LowCutoff = props.flLowCutOff;
ret.Mid1Gain = level_mb_to_gain(static_cast<float>(props.lMid1Gain));
ret.Mid1Center = props.flMid1Center;
ret.Mid1Width = props.flMid1Width;
ret.Mid2Gain = level_mb_to_gain(static_cast<float>(props.lMid2Gain));
ret.Mid2Center = props.flMid2Center;
ret.Mid2Width = props.flMid2Width;
ret.HighGain = level_mb_to_gain(static_cast<float>(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<Exception>(props.mEqualizer); break;
case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.mEqualizer.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.mEqualizer.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.mEqualizer.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.mEqualizer.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.mEqualizer.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.mEqualizer.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.mEqualizer.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.mEqualizer.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.mEqualizer.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(props.mEqualizer.flHighCutOff); break;
case EAXEQUALIZER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXEQUALIZER_LOWGAIN: call.set_value<Exception>(props.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: call.set_value<Exception>(props.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: call.set_value<Exception>(props.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: call.set_value<Exception>(props.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: call.set_value<Exception>(props.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: call.set_value<Exception>(props.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: call.set_value<Exception>(props.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: call.set_value<Exception>(props.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: call.set_value<Exception>(props.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: call.set_value<Exception>(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<AllValidator>(call, props.mEqualizer); break;
case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.mEqualizer.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.mEqualizer.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.mEqualizer.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.mEqualizer.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.mEqualizer.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.mEqualizer.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.mEqualizer.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.mEqualizer.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.mEqualizer.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.mEqualizer.flHighCutOff); break;
case EAXEQUALIZER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXEQUALIZER_LOWGAIN: defer<LowGainValidator>(call, props.lLowGain); break;
case EAXEQUALIZER_LOWCUTOFF: defer<LowCutOffValidator>(call, props.flLowCutOff); break;
case EAXEQUALIZER_MID1GAIN: defer<Mid1GainValidator>(call, props.lMid1Gain); break;
case EAXEQUALIZER_MID1CENTER: defer<Mid1CenterValidator>(call, props.flMid1Center); break;
case EAXEQUALIZER_MID1WIDTH: defer<Mid1WidthValidator>(call, props.flMid1Width); break;
case EAXEQUALIZER_MID2GAIN: defer<Mid2GainValidator>(call, props.lMid2Gain); break;
case EAXEQUALIZER_MID2CENTER: defer<Mid2CenterValidator>(call, props.flMid2Center); break;
case EAXEQUALIZER_MID2WIDTH: defer<Mid2WidthValidator>(call, props.flMid2Width); break;
case EAXEQUALIZER_HIGHGAIN: defer<HighGainValidator>(call, props.lHighGain); break;
case EAXEQUALIZER_HIGHCUTOFF: defer<HighCutOffValidator>(call, props.flHighCutOff); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,18 +1,19 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#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<FShifterDirection> DirectionFromEmum(ALenum value)
constexpr std::optional<FShifterDirection> DirectionFromEmum(ALenum value) noexcept
{
switch(value)
{
@ -28,9 +29,9 @@ al::optional<FShifterDirection> 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<int>(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<EAXFREQUENCYSHIFTERPROPERTIES>(&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<Exception>(props.mFrequencyShifter); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.mFrequencyShifter.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(props.mFrequencyShifter.ulRightDirection); break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: call.set_value<Exception>(props.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: call.set_value<Exception>(props.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: call.set_value<Exception>(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<AllValidator>(call, props.mFrequencyShifter); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.mFrequencyShifter.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.mFrequencyShifter.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.mFrequencyShifter.ulRightDirection); break;
case EAXFREQUENCYSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXFREQUENCYSHIFTER_FREQUENCY: defer<FrequencyValidator>(call, props.flFrequency); break;
case EAXFREQUENCYSHIFTER_LEFTDIRECTION: defer<LeftDirectionValidator>(call, props.ulLeftDirection); break;
case EAXFREQUENCYSHIFTER_RIGHTDIRECTION: defer<RightDirectionValidator>(call, props.ulRightDirection); break;
default: fail_unknown_property_id();
}
}

View file

@ -1,18 +1,19 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#include "AL/al.h"
#include "AL/efx.h"
#include "alc/effects/base.h"
#include "aloptional.h"
#include "effects.h"
#ifdef ALSOFT_EAX
#include <cassert>
#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<ModulatorWaveform> WaveformFromEmum(ALenum value)
constexpr std::optional<ModulatorWaveform> WaveformFromEmum(ALenum value) noexcept
{
switch(value)
{
@ -28,9 +29,9 @@ al::optional<ModulatorWaveform> 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<int>(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<float>(val));
SetParamf(props, param, static_cast<float>(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<int>(props->Modulator.Frequency);
break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF:
*val = static_cast<int>(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<int>(props.Frequency); break;
case AL_RING_MODULATOR_HIGHPASS_CUTOFF: *val = static_cast<int>(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<EAXRINGMODULATORPROPERTIES>(&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<Exception>(props.mModulator); break;
case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.mModulator.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.mModulator.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(props.mModulator.ulWaveform); break;
case EAXRINGMODULATOR_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXRINGMODULATOR_FREQUENCY: call.set_value<Exception>(props.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: call.set_value<Exception>(props.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: call.set_value<Exception>(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<AllValidator>(call, props.mModulator); break;
case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.mModulator.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.mModulator.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.mModulator.ulWaveform); break;
case EAXRINGMODULATOR_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXRINGMODULATOR_FREQUENCY: defer<FrequencyValidator>(call, props.flFrequency); break;
case EAXRINGMODULATOR_HIGHPASSCUTOFF: defer<HighPassCutOffValidator>(call, props.flHighPassCutOff); break;
case EAXRINGMODULATOR_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
default: fail_unknown_property_id();
}
}

View file

@ -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<std::monostate>(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();

View file

@ -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<EAXPITCHSHIFTERPROPERTIES>(&mEaxProps); cur && *cur == props)
return false;
mEaxProps = props;
mAlProps.Pshifter.CoarseTune = static_cast<int>(mEaxProps.mPitchShifter.lCoarseTune);
mAlProps.Pshifter.FineTune = static_cast<int>(mEaxProps.mPitchShifter.lFineTune);
mAlProps = [&]{
PshifterProps ret{};
ret.CoarseTune = static_cast<int>(props.lCoarseTune);
ret.FineTune = static_cast<int>(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<Exception>(props.mPitchShifter); break;
case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.mPitchShifter.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(props.mPitchShifter.lFineTune); break;
case EAXPITCHSHIFTER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXPITCHSHIFTER_COARSETUNE: call.set_value<Exception>(props.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: call.set_value<Exception>(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<AllValidator>(call, props.mPitchShifter); break;
case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.mPitchShifter.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.mPitchShifter.lFineTune); break;
case EAXPITCHSHIFTER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXPITCHSHIFTER_COARSETUNE: defer<CoarseTuneValidator>(call, props.lCoarseTune); break;
case EAXPITCHSHIFTER_FINETUNE: defer<FineTuneValidator>(call, props.lFineTune); break;
default: fail_unknown_property_id();
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,18 @@
#include "config.h"
#include <optional>
#include <stdexcept>
#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 <cassert>
#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<VMorpherPhenome> PhenomeFromEnum(ALenum val)
constexpr std::optional<VMorpherPhenome> PhenomeFromEnum(ALenum val) noexcept
{
#define HANDLE_PHENOME(x) case AL_VOCAL_MORPHER_PHONEME_ ## x: \
return VMorpherPhenome::x
@ -57,10 +57,10 @@ al::optional<VMorpherPhenome> 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<VMorpherWaveform> WaveformFromEmum(ALenum value)
constexpr std::optional<VMorpherWaveform> WaveformFromEmum(ALenum value) noexcept
{
switch(value)
{
@ -108,9 +108,9 @@ al::optional<VMorpherWaveform> 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<int>(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<EAXVOCALMORPHERPROPERTIES>(&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<int>(props.mVocalMorpher.lPhonemeACoarseTuning);
mAlProps.Vmorpher.PhonemeB = get_phoneme(props.mVocalMorpher.ulPhonemeB);
mAlProps.Vmorpher.PhonemeBCoarseTuning = static_cast<int>(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<int>(props.lPhonemeACoarseTuning);
ret.PhonemeB = get_phoneme(props.ulPhonemeB);
ret.PhonemeBCoarseTuning = static_cast<int>(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<Exception>(props.mVocalMorpher);
break;
case EAXVOCALMORPHER_PHONEMEA:
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
call.set_value<Exception>(props.mVocalMorpher.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
call.set_value<Exception>(props.mVocalMorpher.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
call.set_value<Exception>(props.mVocalMorpher.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
call.set_value<Exception>(props.mVocalMorpher.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
call.set_value<Exception>(props.mVocalMorpher.flRate);
break;
default:
fail_unknown_property_id();
case EAXVOCALMORPHER_NONE: break;
case EAXVOCALMORPHER_ALLPARAMETERS: call.set_value<Exception>(props); break;
case EAXVOCALMORPHER_PHONEMEA: call.set_value<Exception>(props.ulPhonemeA); break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING: call.set_value<Exception>(props.lPhonemeACoarseTuning); break;
case EAXVOCALMORPHER_PHONEMEB: call.set_value<Exception>(props.ulPhonemeB); break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: call.set_value<Exception>(props.lPhonemeBCoarseTuning); break;
case EAXVOCALMORPHER_WAVEFORM: call.set_value<Exception>(props.ulWaveform); break;
case EAXVOCALMORPHER_RATE: call.set_value<Exception>(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<AllValidator>(call, props.mVocalMorpher);
break;
case EAXVOCALMORPHER_PHONEMEA:
defer<PhonemeAValidator>(call, props.mVocalMorpher.ulPhonemeA);
break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING:
defer<PhonemeACoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeACoarseTuning);
break;
case EAXVOCALMORPHER_PHONEMEB:
defer<PhonemeBValidator>(call, props.mVocalMorpher.ulPhonemeB);
break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING:
defer<PhonemeBCoarseTuningValidator>(call, props.mVocalMorpher.lPhonemeBCoarseTuning);
break;
case EAXVOCALMORPHER_WAVEFORM:
defer<WaveformValidator>(call, props.mVocalMorpher.ulWaveform);
break;
case EAXVOCALMORPHER_RATE:
defer<RateValidator>(call, props.mVocalMorpher.flRate);
break;
default:
fail_unknown_property_id();
case EAXVOCALMORPHER_NONE: break;
case EAXVOCALMORPHER_ALLPARAMETERS: defer<AllValidator>(call, props); break;
case EAXVOCALMORPHER_PHONEMEA: defer<PhonemeAValidator>(call, props.ulPhonemeA); break;
case EAXVOCALMORPHER_PHONEMEACOARSETUNING: defer<PhonemeACoarseTuningValidator>(call, props.lPhonemeACoarseTuning); break;
case EAXVOCALMORPHER_PHONEMEB: defer<PhonemeBValidator>(call, props.ulPhonemeB); break;
case EAXVOCALMORPHER_PHONEMEBCOARSETUNING: defer<PhonemeBCoarseTuningValidator>(call, props.lPhonemeBCoarseTuning); break;
case EAXVOCALMORPHER_WAVEFORM: defer<WaveformValidator>(call, props.ulWaveform); break;
case EAXVOCALMORPHER_RATE: defer<RateValidator>(call, props.flRate); break;
default: fail_unknown_property_id();
}
}

View file

@ -20,6 +20,8 @@
#include "config.h"
#include "error.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
@ -29,27 +31,45 @@
#include <csignal>
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <limits>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#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<char>(256);
auto message = std::vector<char>(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 = "<internal error constructing message>";
if(msglen >= 0)
msg = message.data();
else
{
msg = "<internal error constructing message>";
msglen = static_cast<int>(strlen(msg));
}
WARN("Error generated on context %p, code 0x%04x, \"%s\"\n",
decltype(std::declval<void*>()){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<uint>(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<ALenum>::max())
return static_cast<ALenum>(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

View file

@ -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 */

View file

@ -3,35 +3,49 @@
#include "event.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cstring>
#include <bitset>
#include <exception>
#include <memory>
#include <mutex>
#include <new>
#include <optional>
#include <string>
#include <string_view>
#include <thread>
#include <tuple>
#include <utility>
#include <variant>
#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<typename... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<typename... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
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<std::mutex> _{context->mEventCbLock};
do {
auto *evt_ptr = reinterpret_cast<AsyncEvent*>(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<std::mutex> eventlock{context->mEventCbLock};
auto evt_span = al::span{std::launder(reinterpret_cast<AsyncEvent*>(evt_data.buf)),
evt_data.len};
for(auto &event : evt_span)
{
quitnow = std::holds_alternative<AsyncKillThread>(event);
if(quitnow) UNLIKELY break;
if(evt.EnumType == AsyncEvent::ReleaseEffectState)
{
al::intrusive_ptr<EffectState>{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<EffectState>{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<ALsizei>(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<ALsizei>(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<ALsizei>(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<ALsizei>(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<ALsizei>(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<ALsizei>(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<AsyncEnableBits> 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<AsyncEvent*>(evt_data.buf), AsyncEvent::KillThread);
std::ignore = InitAsyncEvent<AsyncKillThread>(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<uint>(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<std::mutex> _{context->mEventCbLock};
std::lock_guard<std::mutex> 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<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> __{context->mEventCbLock};
std::lock_guard<std::mutex> eventlock{context->mEventCbLock};
context->mEventCb = callback;
context->mEventParam = userParam;
}
END_API_FUNC

View file

@ -20,63 +20,59 @@
#include "config.h"
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <string_view>
#include <vector>
#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<ALenum>(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

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,40 @@
#ifndef AL_FILTER_H
#define AL_FILTER_H
#include <array>
#include <cstdint>
#include <string_view>
#include <utility>
#include <variant>
#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<typename T>
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<NullFilterTable> { };
struct LowpassFilterTable : public FilterTable<LowpassFilterTable> { };
struct HighpassFilterTable : public FilterTable<HighpassFilterTable> { };
struct BandpassFilterTable : public FilterTable<BandpassFilterTable> { };
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<NullFilterTable,LowpassFilterTable,HighpassFilterTable,
BandpassFilterTable>;
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<std::array<ALfilter,64>*> 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

View file

@ -22,6 +22,7 @@
#include "listener.h"
#include <algorithm>
#include <cmath>
#include <mutex>
@ -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<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> 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<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> 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<const float,3>{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<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> 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<const float,6>{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<std::mutex> _{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<std::mutex> 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<ALfloat>(value1), static_cast<ALfloat>(value2),
static_cast<ALfloat>(value3));
alListener3fDirect(context, param, static_cast<ALfloat>(value1),
static_cast<ALfloat>(value2), static_cast<ALfloat>(value3));
return;
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> 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<std::mutex> _{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<const ALint> 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<ALfloat>(vals[0]),
static_cast<ALfloat>(vals[1]), static_cast<ALfloat>(vals[2]));
return;
case AL_ORIENTATION:
vals = {values, 6_uz};
const std::array fvals{static_cast<ALfloat>(vals[0]), static_cast<ALfloat>(vals[1]),
static_cast<ALfloat>(vals[2]), static_cast<ALfloat>(vals[3]),
static_cast<ALfloat>(vals[4]), static_cast<ALfloat>(vals[5]),
};
alListenerfvDirect(context, param, fvals.data());
return;
}
std::lock_guard<std::mutex> 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<ALfloat>(values[0]), static_cast<ALfloat>(values[1]),
static_cast<ALfloat>(values[2]));
return;
case AL_ORIENTATION:
fvals[0] = static_cast<ALfloat>(values[0]);
fvals[1] = static_cast<ALfloat>(values[1]);
fvals[2] = static_cast<ALfloat>(values[2]);
fvals[3] = static_cast<ALfloat>(values[3]);
fvals[4] = static_cast<ALfloat>(values[4]);
fvals[5] = static_cast<ALfloat>(values[5]);
alListenerfv(param, fvals);
return;
}
}
ContextRef context{GetContextRef()};
if(!context) UNLIKELY return;
std::lock_guard<std::mutex> _{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<std::mutex> _{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<std::mutex> _{context->mPropLock};
std::lock_guard<std::mutex> 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<std::mutex> 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<ALfloat,3>{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<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_ORIENTATION:
al::span<ALfloat,6> 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<std::mutex> _{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<std::mutex> 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<std::mutex> _{context->mPropLock};
if(!value1 || !value2 || !value3)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
std::lock_guard<std::mutex> proplock{context->mPropLock};
switch(param)
{
case AL_POSITION:
*value1 = static_cast<ALint>(listener.Position[0]);
*value2 = static_cast<ALint>(listener.Position[1]);
*value3 = static_cast<ALint>(listener.Position[2]);
break;
return;
case AL_VELOCITY:
*value1 = static_cast<ALint>(listener.Velocity[0]);
*value2 = static_cast<ALint>(listener.Velocity[1]);
*value3 = static_cast<ALint>(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<ALint,3>{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<std::mutex> _{context->mPropLock};
if(!values)
context->setError(AL_INVALID_VALUE, "NULL pointer");
else switch(param)
std::lock_guard<std::mutex> proplock{context->mPropLock};
static constexpr auto f2i = [](const float val) noexcept { return static_cast<ALint>(val); };
switch(param)
{
case AL_ORIENTATION:
auto vals = al::span<ALint,6>{values, 6_uz};
// AT then UP
values[0] = static_cast<ALint>(listener.OrientAt[0]);
values[1] = static_cast<ALint>(listener.OrientAt[1]);
values[2] = static_cast<ALint>(listener.OrientAt[2]);
values[3] = static_cast<ALint>(listener.OrientUp[0]);
values[4] = static_cast<ALint>(listener.OrientUp[1]);
values[5] = static_cast<ALint>(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

View file

@ -3,8 +3,6 @@
#include <array>
#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

File diff suppressed because it is too large Load diff

View file

@ -2,26 +2,26 @@
#define AL_SOURCE_H
#include <array>
#include <atomic>
#include <cstddef>
#include <iterator>
#include <limits>
#include <cstdint>
#include <deque>
#include <limits>
#include <string_view>
#include <utility>
#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<ALuint>(-1)
inline constexpr ALuint InvalidVoiceIndex{std::numeric_limits<ALuint>::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<SendData,MAX_SENDS> Send;
DirectData Direct;
struct SendData {
ALeffectslot *Slot{};
float Gain{};
float GainHF{};
float HFReference{};
float GainLF{};
float LFReference{};
};
std::array<SendData,MaxSendCount> 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<ALbufferQueueItem> mQueue;
std::deque<ALbufferQueueItem> 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<const GUID*,EAX_MAX_FXSLOTS>;
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<const Eax3Props&>(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<const GUID> 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<typename TValidator, size_t TIdCount>
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<GUID,TIdCount> dst_ids)
{
const auto src_ids = call.get_values<const GUID>(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<size_t TIdCount>
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<GUID,TIdCount> dst_ids)
{
eax_defer_active_fx_slot_id<Eax4ActiveFxSlotIdValidator>(call, dst_ids);
}
template<size_t TIdCount>
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<GUID,TIdCount> dst_ids)
{
eax_defer_active_fx_slot_id<Eax5ActiveFxSlotIdValidator>(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<std::array<ALsource,64>*> 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -22,9 +22,6 @@
#include "alconfig.h"
#include <cstdlib>
#include <cctype>
#include <cstring>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
@ -34,25 +31,47 @@
#endif
#include <algorithm>
#include <cstdio>
#include <array>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <istream>
#include <limits>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#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 <winrt/Windows.Media.Core.h> // !!This is important!!
#include <winrt/Windows.Storage.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
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<ConfigEntry> ConfOpts;
std::vector<ConfigEntry> 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<size_t>(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<size_t>(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<char*>(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<char>(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<int>::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<WCHAR,CoTaskMemDeleter> 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<unsigned char,PATH_MAX> fileName{};
if(configURL && CFURLGetFileSystemRepresentation(configURL, true, fileName.data(), fileName.size()))
{
f = al::ifstream{reinterpret_cast<char*>(fileName)};
if(f.is_open())
if(std::ifstream f{reinterpret_cast<char*>(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<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName)
std::optional<std::string> 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<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName)
std::optional<int> 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<int>(std::strtol(val, nullptr, 0));
return al::nullopt;
return std::nullopt;
}
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName)
std::optional<unsigned int> 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<unsigned int>(std::strtoul(val, nullptr, 0));
return al::nullopt;
return std::nullopt;
}
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName)
std::optional<float> 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<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName)
std::optional<bool> 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;
}

View file

@ -1,18 +1,25 @@
#ifndef ALCONFIG_H
#define ALCONFIG_H
#include <optional>
#include <string>
#include <string_view>
#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<std::string> ConfigValueStr(const char *devName, const char *blockName, const char *keyName);
al::optional<int> ConfigValueInt(const char *devName, const char *blockName, const char *keyName);
al::optional<unsigned int> ConfigValueUInt(const char *devName, const char *blockName, const char *keyName);
al::optional<float> ConfigValueFloat(const char *devName, const char *blockName, const char *keyName);
al::optional<bool> ConfigValueBool(const char *devName, const char *blockName, const char *keyName);
std::optional<std::string> ConfigValueStr(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<int> ConfigValueInt(const std::string_view devName, const std::string_view blockName,
const std::string_view keyName);
std::optional<unsigned int> ConfigValueUInt(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<float> ConfigValueFloat(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
std::optional<bool> ConfigValueBool(const std::string_view devName,
const std::string_view blockName, const std::string_view keyName);
#endif /* ALCONFIG_H */

File diff suppressed because it is too large Load diff

View file

@ -2,20 +2,20 @@
#define ALU_H
#include <bitset>
#include "aloptional.h"
#include <cstdint>
#include <optional>
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<StereoEncoding> stereomode);
void aluInitRenderer(ALCdevice *device, int hrtf_id, std::optional<StereoEncoding> stereomode);
void aluInitEffectPanning(EffectSlot *slot, ALCcontext *context);

View file

@ -31,29 +31,33 @@
#include <exception>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#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 <alsa/asoundlib.h>
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<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> 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<DevMap> 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<DevMap> probe_devices(snd_pcm_stream_t stream)
{
al::vector<DevMap> devlist;
std::vector<DevMap> 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<DevMap> 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<DevMap> 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<DevMap> probe_devices(snd_pcm_stream_t stream)
snd_pcm_info_set_device(pcminfo, static_cast<uint>(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<DevMap> 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<DevMap> 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<al::byte> mBuffer;
std::vector<std::byte> mBuffer;
std::atomic<bool> 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<std::mutex> _{mMutex};
std::lock_guard<std::mutex> 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<char*>(areas->addr) + (offset * areas->step / 8)};
mDevice->renderSamples(WritePtr, static_cast<uint>(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<ssize_t>(mBuffer.size()));
std::lock_guard<std::mutex> _{mMutex};
mDevice->renderSamples(WritePtr, static_cast<uint>(avail), mFrameStep);
std::lock_guard<std::mutex> dlock{mMutex};
mDevice->renderSamples(al::to_address(WritePtr), static_cast<uint>(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<snd_pcm_uframes_t>(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<uint>(mDevice->UpdateSize * 1000000_u64 / mDevice->Frequency)};
uint bufferLen{static_cast<uint>(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<std::mutex> _{mMutex};
ret.ClockTime = GetDeviceClockTime(mDevice);
std::lock_guard<std::mutex> 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<al::byte> mBuffer;
std::vector<std::byte> 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<al::byte>(
auto temp = std::vector<std::byte>(
static_cast<size_t>(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<size_t>(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<snd_pcm_uframes_t>(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<int>(amt)));
if(amt == -EAGAIN)
continue;
if((amt=snd_pcm_recover(mPcmHandle, static_cast<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(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<uint>(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<int>(avail)));
if((avail=snd_pcm_recover(mPcmHandle, static_cast<int>(avail), 1)) >= 0)
avail = snd_pcm_recover(mPcmHandle, static_cast<int>(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<int>(amt), 1)) >= 0)
amt = snd_pcm_recover(mPcmHandle, static_cast<int>(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<decltype(p##f)>(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>
{
std::string outnames;
std::vector<std::string> 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;
}

View file

@ -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<std::string> 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 */

View file

@ -7,17 +7,6 @@
#include <array>
#include <atomic>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmreg.h>
#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;

View file

@ -3,13 +3,16 @@
#include <chrono>
#include <cstdarg>
#include <cstddef>
#include <memory>
#include <ratio>
#include <string>
#include <string_view>
#include <vector>
#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<BackendBase>;
@ -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<std::string> = 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

View file

@ -22,18 +22,20 @@
#include "coreaudio.h"
#include <inttypes.h>
#include <cinttypes>
#include <cmath>
#include <memory>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <unistd.h>
#include <cmath>
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include "alnumeric.h"
#include "alstring.h"
#include "core/converter.h"
#include "core/device.h"
#include "core/logging.h"
@ -42,18 +44,40 @@
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
namespace {
#if TARGET_OS_IOS || TARGET_OS_TV
#define CAN_ENUMERATE 0
#else
#include <IOKit/audio/IOAudioTypes.h>
#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<char>(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<UInt32>(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<DeviceEntry> &list, bool isCapture)
auto devIds = std::vector<AudioDeviceID>(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<DeviceEntry> &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<DeviceHelper> 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<CoreAudioPlayback*>(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<CoreAudioPlayback*>(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<CoreAudioCapture*>(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<char> mCaptureData;
std::vector<char> 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<CoreAudioCapture*>(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<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10);
auto FrameCount64 = std::max(static_cast<uint64_t>(std::ceil(mDevice->BufferSize*srateScale)),
static_cast<UInt32>(outputFormat.mSampleRate)/10_u64);
FrameCount64 += MaxResamplerPadding;
if(FrameCount64 > std::numeric_limits<int32_t>::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<UInt32>(maxu64(outputFrameCount, FrameCount64));
outputFrameCount = static_cast<UInt32>(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>
{
std::string outnames;
std::vector<std::string> 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;
}

View file

@ -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<std::string> final;
static BackendFactory &getFactory();
auto createBackend(DeviceBase *device, BackendType type) -> BackendPtr final;
static auto getFactory() -> BackendFactory&;
};
#endif /* BACKENDS_COREAUDIO_H */

View file

@ -25,10 +25,6 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <memory.h>
#include <cguid.h>
#include <mmreg.h>
#ifndef _WAVEFORMATEXTENSIBLE_
@ -36,15 +32,21 @@
#include <ksmedia.h>
#endif
#include <algorithm>
#include <atomic>
#include <cassert>
#include <thread>
#include <string>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <memory.h>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#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<DevMap> PlaybackDevices;
al::vector<DevMap> CaptureDevices;
std::vector<DevMap> PlaybackDevices;
std::vector<DevMap> CaptureDevices;
bool checkName(const al::vector<DevMap> &list, const std::string &name)
bool checkName(const al::span<DevMap> 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<al::vector<DevMap>*>(data);
auto& devices = *static_cast<std::vector<DevMap>*>(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<bool> 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<IDirectSound> 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<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(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<WORD>(mDevice->channelsFromFmt());
OutputType.Format.wBitsPerSample = static_cast<WORD>(mDevice->bytesFromFmt() * 8);
OutputType.Format.nBlockAlign = static_cast<WORD>(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<IDirectSoundNotify>{static_cast<IDirectSoundNotify*>(ptr)};
uint num_updates{mDevice->BufferSize / mDevice->UpdateSize};
assert(num_updates <= MAX_UPDATES);
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> nots;
std::array<DSBPOSITIONNOTIFY,MAX_UPDATES> 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<IDirectSoundCapture> 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>
{
std::string outnames;
std::vector<std::string> 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;
}

Some files were not shown because too many files have changed in this diff Show more