mirror of
https://github.com/love2d/megasource.git
synced 2026-08-18 19:54:37 +02:00
Update OpenAL-soft to 1.21.1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>&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 &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>&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 */
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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 */
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
Reference in New Issue
Block a user