Update OpenAL-soft to 1.21.1

This commit is contained in:
Miku AuahDark
2021-02-11 18:04:50 +08:00
parent 08f227997e
commit 904cc75ba8
358 changed files with 61122 additions and 60973 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
# This is a makehrtf HRIR definition file. It is used to define the layout
# and source data to be processed into an OpenAL Soft compatible HRTF.
# This is a makemhr HRIR definition file. It is used to define the layout and
# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the left and right ear HRIRs from a
# data set used in several papers and articles by Fumitada Itakura, Kazuya
+2 -2
View File
@@ -1,5 +1,5 @@
# This is a makehrtf HRIR definition file. It is used to define the layout
# and source data to be processed into an OpenAL Soft compatible HRTF.
# This is a makemhr HRIR definition file. It is used to define the layout and
# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the left and right ear HRIRs of any
# raw data set from the IRCAM/AKG Listen HRTF database.
+5 -5
View File
@@ -1,5 +1,5 @@
# This is a makehrtf HRIR definition file. It is used to define the layout
# and source data to be processed into an OpenAL Soft compatible HRTF.
# This is a makemhr HRIR definition file. It is used to define the layout and
# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the left ear HRIRs from the full set
# of KEMAR HRIRs provided by Bill Gardner <billg@media.mit.edu> and Keith
@@ -33,9 +33,9 @@ type = mono
points = 512
# The radius of the listener's head (measured ear-to-ear in meters). The
# makehrtf utility uses this value to rescale measured propagation delays
# when a custom head radius is specified on the command line. It is also
# used as the default radius when the spherical model is used to calculate an
# makemhr utility uses this value to rescale measured propagation delays when
# a custom head radius is specified on the command line. It is also used as
# the default radius when the spherical model is used to calculate an
# approximate set of delays. This should match the data set as close as
# possible for accurate rescaling when using the measured delays (the
# default). At the moment, radius rescaling does not adjust HRIR coupling.
+51
View File
@@ -0,0 +1,51 @@
# This is a makemhr HRIR definition file. It is used to define the layout and
# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the SOFA packaged KEMAR HRIRs
# originally provided by Bill Gardner <billg@media.mit.edu> and Keith Martin
# <kdm@media.mit.edu> of MIT Media Laboratory.
#
# The SOFA conversion is available from:
#
# http://sofacoustics.org/data/database/mit/
#
# The original data is available from:
#
# http://sound.media.mit.edu/resources/KEMAR.html
#
# It is copyrighted 1994 by MIT Media Laboratory, and provided free of charge
# with no restrictions on use so long as the authors (above) are cited.
# Sampling rate of the HRIR data (in hertz).
rate = 44100
# The SOFA file is stereo, but the original data was mono. Channels are just
# mirrored by azimuth; so save some memory by allowing OpenAL Soft to mirror
# them at run time.
type = mono
points = 512
radius = 0.09
# The MIT set has only one field with a distance of 1.4m.
distance = 1.4
# The MIT set varies the number of azimuths for each elevation to maintain
# an average distance between them.
azimuths = 1, 12, 24, 36, 45, 56, 60, 72, 72, 72, 72, 72, 60, 56, 45, 36, 24, 12, 1
# Normally the dataset would be composed manually by listing all necessary
# 'sofa' sources with the appropriate radius, elevation, azimuth (counter-
# clockwise for SOFA files) and receiver arguments:
#
# [ 5, 0 ] = sofa (1.4, -40.0, 0.0 : 0) : "./mit_kemar_normal_pinna.sofa"
# [ 5, 1 ] = sofa (1.4, -40.0, 353.6 : 0) : "./mit_kemar_normal_pinna.sofa"
# [ 5, 2 ] = sofa (1.4, -40.0, 347.1 : 0) : "./mit_kemar_normal_pinna.sofa"
# [ 5, 3 ] = sofa (1.4, -40.0, 340.7 : 0) : "./mit_kemar_normal_pinna.sofa"
# ...
#
# If HRIR composition isn't necessary, it's easier to just use the following:
[ * ] = sofa : "./mit_kemar_normal_pinna.sofa" mono
+48
View File
@@ -0,0 +1,48 @@
# This is a makemhr HRIR definition file. It is used to define the layout and
# source data to be processed into an OpenAL Soft compatible HRTF.
#
# This definition is used to transform the near-field KEMAR HRIRs provided by
# Bosun Xie <phbsxie@scut.edu.cn> of the South China University of
# Technology, Guangzhou, China; and converted from SCUT to SOFA format by
# Piotr Majdak <piotr@majdak.com> of the Acoustics Research Institute,
# Austrian Academy of Sciences.
#
# A copy of the data (SCUT_KEMAR_radius_all.sofa) is available from:
#
# http://sofacoustics.org/data/database/scut/SCUT_KEMAR_radius_all.sofa
#
# It is provided under the Creative Commons CC 3.0 BY-SA-NC license:
#
# https://creativecommons.org/licenses/by-nc-sa/3.0/
rate = 44100
# While the SOFA file is stereo, doubling the size of the data set will cause
# the utility to exhaust its address space if compiled 32-bit. Since the
# dummy head is symmetric, the same results (ignoring variations caused by
# measurement error) can be obtained using mono channel processing.
type = mono
points = 512
radius = 0.09
# This data set has 10 fields ranging from 0.2m to 1m. The layout was
# obtained using the sofa-info utility.
distance = 0.2, 0.25, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0
azimuths = 1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1;
1, 24, 36, 72, 72, 72, 72, 72, 72, 72, 36, 24, 1
# Given the above compatible layout, we can automatically process the entire
# data set.
[ * ] = sofa : "./SCUT_KEMAR_radius_all.sofa" mono
@@ -1,58 +1,31 @@
project(alsoft-config)
option(ALSOFT_NO_QT5 "Use Qt4 instead of Qt5 for alsoft-config" FALSE)
if(Qt5Widgets_FOUND)
qt5_wrap_ui(UIS mainwindow.ui)
include_directories("${alsoft-config_BINARY_DIR}")
qt5_wrap_cpp(MOCS mainwindow.h)
set(alsoft-config_SRCS
main.cpp
mainwindow.cpp
mainwindow.h
)
set(alsoft-config_UIS mainwindow.ui)
set(alsoft-config_MOCS mainwindow.h)
find_package(Qt5Widgets)
if(Qt5Widgets_FOUND AND NOT ALSOFT_NO_QT5)
qt5_wrap_ui(UIS ${alsoft-config_UIS})
qt5_wrap_cpp(MOCS ${alsoft-config_MOCS})
add_executable(alsoft-config ${alsoft-config_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS})
add_executable(alsoft-config
main.cpp
mainwindow.cpp
mainwindow.h
verstr.cpp
verstr.h
${UIS} ${RSCS} ${TRS} ${MOCS})
target_link_libraries(alsoft-config Qt5::Widgets)
set_property(TARGET alsoft-config APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS})
target_include_directories(alsoft-config PRIVATE "${alsoft-config_BINARY_DIR}"
"${OpenAL_BINARY_DIR}")
set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR})
if(TARGET build_version)
add_dependencies(alsoft-config build_version)
endif()
install(TARGETS alsoft-config
message(STATUS "Building configuration program")
if(ALSOFT_INSTALL_UTILS)
install(TARGETS alsoft-config
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
else()
# Need Qt 4.8.0 or newer for the iconset theme attribute to work
find_package(Qt4 4.8.0 COMPONENTS QtCore QtGui)
if(QT4_FOUND)
include(${QT_USE_FILE})
qt4_wrap_ui(UIS ${alsoft-config_UIS})
qt4_wrap_cpp(MOCS ${alsoft-config_MOCS})
add_executable(alsoft-config ${alsoft-config_SRCS} ${UIS} ${RSCS} ${TRS} ${MOCS})
target_link_libraries(alsoft-config ${QT_LIBRARIES})
set_property(TARGET alsoft-config APPEND PROPERTY COMPILE_FLAGS ${EXTRA_CFLAGS})
set_target_properties(alsoft-config PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${OpenAL_BINARY_DIR})
if(TARGET build_version)
add_dependencies(alsoft-config build_version)
endif()
install(TARGETS alsoft-config
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
endif()
File diff suppressed because it is too large Load Diff
@@ -43,6 +43,7 @@ private slots:
void updateJackBufferSizeEdit(int size);
void updateJackBufferSizeSlider();
void updateHrtfModeLabel(int num);
void addHrtfFile();
void removeHrtfFile();
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>564</width>
<height>460</height>
<height>469</height>
</rect>
</property>
<property name="minimumSize">
@@ -21,8 +21,7 @@
</property>
<property name="windowIcon">
<iconset theme="preferences-desktop-sound">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<widget class="QWidget" name="centralWidget">
<widget class="QPushButton" name="applyButton">
@@ -31,7 +30,7 @@
<x>470</x>
<y>405</y>
<width>81</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -39,8 +38,7 @@
</property>
<property name="icon">
<iconset theme="dialog-ok-apply">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
<widget class="QTabWidget" name="tabWidget">
@@ -53,7 +51,7 @@
</rect>
</property>
<property name="currentIndex">
<number>5</number>
<number>0</number>
</property>
<widget class="QWidget" name="tab_3">
<attribute name="title">
@@ -64,8 +62,8 @@
<rect>
<x>110</x>
<y>50</y>
<width>78</width>
<height>21</height>
<width>76</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -82,7 +80,7 @@ float and converted to the output sample type as needed.</string>
<x>0</x>
<y>50</y>
<width>101</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -98,7 +96,7 @@ float and converted to the output sample type as needed.</string>
<x>0</x>
<y>20</y>
<width>101</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -113,8 +111,8 @@ float and converted to the output sample type as needed.</string>
<rect>
<x>110</x>
<y>20</y>
<width>78</width>
<height>21</height>
<width>76</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -131,8 +129,8 @@ to stereo output.</string>
<rect>
<x>380</x>
<y>20</y>
<width>80</width>
<height>20</height>
<width>96</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -194,7 +192,7 @@ to stereo output.</string>
<x>290</x>
<y>20</y>
<width>81</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -210,7 +208,7 @@ to stereo output.</string>
<x>290</x>
<y>50</y>
<width>81</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -225,8 +223,8 @@ to stereo output.</string>
<rect>
<x>380</x>
<y>50</y>
<width>78</width>
<height>21</height>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -256,7 +254,7 @@ otherwise be suitable for speakers.</string>
<x>20</x>
<y>30</y>
<width>511</width>
<height>91</height>
<height>81</height>
</rect>
</property>
<property name="title">
@@ -283,9 +281,9 @@ mixed and being heard.</string>
<widget class="QLabel" name="label_11">
<property name="geometry">
<rect>
<x>20</x>
<x>60</x>
<y>0</y>
<width>201</width>
<width>161</width>
<height>21</height>
</rect>
</property>
@@ -299,9 +297,9 @@ mixed and being heard.</string>
<widget class="QSlider" name="periodCountSlider">
<property name="geometry">
<rect>
<x>80</x>
<x>99</x>
<y>20</y>
<width>160</width>
<width>141</width>
<height>21</height>
</rect>
</property>
@@ -336,7 +334,7 @@ mixed and being heard.</string>
<widget class="QLineEdit" name="periodCountEdit">
<property name="geometry">
<rect>
<x>20</x>
<x>40</x>
<y>20</y>
<width>51</width>
<height>21</height>
@@ -365,24 +363,24 @@ frames needed for each mixing update.</string>
<rect>
<x>60</x>
<y>20</y>
<width>160</width>
<width>191</width>
<height>21</height>
</rect>
</property>
<property name="minimum">
<number>0</number>
<number>63</number>
</property>
<property name="maximum">
<number>8192</number>
</property>
<property name="singleStep">
<number>64</number>
<number>1</number>
</property>
<property name="pageStep">
<number>1024</number>
</property>
<property name="value">
<number>0</number>
<number>63</number>
</property>
<property name="tracking">
<bool>true</bool>
@@ -423,7 +421,7 @@ frames needed for each mixing update.</string>
</rect>
</property>
<property name="placeholderText">
<string>1024</string>
<string>20ms</string>
</property>
</widget>
</widget>
@@ -432,9 +430,9 @@ frames needed for each mixing update.</string>
<property name="geometry">
<rect>
<x>130</x>
<y>130</y>
<width>131</width>
<height>21</height>
<y>120</y>
<width>111</width>
<height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -451,9 +449,9 @@ receiver.</string>
<property name="geometry">
<rect>
<x>20</x>
<y>130</y>
<y>120</y>
<width>101</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -466,23 +464,26 @@ receiver.</string>
<widget class="QLabel" name="label_30">
<property name="geometry">
<rect>
<x>270</x>
<y>130</y>
<width>111</width>
<height>21</height>
<x>260</x>
<y>120</y>
<width>121</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Ambisonic Format:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QComboBox" name="ambiFormatComboBox">
<property name="geometry">
<rect>
<x>390</x>
<y>130</y>
<y>120</y>
<width>131</width>
<height>21</height>
<height>31</height>
</rect>
</property>
</widget>
@@ -533,7 +534,7 @@ quantization with low-level whitenoise.</string>
<property name="geometry">
<rect>
<x>60</x>
<y>80</y>
<y>90</y>
<width>421</width>
<height>81</height>
</rect>
@@ -611,7 +612,7 @@ quantization with low-level whitenoise.</string>
<widget class="QCheckBox" name="decoderHQModeCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<x>30</x>
<y>20</y>
<width>181</width>
<height>21</height>
@@ -635,7 +636,7 @@ appropriate speaker configuration you intend to use.</string>
<widget class="QCheckBox" name="decoderDistCompCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<x>30</x>
<y>50</y>
<width>181</width>
<height>21</height>
@@ -655,7 +656,7 @@ configuration file.</string>
<property name="text">
<string>Distance Compensation:</string>
</property>
<property name="checked">
<property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -834,7 +835,7 @@ configuration file.</string>
<widget class="QCheckBox" name="decoderNFEffectsCheckBox">
<property name="geometry">
<rect>
<x>10</x>
<x>30</x>
<y>80</y>
<width>181</width>
<height>21</height>
@@ -847,8 +848,7 @@ creates a more realistic perception of sound distance.
Note that the effect may be stronger or weaker than
intended if the application doesn't use or specify an
appropriate unit scale, or if incorrect speaker distances
are set in the decoder configuration file. Requires High
Quality Mode to be enabled.</string>
are set in the decoder configuration file.</string>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
@@ -856,7 +856,7 @@ Quality Mode to be enabled.</string>
<property name="text">
<string>Near-Field Effects:</string>
</property>
<property name="checked">
<property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -885,7 +885,7 @@ normal output is created with no near-field simulation.</string>
<rect>
<x>20</x>
<y>0</y>
<width>151</width>
<width>171</width>
<height>21</height>
</rect>
</property>
@@ -899,9 +899,9 @@ normal output is created with no near-field simulation.</string>
<widget class="QDoubleSpinBox" name="decoderNFRefDelaySpinBox">
<property name="geometry">
<rect>
<x>180</x>
<x>200</x>
<y>0</y>
<width>91</width>
<width>81</width>
<height>21</height>
</rect>
</property>
@@ -999,8 +999,7 @@ normal output is created with no near-field simulation.</string>
</property>
<property name="icon">
<iconset theme="list-add">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<property name="flat">
<bool>false</bool>
@@ -1040,8 +1039,7 @@ listed above.</string>
</property>
<property name="icon">
<iconset theme="list-remove">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</widget>
@@ -1049,10 +1047,10 @@ listed above.</string>
<widget class="QLabel" name="label_16">
<property name="geometry">
<rect>
<x>40</x>
<y>50</y>
<x>50</x>
<y>60</y>
<width>71</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -1066,9 +1064,9 @@ listed above.</string>
<property name="geometry">
<rect>
<x>130</x>
<y>50</y>
<y>60</y>
<width>161</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="toolTip">
@@ -1097,10 +1095,10 @@ application or system to determine if it should be used.</string>
<widget class="QLabel" name="label_12">
<property name="geometry">
<rect>
<x>20</x>
<x>30</x>
<y>20</y>
<width>91</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -1116,13 +1114,84 @@ application or system to determine if it should be used.</string>
<x>130</x>
<y>20</y>
<width>161</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="toolTip">
<string>The default HRTF to use if the application doesn't request one.</string>
</property>
</widget>
<widget class="QGroupBox" name="groupBox_9">
<property name="geometry">
<rect>
<x>50</x>
<y>100</y>
<width>441</width>
<height>81</height>
</rect>
</property>
<property name="title">
<string>HRTF Render Method</string>
</property>
<widget class="QLabel" name="label_31">
<property name="geometry">
<rect>
<x>20</x>
<y>30</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Speed</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_32">
<property name="geometry">
<rect>
<x>340</x>
<y>30</y>
<width>51</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Quality</string>
</property>
</widget>
<widget class="QSlider" name="hrtfmodeSlider">
<property name="geometry">
<rect>
<x>80</x>
<y>30</y>
<width>251</width>
<height>21</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<widget class="QLabel" name="hrtfmodeLabel">
<property name="geometry">
<rect>
<x>50</x>
<y>50</y>
<width>321</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Default</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</widget>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
@@ -1185,6 +1254,9 @@ application or system to determine if it should be used.</string>
<height>361</height>
</rect>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="page">
<widget class="QCheckBox" name="backendCheckBox">
<property name="geometry">
@@ -1279,7 +1351,7 @@ is not already running.</string>
<property name="text">
<string>AutoSpawn Server</string>
</property>
<property name="checked">
<property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -1301,6 +1373,9 @@ to match the new device.</string>
<property name="text">
<string>Allow Moving Streams</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="pulseFixRateCheckBox">
<property name="geometry">
@@ -1318,6 +1393,32 @@ rate to match the PulseAudio device.</string>
<property name="text">
<string>Fix Sample Rate</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="pulseAdjLatencyCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>100</y>
<width>111</width>
<height>21</height>
</rect>
</property>
<property name="toolTip">
<string>Attempts to adjust the overall latency of device
playback. Note that this may have adverse effects
on the resulting internal buffer sizes and mixing
updates, leading to performance problems and
drop-outs.</string>
</property>
<property name="text">
<string>Adjust Latency</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_7">
@@ -1333,12 +1434,15 @@ rate to match the PulseAudio device.</string>
<property name="text">
<string>AutoSpawn Server</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
<widget class="QGroupBox" name="groupBox_7">
<property name="geometry">
<rect>
<x>10</x>
<y>40</y>
<y>70</y>
<width>401</width>
<height>80</height>
</rect>
@@ -1396,6 +1500,22 @@ processing requests. Must be a power of 2.</string>
</property>
</widget>
</widget>
<widget class="QCheckBox" name="jackConnectPortsCheckBox">
<property name="geometry">
<rect>
<x>20</x>
<y>40</y>
<width>141</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>AutoConnect Ports</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QWidget" name="page_3">
<widget class="QLabel" name="label_17">
@@ -1474,6 +1594,9 @@ resample pass on top of OpenAL's resampler.</string>
<property name="text">
<string>Allow Resampler</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="alsaMmapCheckBox">
<property name="geometry">
@@ -1492,7 +1615,7 @@ during updates.</string>
<property name="text">
<string>MMap Buffer</string>
</property>
<property name="checked">
<property name="tristate">
<bool>true</bool>
</property>
</widget>
@@ -2156,6 +2279,22 @@ added by the ALC_EXT_DEDICATED extension.</string>
<bool>true</bool>
</property>
</widget>
<widget class="QCheckBox" name="enableVocalMorpherCheck">
<property name="geometry">
<rect>
<x>320</x>
<y>210</y>
<width>131</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>Vocal morpher</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QLabel" name="label_13">
<property name="geometry">
@@ -2163,7 +2302,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<x>10</x>
<y>20</y>
<width>141</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -2178,8 +2317,8 @@ added by the ALC_EXT_DEDICATED extension.</string>
<rect>
<x>160</x>
<y>20</y>
<width>108</width>
<height>20</height>
<width>131</width>
<height>31</height>
</rect>
</property>
<property name="sizeAdjustPolicy">
@@ -2329,7 +2468,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<x>370</x>
<y>405</y>
<width>91</width>
<height>21</height>
<height>31</height>
</rect>
</property>
<property name="text">
@@ -2337,8 +2476,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
</property>
<property name="icon">
<iconset theme="window-close">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</widget>
@@ -2348,7 +2486,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<x>0</x>
<y>0</y>
<width>564</width>
<height>21</height>
<height>29</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -2372,8 +2510,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<action name="actionQuit">
<property name="icon">
<iconset theme="application-exit">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Quit</string>
@@ -2382,8 +2519,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<action name="actionSave_As">
<property name="icon">
<iconset theme="document-save-as">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>Save &amp;As...</string>
@@ -2395,8 +2531,7 @@ added by the ALC_EXT_DEDICATED extension.</string>
<action name="actionLoad">
<property name="icon">
<iconset theme="document-open">
<normaloff/>
</iconset>
<normaloff>.</normaloff>.</iconset>
</property>
<property name="text">
<string>&amp;Load...</string>
@@ -2414,22 +2549,6 @@ added by the ALC_EXT_DEDICATED extension.</string>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections>
<connection>
<sender>actionQuit</sender>
<signal>activated()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>267</x>
<y>181</y>
</hint>
</hints>
</connection>
<connection>
<sender>backendListWidget</sender>
<signal>currentRowChanged(int)</signal>
@@ -0,0 +1,10 @@
#include "verstr.h"
#include "version.h"
QString GetVersionString()
{
return QStringLiteral(ALSOFT_VERSION "-" ALSOFT_GIT_COMMIT_HASH " (" ALSOFT_GIT_BRANCH " branch).");
}
@@ -0,0 +1,8 @@
#ifndef VERSTR_H
#define VERSTR_H
#include <QString>
QString GetVersionString();
#endif /* VERSTR_H */
-404
View File
@@ -1,404 +0,0 @@
/*
* Sinc interpolator coefficient and delta generator for the OpenAL Soft
* cross platform audio library.
*
* Copyright (C) 2015 by Christopher Fitzgerald.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* Or visit: http://www.gnu.org/licenses/old-licenses/lgpl-2.0.html
*
* --------------------------------------------------------------------------
*
* This is a modified version of the bandlimited windowed sinc interpolator
* algorithm presented here:
*
* Smith, J.O. "Windowed Sinc Interpolation", in
* Physical Audio Signal Processing,
* https://ccrma.stanford.edu/~jos/pasp/Windowed_Sinc_Interpolation.html,
* online book,
* accessed October 2012.
*/
#define _UNICODE
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include "win_main_utf8.h"
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
#if defined(__ANDROID__) && !(defined(_ISOC99_SOURCE) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L))
#define log2(x) (log(x) / log(2.0))
#endif
// The number of distinct scale and phase intervals within the filter table.
// Must be the same as in alu.h!
#define BSINC_SCALE_COUNT (16)
#define BSINC_PHASE_COUNT (16)
/* 48 points includes the doubling for downsampling, so the maximum number of
* base sample points is 24, which is 23rd order.
*/
#define BSINC_POINTS_MAX (48)
static double MinDouble(double a, double b)
{ return (a <= b) ? a : b; }
static double MaxDouble(double a, double b)
{ return (a >= b) ? a : b; }
/* NOTE: This is the normalized (instead of just sin(x)/x) cardinal sine
* function.
* 2 f_t sinc(2 f_t x)
* f_t -- normalized transition frequency (0.5 is nyquist)
* x -- sample index (-N to N)
*/
static double Sinc(const double x)
{
if(fabs(x) < 1e-15)
return 1.0;
return sin(M_PI * x) / (M_PI * x);
}
static double BesselI_0(const double x)
{
double term, sum, last_sum, x2, y;
int i;
term = 1.0;
sum = 1.0;
x2 = x / 2.0;
i = 1;
do {
y = x2 / i;
i++;
last_sum = sum;
term *= y * y;
sum += term;
} while(sum != last_sum);
return sum;
}
/* NOTE: k is assumed normalized (-1 to 1)
* beta is equivalent to 2 alpha
*/
static double Kaiser(const double b, const double k)
{
if(!(k >= -1.0 && k <= 1.0))
return 0.0;
return BesselI_0(b * sqrt(1.0 - k*k)) / BesselI_0(b);
}
/* Calculates the (normalized frequency) transition width of the Kaiser window.
* Rejection is in dB.
*/
static double CalcKaiserWidth(const double rejection, const int order)
{
double w_t = 2.0 * M_PI;
if(rejection > 21.0)
return (rejection - 7.95) / (order * 2.285 * w_t);
/* This enforces a minimum rejection of just above 21.18dB */
return 5.79 / (order * w_t);
}
static double CalcKaiserBeta(const double rejection)
{
if(rejection > 50.0)
return 0.1102 * (rejection - 8.7);
else if(rejection >= 21.0)
return (0.5842 * pow(rejection - 21.0, 0.4)) +
(0.07886 * (rejection - 21.0));
return 0.0;
}
/* Generates the coefficient, delta, and index tables required by the bsinc resampler */
static void BsiGenerateTables(FILE *output, const char *tabname, const double rejection, const int order)
{
static double filter[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX];
static double scDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
static double phDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT + 1][BSINC_POINTS_MAX];
static double spDeltas[BSINC_SCALE_COUNT][BSINC_PHASE_COUNT ][BSINC_POINTS_MAX];
static int mt[BSINC_SCALE_COUNT];
static double at[BSINC_SCALE_COUNT];
const int num_points_min = order + 1;
double width, beta, scaleBase, scaleRange;
int si, pi, i;
memset(filter, 0, sizeof(filter));
memset(scDeltas, 0, sizeof(scDeltas));
memset(phDeltas, 0, sizeof(phDeltas));
memset(spDeltas, 0, sizeof(spDeltas));
/* Calculate windowing parameters. The width describes the transition
band, but it may vary due to the linear interpolation between scales
of the filter.
*/
width = CalcKaiserWidth(rejection, order);
beta = CalcKaiserBeta(rejection);
scaleBase = width / 2.0;
scaleRange = 1.0 - scaleBase;
// Determine filter scaling.
for(si = 0; si < BSINC_SCALE_COUNT; si++)
{
const double scale = scaleBase + (scaleRange * si / (BSINC_SCALE_COUNT - 1));
const double a = MinDouble(floor(num_points_min / (2.0 * scale)), num_points_min);
const int m = 2 * (int)a;
mt[si] = m;
at[si] = a;
}
/* Calculate the Kaiser-windowed Sinc filter coefficients for each scale
and phase.
*/
for(si = 0; si < BSINC_SCALE_COUNT; si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
const int l = (m / 2) - 1;
const double a = at[si];
const double scale = scaleBase + (scaleRange * si / (BSINC_SCALE_COUNT - 1));
const double cutoff = (0.5 * scale) - (scaleBase * MaxDouble(0.5, scale));
for(pi = 0; pi <= BSINC_PHASE_COUNT; pi++)
{
const double phase = l + ((double)pi / BSINC_PHASE_COUNT);
for(i = 0; i < m; i++)
{
const double x = i - phase;
filter[si][pi][o + i] = Kaiser(beta, x / a) * 2.0 * cutoff * Sinc(2.0 * cutoff * x);
}
}
}
/* Linear interpolation between scales is simplified by pre-calculating
the delta (b - a) in: x = a + f (b - a)
Given a difference in points between scales, the destination points
will be 0, thus: x = a + f (-a)
*/
for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
{
for(i = 0; i < m; i++)
scDeltas[si][pi][o + i] = filter[si + 1][pi][o + i] - filter[si][pi][o + i];
}
}
// Linear interpolation between phases is also simplified.
for(si = 0; si < BSINC_SCALE_COUNT; si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
{
for(i = 0; i < m; i++)
phDeltas[si][pi][o + i] = filter[si][pi + 1][o + i] - filter[si][pi][o + i];
}
}
/* This last simplification is done to complete the bilinear equation for
the combination of scale and phase.
*/
for(si = 0; si < (BSINC_SCALE_COUNT - 1); si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
{
for(i = 0; i < m; i++)
spDeltas[si][pi][o + i] = phDeltas[si + 1][pi][o + i] - phDeltas[si][pi][o + i];
}
}
// Make sure the number of points is a multiple of 4 (for SIMD).
for(si = 0; si < BSINC_SCALE_COUNT; si++)
mt[si] = (mt[si]+3) & ~3;
fprintf(output,
"/* This %d%s order filter has a rejection of -%.0fdB, yielding a transition width\n"
" * of ~%.3f (normalized frequency). Order increases when downsampling to a\n"
" * limit of one octave, after which the quality of the filter (transition\n"
" * width) suffers to reduce the CPU cost. The bandlimiting will cut all sound\n"
" * after downsampling by ~%.2f octaves.\n"
" */\n"
"const BSincTable %s = {\n",
order, (((order%100)/10) == 1) ? "th" :
((order%10) == 1) ? "st" :
((order%10) == 2) ? "nd" :
((order%10) == 3) ? "rd" : "th",
rejection, width, log2(1.0/scaleBase), tabname);
/* The scaleBase is calculated from the Kaiser window transition width.
It represents the absolute limit to the filter before it fully cuts
the signal. The limit in octaves can be calculated by taking the
base-2 logarithm of its inverse: log_2(1 / scaleBase)
*/
fprintf(output, " /* scaleBase */ %.9ef, /* scaleRange */ %.9ef,\n", scaleBase, 1.0 / scaleRange);
fprintf(output, " /* m */ {");
fprintf(output, " %d", mt[0]);
for(si = 1; si < BSINC_SCALE_COUNT; si++)
fprintf(output, ", %d", mt[si]);
fprintf(output, " },\n");
fprintf(output, " /* filterOffset */ {");
fprintf(output, " %d", 0);
i = mt[0]*4*BSINC_PHASE_COUNT;
for(si = 1; si < BSINC_SCALE_COUNT; si++)
{
fprintf(output, ", %d", i);
i += mt[si]*4*BSINC_PHASE_COUNT;
}
fprintf(output, " },\n");
// Calculate the table size.
i = 0;
for(si = 0; si < BSINC_SCALE_COUNT; si++)
i += 4 * BSINC_PHASE_COUNT * mt[si];
fprintf(output, "\n /* Tab (%d entries) */ {\n", i);
for(si = 0; si < BSINC_SCALE_COUNT; si++)
{
const int m = mt[si];
const int o = num_points_min - (m / 2);
for(pi = 0; pi < BSINC_PHASE_COUNT; pi++)
{
fprintf(output, " /* %2d,%2d (%d) */", si, pi, m);
fprintf(output, "\n ");
for(i = 0; i < m; i++)
fprintf(output, " %+14.9ef,", filter[si][pi][o + i]);
fprintf(output, "\n ");
for(i = 0; i < m; i++)
fprintf(output, " %+14.9ef,", scDeltas[si][pi][o + i]);
fprintf(output, "\n ");
for(i = 0; i < m; i++)
fprintf(output, " %+14.9ef,", phDeltas[si][pi][o + i]);
fprintf(output, "\n ");
for(i = 0; i < m; i++)
fprintf(output, " %+14.9ef,", spDeltas[si][pi][o + i]);
fprintf(output, "\n");
}
}
fprintf(output, " }\n};\n\n");
}
/* These methods generate a much simplified 4-point sinc interpolator using a
* Kaiser window. This is much simpler to process at run-time, but has notably
* more aliasing noise.
*/
/* Same as in alu.h! */
#define FRACTIONBITS (12)
#define FRACTIONONE (1<<FRACTIONBITS)
static void Sinc4GenerateTables(FILE *output, const double rejection)
{
static double filter[FRACTIONONE][4];
const double width = CalcKaiserWidth(rejection, 3);
const double beta = CalcKaiserBeta(rejection);
const double scaleBase = width / 2.0;
const double scaleRange = 1.0 - scaleBase;
const double scale = scaleBase + scaleRange;
const double a = MinDouble(4.0, floor(4.0 / (2.0*scale)));
const int m = 2 * (int)a;
const int l = (m/2) - 1;
int pi;
for(pi = 0;pi < FRACTIONONE;pi++)
{
const double phase = l + ((double)pi / FRACTIONONE);
int i;
for(i = 0;i < m;i++)
{
double x = i - phase;
filter[pi][i] = Kaiser(beta, x / a) * Sinc(x);
}
}
fprintf(output, "alignas(16) static const float sinc4Tab[FRACTIONONE][4] = {\n");
for(pi = 0;pi < FRACTIONONE;pi++)
fprintf(output, " { %+14.9ef, %+14.9ef, %+14.9ef, %+14.9ef },\n",
filter[pi][0], filter[pi][1], filter[pi][2], filter[pi][3]);
fprintf(output, "};\n\n");
}
int main(int argc, char *argv[])
{
FILE *output;
if(argc > 2)
{
fprintf(stderr, "Usage: %s [output file]\n", argv[0]);
return 1;
}
if(argc == 2)
{
output = fopen(argv[1], "wb");
if(!output)
{
fprintf(stderr, "Failed to open %s for writing\n", argv[1]);
return 1;
}
}
else
output = stdout;
fprintf(output, "/* Generated by bsincgen, do not edit! */\n\n"
"static_assert(BSINC_SCALE_COUNT == %d, \"Unexpected BSINC_SCALE_COUNT value!\");\n"
"static_assert(BSINC_PHASE_COUNT == %d, \"Unexpected BSINC_PHASE_COUNT value!\");\n"
"static_assert(FRACTIONONE == %d, \"Unexpected FRACTIONONE value!\");\n\n"
"typedef struct BSincTable {\n"
" const float scaleBase, scaleRange;\n"
" const int m[BSINC_SCALE_COUNT];\n"
" const int filterOffset[BSINC_SCALE_COUNT];\n"
" alignas(16) const float Tab[];\n"
"} BSincTable;\n\n", BSINC_SCALE_COUNT, BSINC_PHASE_COUNT, FRACTIONONE);
/* A 23rd order filter with a -60dB drop at nyquist. */
BsiGenerateTables(output, "bsinc24", 60.0, 23);
/* An 11th order filter with a -60dB drop at nyquist. */
BsiGenerateTables(output, "bsinc12", 60.0, 11);
Sinc4GenerateTables(output, 60.0);
if(output != stdout)
fclose(output);
output = NULL;
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
#ifndef LOADDEF_H
#define LOADDEF_H
#include <istream>
#include "makemhr.h"
bool LoadDefInput(std::istream &istream, const char *startbytes, std::streamsize startbytecount,
const char *filename, const uint fftSize, const uint truncSize, const ChannelModeT chanMode,
HrirDataT *hData);
#endif /* LOADDEF_H */
+568
View File
@@ -0,0 +1,568 @@
/*
* HRTF utility for producing and demonstrating the process of creating an
* OpenAL Soft compatible HRIR data set.
*
* Copyright (C) 2018-2019 Christopher Fitzgerald
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
#include "loadsofa.h"
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <functional>
#include <future>
#include <iterator>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
#include "makemhr.h"
#include "polyphase_resampler.h"
#include "sofa-support.h"
#include "mysofa.h"
using uint = unsigned int;
/* Attempts to produce a compatible layout. Most data sets tend to be
* uniform and have the same major axis as used by OpenAL Soft's HRTF model.
* This will remove outliers and produce a maximally dense layout when
* possible. Those sets that contain purely random measurements or use
* different major axes will fail.
*/
static bool PrepareLayout(const uint m, const float *xyzs, HrirDataT *hData)
{
fprintf(stdout, "Detecting compatible layout...\n");
auto fds = GetCompatibleLayout(m, xyzs);
if(fds.size() > MAX_FD_COUNT)
{
fprintf(stdout, "Incompatible layout (inumerable radii).\n");
return false;
}
double distances[MAX_FD_COUNT]{};
uint evCounts[MAX_FD_COUNT]{};
auto azCounts = std::vector<uint>(MAX_FD_COUNT*MAX_EV_COUNT, 0u);
uint fi{0u}, ir_total{0u};
for(const auto &field : fds)
{
distances[fi] = field.mDistance;
evCounts[fi] = field.mEvCount;
for(uint ei{0u};ei < field.mEvStart;ei++)
azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[field.mEvCount-ei-1];
for(uint ei{field.mEvStart};ei < field.mEvCount;ei++)
{
azCounts[fi*MAX_EV_COUNT + ei] = field.mAzCounts[ei];
ir_total += field.mAzCounts[ei];
}
++fi;
}
fprintf(stdout, "Using %u of %u IRs.\n", ir_total, m);
return PrepareHrirData(fi, distances, evCounts, azCounts.data(), hData) != 0;
}
bool PrepareSampleRate(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
{
const char *srate_dim{nullptr};
const char *srate_units{nullptr};
MYSOFA_ARRAY *srate_array{&sofaHrtf->DataSamplingRate};
MYSOFA_ATTRIBUTE *srate_attrs{srate_array->attributes};
while(srate_attrs)
{
if(std::string{"DIMENSION_LIST"} == srate_attrs->name)
{
if(srate_dim)
{
fprintf(stderr, "Duplicate SampleRate.DIMENSION_LIST\n");
return false;
}
srate_dim = srate_attrs->value;
}
else if(std::string{"Units"} == srate_attrs->name)
{
if(srate_units)
{
fprintf(stderr, "Duplicate SampleRate.Units\n");
return false;
}
srate_units = srate_attrs->value;
}
else
fprintf(stderr, "Unexpected sample rate attribute: %s = %s\n", srate_attrs->name,
srate_attrs->value);
srate_attrs = srate_attrs->next;
}
if(!srate_dim)
{
fprintf(stderr, "Missing sample rate dimensions\n");
return false;
}
if(srate_dim != std::string{"I"})
{
fprintf(stderr, "Unsupported sample rate dimensions: %s\n", srate_dim);
return false;
}
if(!srate_units)
{
fprintf(stderr, "Missing sample rate unit type\n");
return false;
}
if(srate_units != std::string{"hertz"})
{
fprintf(stderr, "Unsupported sample rate unit type: %s\n", srate_units);
return false;
}
/* I dimensions guarantees 1 element, so just extract it. */
hData->mIrRate = static_cast<uint>(srate_array->values[0] + 0.5f);
if(hData->mIrRate < MIN_RATE || hData->mIrRate > MAX_RATE)
{
fprintf(stderr, "Sample rate out of range: %u (expected %u to %u)", hData->mIrRate,
MIN_RATE, MAX_RATE);
return false;
}
return true;
}
bool PrepareDelay(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
{
const char *delay_dim{nullptr};
MYSOFA_ARRAY *delay_array{&sofaHrtf->DataDelay};
MYSOFA_ATTRIBUTE *delay_attrs{delay_array->attributes};
while(delay_attrs)
{
if(std::string{"DIMENSION_LIST"} == delay_attrs->name)
{
if(delay_dim)
{
fprintf(stderr, "Duplicate Delay.DIMENSION_LIST\n");
return false;
}
delay_dim = delay_attrs->value;
}
else
fprintf(stderr, "Unexpected delay attribute: %s = %s\n", delay_attrs->name,
delay_attrs->value);
delay_attrs = delay_attrs->next;
}
if(!delay_dim)
{
fprintf(stderr, "Missing delay dimensions\n");
/*return false;*/
}
else if(delay_dim != std::string{"I,R"})
{
fprintf(stderr, "Unsupported delay dimensions: %s\n", delay_dim);
return false;
}
else if(hData->mChannelType == CT_STEREO)
{
/* I,R is 1xChannelCount. Makemhr currently removes any delay constant,
* so we can ignore this as long as it's equal.
*/
if(delay_array->values[0] != delay_array->values[1])
{
fprintf(stderr, "Mismatched delays not supported: %f, %f\n", delay_array->values[0],
delay_array->values[1]);
return false;
}
}
return true;
}
bool CheckIrData(MYSOFA_HRTF *sofaHrtf)
{
const char *ir_dim{nullptr};
MYSOFA_ARRAY *ir_array{&sofaHrtf->DataIR};
MYSOFA_ATTRIBUTE *ir_attrs{ir_array->attributes};
while(ir_attrs)
{
if(std::string{"DIMENSION_LIST"} == ir_attrs->name)
{
if(ir_dim)
{
fprintf(stderr, "Duplicate IR.DIMENSION_LIST\n");
return false;
}
ir_dim = ir_attrs->value;
}
else
fprintf(stderr, "Unexpected IR attribute: %s = %s\n", ir_attrs->name,
ir_attrs->value);
ir_attrs = ir_attrs->next;
}
if(!ir_dim)
{
fprintf(stderr, "Missing IR dimensions\n");
return false;
}
if(ir_dim != std::string{"M,R,N"})
{
fprintf(stderr, "Unsupported IR dimensions: %s\n", ir_dim);
return false;
}
return true;
}
/* Calculate the onset time of a HRIR. */
static constexpr int OnsetRateMultiple{10};
static double CalcHrirOnset(PPhaseResampler &rs, const uint rate, const uint n,
std::vector<double> &upsampled, const double *hrir)
{
rs.process(n, hrir, static_cast<uint>(upsampled.size()), upsampled.data());
auto abs_lt = [](const double &lhs, const double &rhs) -> bool
{ return std::abs(lhs) < std::abs(rhs); };
auto iter = std::max_element(upsampled.cbegin(), upsampled.cend(), abs_lt);
return static_cast<double>(std::distance(upsampled.cbegin(), iter)) /
(double{OnsetRateMultiple}*rate);
}
/* Calculate the magnitude response of a HRIR. */
static void CalcHrirMagnitude(const uint points, const uint n, std::vector<complex_d> &h,
double *hrir)
{
auto iter = std::copy_n(hrir, points, h.begin());
std::fill(iter, h.end(), complex_d{0.0, 0.0});
FftForward(n, h.data());
MagnitudeResponse(n, h.data(), hrir);
}
static bool LoadResponses(MYSOFA_HRTF *sofaHrtf, HrirDataT *hData)
{
std::atomic<uint> loaded_count{0u};
auto load_proc = [sofaHrtf,hData,&loaded_count]() -> bool
{
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
hData->mHrirsBase.resize(channels * hData->mIrCount * hData->mIrSize, 0.0);
double *hrirs = hData->mHrirsBase.data();
for(uint si{0u};si < sofaHrtf->M;++si)
{
loaded_count.fetch_add(1u);
float aer[3]{
sofaHrtf->SourcePosition.values[3*si],
sofaHrtf->SourcePosition.values[3*si + 1],
sofaHrtf->SourcePosition.values[3*si + 2]
};
mysofa_c2s(aer);
if(std::abs(aer[1]) >= 89.999f)
aer[0] = 0.0f;
else
aer[0] = std::fmod(360.0f - aer[0], 360.0f);
auto field = std::find_if(hData->mFds.cbegin(), hData->mFds.cend(),
[&aer](const HrirFdT &fld) -> bool
{
double delta = aer[2] - fld.mDistance;
return (std::abs(delta) < 0.001);
});
if(field == hData->mFds.cend())
continue;
double ef{(90.0+aer[1]) / 180.0 * (field->mEvCount-1)};
auto ei = static_cast<int>(std::round(ef));
ef = (ef-ei) * 180.0 / (field->mEvCount-1);
if(std::abs(ef) >= 0.1) continue;
double af{aer[0] / 360.0 * field->mEvs[ei].mAzCount};
auto ai = static_cast<int>(std::round(af));
af = (af-ai) * 360.0 / field->mEvs[ei].mAzCount;
ai %= field->mEvs[ei].mAzCount;
if(std::abs(af) >= 0.1) continue;
HrirAzT *azd = &field->mEvs[ei].mAzs[ai];
if(azd->mIrs[0] != nullptr)
{
fprintf(stderr, "\nMultiple measurements near [ a=%f, e=%f, r=%f ].\n",
aer[0], aer[1], aer[2]);
return false;
}
for(uint ti{0u};ti < channels;++ti)
{
azd->mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd->mIndex)];
std::copy_n(&sofaHrtf->DataIR.values[(si*sofaHrtf->R + ti)*sofaHrtf->N],
hData->mIrPoints, azd->mIrs[ti]);
}
/* TODO: Since some SOFA files contain minimum phase HRIRs,
* it would be beneficial to check for per-measurement delays
* (when available) to reconstruct the HRTDs.
*/
}
return true;
};
std::future_status load_status{};
auto load_future = std::async(std::launch::async, load_proc);
do {
load_status = load_future.wait_for(std::chrono::milliseconds{50});
printf("\rLoading HRIRs... %u of %u", loaded_count.load(), sofaHrtf->M);
fflush(stdout);
} while(load_status != std::future_status::ready);
fputc('\n', stdout);
return load_future.get();
}
/* Calculates the frequency magnitudes of the HRIR set. Work is delegated to
* this struct, which runs asynchronously on one or more threads (sharing the
* same calculator object).
*/
struct MagCalculator {
const uint mFftSize{};
const uint mIrPoints{};
std::vector<double*> mIrs{};
std::atomic<size_t> mCurrent{};
std::atomic<size_t> mDone{};
void Worker()
{
auto htemp = std::vector<complex_d>(mFftSize);
while(1)
{
/* Load the current index to process. */
size_t idx{mCurrent.load()};
do {
/* If the index is at the end, we're done. */
if(idx >= mIrs.size())
return;
/* Otherwise, increment the current index atomically so other
* threads know to go to the next one. If this call fails, the
* current index was just changed by another thread and the new
* value is loaded into idx, which we'll recheck.
*/
} while(!mCurrent.compare_exchange_weak(idx, idx+1, std::memory_order_relaxed));
CalcHrirMagnitude(mIrPoints, mFftSize, htemp, mIrs[idx]);
/* Increment the number of IRs done. */
mDone.fetch_add(1);
}
}
};
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData)
{
int err;
MySofaHrtfPtr sofaHrtf{mysofa_load(filename, &err)};
if(!sofaHrtf)
{
fprintf(stdout, "Error: Could not load %s: %s\n", filename, SofaErrorStr(err));
return false;
}
/* NOTE: Some valid SOFA files are failing this check. */
err = mysofa_check(sofaHrtf.get());
if(err != MYSOFA_OK)
fprintf(stderr, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
SofaErrorStr(err));
mysofa_tocartesian(sofaHrtf.get());
/* Make sure emitter and receiver counts are sane. */
if(sofaHrtf->E != 1)
{
fprintf(stderr, "%u emitters not supported\n", sofaHrtf->E);
return false;
}
if(sofaHrtf->R > 2 || sofaHrtf->R < 1)
{
fprintf(stderr, "%u receivers not supported\n", sofaHrtf->R);
return false;
}
/* Assume R=2 is a stereo measurement, and R=1 is mono left-ear-only. */
if(sofaHrtf->R == 2 && chanMode == CM_AllowStereo)
hData->mChannelType = CT_STEREO;
else
hData->mChannelType = CT_MONO;
/* Check and set the FFT and IR size. */
if(sofaHrtf->N > fftSize)
{
fprintf(stderr, "Sample points exceeds the FFT size.\n");
return false;
}
if(sofaHrtf->N < truncSize)
{
fprintf(stderr, "Sample points is below the truncation size.\n");
return false;
}
hData->mIrPoints = sofaHrtf->N;
hData->mFftSize = fftSize;
hData->mIrSize = std::max(1u + (fftSize/2u), sofaHrtf->N);
/* Assume a default head radius of 9cm. */
hData->mRadius = 0.09;
if(!PrepareSampleRate(sofaHrtf.get(), hData) || !PrepareDelay(sofaHrtf.get(), hData)
|| !CheckIrData(sofaHrtf.get()))
return false;
if(!PrepareLayout(sofaHrtf->M, sofaHrtf->SourcePosition.values, hData))
return false;
if(!LoadResponses(sofaHrtf.get(), hData))
return false;
sofaHrtf = nullptr;
for(uint fi{0u};fi < hData->mFdCount;fi++)
{
uint ei{0u};
for(;ei < hData->mFds[fi].mEvCount;ei++)
{
uint ai{0u};
for(;ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
if(azd.mIrs[0] != nullptr) break;
}
if(ai < hData->mFds[fi].mEvs[ei].mAzCount)
break;
}
if(ei >= hData->mFds[fi].mEvCount)
{
fprintf(stderr, "Missing source references [ %d, *, * ].\n", fi);
return false;
}
hData->mFds[fi].mEvStart = ei;
for(;ei < hData->mFds[fi].mEvCount;ei++)
{
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
if(azd.mIrs[0] == nullptr)
{
fprintf(stderr, "Missing source reference [ %d, %d, %d ].\n", fi, ei, ai);
return false;
}
}
}
}
size_t hrir_total{0};
const uint channels{(hData->mChannelType == CT_STEREO) ? 2u : 1u};
double *hrirs = hData->mHrirsBase.data();
for(uint fi{0u};fi < hData->mFdCount;fi++)
{
for(uint ei{0u};ei < hData->mFds[fi].mEvStart;ei++)
{
for(uint ai{0u};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(uint ti{0u};ti < channels;ti++)
azd.mIrs[ti] = &hrirs[hData->mIrSize * (hData->mIrCount*ti + azd.mIndex)];
}
}
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++)
hrir_total += hData->mFds[fi].mEvs[ei].mAzCount * channels;
}
std::atomic<size_t> hrir_done{0};
auto onset_proc = [hData,channels,&hrir_done]() -> bool
{
/* Temporary buffer used to calculate the IR's onset. */
auto upsampled = std::vector<double>(OnsetRateMultiple * hData->mIrPoints);
/* This resampler is used to help detect the response onset. */
PPhaseResampler rs;
rs.init(hData->mIrRate, OnsetRateMultiple*hData->mIrRate);
for(uint fi{0u};fi < hData->mFdCount;fi++)
{
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++)
{
for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(uint ti{0};ti < channels;ti++)
{
hrir_done.fetch_add(1u, std::memory_order_acq_rel);
azd.mDelays[ti] = CalcHrirOnset(rs, hData->mIrRate, hData->mIrPoints,
upsampled, azd.mIrs[ti]);
}
}
}
}
return true;
};
std::future_status load_status{};
auto load_future = std::async(std::launch::async, onset_proc);
do {
load_status = load_future.wait_for(std::chrono::milliseconds{50});
printf("\rCalculating HRIR onsets... %zu of %zu", hrir_done.load(), hrir_total);
fflush(stdout);
} while(load_status != std::future_status::ready);
fputc('\n', stdout);
if(!load_future.get())
return false;
MagCalculator calculator{hData->mFftSize, hData->mIrPoints};
for(uint fi{0u};fi < hData->mFdCount;fi++)
{
for(uint ei{hData->mFds[fi].mEvStart};ei < hData->mFds[fi].mEvCount;ei++)
{
for(uint ai{0};ai < hData->mFds[fi].mEvs[ei].mAzCount;ai++)
{
HrirAzT &azd = hData->mFds[fi].mEvs[ei].mAzs[ai];
for(uint ti{0};ti < channels;ti++)
calculator.mIrs.push_back(azd.mIrs[ti]);
}
}
}
std::vector<std::thread> thrds;
thrds.reserve(numThreads);
for(size_t i{0};i < numThreads;++i)
thrds.emplace_back(std::mem_fn(&MagCalculator::Worker), &calculator);
size_t count;
do {
std::this_thread::sleep_for(std::chrono::milliseconds{50});
count = calculator.mDone.load();
printf("\rCalculating HRIR magnitudes... %zu of %zu", count, calculator.mIrs.size());
fflush(stdout);
} while(count != calculator.mIrs.size());
fputc('\n', stdout);
for(auto &thrd : thrds)
{
if(thrd.joinable())
thrd.join();
}
return true;
}
+10
View File
@@ -0,0 +1,10 @@
#ifndef LOADSOFA_H
#define LOADSOFA_H
#include "makemhr.h"
bool LoadSofaFile(const char *filename, const uint numThreads, const uint fftSize,
const uint truncSize, const ChannelModeT chanMode, HrirDataT *hData);
#endif /* LOADSOFA_H */
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
#ifndef MAKEMHR_H
#define MAKEMHR_H
#include <vector>
#include <complex>
#include "polyphase_resampler.h"
// The maximum path length used when processing filenames.
#define MAX_PATH_LEN (256)
// The limit to the number of 'distances' listed in the data set definition.
// Must be less than 256
#define MAX_FD_COUNT (16)
// The limits to the number of 'elevations' listed in the data set definition.
// Must be less than 256.
#define MIN_EV_COUNT (5)
#define MAX_EV_COUNT (181)
// The limits for each of the 'azimuths' listed in the data set definition.
// Must be less than 256.
#define MIN_AZ_COUNT (1)
#define MAX_AZ_COUNT (255)
// The limits for the 'distance' from source to listener for each field in
// the definition file.
#define MIN_DISTANCE (0.05)
#define MAX_DISTANCE (2.50)
// The limits for the sample 'rate' metric in the data set definition and for
// resampling.
#define MIN_RATE (32000)
#define MAX_RATE (96000)
// The limits for the HRIR 'points' metric in the data set definition.
#define MIN_POINTS (16)
#define MAX_POINTS (8192)
using uint = unsigned int;
/* Complex double type. */
using complex_d = std::complex<double>;
enum ChannelModeT : bool {
CM_AllowStereo = false,
CM_ForceMono = true
};
// Sample and channel type enum values.
enum SampleTypeT {
ST_S16 = 0,
ST_S24 = 1
};
// Certain iterations rely on these integer enum values.
enum ChannelTypeT {
CT_NONE = -1,
CT_MONO = 0,
CT_STEREO = 1
};
// Structured HRIR storage for stereo azimuth pairs, elevations, and fields.
struct HrirAzT {
double mAzimuth{0.0};
uint mIndex{0u};
double mDelays[2]{0.0, 0.0};
double *mIrs[2]{nullptr, nullptr};
};
struct HrirEvT {
double mElevation{0.0};
uint mIrCount{0u};
uint mAzCount{0u};
HrirAzT *mAzs{nullptr};
};
struct HrirFdT {
double mDistance{0.0};
uint mIrCount{0u};
uint mEvCount{0u};
uint mEvStart{0u};
HrirEvT *mEvs{nullptr};
};
// The HRIR metrics and data set used when loading, processing, and storing
// the resulting HRTF.
struct HrirDataT {
uint mIrRate{0u};
SampleTypeT mSampleType{ST_S24};
ChannelTypeT mChannelType{CT_NONE};
uint mIrPoints{0u};
uint mFftSize{0u};
uint mIrSize{0u};
double mRadius{0.0};
uint mIrCount{0u};
uint mFdCount{0u};
std::vector<double> mHrirsBase;
std::vector<HrirEvT> mEvsBase;
std::vector<HrirAzT> mAzsBase;
std::vector<HrirFdT> mFds;
};
int PrepareHrirData(const uint fdCount, const double (&distances)[MAX_FD_COUNT], const uint (&evCounts)[MAX_FD_COUNT], const uint azCounts[MAX_FD_COUNT * MAX_EV_COUNT], HrirDataT *hData);
void MagnitudeResponse(const uint n, const complex_d *in, double *out);
void FftForward(const uint n, complex_d *inout);
void FftInverse(const uint n, complex_d *inout);
// Performs linear interpolation.
inline double Lerp(const double a, const double b, const double f)
{ return a + f * (b - a); }
#endif /* MAKEMHR_H */
+9 -5
View File
@@ -22,14 +22,18 @@
* THE SOFTWARE.
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string.h>
#include "AL/alc.h"
#include "AL/al.h"
#include "AL/alext.h"
#include "win_main_utf8.h"
#ifndef ALC_ENUMERATE_ALL_EXT
#define ALC_DEFAULT_ALL_DEVICES_SPECIFIER 0x1012
#define ALC_ALL_DEVICES_SPECIFIER 0x1013
@@ -53,7 +57,7 @@ static WCHAR *FromUTF8(const char *str)
if((len=MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0)) > 0)
{
out = calloc(sizeof(WCHAR), len);
out = calloc(sizeof(WCHAR), (unsigned int)(len));
MultiByteToWideChar(CP_UTF8, 0, str, -1, out, len);
}
return out;
@@ -124,7 +128,7 @@ static void printList(const char *list, char separator)
next = strchr(list, separator);
if(next)
{
len = next-list;
len = (size_t)(next-list);
do {
next++;
} while(*next == separator);
@@ -223,7 +227,7 @@ static void printHRTFInfo(ALCdevice *device)
return;
}
alcGetStringiSOFT = alcGetProcAddress(device, "alcGetStringiSOFT");
alcGetStringiSOFT = (LPALCGETSTRINGISOFT)alcGetProcAddress(device, "alcGetStringiSOFT");
alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &num_hrtfs);
if(!num_hrtfs)
@@ -263,7 +267,7 @@ static void printResamplerInfo(void)
return;
}
alGetStringiSOFT = alGetProcAddress("alGetStringiSOFT");
alGetStringiSOFT = (LPALGETSTRINGISOFT)alGetProcAddress("alGetStringiSOFT");
num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
def_resampler = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
+139
View File
@@ -0,0 +1,139 @@
/*
* SOFA info utility for inspecting SOFA file metrics and determining HRTF
* utility compatible layouts.
*
* Copyright (C) 2018-2019 Christopher Fitzgerald
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
#include <stdio.h>
#include <memory>
#include <vector>
#include "sofa-support.h"
#include "mysofa.h"
#include "win_main_utf8.h"
using uint = unsigned int;
static void PrintSofaAttributes(const char *prefix, struct MYSOFA_ATTRIBUTE *attribute)
{
while(attribute)
{
fprintf(stdout, "%s.%s: %s\n", prefix, attribute->name, attribute->value);
attribute = attribute->next;
}
}
static void PrintSofaArray(const char *prefix, struct MYSOFA_ARRAY *array)
{
PrintSofaAttributes(prefix, array->attributes);
for(uint i{0u};i < array->elements;i++)
fprintf(stdout, "%s[%u]: %.6f\n", prefix, i, array->values[i]);
}
/* Attempts to produce a compatible layout. Most data sets tend to be
* uniform and have the same major axis as used by OpenAL Soft's HRTF model.
* This will remove outliers and produce a maximally dense layout when
* possible. Those sets that contain purely random measurements or use
* different major axes will fail.
*/
static void PrintCompatibleLayout(const uint m, const float *xyzs)
{
fputc('\n', stdout);
auto fds = GetCompatibleLayout(m, xyzs);
if(fds.empty())
{
fprintf(stdout, "No compatible field layouts in SOFA file.\n");
return;
}
uint used_elems{0};
for(size_t fi{0u};fi < fds.size();++fi)
{
for(uint ei{fds[fi].mEvStart};ei < fds[fi].mEvCount;++ei)
used_elems += fds[fi].mAzCounts[ei];
}
fprintf(stdout, "Compatible Layout (%u of %u measurements):\n\ndistance = %.3f", used_elems, m,
fds[0].mDistance);
for(size_t fi{1u};fi < fds.size();fi++)
fprintf(stdout, ", %.3f", fds[fi].mDistance);
fprintf(stdout, "\nazimuths = ");
for(size_t fi{0u};fi < fds.size();++fi)
{
for(uint ei{0u};ei < fds[fi].mEvStart;++ei)
fprintf(stdout, "%d%s", fds[fi].mAzCounts[fds[fi].mEvCount - 1 - ei], ", ");
for(uint ei{fds[fi].mEvStart};ei < fds[fi].mEvCount;++ei)
fprintf(stdout, "%d%s", fds[fi].mAzCounts[ei],
(ei < (fds[fi].mEvCount - 1)) ? ", " :
(fi < (fds.size() - 1)) ? ";\n " : "\n");
}
}
// Load and inspect the given SOFA file.
static void SofaInfo(const char *filename)
{
int err;
MySofaHrtfPtr sofa{mysofa_load(filename, &err)};
if(!sofa)
{
fprintf(stdout, "Error: Could not load source file '%s' (%s).\n", filename,
SofaErrorStr(err));
return;
}
/* NOTE: Some valid SOFA files are failing this check. */
err = mysofa_check(sofa.get());
if(err != MYSOFA_OK)
fprintf(stdout, "Warning: Supposedly malformed source file '%s' (%s).\n", filename,
SofaErrorStr(err));
mysofa_tocartesian(sofa.get());
PrintSofaAttributes("Info", sofa->attributes);
fprintf(stdout, "Measurements: %u\n", sofa->M);
fprintf(stdout, "Receivers: %u\n", sofa->R);
fprintf(stdout, "Emitters: %u\n", sofa->E);
fprintf(stdout, "Samples: %u\n", sofa->N);
PrintSofaArray("SampleRate", &sofa->DataSamplingRate);
PrintSofaArray("DataDelay", &sofa->DataDelay);
PrintCompatibleLayout(sofa->M, sofa->SourcePosition.values);
}
int main(int argc, char *argv[])
{
if(argc != 2)
{
fprintf(stdout, "Usage: %s <sofa-file>\n", argv[0]);
return 0;
}
SofaInfo(argv[1]);
return 0;
}
+292
View File
@@ -0,0 +1,292 @@
/*
* SOFA utility methods for inspecting SOFA file metrics and determining HRTF
* utility compatible layouts.
*
* Copyright (C) 2018-2019 Christopher Fitzgerald
* Copyright (C) 2019 Christopher Robinson
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Or visit: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/
#include "sofa-support.h"
#include <stdio.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <utility>
#include <vector>
#include "mysofa.h"
namespace {
using uint = unsigned int;
using double3 = std::array<double,3>;
/* Produces a sorted array of unique elements from a particular axis of the
* triplets array. The filters are used to focus on particular coordinates
* of other axes as necessary. The epsilons are used to constrain the
* equality of unique elements.
*/
std::vector<double> GetUniquelySortedElems(const std::vector<double3> &aers, const uint axis,
const double *const (&filters)[3], const double (&epsilons)[3])
{
std::vector<double> elems;
for(const double3 &aer : aers)
{
const double elem{aer[axis]};
uint j;
for(j = 0;j < 3;j++)
{
if(filters[j] && std::abs(aer[j] - *filters[j]) > epsilons[j])
break;
}
if(j < 3)
continue;
auto iter = elems.begin();
for(;iter != elems.end();++iter)
{
const double delta{elem - *iter};
if(delta > epsilons[axis]) continue;
if(delta >= -epsilons[axis]) break;
iter = elems.emplace(iter, elem);
break;
}
if(iter == elems.end())
elems.emplace_back(elem);
}
return elems;
}
/* Given a list of azimuths, this will produce the smallest step size that can
* uniformly cover the list. Ideally this will be over half, but in degenerate
* cases this can fall to a minimum of 5 (the lower limit).
*/
double GetUniformAzimStep(const double epsilon, const std::vector<double> &elems)
{
if(elems.size() < 5) return 0.0;
/* Get the maximum count possible, given the first two elements. It would
* be impossible to have more than this since the first element must be
* included.
*/
uint count{static_cast<uint>(std::ceil(360.0 / (elems[1]-elems[0])))};
count = std::min(count, 255u);
for(;count >= 5;--count)
{
/* Given the stepping value for this number of elements, check each
* multiple to ensure there's a matching element.
*/
const double step{360.0 / count};
bool good{true};
size_t idx{1u};
for(uint mult{1u};mult < count && good;++mult)
{
const double target{step*mult + elems[0]};
while(idx < elems.size() && target-elems[idx] > epsilon)
++idx;
good &= (idx < elems.size()) && !(std::abs(target-elems[idx++]) > epsilon);
}
if(good)
return step;
}
return 0.0;
}
/* Given a list of elevations, this will produce the smallest step size that
* can uniformly cover the list. Ideally this will be over half, but in
* degenerate cases this can fall to a minimum of 5 (the lower limit).
*/
double GetUniformElevStep(const double epsilon, std::vector<double> &elems)
{
if(elems.size() < 5) return 0.0;
/* Reverse the elevations so it increments starting with -90 (flipped from
* +90). This makes it easier to work out a proper stepping value.
*/
std::reverse(elems.begin(), elems.end());
for(auto &v : elems) v *= -1.0;
uint count{static_cast<uint>(std::ceil(180.0 / (elems[1]-elems[0])))};
count = std::min(count, 255u);
double ret{0.0};
for(;count >= 5;--count)
{
const double step{180.0 / count};
bool good{true};
size_t idx{1u};
/* Elevations don't need to match all multiples if there's not enough
* elements to check. Missing elevations can be synthesized.
*/
for(uint mult{1u};mult <= count && idx < elems.size() && good;++mult)
{
const double target{step*mult + elems[0]};
while(idx < elems.size() && target-elems[idx] > epsilon)
++idx;
good &= !(idx < elems.size()) || !(std::abs(target-elems[idx++]) > epsilon);
}
if(good)
{
ret = step;
break;
}
}
/* Re-reverse the elevations to restore the correct order. */
for(auto &v : elems) v *= -1.0;
std::reverse(elems.begin(), elems.end());
return ret;
}
} // namespace
const char *SofaErrorStr(int err)
{
switch(err)
{
case MYSOFA_OK: return "OK";
case MYSOFA_INVALID_FORMAT: return "Invalid format";
case MYSOFA_UNSUPPORTED_FORMAT: return "Unsupported format";
case MYSOFA_INTERNAL_ERROR: return "Internal error";
case MYSOFA_NO_MEMORY: return "Out of memory";
case MYSOFA_READ_ERROR: return "Read error";
}
return "Unknown";
}
std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs)
{
auto aers = std::vector<double3>(m, double3{});
for(size_t i{0u};i < m;++i)
{
float vals[3]{xyzs[i*3], xyzs[i*3 + 1], xyzs[i*3 + 2]};
mysofa_c2s(&vals[0]);
aers[i] = {vals[0], vals[1], vals[2]};
}
auto radii = GetUniquelySortedElems(aers, 2, {}, {0.1, 0.1, 0.001});
std::vector<SofaField> fds;
fds.reserve(radii.size());
for(const double dist : radii)
{
auto elevs = GetUniquelySortedElems(aers, 1, {nullptr, nullptr, &dist}, {0.1, 0.1, 0.001});
/* Remove elevations that don't have a valid set of azimuths. */
auto invalid_elev = [&dist,&aers](const double ev) -> bool
{
auto azims = GetUniquelySortedElems(aers, 0, {nullptr, &ev, &dist}, {0.1, 0.1, 0.001});
if(std::abs(ev) > 89.999)
return azims.size() != 1;
if(azims.empty() || !(std::abs(azims[0]) < 0.1))
return true;
return GetUniformAzimStep(0.1, azims) <= 0.0;
};
elevs.erase(std::remove_if(elevs.begin(), elevs.end(), invalid_elev), elevs.end());
double step{GetUniformElevStep(0.1, elevs)};
if(step <= 0.0)
{
if(elevs.empty())
fprintf(stdout, "No usable elevations on field distance %f.\n", dist);
else
{
fprintf(stdout, "Non-uniform elevations on field distance %.3f.\nGot: %+.2f", dist,
elevs[0]);
for(size_t ei{1u};ei < elevs.size();++ei)
fprintf(stdout, ", %+.2f", elevs[ei]);
fputc('\n', stdout);
}
continue;
}
uint evStart{0u};
for(uint ei{0u};ei < elevs.size();ei++)
{
if(!(elevs[ei] < 0.0))
{
fprintf(stdout, "Too many missing elevations on field distance %f.\n", dist);
return fds;
}
double eif{(90.0+elevs[ei]) / step};
const double ev_start{std::round(eif)};
if(std::abs(eif - ev_start) < (0.1/step))
{
evStart = static_cast<uint>(ev_start);
break;
}
}
const auto evCount = static_cast<uint>(std::round(180.0 / step)) + 1;
if(evCount < 5)
{
fprintf(stdout, "Too few uniform elevations on field distance %f.\n", dist);
continue;
}
SofaField field{};
field.mDistance = dist;
field.mEvCount = evCount;
field.mEvStart = evStart;
field.mAzCounts.resize(evCount, 0u);
auto &azCounts = field.mAzCounts;
for(uint ei{evStart};ei < evCount;ei++)
{
double ev{-90.0 + ei*180.0/(evCount - 1)};
auto azims = GetUniquelySortedElems(aers, 0, {nullptr, &ev, &dist}, {0.1, 0.1, 0.001});
if(ei == 0 || ei == (evCount-1))
{
if(azims.size() != 1)
{
fprintf(stdout, "Non-singular poles on field distance %f.\n", dist);
return fds;
}
azCounts[ei] = 1;
}
else
{
step = GetUniformAzimStep(0.1, azims);
if(step <= 0.0)
{
fprintf(stdout, "Non-uniform azimuths on elevation %f, field distance %f.\n",
ev, dist);
return fds;
}
azCounts[ei] = static_cast<uint>(std::round(360.0f / step));
}
}
fds.emplace_back(std::move(field));
}
return fds;
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef UTILS_SOFA_SUPPORT_H
#define UTILS_SOFA_SUPPORT_H
#include <cstddef>
#include <memory>
#include <vector>
#include "mysofa.h"
struct MySofaDeleter {
void operator()(MYSOFA_HRTF *sofa) { mysofa_free(sofa); }
};
using MySofaHrtfPtr = std::unique_ptr<MYSOFA_HRTF,MySofaDeleter>;
// Per-field measurement info.
struct SofaField {
using uint = unsigned int;
double mDistance{0.0};
uint mEvCount{0u};
uint mEvStart{0u};
std::vector<uint> mAzCounts;
};
const char *SofaErrorStr(int err);
std::vector<SofaField> GetCompatibleLayout(const size_t m, const float *xyzs);
#endif /* UTILS_SOFA_SUPPORT_H */