├── .gitignore ├── .mailmap ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── Modules │ └── FindMySofa.cmake └── spatialaudio.pc.cmake ├── include ├── AdmConversions.h ├── AdmDirectSpeakerGainCalc.h ├── AdmMappingRules.h ├── AdmMetadata.h ├── AdmRenderer.h ├── AmbisonicBase.h ├── AmbisonicBinauralizer.h ├── AmbisonicCommons.h ├── AmbisonicDecoder.h ├── AmbisonicDecoderPresets.h ├── AmbisonicEncoder.h ├── AmbisonicEncoderDist.h ├── AmbisonicMicrophone.h ├── AmbisonicProcessor.h ├── AmbisonicPsychoacousticFilters.h ├── AmbisonicShelfFilters.h ├── AmbisonicSource.h ├── AmbisonicSpeaker.h ├── AmbisonicTypesDefinesCommons.h ├── AmbisonicZoomer.h ├── Ambisonics.h ├── BFormat.h ├── Coordinates.h ├── Decorrelate.h ├── GainCalculator.h ├── GainInterp.h ├── LoudspeakerLayouts.h ├── PointSourcePannerGainCalc.h ├── PolarExtent.h ├── RegionHandlers.h ├── Screen.h ├── ScreenCommon.h ├── SpeakersBinauralizer.h ├── Tests.h ├── Tools.h ├── config.h.in ├── hrtf │ ├── hrtf.h │ ├── mit_hrtf.h │ └── sofa_hrtf.h ├── mit_hrtf_filter.h ├── mit_hrtf_lib.h └── normal │ ├── mit_hrtf_normal_44100.h │ ├── mit_hrtf_normal_48000.h │ ├── mit_hrtf_normal_88200.h │ └── mit_hrtf_normal_96000.h ├── octave ├── FibonacciSphereSampling.m ├── WeightingFunctionTest.m ├── cart.m ├── fibonacciSphere.m └── pol.m └── source ├── AdmDirectSpeakerGainCalc.cpp ├── AdmRenderer.cpp ├── AmbisonicBase.cpp ├── AmbisonicBinauralizer.cpp ├── AmbisonicCommons.cpp ├── AmbisonicDecoder.cpp ├── AmbisonicEncoder.cpp ├── AmbisonicEncoderDist.cpp ├── AmbisonicMicrophone.cpp ├── AmbisonicProcessor.cpp ├── AmbisonicShelfFilters.cpp ├── AmbisonicSource.cpp ├── AmbisonicSpeaker.cpp ├── AmbisonicZoomer.cpp ├── BFormat.cpp ├── Decorrelate.cpp ├── GainCalculator.cpp ├── GainInterp.cpp ├── PointSourcePannerGainCalc.cpp ├── PolarExtent.cpp ├── RegionHandlers.cpp ├── Screen.cpp ├── SpeakersBinauralizer.cpp ├── hrtf ├── mit_hrtf.cpp └── sofa_hrtf.cpp ├── kiss_fft ├── _kiss_fft_guts.h ├── kiss_fft.c ├── kiss_fft.h ├── kiss_fftr.c └── kiss_fftr.h └── mit_hrtf_lib.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | install/ 3 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Peter Stitt 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | set(PACKAGE_VERSION_MAJOR "0") 4 | set(PACKAGE_VERSION_MINOR "3") 5 | set(PACKAGE_VERSION_PATCH "1") 6 | project(spatialaudio VERSION "${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}.${PACKAGE_VERSION_PATCH}") 7 | 8 | set(LIBRARY_SOVERSION_MAJOR "1") 9 | set(LIBRARY_SOVERSION_MINOR "0") 10 | set(LIBRARY_SOVERSION_PATCH "0") 11 | 12 | set(CMAKE_CXX_STANDARD 14) 13 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 14 | 15 | option(BUILD_SHARED_LIBS "Build shared library" ON) 16 | option(BUILD_STATIC_LIBS "Build static library" ON) 17 | 18 | include(GNUInstallDirs) 19 | 20 | list(APPEND headers 21 | include/AdmConversions.h 22 | include/AdmDirectSpeakerGainCalc.h 23 | include/AdmMappingRules.h 24 | include/AdmMetadata.h 25 | include/AdmRenderer.h 26 | include/AmbisonicBase.h 27 | include/AmbisonicBinauralizer.h 28 | include/AmbisonicCommons.h 29 | include/AmbisonicDecoder.h 30 | include/AmbisonicDecoderPresets.h 31 | include/AmbisonicEncoderDist.h 32 | include/AmbisonicEncoder.h 33 | include/AmbisonicMicrophone.h 34 | include/AmbisonicProcessor.h 35 | include/AmbisonicPsychoacousticFilters.h 36 | include/Ambisonics.h 37 | include/AmbisonicShelfFilters.h 38 | include/AmbisonicSource.h 39 | include/AmbisonicSpeaker.h 40 | include/AmbisonicTypesDefinesCommons.h 41 | include/AmbisonicZoomer.h 42 | include/BFormat.h 43 | include/Coordinates.h 44 | include/Decorrelate.h 45 | include/GainCalculator.h 46 | include/GainInterp.h 47 | include/hrtf/hrtf.h 48 | include/hrtf/mit_hrtf.h 49 | include/hrtf/sofa_hrtf.h 50 | include/LoudspeakerLayouts.h 51 | include/mit_hrtf_filter.h 52 | include/mit_hrtf_lib.h 53 | include/normal/mit_hrtf_normal_44100.h 54 | include/normal/mit_hrtf_normal_48000.h 55 | include/normal/mit_hrtf_normal_88200.h 56 | include/normal/mit_hrtf_normal_96000.h 57 | include/PointSourcePannerGainCalc.h 58 | include/PolarExtent.h 59 | include/RegionHandlers.h 60 | include/Screen.h 61 | include/ScreenCommon.h 62 | include/SpeakersBinauralizer.h 63 | include/Tools.h 64 | source/kiss_fft/kiss_fft.h 65 | source/kiss_fft/kiss_fftr.h 66 | ) 67 | 68 | list(APPEND sources 69 | source/AmbisonicEncoder.cpp 70 | source/AmbisonicMicrophone.cpp 71 | source/AmbisonicCommons.cpp 72 | source/mit_hrtf_lib.c 73 | source/AmbisonicProcessor.cpp 74 | source/AmbisonicDecoder.cpp 75 | source/AmbisonicBinauralizer.cpp 76 | source/AmbisonicSource.cpp 77 | source/AmbisonicShelfFilters.cpp 78 | source/hrtf/mit_hrtf.cpp 79 | source/hrtf/sofa_hrtf.cpp 80 | source/BFormat.cpp 81 | source/SpeakersBinauralizer.cpp 82 | source/kiss_fft/kiss_fftr.c 83 | source/kiss_fft/kiss_fft.c 84 | source/AmbisonicBase.cpp 85 | source/AmbisonicSpeaker.cpp 86 | source/AmbisonicEncoderDist.cpp 87 | source/AmbisonicZoomer.cpp 88 | source/Decorrelate.cpp 89 | source/AdmDirectSpeakerGainCalc.cpp 90 | source/AdmRenderer.cpp 91 | source/Decorrelate.cpp 92 | source/GainCalculator.cpp 93 | source/GainInterp.cpp 94 | source/PointSourcePannerGainCalc.cpp 95 | source/PolarExtent.cpp 96 | source/RegionHandlers.cpp 97 | source/Screen.cpp 98 | ) 99 | 100 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") 101 | 102 | find_package(MySofa QUIET) 103 | set(HAVE_MYSOFA ${MYSOFA_FOUND}) 104 | 105 | include_directories(include include/hrtf source source/kiss_fft ${PROJECT_BINARY_DIR}) 106 | 107 | if(MYSOFA_FOUND) 108 | include_directories(${MYSOFA_INCLUDE_DIRS}) 109 | endif(MYSOFA_FOUND) 110 | 111 | if(BUILD_STATIC_LIBS) 112 | add_library(spatialaudio-static STATIC ${sources}) 113 | if(MYSOFA_FOUND) 114 | target_link_libraries(spatialaudio-static ${MYSOFA_LIBRARIES}) 115 | endif(MYSOFA_FOUND) 116 | SET_TARGET_PROPERTIES(spatialaudio-static PROPERTIES OUTPUT_NAME spatialaudio CLEAN_DIRECT_OUTPUT 1 POSITION_INDEPENDENT_CODE ON) 117 | install(TARGETS spatialaudio-static ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 118 | endif(BUILD_STATIC_LIBS) 119 | 120 | if(BUILD_SHARED_LIBS) 121 | add_library(spatialaudio-shared SHARED ${sources}) 122 | if(MYSOFA_FOUND) 123 | target_link_libraries(spatialaudio-shared ${MYSOFA_LIBRARIES}) 124 | endif(MYSOFA_FOUND) 125 | SET_TARGET_PROPERTIES(spatialaudio-shared PROPERTIES OUTPUT_NAME spatialaudio CLEAN_DIRECT_OUTPUT 1) 126 | set_property(TARGET spatialaudio-shared PROPERTY VERSION "${LIBRARY_SOVERSION_MAJOR}.${LIBRARY_SOVERSION_MINOR}.${LIBRARY_SOVERSION_PATCH}") 127 | set_property(TARGET spatialaudio-shared PROPERTY SOVERSION ${LIBRARY_SOVERSION_MAJOR} ) 128 | set_property(TARGET spatialaudio-shared PROPERTY C_VISIBILITY_PRESET hidden) 129 | install(TARGETS spatialaudio-shared LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) 130 | endif(BUILD_SHARED_LIBS) 131 | 132 | option(HAVE_MIT_HRTF "Should MIT HRTF be built-in" ON) 133 | 134 | configure_file( 135 | "${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in" 136 | "${CMAKE_CURRENT_BINARY_DIR}/config.h" 137 | ) 138 | 139 | if(MYSOFA_FOUND) 140 | set(MYSOFA_LIB "-L${MYSOFA_LIBRARY_DIRS} -lmysofa") 141 | set(MYSOFA_INCLUDE "-I${MYSOFA_INCLUDE_DIRS}") 142 | endif(MYSOFA_FOUND) 143 | 144 | configure_file( 145 | "${CMAKE_CURRENT_SOURCE_DIR}/cmake/spatialaudio.pc.cmake" 146 | "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" 147 | @ONLY 148 | ) 149 | 150 | install(FILES ${headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/spatialaudio) 151 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) 152 | install(FILES ${PROJECT_BINARY_DIR}/config.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/spatialaudio) 153 | 154 | #Tarballs generation 155 | set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) 156 | set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) 157 | set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) 158 | set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") 159 | set(CPACK_SOURCE_GENERATOR "TBZ2") 160 | set(CPACK_SOURCE_IGNORE_FILES "/build*;/.git*;~$;${CPACK_SOURCE_IGNORE_FILES}") 161 | include(CPack) 162 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Ambisonic encoding / decoding and binauralization library 2 | ============= 3 | 4 | **libspatialaudio** is an open-source and cross-platform C++ library for **Ambisonic** encoding and decoding, filtering and **binaural** rendering. 5 | It is targetted to render *High-Order Ambisonic* (**HOA**) and *VR/3D audio* samples in multiple environments, from headphones to classic loudspeakers. 6 | Its *binaural rendering* can be used for classical 5.1/7.1 spatial channels as well as Ambisonics inputs. 7 | 8 | Originally it is a fork of **ambisonic-lib** from Aristotel Digenis. This version was developed to support Higher Order Ambisonics *HOA* and to support **ACN/SN3D** Ambisonics audio streams following the **Google spatial audio** specification: 9 | https://github.com/google/spatial-media/blob/master/docs/spatial-audio-rfc.md and the **IETF codec Ambisonics** specification https://tools.ietf.org/html/draft-ietf-codec-ambisonics 10 | 11 | The library allows you to encode, decode, rotate, zoom **HOA Ambisonics** audio streams up to the *3rd order*. It can output to standard and custom loudspeakers arrays. To playback with headphones, the binauralizer applies an HRTF *(either a SOFA file or the included MIT HRTF)* to provide a spatial binaural rendering effect. The binauralization can also be used to render multichannels streams (5.1, 7.1...). 12 | 13 | A central part of the library is the CBFormat object which acts as a buffer for B-Format. There are several other objects, each with a specific tasks, such as encoding, decoding, and processing for Ambisonics. All of these objects handle CBFormat objects at some point. 14 | 15 | 16 | ## Features 17 | ### Encoder (CAmbisonicEncoder): 18 | Simple encoder up to 3rd order 3D, without any distance cues. 19 | 20 | ### Encoder with distance (CAmbisonicEncoderDist): 21 | As the simple encoder, but with the addition of the following: 22 | * Distance level-simulation 23 | * Fractional delay lines 24 | * Interior effect (W-Panning) 25 | 26 | ### Decoder (CAmbisonicDecoder): 27 | Simple decoder up to the 3rd Order 3D with: 28 | * Preset & custom speaker arrays 29 | * Decoder that improves the rendering with a 5.1 speaker set 30 | 31 | ### Processor (CAmbisonicProcessor): 32 | Up to 3rd order 3D yaw/roll/pitch of the soundfield 33 | 34 | Up to 3rd order psychoacoustic optimisation shelf-filters for 2D and 3D playback 35 | 36 | ### Binauralizer (CAmbisonicBinauralizer): 37 | Up to 3rd order 3D decoding to headphones 38 | 39 | Optional symmetric head decoder to reduce the number of convolutions 40 | 41 | ### Zoomer (CAmbisonicZoomer): 42 | Up to 1st order 3D front-back dominance control of the soundfield 43 | 44 | ## Overview of the Implemented Algorithms 45 | ### Psychoacoustic Optimisation Shelf-filters 46 | Implemented as linear phase FIR shelf-filters ensure basic and max rE decodes in low- and high-frequency ranges respectively. See [[1]](#ref1) for more details why and [[2]](#ref2) for the mathematical theory used for higher orders. 47 | 48 | The transition frequency between the two decoder types depends on the order being decoded, increasing with Ambisonic order. 49 | 50 | The frequency is given by [[3]](#ref3): 51 | ```c 52 | 53 | f_lim = speedofsound*M / (4*R*(M+1)*sin(PI / (2*M+2))) 54 | ``` 55 | where speedofsound = 343 m/s, R = 0.09 m (roughly the radius of human head) and M = Ambisonic order. 56 | 57 | A different gain is applied to each of the channels of a particular order. These are given by [[2](#ref2),[4](#ref4)]: 58 | ```c 59 | 2D: g_m = cos(pi*m/(2M + 2)) 60 | 3D: g_m = legendre(m, cos(137.9*(PI/180)/(M+1.51))) 61 | ``` 62 | where m = floor(sqrt(Channel Number)) and legendre(m,x) is a Legendre polynomial of degree m evaluated for a value of x. 63 | 64 | 65 | ### Binaural Decoding 66 | The binaural decoder uses a two different virtual loudspeaker arrays depending on the order: 67 | * 1st order: cuboid loudspeaker array 68 | * 2nd and 3rd order: Dodecahedron 69 | 70 | To keep the number of convolutions to a minimum, the HRTFs are decomposed into spherical harmonics. This gives a pair of HRTF filters for each of the Ambisonics channel. 71 | The advantage of this method is that the number of convolutions is limited to the number of Ambisonic channels, regardless of the number of virtual loudspeakers used. 72 | 73 | ### Symmetric Head Binaural Decoder 74 | The binaural decoder can reduce the number of convolutions needed for the binaural decoding by two. 75 | 76 | The Ambisonic input channels are convolved with the corresponding HRTF channel for the left ear. The left ear signal is the sum of these convolved channels. To generate the right ear signal the soundfield is reflected left-right by multiplying several of the convolved channels by -1. These are then summed to produce the right ear signal. 77 | 78 | ## How do I use it? 79 | 80 | The following sample code shows the encoding of a sine wave into an Ambisonic soundfield, and then decoding that soundfield over a Quad speaker setup. 81 | 82 | ```c 83 | // Generation of mono test signal 84 | float sinewave[512]; 85 | for(int ni = 0; ni < 512; ni++) 86 | sinewave[ni] = (float)sin((ni / 128.f) * (M_PI * 2)); 87 | 88 | // CBFormat as 1st order 3D, and 512 samples 89 | CBFormat myBFormat; 90 | 91 | // Ambisonic encoder, also 3rd order 3D 92 | CAmbisonicEncoder myEncoder; 93 | myEncoder.Configure(1, true, 0); 94 | 95 | // Set test signal's position in the soundfield 96 | PolarPoint position; 97 | position.fAzimuth = 0; 98 | position.fElevation = 0; 99 | position.fDistance = 5; 100 | myEncoder.SetPosition(position); 101 | myEncoder.Refresh(); 102 | 103 | // Encode test signal into BFormat buffer 104 | myEncoder.Process(sinewave, 512, &myBFormat); 105 | 106 | // Ambisonic decoder, also 1st order 3D, for a 5.0 setup 107 | CAmbisonicDecoder myDecoder; 108 | myDecoder.Configure(1, true, kAmblib_50, 5); 109 | 110 | // Allocate buffers for speaker feeds 111 | float** ppfSpeakerFeeds = new float*[5]; 112 | for(int niSpeaker = 0; niSpeaker < 5; niSpeaker++) 113 | ppfSpeakerFeeds[niSpeaker] = new float[512]; 114 | 115 | // Decode to get the speaker feeds 116 | myDecoder.Process(&myBFormat, 512, ppfSpeakerFeeds); 117 | 118 | // De-allocate speaker feed buffers 119 | for(int niSpeaker = 0; niSpeaker < 5; niSpeaker++) 120 | delete [] ppfSpeakerFeeds[niSpeaker]; 121 | delete [] ppfSpeakerFeeds; 122 | ``` 123 | 124 | ## References 125 | 126 | [1] M. A. Gerzon, “Practical Periphony: The Reproduction of Full-Sphere Sound,” in Audio Engineering Society Convention, 1980, pp. 1–12. 127 | 128 | [2] J. Daniel, “Représentation de champs acoustiques, application à la transmission et à la reproduction de scènes sonores complexes dans un contexte multimédia,” University of Paris, 2000. 129 | 130 | [3] S. Bertet, J. Daniel, E. Parizet, and O. Warusfel, “Investigation on localisation accuracy for first and higher order Ambisonics reproduced sound sources,” Acta Acust. united with Acust., vol. 99, no. 4, pp. 642–657, 2013. 131 | 132 | [4] F. Zotter and M. Frank, “All-round ambisonic panning and decoding,” J. Audio Eng. Soc., vol. 60, no. 10, pp. 807–820, 2012. 133 | -------------------------------------------------------------------------------- /cmake/Modules/FindMySofa.cmake: -------------------------------------------------------------------------------- 1 | # Try to find libMySofa headers and library. 2 | # 3 | # Usage of this module as follows: 4 | # 5 | # find_package(MySofa) 6 | # 7 | # Variables used by this module, they can change the default behaviour and need 8 | # to be set before calling find_package: 9 | # 10 | # MYSOFA_ROOT_DIR Set this variable to the root installation of 11 | # libMySofa if the module has problems finding the 12 | # proper installation path. 13 | # 14 | # Variables defined by this module: 15 | # 16 | # MYSOFA_FOUND System has libMySofa library/headers. 17 | # MYSOFA_LIBRARIES The libMySofa library. 18 | # MYSOFA_INCLUDE_DIRS The location of libMySofa headers. 19 | 20 | find_package(PkgConfig QUIET) 21 | 22 | if(PKG_CONFIG_FOUND) 23 | pkg_check_modules(MYSOFA libmysofa) 24 | endif(PKG_CONFIG_FOUND) 25 | 26 | if(MYSOFA) 27 | set(MYSOFA_INCLUDE_DIRS ${MYSOFA_PKG_CONFIG_INCLUDE_DIRS}) 28 | set(MYSOFA_LIBRARY_DIRS ${MYSOFA_PKG_CONFIG_LIBRARY_DIRS}) 29 | endif() 30 | 31 | if(NOT MYSOFA_LIBRARIES) 32 | find_library(MYSOFA_LIBRARIES 33 | NAMES mysofa 34 | ) 35 | endif(NOT MYSOFA_LIBRARIES) 36 | 37 | if(NOT MYSOFA_LIBRARY_DIRS) 38 | find_path(MYSOFA_LIBRARY_DIRS 39 | NAMES libmysofa.so 40 | ) 41 | endif(NOT MYSOFA_LIBRARY_DIRS) 42 | 43 | if(NOT MYSOFA_INCLUDE_DIRS) 44 | find_path(MYSOFA_INCLUDE_DIRS 45 | NAMES mysofa.h 46 | ) 47 | endif(NOT MYSOFA_INCLUDE_DIRS) 48 | 49 | include(FindPackageHandleStandardArgs) 50 | find_package_handle_standard_args(MySofa DEFAULT_MSG 51 | MYSOFA_LIBRARIES 52 | MYSOFA_INCLUDE_DIRS 53 | ) 54 | 55 | mark_as_advanced( 56 | MYSOFA_LIBRARIES 57 | MYSOFA_INCLUDE_DIRS 58 | ) 59 | -------------------------------------------------------------------------------- /cmake/spatialaudio.pc.cmake: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@ 3 | includedir=${prefix}/include 4 | 5 | Name: libspatialaudio 6 | Description: Spatial audio rendering library 7 | Version: @PACKAGE_VERSION_MAJOR@.@PACKAGE_VERSION_MINOR@.@PACKAGE_VERSION_PATCH@ 8 | Libs: -L${libdir} -lspatialaudio @MYSOFA_LIB@ -lm -lz 9 | Cflags: -I${includedir} @MYSOFA_INCLUDE@ 10 | -------------------------------------------------------------------------------- /include/AdmDirectSpeakerGainCalc.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Calculate the gain vector to spatialise a DirectSpeaker channel. #*/ 4 | /*# CAdmDirectSpeakersGainCalc #*/ 5 | /*# Copyright © 2020 Peter Stitt #*/ 6 | /*# #*/ 7 | /*# Filename: AdmDirectSpeakersGainCalc.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 23/06/2020 #*/ 10 | /*# Author(s): Peter Stitt #*/ 11 | /*# Licence: LGPL + proprietary #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | #pragma once 16 | 17 | #include "AdmMetadata.h" 18 | #include "LoudspeakerLayouts.h" 19 | #include "Tools.h" 20 | #include "AdmMappingRules.h" 21 | #include "PointSourcePannerGainCalc.h" 22 | #include "Screen.h" 23 | 24 | namespace admrender { 25 | 26 | /** 27 | A class to calculate the gains to be applied to a set of loudspeakers for DirectSpeaker processing 28 | */ 29 | class CAdmDirectSpeakersGainCalc 30 | { 31 | public: 32 | CAdmDirectSpeakersGainCalc(Layout layoutWithLFE); 33 | ~CAdmDirectSpeakersGainCalc(); 34 | 35 | /** 36 | Calculate the gain vector corresponding to the metadata input 37 | */ 38 | std::vector calculateGains(DirectSpeakerMetadata metadata); 39 | 40 | private: 41 | unsigned int m_nCh = 0; 42 | Layout m_layout; 43 | CPointSourcePannerGainCalc m_pointSourcePannerGainCalc; 44 | 45 | CScreenEdgeLock m_screenEdgeLock; 46 | 47 | bool isLFE(DirectSpeakerMetadata metadata); 48 | 49 | /** 50 | Find the closest speaker in the layout within the tolerance bounds 51 | set 52 | */ 53 | int findClosestWithinBounds(DirectSpeakerPolarPosition direction, double tol); 54 | 55 | /** 56 | Determine if a given mapping rule applies for input layout, speaker label and output layout. 57 | See Rec. ITU-R BS.2127-0 sec 8.4 58 | */ 59 | bool MappingRuleApplies(const MappingRule& rule, const std::string& input_layout, const std::string& speakerLabel, Layout& output_layout); 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /include/AdmMappingRules.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# ADM mapping rules for DirectSpeaker downmixing. #*/ 4 | /*# #*/ 5 | /*# Copyright © 2020 Peter Stitt #*/ 6 | /*# #*/ 7 | /*# Filename: AdmMappingRules.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 23/06/2020 #*/ 10 | /*# Author(s): Peter Stitt #*/ 11 | /*# Licence: LGPL + proprietary #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | #include 16 | 17 | #pragma once 18 | 19 | namespace admrender { 20 | 21 | // Structing containing a Mapping Rule c.f. Rec. ITU-R BS.2127-0 sec. 8.1 22 | struct MappingRule { 23 | std::string speakerLabel; 24 | std::vector> gains; 25 | 26 | std::vector inputLayouts; 27 | 28 | std::vector outputLayouts; 29 | }; 30 | 31 | const double _1_sqrt2 = 1. / sqrt(2.); 32 | const double sqrt2_3 = std::sqrt(2. / 3.); 33 | const double _1_sqrt3 = std::sqrt(1. / 3.); 34 | 35 | // Mapping rules from Rec. ITU-R BS.2127-0 Table 16 36 | const std::vector mappingRules = 37 | { 38 | {"M+000", {{"M+000", 1.0}}, {}, {}}, 39 | {"M+000",{{"M+030", _1_sqrt2}, {"M-030", _1_sqrt2}},{},{}}, 40 | {"M+060", {{"M+060", 1.0}}, {}, {}}, 41 | {"M-060", {{"M-060", 1.0}}, {}, {}}, 42 | {"M+060", {{"M+030", sqrt2_3}, {"M+110", _1_sqrt3}}, {},{}}, 43 | {"M-060", {{"M-030", sqrt2_3}, {"M-110", _1_sqrt3}},{},{}}, 44 | {"M+060",{{"M+030", _1_sqrt2}, {"M+090", _1_sqrt2}},{},{}}, 45 | {"M-060",{{"M-030", _1_sqrt2}, {"M-090", _1_sqrt2}},{},{}}, 46 | {"M+060", {{"M+030", 1.0}}, {}, {}}, 47 | {"M-060", {{"M-030", 1.0}}, {}, {}}, 48 | {"M+090", {{"M+090", 1.0}}, {}, {}}, 49 | {"M-090", {{"M-090", 1.0}}, {}, {}}, 50 | {"M+090",{{"M+030", _1_sqrt3}, {"M+110", sqrt2_3}},{"9+10+3"},{}}, 51 | {"M-090",{{"M-030", _1_sqrt3}, {"M-110", sqrt2_3}},{"9+10+3"},{}}, 52 | {"M+090",{{"M+030", _1_sqrt2}, {"M+110", _1_sqrt2}},{},{}}, 53 | {"M-090",{{"M-030", _1_sqrt2}, {"M-110", _1_sqrt2}},{},{}}, 54 | {"M+090", {{"M+030", _1_sqrt2}}, {}, {}}, 55 | {"M-090", {{"M-030", _1_sqrt2}}, {}, {}}, 56 | {"M+110", {{"M+110", 1.0}}, {}, {}}, 57 | {"M-110", {{"M-110", 1.0}}, {}, {}}, 58 | {"M+110", {{"M+135", 1.0}}, {}, {}}, 59 | {"M-110", {{"M-135", 1.0}}, {}, {}}, 60 | {"M+110", {{"M+030", _1_sqrt2}}, {}, {}}, 61 | {"M-110", {{"M-030", _1_sqrt2}}, {}, {}}, 62 | {"M+135", {{"M+135", 1.0}}, {}, {}}, 63 | {"M-135", {{"M-135", 1.0}}, {}, {}}, 64 | {"M+135", {{"M+110", 1.0}}, {}, {}}, 65 | {"M-135", {{"M-110", 1.0}}, {}, {}}, 66 | {"M+135", {{"M+030", _1_sqrt2}}, {}, {}}, 67 | {"M-135", {{"M-030", _1_sqrt2}}, {}, {}}, 68 | {"M+180", {{"M+180", 1.0}}, {}, {}}, 69 | {"M+180", {{"M+135", _1_sqrt2}, {"M-135", _1_sqrt2}},{},{}}, 70 | {"M+180",{{"M+110", _1_sqrt2}, {"M-110", _1_sqrt2}},{},{}}, 71 | {"M+180",{{"M+030", 0.5}, {"M-030", 0.5}},{},{}}, 72 | {"U+000", {{"U+000", 1.0}}, {}, {}}, 73 | {"U+000",{{"U+030", _1_sqrt2}, {"U-030", _1_sqrt2}},{},{}}, 74 | {"U+000",{{"U+045", _1_sqrt2}, {"U-045", _1_sqrt2}},{},{}}, 75 | {"U+000", {{"M+000", 1.0}}, {}, {}}, 76 | {"U+000",{{"M+030", _1_sqrt2}, {"M-030", _1_sqrt2}},{},{}}, 77 | {"U+030", {{"U+030", 1.0}}, {}, {}}, 78 | {"U-030", {{"U-030", 1.0}}, {}, {}}, 79 | {"U+030", {{"U+045", 1.0}}, {}, {}}, 80 | {"U-030", {{"U-045", 1.0}}, {}, {}}, 81 | {"U+030", {{"M+030", 1.0}}, {}, {}}, 82 | {"U-030", {{"M-030", 1.0}}, {}, {}}, 83 | {"U+045", {{"U+045", 1.0}}, {}, {}}, 84 | {"U-045", {{"U-045", 1.0}}, {}, {}}, 85 | {"U+045", {{"U+030", 1.0}}, {}, {}}, 86 | {"U-045", {{"U-030", 1.0}}, {}, {}}, 87 | {"U+045", {{"M+030", 1.0}}, {}, {}}, 88 | {"U-045", {{"M-030", 1.0}}, {}, {}}, 89 | {"U+090", {{"U+090", 1.0}}, {}, {}}, 90 | {"U-090", {{"U-090", 1.0}}, {}, {}}, 91 | {"U+090",{{"U+045", sqrt2_3}, {"UH+180", _1_sqrt3}},{"9+10+3"},{}}, 92 | {"U-090",{{"U-045", sqrt2_3}, {"UH+180", _1_sqrt3}},{"9+10+3"},{}}, 93 | {"U+090",{{"U+030", _1_sqrt2}, {"U+110", _1_sqrt2}},{},{}}, 94 | {"U-090",{{"U-030", _1_sqrt2}, {"U-110", _1_sqrt2}},{},{}}, 95 | {"U+090",{{"U+045", _1_sqrt2}, {"U+135", _1_sqrt2}},{},{}}, 96 | {"U-090",{{"U-045", _1_sqrt2}, {"U-135", _1_sqrt2}},{},{}}, 97 | {"U+090", {{"M+090", 1.0}}, {}, {}}, 98 | {"U-090", {{"M-090", 1.0}}, {}, {}}, 99 | {"U+090", {{"U+030", _1_sqrt2}, {"M+110", _1_sqrt2}}, {}, {}}, 100 | {"U-090",{{"U-030", _1_sqrt2}, {"M-110", _1_sqrt2}}, {}, {}}, 101 | {"U+090", {{"M+030", _1_sqrt2}, {"M+110", _1_sqrt2}}, {}, {}}, 102 | {"U-090", {{"M-030", _1_sqrt2}, {"M-110", _1_sqrt2}}, {},{}}, 103 | {"U+090", {{"M+030", _1_sqrt2}}, {}, {}}, 104 | {"U-090", {{"M-030", _1_sqrt2}}, {}, {}}, 105 | {"U+110", {{"U+110", 1.0}}, {}, {}}, 106 | {"U-110", {{"U-110", 1.0}}, {}, {}}, 107 | {"U+110", {{"U+135", 1.0}}, {}, {}}, 108 | {"U-110", {{"U-135", 1.0}}, {}, {}}, 109 | {"U+110", {{"U+045", _1_sqrt2}, {"UH+180", _1_sqrt2}},{}, {}}, 110 | {"U-110", {{"U-045", _1_sqrt2}, {"UH+180", _1_sqrt2}},{},{}}, 111 | {"U+110", {{"M+110", 1.0}}, {}, {}}, 112 | {"U-110", {{"M-110", 1.0}}, {}, {}}, 113 | {"U+110", {{"M+135", 1.0}}, {}, {}}, 114 | {"U-110", {{"M-135", 1.0}}, {}, {}}, 115 | {"U+110", {{"M+030", _1_sqrt2}}, {}, {}}, 116 | {"U-110", {{"M-030", _1_sqrt2}}, {}, {}}, 117 | {"U+135", {{"U+135", 1.0}}, {}, {}}, 118 | {"U-135", {{"U-135", 1.0}}, {}, {}}, 119 | {"U+135", {{"U+110", 1.0}}, {}, {}}, 120 | {"U-135", {{"U-110", 1.0}}, {}, {}}, 121 | {"U+135", {{"U+045", _1_sqrt3}, {"UH+180", sqrt2_3}},{"9+10+3"},{}}, 122 | {"U-135", {{"U-045", _1_sqrt3}, {"UH+180", sqrt2_3}}, {"9+10+3"}, {}}, 123 | {"U+135", {{"U+045", _1_sqrt2}, {"UH+180", _1_sqrt2}}, {},{}}, 124 | {"U-135", {{"U-045", _1_sqrt2}, {"UH+180", _1_sqrt2}}, {},{}}, 125 | {"U+135", {{"M+135", 1.0}}, {}, {}}, 126 | {"U-135", {{"M-135", 1.0}}, {}, {}}, 127 | {"U+135", {{"M+110", 1.0}}, {}, {}}, 128 | {"U-135", {{"M-110", 1.0}}, {}, {}}, 129 | {"U+135", {{"M+030", _1_sqrt2}}, {}, {}}, 130 | {"U-135", {{"M-030", _1_sqrt2}}, {}, {}}, 131 | {"U+180", {{"U+180", 1.0}}, {}, {}}, 132 | {"U+180", {{"UH+180", 1.0}}, {}, {}}, 133 | {"U+180",{{"U+135", _1_sqrt2}, {"U-135", _1_sqrt2}},{}, {}}, 134 | {"U+180", {{"U+110", _1_sqrt2}, {"U-110", _1_sqrt2}}, {},{}}, 135 | {"U+180", {{"M+135", _1_sqrt2}, {"M-135", _1_sqrt2}}, {},{}}, 136 | {"U+180",{{"M+110", _1_sqrt2}, {"M-110", _1_sqrt2}},{},{}}, 137 | {"U+180",{{"M+030", 0.5}, {"M-030", 0.5}},{},{}}, 138 | {"UH+180", {{"UH+180", 1.0}}, {}, {}}, 139 | {"UH+180", {{"U+180", 1.0}}, {}, {}}, 140 | {"UH+180",{{"U+135", _1_sqrt2}, {"U-135", _1_sqrt2}},{},{}}, 141 | {"UH+180",{{"U+110", _1_sqrt2}, {"U-110", _1_sqrt2}},{},{}}, 142 | {"UH+180",{{"M+135", _1_sqrt2}, {"M-135", _1_sqrt2}},{},{}}, 143 | {"UH+180",{{"M+110", _1_sqrt2}, {"M-110", _1_sqrt2}},{},{}}, 144 | {"UH+180",{{"M+030", 0.5}, {"M-030", 0.5}},{},{}}, 145 | {"T+000", {{"T+000", 1.0}}, {}, {}}, 146 | {"T+000",{{"U+045", 0.5},{"U-045", 0.5},{"U+135", 0.5},{"U-135", 0.5}},{},{}}, 147 | {"T+000",{{"U+030", 0.5},{"U-030", 0.5},{"U+110", 0.5},{"U-110", 0.5}},{},{}}, 148 | {"T+000",{{"U+045", _1_sqrt3},{"U-045", _1_sqrt3},{"UH+180", _1_sqrt3}},{},{}}, 149 | {"T+000",{{"U+045", 0.5},{"U-045", 0.5},{"M+135", 0.5},{"M-135", 0.5}},{},{}}, 150 | {"T+000",{{"U+030", 0.5},{"U-030", 0.5},{"M+110", 0.5},{"M-110", 0.5}},{},{}}, 151 | {"T+000",{{"M+030", 0.5},{"M-030", 0.5},{"M+135", 0.5},{"M-135", 0.5}},{},{}}, 152 | {"T+000",{{"M+030", 0.5},{"M-030", 0.5},{"M+110", 0.5},{"M-110", 0.5}},{},{}}, 153 | {"T+000",{{"M+030", 0.5}, {"M-030", 0.5}},{},{}}, 154 | {"B+000", {{"B+000", 1.0}}, {}, {}}, 155 | {"B+000", {{"M+000", 1.0}}, {}, {}}, 156 | {"B+000",{{"M+030", _1_sqrt2}, {"M-030", _1_sqrt2}},{},{}}, 157 | {"B+045", {{"B+045", 1.0}}, {}, {}}, 158 | {"B-045", {{"B-045", 1.0}}, {}, {}}, 159 | {"B+045", {{"M+030", 1.0}}, {}, {}}, 160 | {"B-045", {{"M-030", 1.0}}, {}, {}}, 161 | {"LFE1", {{"LFE1", 1.0}}, {"9+10+3", "3+7+0"}, {"9+10+3", "3+7+0"}}, 162 | {"LFE2", {{"LFE2", 1.0}}, {"9+10+3", "3+7+0"}, {"9+10+3", "3+7+0"}}, 163 | {"LFE1", {{"LFE1", _1_sqrt2}}, {"9+10+3", "3+7+0"}, {}}, 164 | {"LFE2", {{"LFE1", _1_sqrt2}}, {"9+10+3", "3+7+0"}, {}}, 165 | {"LFE1", {{"LFE1", 1.0}}, {}, {}}, 166 | }; 167 | 168 | } 169 | -------------------------------------------------------------------------------- /include/AmbisonicBase.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicBase - Ambisonic Base #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicBase.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONIC_BASE_H 17 | #define _AMBISONIC_BASE_H 18 | 19 | #include "AmbisonicCommons.h" 20 | 21 | /// Ambisonic base class. 22 | 23 | /** This is the base class for most if not all of the classes that make up this 24 | library. */ 25 | 26 | class CAmbisonicBase 27 | { 28 | public: 29 | CAmbisonicBase(); 30 | virtual ~CAmbisonicBase() = default; 31 | /** 32 | Gets the order of the current Ambisonic configuration. 33 | */ 34 | unsigned GetOrder(); 35 | /** 36 | Gets true or false depending on whether the current Ambisonic 37 | configuration has height(3D). 38 | */ 39 | bool GetHeight(); 40 | /** 41 | Gets the number of B-Format channels in the current Ambisonic 42 | configuration. 43 | */ 44 | unsigned GetChannelCount(); 45 | /** 46 | Re-create the object for the given configuration. Previous data is 47 | lost. 48 | */ 49 | virtual bool Configure(unsigned nOrder, bool b3D, unsigned nMisc); 50 | /** 51 | Not implemented. 52 | */ 53 | virtual void Reset() = 0; 54 | /** 55 | Not implemented. 56 | */ 57 | virtual void Refresh() = 0; 58 | 59 | protected: 60 | unsigned m_nOrder; 61 | bool m_b3D; 62 | unsigned m_nChannelCount; 63 | }; 64 | 65 | #endif //_AMBISONIC_BASE_H 66 | -------------------------------------------------------------------------------- /include/AmbisonicBinauralizer.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicBinauralizer - Ambisonic Binauralizer #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicBinauralizer.h #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 12 | /*# Licence: LGPL #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #ifndef _AMBISONIC_BINAURALIZER_H 18 | #define _AMBISONIC_BINAURALIZER_H 19 | 20 | #include 21 | #include 22 | 23 | #include "AmbisonicShelfFilters.h" 24 | #include "AmbisonicDecoder.h" 25 | #include "AmbisonicEncoder.h" 26 | #include "kiss_fftr.h" 27 | 28 | #include "mit_hrtf.h" 29 | #include "sofa_hrtf.h" 30 | 31 | /// Ambisonic binauralizer 32 | 33 | /** B-Format to binaural decoder. */ 34 | 35 | class CAmbisonicBinauralizer : public CAmbisonicBase 36 | { 37 | public: 38 | CAmbisonicBinauralizer(); 39 | /** 40 | Re-create the object for the given configuration. Previous data is 41 | lost. The tailLength variable it updated with the number of taps 42 | used for the processing, and this can be used to offset the delay 43 | this causes. The function returns true if the call is successful. 44 | */ 45 | virtual bool Configure(unsigned nOrder, 46 | bool b3D, 47 | unsigned nSampleRate, 48 | unsigned nBlockSize, 49 | unsigned& tailLength, 50 | std::string HRTFPath = ""); 51 | /** 52 | Resets members. 53 | */ 54 | virtual void Reset(); 55 | /** 56 | Refreshes coefficients. 57 | */ 58 | virtual void Refresh(); 59 | /** 60 | Decode B-Format to binaural feeds. There is no arguement for the number 61 | of samples to process, as this is determined by the nBlockSize argument 62 | in the constructor and Configure() function. It is the responsibility of 63 | program using this library to handle the blocks of the signal by FIFO 64 | buffers or other means. 65 | 66 | pBFSrc = the B-format audio to be rendered to binaural 67 | ppfDst = the output destination 68 | nSamples = the number of samples to be in the input output. Useful if 69 | working with variable sizes of buffers. Must be less than the max size 70 | set at Configure 71 | */ 72 | void Process(CBFormat* pBFSrc, float** ppfDst); 73 | void Process(CBFormat* pBFSrc, float** ppfDst, unsigned int nSamples); 74 | 75 | protected: 76 | CAmbisonicDecoder m_AmbDecoder; 77 | 78 | CAmbisonicShelfFilters shelfFilters; 79 | 80 | unsigned m_nBlockSize; 81 | unsigned m_nTaps; 82 | unsigned m_nFFTSize; 83 | unsigned m_nFFTBins; 84 | float m_fFFTScaler; 85 | unsigned m_nOverlapLength; 86 | 87 | std::unique_ptr m_pFFT_cfg; 88 | std::unique_ptr m_pIFFT_cfg; 89 | std::vector> m_ppcpFilters[2]; 90 | std::unique_ptr m_pcpScratch; 91 | 92 | std::vector m_pfScratchBufferA; 93 | std::vector m_pfScratchBufferB; 94 | std::vector m_pfScratchBufferC; 95 | std::vector m_pfOverlap[2]; 96 | 97 | HRTF *getHRTF(unsigned nSampleRate, std::string HRTFPath); 98 | virtual void ArrangeSpeakers(); 99 | virtual void AllocateBuffers(); 100 | }; 101 | 102 | #endif // _AMBISONIC_BINAURALIZER_H 103 | -------------------------------------------------------------------------------- /include/AmbisonicCommons.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# Copyright © 2007 Aristotel Digenis #*/ 5 | /*# Copyright © 2017 Videolabs #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicCommons.h #*/ 8 | /*# Version: 0.2 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 11 | /*# Licence: LGPL #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONICCOMMONS_H 17 | #define _AMBISONICCOMMONS_H 18 | 19 | #define _USE_MATH_DEFINES 20 | #include 21 | #include 22 | 23 | #define DEFAULT_ORDER 1 24 | #define DEFAULT_HEIGHT true 25 | #define DEFAULT_BFORMAT_SAMPLECOUNT 512 26 | #define DEFAULT_SAMPLERATE 44100 27 | #define DEFAULT_BLOCKSIZE 512 28 | #define DEFAULT_HRTFSET_DIFFUSED false 29 | 30 | 31 | //TODO 32 | enum BFormatChannels3D 33 | { 34 | kW, 35 | kY, kZ, kX, 36 | kV, kT, kR, kS, kU, 37 | kQ, kO, kM, kK, kL, kN, kP, 38 | kNumOfBformatChannels3D 39 | }; 40 | 41 | /*enum BFormatChannels2D 42 | { 43 | kW, 44 | kX, kY, 45 | kU, kV, 46 | kP, kQ, 47 | kNumOfBformatChannels2D 48 | };*/ 49 | 50 | /// Struct for source positioning in soundfield. 51 | typedef struct PolarPoint 52 | { 53 | /** horizontal positioning */ 54 | float fAzimuth; 55 | /** vertical positioning */ 56 | float fElevation; 57 | /** distance from centre of soundfield (radius)*/ 58 | float fDistance; 59 | } PolarPoint; 60 | 61 | /** 62 | Convert degrees to radians. 63 | */ 64 | float DegreesToRadians(float fDegrees); 65 | 66 | /** 67 | Convert radians to degrees. 68 | */ 69 | float RadiansToDegrees(float fRadians); 70 | 71 | /** 72 | Get the number of BFormat components for a given BFormat configuration. 73 | */ 74 | unsigned OrderToComponents(unsigned nOrder, bool b3D); 75 | 76 | /** 77 | Returns the index component of a BFormat stream where components of a given 78 | configuration start. For example, in a BFormat stream, the components of a 79 | 2nd order 3D configuration, would start at index 4. 80 | */ 81 | unsigned OrderToComponentPosition(unsigned nOrder, bool b3D); 82 | 83 | /** 84 | Get the recommended minimum speakers needed to decode a BFormat stream of 85 | a given configuration. 86 | */ 87 | unsigned OrderToSpeakers(unsigned nOrder, bool b3D); 88 | 89 | /** 90 | Get the label for a given index component in a BFormat stream. 91 | */ 92 | char ComponentToChannelLabel(unsigned nComponent, bool b3D); 93 | 94 | #endif //_AMBISONICCOMMONS_H 95 | -------------------------------------------------------------------------------- /include/AmbisonicDecoder.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicDecoder - Ambisonic Decoder #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicDecoder.h #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 12 | /*# Licence: LGPL #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #ifndef _AMBISONIC_DECODER_H 18 | #define _AMBISONIC_DECODER_H 19 | 20 | #include "AmbisonicBase.h" 21 | #include "BFormat.h" 22 | #include "AmbisonicSpeaker.h" 23 | #include "AmbisonicShelfFilters.h" 24 | 25 | enum Amblib_SpeakerSetUps 26 | { 27 | kAmblib_CustomSpeakerSetUp = -1, 28 | ///2D Speaker Setup 29 | kAmblib_Mono, kAmblib_Stereo, kAmblib_LCR, kAmblib_Quad, kAmblib_50, kAmblib_70, kAmblib_51, kAmblib_71, 30 | kAmblib_Pentagon, kAmblib_Hexagon, kAmblib_HexagonWithCentre, kAmblib_Octagon, 31 | kAmblib_Decadron, kAmblib_Dodecadron, 32 | ///3D Speaker Setup 33 | kAmblib_Cube, 34 | kAmblib_Dodecahedron, 35 | kAmblib_Cube2, 36 | kAmblib_MonoCustom, 37 | kAmblib_NumOfSpeakerSetUps 38 | }; 39 | 40 | /// Ambisonic decoder 41 | 42 | /** This is a basic decoder, handling both default and custom speaker 43 | configurations. */ 44 | 45 | class CAmbisonicDecoder : public CAmbisonicBase 46 | { 47 | public: 48 | CAmbisonicDecoder(); 49 | ~CAmbisonicDecoder(); 50 | /** 51 | Re-create the object for the given configuration. Previous data is 52 | lost. nSpeakerSetUp can be any of the ::SpeakerSetUps enumerations. If 53 | ::kCustomSpeakerSetUp is used, then nSpeakers must also be given, 54 | indicating the number of speakers in the custom speaker configuration. 55 | Else, if using one of the default configurations, nSpeakers does not 56 | need to be specified. Function returns true if successful. 57 | */ 58 | bool Configure(unsigned nOrder, bool b3D, unsigned nBlockSize, int nSpeakerSetUp, unsigned nSpeakers = 0); 59 | /** 60 | Resets all the speakers. 61 | */ 62 | void Reset(); 63 | /** 64 | Refreshes all the speakers. 65 | */ 66 | void Refresh(); 67 | /** 68 | Decode B-Format to speaker feeds. 69 | */ 70 | void Process(CBFormat* pBFSrc, unsigned nSamples, float** ppfDst); 71 | /** 72 | Returns the current speaker setup, which is a ::SpeakerSetUps 73 | enumeration. 74 | */ 75 | int GetSpeakerSetUp(); 76 | /** 77 | Returns the number of speakers in the current speaker setup. 78 | */ 79 | unsigned GetSpeakerCount(); 80 | /** 81 | Used when current speaker setup is ::kCustomSpeakerSetUp, to position 82 | each speaker. Should be used by iterating nSpeaker for the number of speakers 83 | declared present in the current speaker setup, using polPosition to position 84 | each on. 85 | */ 86 | void SetPosition(unsigned nSpeaker, PolarPoint polPosition); 87 | /** 88 | Used when current speaker setup is ::kCustomSpeakerSetUp, it returns 89 | the position of the speaker of index nSpeaker, in the current speaker 90 | setup. 91 | */ 92 | PolarPoint GetPosition(unsigned nSpeaker); 93 | /** 94 | Sets the weight [0,1] for the spherical harmonics of the given order, 95 | at the given speaker. 96 | */ 97 | void SetOrderWeight(unsigned nSpeaker, unsigned nOrder, float fWeight); 98 | /** 99 | Returns the weight [0,1] for the spherical harmonics of the given order, 100 | at the given speaker. 101 | */ 102 | float GetOrderWeight(unsigned nSpeaker, unsigned nOrder); 103 | /** 104 | Gets the coefficient of the specified channel/component of the 105 | specified speaker. Useful for the Binauralizer. 106 | */ 107 | virtual float GetCoefficient(unsigned nSpeaker, unsigned nChannel); 108 | /** 109 | Sets the coefficient of the specified channel/component of the 110 | specified speaker. Useful for presets for irregular physical loudspeakery arrays 111 | */ 112 | void SetCoefficient(unsigned nSpeaker, unsigned nChannel, float fCoeff); 113 | /** 114 | Gets whether a preset has been loaded or if the coefficients are calculated 115 | */ 116 | bool GetPresetLoaded(); 117 | 118 | protected: 119 | void SpeakerSetUp(int nSpeakerSetUp, unsigned nSpeakers = 1); 120 | 121 | /** 122 | Checks if the current speaker arrangement is one that has a pre-defined 123 | preset. If true, sets the m_nSpeakerSetUp to the correct value 124 | */ 125 | void CheckSpeakerSetUp(); 126 | 127 | /** 128 | Load a pre-defined decoder preset if the speaker set-up is a supported 129 | layout 130 | */ 131 | void LoadDecoderPreset(); 132 | 133 | int m_nSpeakerSetUp; 134 | unsigned m_nSpeakers; 135 | CAmbisonicSpeaker* m_pAmbSpeakers; 136 | bool m_bPresetLoaded; 137 | 138 | private: 139 | CAmbisonicShelfFilters shelfFilters; 140 | }; 141 | 142 | #endif // _AMBISONIC_DECODER_H 143 | -------------------------------------------------------------------------------- /include/AmbisonicDecoderPresets.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# Copyright © 2017 Videolabs #*/ 5 | /*# #*/ 6 | /*# Filename: AmbisonicProcessor.cpp #*/ 7 | /*# Version: 0.2 #*/ 8 | /*# Date: 19/05/2007 #*/ 9 | /*# Author(s): Peter Stitt, Bruce Wiggins #*/ 10 | /*# Licence: LGPL #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | // Decoder coefficients for Ambisonics to stereo. Useful for conversion to 2-channels when not using headphone. 15 | const float decoder_coefficient_stereo[][16] = 16 | { 17 | {0.5f, 0.5f / 3.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}, 18 | {0.5f, -0.5f / 3.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f} 19 | }; 20 | 21 | // First order decoder for 5.1 loudspeaker array 22 | // Do not activate psychoacoustic optimisation filters 23 | const float decoder_coefficient_first_5_1[][4] = { {0.300520f, 0.135000f, 0.000000f, 0.120000f}, 24 | {0.300520f, -0.135000f, 0.000000f, 0.120000f}, 25 | {0.332340f, 0.138333f, 0.000000f, -0.110000f}, 26 | {0.332340f, -0.138333f, 0.000000f, -0.110000f}, 27 | {0.141421f, 0.000000f, 0.000000f, 0.053333f}, 28 | {0.500000f, 0.000000f, 0.000000f, 0.000000f} }; 29 | 30 | // Second order decoder for 5.1 loudspeaker array 31 | // Do not activate psychoacoustic optimisation filters 32 | const float decoder_coefficient_second_5_1[][9] = { {0.286378f, 0.103333f, -0.000000f, 0.106667f, 0.028868f, 0.000000f, 0.000000f, 0.000000f, 0.019630f}, 33 | {0.286378f, -0.103333f, -0.000000f, 0.106667f, -0.028868f, 0.000000f, 0.000000f, -0.000000f, 0.019630f}, 34 | {0.449013f, 0.093333f, -0.000000f, -0.111667f, 0.018475f, -0.000000f, -0.000000f, 0.000000f, -0.018475f}, 35 | {0.449013f, -0.093333f, -0.000000f, -0.111667f, -0.018475f, -0.000000f, -0.000000f, 0.000000f, -0.018475f}, 36 | {0.060104f, 0.000000f, 0.000000f, 0.013333f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.010392f}, 37 | {0.500000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f} }; 38 | 39 | // Third order decoder for 5.1 loudspeaker array 40 | // Do not activate psychoacoustic optimisation filters 41 | const float decoder_coefficient_third_5_1[][16] = { {0.219203f, 0.095000f, 0.000000f, 0.103333f, 0.042724f, 0.000000f, 0.000000f, 0.000000f, 0.001155f, 0.010842f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.004518f}, 42 | {0.219203f, -0.095000f, 0.000000f, 0.103333f, -0.042724f, 0.000000f, 0.000000f, 0.000000f, 0.001155f, -0.010842f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.004518f}, 43 | {0.417193f, 0.128333f, 0.000000f, -0.111667f, 0.004619f, 0.000000f, 0.000000f, 0.000000f, -0.005774f, -0.011746f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.004518f}, 44 | {0.417193f, -0.128333f, 0.000000f, -0.111667f, -0.004619f, 0.000000f, 0.000000f, 0.000000f, -0.005774f, 0.011746f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.004518f}, 45 | {0.095459f, 0.000000f, 0.000000f, 0.088333f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.049652f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.018974f}, 46 | {0.500000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f} }; 47 | 48 | 49 | // 7.1 Decoders 50 | // First order decoder for 7.1 loudspeaker array 51 | // Do not activate psychoacoustic optimisation filters 52 | const float decoder_coefficient_first_7_1[][4] = { {0.303082f, 0.095958f, 0.000000f, 0.114243f}, 53 | {0.303082f, -0.095958f, 0.000000f, 0.114243f}, 54 | {0.300098f, 0.124767f, 0.000000f, -0.017447f}, 55 | {0.300098f, -0.124767f, 0.000000f, -0.017447f}, 56 | {0.259458f, 0.053266f, 0.000000f, -0.117329f}, 57 | {0.259458f, -0.053266f, 0.000000f, -0.117329f}, 58 | {0.066262f, 0.000000f, 0.000000f, 0.031737f}, 59 | {0.500000f, 0.000000f, 0.000000f, 0.000000f} }; 60 | 61 | // Second order decoder for 7.1 loudspeaker array 62 | // Do not activate psychoacoustic optimisation filters 63 | const float decoder_coefficient_second_7_1[][9] = { {0.268964f, 0.090325f, 0.000000f, 0.111024f, 0.044867f, 0.000000f, 0.000000f, 0.000000f, 0.015736f}, 64 | {0.268964f, -0.090325f, 0.000000f, 0.111024f, -0.044867f, -0.000000f, 0.000000f, 0.000000f, 0.015736f}, 65 | {0.229483f, 0.136694f, 0.000000f, -0.018120f, -0.020953f, 0.000000f, 0.000000f, 0.000000f, -0.049001f}, 66 | {0.229483f, -0.136694f, 0.000000f, -0.018120f, 0.020953f, -0.000000f, 0.000000f, 0.000000f, -0.049001f}, 67 | {0.216456f, 0.042012f, 0.000000f, -0.116220f, -0.038878f, 0.000000f, 0.000000f, 0.000000f, 0.032005f}, 68 | {0.216456f, -0.042012f, 0.000000f, -0.116220f, 0.038878f, -0.000000f, 0.000000f, 0.000000f, 0.032005f}, 69 | {0.058222f, 0.000000f, 0.000000f, 0.048933f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.025293f}, 70 | {0.500000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f} }; 71 | 72 | // Third order decoder for 7.1 loudspeaker array 73 | // Do not activate psychoacoustic optimisation filters 74 | const float decoder_coefficient_third_7_1[][16] = { {0.238475f, 0.085873f, 0.000000f, 0.114877f, 0.054573f, 0.000000f, 0.000000f, 0.000000f, 0.015163f, 0.006254f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.006185f}, 75 | {0.238475f, -0.085873f, 0.000000f, 0.114877f, -0.054573f, -0.000000f, 0.000000f, 0.000000f, 0.015163f, -0.006254f, -0.000000f, -0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.006185f}, 76 | {0.214882f, 0.124042f, 0.000000f, -0.017580f, -0.018064f, 0.000000f, 0.000000f, 0.000000f, -0.060255f, -0.011908f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.008159f}, 77 | {0.214882f, -0.124042f, 0.000000f, -0.017580f, 0.018064f, -0.000000f, 0.000000f, 0.000000f, -0.060255f, 0.011908f, -0.000000f, -0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.008159f}, 78 | {0.197904f, 0.043357f, 0.000000f, -0.115673f, -0.048364f, 0.000000f, 0.000000f, 0.000000f, 0.034129f, 0.017198f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.001868f}, 79 | {0.197904f, -0.043357f, 0.000000f, -0.115673f, 0.048364f, -0.000000f, 0.000000f, 0.000000f, 0.034129f, -0.017198f, -0.000000f, -0.000000f, 0.000000f, 0.000000f, 0.000000f, -0.001868f}, 80 | {0.077144f, 0.000000f, 0.000000f, 0.045620f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.030548f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.025329f}, 81 | {0.500000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f} }; 82 | -------------------------------------------------------------------------------- /include/AmbisonicEncoder.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicEncoder - Ambisonic Encoder #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicEncoder.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONIC_ENCODER_H 17 | #define _AMBISONIC_ENCODER_H 18 | 19 | #include "AmbisonicSource.h" 20 | #include "BFormat.h" 21 | 22 | #include // for std::max 23 | 24 | /// Ambisonic encoder. 25 | 26 | /** This is a basic encoder that only takes the source's azimuth an elevation 27 | into account. If distance cues are going to be used, then use 28 | CAmbisonicEncoderDist instead. */ 29 | 30 | class CAmbisonicEncoder : public CAmbisonicSource 31 | { 32 | public: 33 | CAmbisonicEncoder(); 34 | ~CAmbisonicEncoder(); 35 | /** 36 | Re-create the object for the given configuration. Previous data is 37 | lost. Returns true if successful. 38 | */ 39 | virtual bool Configure(unsigned nOrder, bool b3D, unsigned nMisc); 40 | /** 41 | Recalculate coefficients, and apply normalisation factors. 42 | */ 43 | void Refresh(); 44 | /** 45 | Set the position of the source with the option to interpolate over a duration 46 | of the frame to the new position. 47 | The duration is in the range 0.f to 1.f where 1.f interpolates over a full frame. 48 | */ 49 | void SetPosition(PolarPoint polPosition, float interpDur = 0.f); 50 | /** 51 | Encode mono stream to B-Format. 52 | */ 53 | void Process(float* pfSrc, unsigned nSamples, CBFormat* pBFDst); 54 | /** 55 | Encode mono stream to B-Format and adds it to the pBFDst buffer. 56 | Allows an optional offset for the position in samples at which the input data is to be written 57 | */ 58 | void ProcessAccumul(float* pfSrc, unsigned nSamples, CBFormat* pBFDst, unsigned int nOffset = 0, float fGain = 1.f); 59 | 60 | private: 61 | // The last set HOA coefficients 62 | std::vector m_pfCoeffOld; 63 | // The duration [0,1] of the interpolation from the old to the new HOA coefficients 64 | float m_fInterpDur = 0.f; 65 | }; 66 | 67 | #endif // _AMBISONIC_ENCODER_H 68 | -------------------------------------------------------------------------------- /include/AmbisonicEncoderDist.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicEncoderDist - Ambisonic Encoder with distance #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicEncoderDist.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONIC_ENCODER_DIST_H 17 | #define _AMBISONIC_ENCODER_DIST_H 18 | 19 | #include "AmbisonicEncoder.h" 20 | 21 | const unsigned knSpeedOfSound = 344; 22 | const unsigned knMaxDistance = 150; 23 | 24 | /// Ambisonic encoder with distance cues. 25 | 26 | /** This is similar to a normal the ambisonic encoder, but takes the source's 27 | distance into account, delaying the signal, adjusting its gain, and 28 | implementing "W-Panning"(interior effect). If distance is not an issue, 29 | then use CAmbisonicEncoder which is more efficient. */ 30 | 31 | class CAmbisonicEncoderDist : public CAmbisonicEncoder 32 | { 33 | public: 34 | CAmbisonicEncoderDist(); 35 | ~CAmbisonicEncoderDist(); 36 | /** 37 | Re-create the object for the given configuration. Previous data is 38 | lost. Returns true if successful. 39 | */ 40 | virtual bool Configure(unsigned nOrder, bool b3D, unsigned nSampleRate); 41 | /** 42 | Resets members such as delay lines. 43 | */ 44 | virtual void Reset(); 45 | /** 46 | Refreshes coefficients. 47 | */ 48 | virtual void Refresh(); 49 | /** 50 | Encode mono stream to B-Format. 51 | */ 52 | void Process(float* pfSrc, unsigned nSamples, CBFormat* pBFDst); 53 | /** 54 | Set the radius of the intended playback speaker setup which is used for 55 | the interior effect (W-Panning). 56 | */ 57 | void SetRoomRadius(float fRoomRadius); 58 | /** 59 | Returns the radius of the intended playback speaker setup, which is 60 | used for the interior effect (W-Panning). 61 | */ 62 | float GetRoomRadius(); 63 | 64 | protected: 65 | unsigned m_nSampleRate; 66 | float m_fDelay; 67 | int m_nDelay; 68 | unsigned m_nDelayBufferLength; 69 | float* m_pfDelayBuffer; 70 | int m_nIn; 71 | int m_nOutA; 72 | int m_nOutB; 73 | float m_fRoomRadius; 74 | float m_fInteriorGain; 75 | float m_fExteriorGain; 76 | }; 77 | 78 | #endif // _AMBISONIC_ENCODER_DIST_H 79 | -------------------------------------------------------------------------------- /include/AmbisonicMicrophone.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicMicrophone - Ambisonic Microphone #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicMicrophone.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONIC_MICROPHONE_H 17 | #define _AMBISONIC_MICROPHONE_H 18 | 19 | #include "AmbisonicSource.h" 20 | #include "BFormat.h" 21 | 22 | /// Ambisonic microphone 23 | 24 | /** This is a microphone class. It is similar to ::CAmbisonicSpeaker, with the 25 | addition of having directivity control. */ 26 | 27 | class CAmbisonicMicrophone : public CAmbisonicSource 28 | { 29 | public: 30 | CAmbisonicMicrophone(); 31 | ~CAmbisonicMicrophone(); 32 | /** 33 | Recalculate coefficients, and apply normalisation factors. 34 | */ 35 | void Refresh(); 36 | /** 37 | Decode B-Format to speaker feed. 38 | */ 39 | void Process(CBFormat* pBFSrc, unsigned nSamples, float* pfDst); 40 | /** 41 | Set the microphone's directivity. 42 | */ 43 | void SetDirectivity(float fDirectivity); 44 | /** 45 | Get the microphone's directivity. 46 | */ 47 | float GetDirectivity(); 48 | 49 | protected: 50 | float m_fDirectivity; 51 | }; 52 | 53 | #endif // _AMBISONIC_MICROPHONE_H 54 | -------------------------------------------------------------------------------- /include/AmbisonicProcessor.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicProcessor - Ambisonic Processor #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicProcessor.h #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 12 | /*# Licence: LGPL (+ Proprietary) #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #ifndef _AMBISONIC_PROCESSOR_H 18 | #define _AMBISONIC_PROCESSOR_H 19 | 20 | #include "AmbisonicBase.h" 21 | #include "BFormat.h" 22 | #include "AmbisonicZoomer.h" 23 | 24 | enum ProcessorDOR 25 | { 26 | kYaw, kRoll, kPitch, kNumProcessorDOR //(Degrees of Rotation) 27 | }; 28 | 29 | enum ProcessorModes 30 | { 31 | kYawRollPitch, kYawPitchRoll, 32 | kRollYawPitch, kRollPitchYaw, 33 | kPitchYawRoll, kPitchRollYaw, 34 | kNumProcessorModes 35 | }; 36 | 37 | 38 | class CAmbisonicProcessor; 39 | 40 | 41 | /// Struct for soundfield rotation. 42 | class Orientation 43 | { 44 | public: 45 | Orientation(float fYaw, float fPitch, float fRoll) 46 | : fYaw(fYaw), fPitch(fPitch), fRoll(fRoll) 47 | { 48 | float fCosYaw = cosf(fYaw); 49 | float fSinYaw = sinf(fYaw); 50 | float fCosRoll = cosf(fRoll); 51 | float fSinRoll = sinf(fRoll); 52 | float fCosPitch = cosf(fPitch); 53 | float fSinPitch = sinf(fPitch); 54 | 55 | /* Conversion from yaw, pitch, roll (ZYX) to ZYZ convention to match rotation matrices 56 | This method reduces the complexity of the rotation matrices since the Z0 and Z1 rotations are the same form */ 57 | float r33 = fCosPitch * fCosRoll; 58 | if (r33 == 1.f) 59 | { 60 | fBeta = 0.f; 61 | fGamma = 0.f; 62 | fAlpha = atan2f(fSinYaw, fCosYaw); 63 | } 64 | else 65 | { 66 | if (r33 == -1.f) 67 | { 68 | fBeta = (float)M_PI; 69 | fGamma = 0.f; 70 | fAlpha = atan2f(-fSinYaw, fCosYaw); 71 | } 72 | else 73 | { 74 | 75 | float r32 = -fCosYaw * fSinRoll + fCosRoll * fSinPitch * fSinYaw ; 76 | float r31 = fCosRoll * fCosYaw * fSinPitch + fSinRoll * fSinYaw ; 77 | fAlpha = atan2f( r32 , r31 ); 78 | 79 | fBeta = acosf( r33 ); 80 | 81 | float r23 = fCosPitch * fSinRoll; 82 | float r13 = -fSinPitch; 83 | fGamma = atan2f( r23 , -r13 ); 84 | } 85 | } 86 | } 87 | 88 | friend class CAmbisonicProcessor; 89 | 90 | private: 91 | /** rotation around the Z axis (yaw) */ 92 | float fYaw; 93 | /** rotation around the Y axis (pitch) */ 94 | float fPitch; 95 | /** rotation around the X axis (roll) */ 96 | float fRoll; 97 | 98 | /** These angles are obtained from Yaw, Pitch and Roll (ZYX convention)**/ 99 | /** They follow the ZYZ convention to match the rotation equations **/ 100 | /** rotation around the Z axis */ 101 | float fAlpha; 102 | /** rotation around the X axis */ 103 | float fBeta; 104 | /** rotation around the new Z axis */ 105 | float fGamma; 106 | }; 107 | 108 | 109 | /// Ambisonic processor. 110 | 111 | /** This object is used to rotate the BFormat signal around all three axes. 112 | Orientation structs are used to define the the soundfield's orientation. */ 113 | 114 | class CAmbisonicProcessor : public CAmbisonicBase 115 | { 116 | public: 117 | CAmbisonicProcessor(); 118 | ~CAmbisonicProcessor(); 119 | /** 120 | Re-create the object for the given configuration. Previous data is 121 | lost. The last argument is not used, it is just there to match with 122 | the base class's form. Returns true if successful. 123 | */ 124 | bool Configure(unsigned nOrder, bool b3D, unsigned nBlockSize, unsigned nMisc); 125 | /** 126 | Not implemented. 127 | */ 128 | void Reset(); 129 | /** 130 | Recalculate coefficients. 131 | */ 132 | void Refresh(); 133 | /** 134 | Set yaw, roll, and pitch settings. 135 | */ 136 | void SetOrientation(Orientation orientation); 137 | /** 138 | Get yaw, roll, and pitch settings. 139 | */ 140 | Orientation GetOrientation(); 141 | /** 142 | Rotate B-Format stream. 143 | */ 144 | void Process(CBFormat* pBFSrcDst, unsigned nSamples); 145 | 146 | private: 147 | void ProcessOrder1_3D(CBFormat* pBFSrcDst, unsigned nSamples); 148 | void ProcessOrder2_3D(CBFormat* pBFSrcDst, unsigned nSamples); 149 | void ProcessOrder3_3D(CBFormat* pBFSrcDst, unsigned nSamples); 150 | 151 | protected: 152 | Orientation m_orientation; 153 | float* m_pfTempSample; 154 | 155 | float m_fCosAlpha; 156 | float m_fSinAlpha; 157 | float m_fCosBeta; 158 | float m_fSinBeta; 159 | float m_fCosGamma; 160 | float m_fSinGamma; 161 | float m_fCos2Alpha; 162 | float m_fSin2Alpha; 163 | float m_fCos2Beta; 164 | float m_fSin2Beta; 165 | float m_fCos2Gamma; 166 | float m_fSin2Gamma; 167 | float m_fCos3Alpha; 168 | float m_fSin3Alpha; 169 | float m_fCos3Beta; 170 | float m_fSin3Beta; 171 | float m_fCos3Gamma; 172 | float m_fSin3Gamma; 173 | }; 174 | 175 | #endif // _AMBISONIC_PROCESSOR_H 176 | -------------------------------------------------------------------------------- /include/AmbisonicPsychoacousticFilters.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicSpeaker - Ambisonic Speaker #*/ 5 | /*# Copyright © 2017 Videolabs #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicSpeaker.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: March 2017 #*/ 10 | /*# Author(s): Peter Stitt #*/ 11 | /*# Licence: LGPL (+ Proprietary) #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include 17 | 18 | const int16_t first_order_3D[][101] = 19 | { 20 | {3,3,3,3,3,3,3,3,3,3,3,2,2,1,0,-1,-3,-5,-7,-10,-13,-17,-21,-25,-30,-35,-41,-47,-54,-60,-67,-75,-82,-90,-97,-105,-113,-120,-127,-134,-141,-147,-153,-158,-163,-167,-170,-173,-175,-176,23040,-176,-175,-173,-170,-167,-163,-158,-153,-147,-141,-134,-127,-120,-113,-105,-97,-90,-82,-75,-67,-60,-54,-47,-41,-35,-30,-25,-21,-17,-13,-10,-7,-5,-3,-1,0,1,2,2,3,3,3,3,3,3,3,3,3,3,3}, 21 | {-2,-2,-2,-2,-2,-2,-2,-3,-2,-2,-2,-2,-2,-1,-1,0,1,2,3,5,7,9,11,14,16,19,23,26,30,34,38,42,46,51,55,60,64,68,73,77,80,84,87,90,93,95,97,99,100,101,13438,101,100,99,97,95,93,90,87,84,80,77,73,68,64,60,55,51,46,42,38,34,30,26,23,19,16,14,11,9,7,5,3,2,1,0,-1,-1,-2,-2,-2,-2,-2,-3,-2,-2,-2,-2,-2,-2,-2} 22 | }; 23 | 24 | const int16_t second_order_3D[][101] = 25 | { 26 | {-5,-5,-6,-6,-7,-7,-7,-7,-7,-6,-5,-3,-1,2,6,10,15,21,26,32,38,44,49,53,55,56,54,50,42,32,19,2,-19,-43,-70,-100,-133,-167,-203,-241,-278,-315,-350,-384,-414,-442,-465,-484,-497,-506,25438,-506,-497,-484,-465,-442,-414,-384,-350,-315,-278,-241,-203,-167,-133,-100,-70,-43,-19,2,19,32,42,50,54,56,55,53,49,44,38,32,26,21,15,10,6,2,-1,-3,-5,-6,-7,-7,-7,-7,-7,-6,-6,-5,-5}, 27 | {-2,-2,-3,-3,-3,-3,-3,-3,-3,-3,-2,-2,-1,0,2,4,6,8,10,12,15,17,19,20,21,21,21,19,16,12,7,0,-8,-17,-27,-39,-52,-65,-79,-94,-108,-123,-136,-149,-161,-172,-181,-188,-194,-197,19884,-197,-194,-188,-181,-172,-161,-149,-136,-123,-108,-94,-79,-65,-52,-39,-27,-17,-8,0,7,12,16,19,21,21,21,20,19,17,15,12,10,8,6,4,2,0,-1,-2,-2,-3,-3,-3,-3,-3,-3,-3,-3,-2,-2}, 28 | {2,3,3,3,3,4,4,4,4,3,2,1,0,-2,-4,-7,-10,-14,-17,-21,-25,-28,-31,-34,-35,-36,-35,-32,-27,-21,-12,-2,11,26,43,62,82,104,127,150,173,196,219,240,259,276,290,302,311,316,10659,316,311,302,290,276,259,240,219,196,173,150,127,104,82,62,43,26,11,-2,-12,-21,-27,-32,-35,-36,-35,-34,-31,-28,-25,-21,-17,-14,-10,-7,-4,-2,0,1,2,3,4,4,4,4,3,3,3,3,2} 29 | }; 30 | 31 | const int16_t third_order_3D[][101] = 32 | { 33 | {1,3,4,5,6,8,8,8,8,7,5,1,-3,-8,-15,-21,-28,-34,-38,-41,-41,-38,-31,-20,-5,13,33,56,78,99,117,130,136,133,120,95,57,8,-54,-126,-206,-294,-384,-476,-564,-646,-718,-778,-823,-850,26604,-850,-823,-778,-718,-646,-564,-476,-384,-294,-206,-126,-54,8,57,95,120,133,136,130,117,99,78,56,33,13,-5,-20,-31,-38,-41,-41,-38,-34,-28,-21,-15,-8,-3,1,5,7,8,8,8,8,6,5,4,3,1}, 34 | {1,2,2,3,4,5,5,5,5,4,3,1,-2,-6,-10,-14,-18,-22,-25,-27,-27,-25,-20,-13,-4,8,22,36,51,65,77,85,89,87,78,62,37,5,-35,-82,-135,-192,-252,-311,-369,-423,-470,-509,-538,-556,23082,-556,-538,-509,-470,-423,-369,-311,-252,-192,-135,-82,-35,5,37,62,78,87,89,85,77,65,51,36,22,8,-4,-13,-20,-25,-27,-27,-25,-22,-18,-14,-10,-6,-2,1,3,4,5,5,5,5,4,3,2,2,1}, 35 | {0,0,0,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,-2,-2,-2,-2,-2,-2,-1,-1,0,1,1,2,3,4,4,4,4,4,3,1,0,-2,-5,-8,-11,-14,-17,-20,-23,-25,-27,-29,-30,16773,-30,-29,-27,-25,-23,-20,-17,-14,-11,-8,-5,-2,0,1,3,4,4,4,4,4,3,2,1,1,0,-1,-1,-2,-2,-2,-2,-2,-2,-1,-1,-1,-1,-1,0,0,0,0,0,0,0,0,0,0,0,0}, 36 | {-2,-3,-4,-5,-6,-6,-7,-7,-7,-6,-4,-2,1,5,10,15,19,24,27,29,29,27,22,14,3,-10,-25,-42,-58,-74,-87,-96,-101,-98,-88,-70,-43,-7,38,91,150,214,280,347,412,472,525,568,601,621,8977,621,601,568,525,472,412,347,280,214,150,91,38,-7,-43,-70,-88,-98,-101,-96,-87,-74,-58,-42,-25,-10,3,14,22,27,29,29,27,24,19,15,10,5,1,-2,-4,-6,-7,-7,-7,-6,-6,-5,-4,-3,-2} 37 | }; 38 | 39 | 40 | 41 | const int16_t first_order_2D[][101] = 42 | { 43 | {1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,-1,-2,-3,-4,-5,-7,-9,-10,-13,-15,-18,-21,-24,-27,-30,-34,-37,-41,-45,-49,-52,-56,-60,-63,-67,-70,-73,-76,-79,-81,-83,-85,-86,-87,-88,19968,-88,-87,-86,-85,-83,-81,-79,-76,-73,-70,-67,-63,-60,-56,-52,-49,-45,-41,-37,-34,-30,-27,-24,-21,-18,-15,-13,-10,-9,-7,-5,-4,-3,-2,-1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1}, 44 | {-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-1,-1,-1,0,1,1,2,4,5,7,8,10,12,15,17,20,23,26,29,32,35,39,42,46,49,52,55,58,61,64,67,69,71,73,74,76,76,77,14259,77,76,76,74,73,71,69,67,64,61,58,55,52,49,46,42,39,35,32,29,26,23,20,17,15,12,10,8,7,5,4,2,1,1,0,-1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2} 45 | }; 46 | 47 | const int16_t second_order_2D[][101] = 48 | { 49 | {-3,-3,-3,-3,-4,-4,-4,-4,-4,-3,-3,-2,-1,1,3,5,7,10,13,16,19,22,24,26,27,28,27,25,21,16,9,1,-10,-22,-35,-50,-67,-84,-102,-121,-139,-158,-176,-192,-208,-222,-233,-243,-249,-254,20905,-254,-249,-243,-233,-222,-208,-192,-176,-158,-139,-121,-102,-84,-67,-50,-35,-22,-10,1,9,16,21,25,27,28,27,26,24,22,19,16,13,10,7,5,3,1,-1,-2,-3,-3,-4,-4,-4,-4,-4,-3,-3,-3,-3}, 50 | {-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-1,-1,-1,0,1,2,3,4,5,6,8,9,10,10,11,11,11,10,8,6,3,0,-4,-9,-15,-21,-28,-35,-42,-50,-58,-65,-72,-79,-86,-91,-96,-100,-103,-104,18220,-104,-103,-100,-96,-91,-86,-79,-72,-65,-58,-50,-42,-35,-28,-21,-15,-9,-4,0,3,6,8,10,11,11,11,10,10,9,8,6,5,4,3,2,1,0,-1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-1,-1}, 51 | {2,2,3,3,3,3,4,4,3,3,2,1,0,-2,-4,-7,-10,-13,-17,-20,-24,-27,-30,-33,-34,-34,-33,-31,-26,-20,-12,-2,11,25,41,59,79,100,122,144,166,189,210,230,249,265,279,290,298,303,10885,303,298,290,279,265,249,230,210,189,166,144,122,100,79,59,41,25,11,-2,-12,-20,-26,-31,-33,-34,-34,-33,-30,-27,-24,-20,-17,-13,-10,-7,-4,-2,0,1,2,3,3,4,4,3,3,3,3,2,2} 52 | }; 53 | 54 | const int16_t third_order_2D[][101] = 55 | { 56 | {0,1,2,2,3,3,4,4,4,3,2,0,-2,-4,-7,-10,-13,-16,-18,-20,-20,-18,-15,-10,-3,6,16,26,37,47,56,62,64,63,57,45,27,3,-26,-60,-98,-140,-183,-226,-268,-307,-342,-370,-391,-404,21262,-404,-391,-370,-342,-307,-268,-226,-183,-140,-98,-60,-26,3,27,45,57,63,64,62,56,47,37,26,16,6,-3,-10,-15,-18,-20,-20,-18,-16,-13,-10,-7,-4,-2,0,2,3,4,4,4,3,3,2,2,1,0}, 57 | {0,1,1,1,2,2,2,2,2,2,1,0,-1,-3,-5,-7,-9,-11,-13,-14,-14,-13,-10,-7,-2,4,10,18,25,32,38,42,44,43,39,30,18,2,-18,-41,-68,-96,-126,-155,-184,-211,-234,-254,-268,-277,19741,-277,-268,-254,-234,-211,-184,-155,-126,-96,-68,-41,-18,2,18,30,39,43,44,42,38,32,25,18,10,4,-2,-7,-10,-13,-14,-14,-13,-11,-9,-7,-5,-3,-1,0,1,2,2,2,2,2,2,1,1,1,0}, 58 | {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,0,1,2,2,3,3,4,4,3,3,1,0,-2,-4,-6,-8,-10,-12,-14,-14,-14,-12,-10,-6,-1,5,12,20,29,38,47,56,64,71,77,82,84,15409,84,82,77,71,64,56,47,38,29,20,12,5,-1,-6,-10,-12,-14,-14,-14,-12,-10,-8,-6,-4,-2,0,1,3,3,4,4,3,3,2,2,1,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1}, 59 | {-2,-3,-4,-5,-6,-6,-7,-7,-7,-6,-4,-2,1,5,10,15,20,24,27,29,29,27,22,14,3,-10,-25,-42,-58,-74,-87,-97,-101,-99,-89,-71,-43,-7,39,92,151,215,282,350,415,475,528,572,605,625,8926,625,605,572,528,475,415,350,282,215,151,92,39,-7,-43,-71,-89,-99,-101,-97,-87,-74,-58,-42,-25,-10,3,14,22,27,29,29,27,24,20,15,10,5,1,-2,-4,-6,-7,-7,-7,-6,-6,-5,-4,-3,-2}, 60 | }; 61 | -------------------------------------------------------------------------------- /include/AmbisonicShelfFilters.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicShelfFilters - Ambisonic psychoactic optimising filters #*/ 5 | /*# Copyright © 2020 Videolabs #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicShelfFilters.h #*/ 8 | /*# Version: 0.2 #*/ 9 | /*# Date: 23/03/2020 #*/ 10 | /*# Author(s): Peter Stitt #*/ 11 | /*# Licence: LGPL #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONIC_SHELF_FILTERS_H 17 | #define _AMBISONIC_SHELF_FILTERS_H 18 | 19 | #include "AmbisonicBase.h" 20 | #include "BFormat.h" 21 | #include "kiss_fftr.h" 22 | #include "AmbisonicPsychoacousticFilters.h" 23 | 24 | /// Ambisonic processor. 25 | 26 | /** This object is used to rotate the BFormat signal around all three axes. 27 | Orientation structs are used to define the the soundfield's orientation. */ 28 | 29 | class CAmbisonicShelfFilters; 30 | 31 | class CAmbisonicShelfFilters : public CAmbisonicBase 32 | { 33 | public: 34 | CAmbisonicShelfFilters(); 35 | ~CAmbisonicShelfFilters(); 36 | /** 37 | Re-create the object for the given configuration. Previous data is 38 | lost. The last argument is not used, it is just there to match with 39 | the base class's form. Returns true if successful. 40 | */ 41 | bool Configure(unsigned nOrder, bool b3D, unsigned nBlockSize, unsigned nMisc); 42 | /** 43 | Not implemented. 44 | */ 45 | void Reset(); 46 | /** 47 | Recalculate coefficients. 48 | */ 49 | void Refresh(); 50 | /** 51 | Filter B-Format stream. 52 | Overload with number of samples (nSamples < m_nBlockSize) to process shorter block sizes 53 | */ 54 | void Process(CBFormat* pBFSrcDst); 55 | void Process(CBFormat* pBFSrcDst, unsigned int nSamples); 56 | 57 | protected: 58 | kiss_fftr_cfg m_pFFT_psych_cfg; 59 | kiss_fftr_cfg m_pIFFT_psych_cfg; 60 | 61 | float* m_pfScratchBufferA; 62 | float** m_pfOverlap; 63 | unsigned m_nFFTSize; 64 | unsigned m_nBlockSize; 65 | unsigned m_nTaps; 66 | unsigned m_nOverlapLength; 67 | unsigned m_nFFTBins; 68 | float m_fFFTScaler; 69 | 70 | kiss_fft_cpx** m_ppcpPsychFilters; 71 | kiss_fft_cpx* m_pcpScratch; 72 | }; 73 | 74 | #endif // _AMBISONIC_SHELF_FILTERS_H 75 | -------------------------------------------------------------------------------- /include/AmbisonicSource.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicSource - Ambisonic Source #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicSource.h #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis #*/ 12 | /*# Licence: LGPL #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #ifndef _AMBISONIC_SOURCE_H 18 | #define _AMBISONIC_SOURCE_H 19 | 20 | #include "AmbisonicBase.h" 21 | 22 | #include 23 | 24 | /// Base class for encoder and speaker 25 | 26 | /** This acts as a base class for single point 3D objects such as encoding a 27 | mono stream into a 3D soundfield, or a single speaker for decoding a 3D 28 | soundfield. */ 29 | 30 | class CAmbisonicSource : public CAmbisonicBase 31 | { 32 | public: 33 | CAmbisonicSource(); 34 | /** 35 | Re-create the object for the given configuration. Previous data is 36 | lost. The last argument is not used, it is just there to match with 37 | the base class's form. Returns true if successful. 38 | */ 39 | virtual bool Configure(unsigned nOrder, bool b3D, unsigned nMisc); 40 | /** 41 | Not implemented. 42 | */ 43 | virtual void Reset(); 44 | /** 45 | Recalculates coefficients. 46 | */ 47 | virtual void Refresh(); 48 | /** 49 | Set azimuth, elevation, and distance settings. 50 | */ 51 | virtual void SetPosition(PolarPoint polPosition); 52 | /** 53 | Get azimuth, elevation, and distance settings. 54 | */ 55 | virtual PolarPoint GetPosition(); 56 | /** 57 | Sets the weight [0,1] for the spherical harmonics of the given order. 58 | */ 59 | virtual void SetOrderWeight(unsigned nOrder, float fWeight); 60 | /** 61 | Sets the weight [0,1] for the spherical harmonics of all orders. 62 | */ 63 | virtual void SetOrderWeightAll(float fWeight); 64 | /** 65 | Sets the spherical harmonic coefficient for a given component 66 | Can be used for preset decoders to non-regular arrays where a Sampling decoder is sub-optimal 67 | */ 68 | virtual void SetCoefficient(unsigned nChannel, float fCoeff); 69 | /** 70 | Gets the weight [0,1] for the spherical harmonics of the given order. 71 | */ 72 | virtual float GetOrderWeight(unsigned nOrder); 73 | /** 74 | Gets the coefficient of the specified channel/component. Useful for the 75 | Binauralizer. 76 | */ 77 | virtual float GetCoefficient(unsigned nChannel); 78 | /** 79 | Get the vector of coefficients 80 | */ 81 | virtual std::vector GetCoefficients(); 82 | /** 83 | Sets the source's gain. 84 | */ 85 | virtual void SetGain(float fGain); 86 | /** 87 | Gets the source's gain. 88 | */ 89 | virtual float GetGain(); 90 | 91 | protected: 92 | std::vector m_pfCoeff; 93 | std::vector m_pfOrderWeights; 94 | PolarPoint m_polPosition; 95 | float m_fGain; 96 | }; 97 | 98 | #endif // _AMBISONIC_SOURCE_H 99 | -------------------------------------------------------------------------------- /include/AmbisonicSpeaker.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicSpeaker - Ambisonic Speaker #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicSpeaker.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _AMBISONIC_SPEAKER_H 17 | #define _AMBISONIC_SPEAKER_H 18 | 19 | #include "AmbisonicSource.h" 20 | #include "BFormat.h" 21 | 22 | /// Ambisonic speaker 23 | 24 | /** This is a speaker class to be used in the decoder. */ 25 | 26 | class CAmbisonicSpeaker : public CAmbisonicSource 27 | { 28 | public: 29 | CAmbisonicSpeaker(); 30 | ~CAmbisonicSpeaker(); 31 | /** 32 | Re-create the object for the given configuration. Previous data is 33 | lost. The last argument is not used, it is just there to match with 34 | the base class's form. Returns true if successful. 35 | */ 36 | virtual bool Configure(unsigned nOrder, bool b3D, unsigned nMisc); 37 | /** 38 | Recalculate coefficients, and apply normalisation factors. 39 | */ 40 | void Refresh(); 41 | /** 42 | Decode B-Format to speaker feed. 43 | */ 44 | void Process(CBFormat* pBFSrc, unsigned nSamples, float* pfDst); 45 | }; 46 | 47 | #endif // _AMBISONIC_SPEAKER_H 48 | -------------------------------------------------------------------------------- /include/AmbisonicTypesDefinesCommons.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CBFormat - Ambisonic BFormat #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicTypesDefinesCommon.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | #ifndef _AMBISONICTYPEDEFINESCOMMONS_H 16 | #define _AMBISONICTYPEDEFINESCOMMONS_H 17 | 18 | #define _USE_MATH_DEFINES 19 | #include 20 | #include 21 | 22 | #define DEFAULT_ORDER 1 23 | #define DEFAULT_HEIGHT true 24 | #define DEFAULT_BFORMAT_SAMPLES 512 25 | 26 | 27 | 28 | #endif //_AMBISONICTYPEDEFINESCOMMONS_H 29 | -------------------------------------------------------------------------------- /include/AmbisonicZoomer.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicZoomer - Ambisonic Zoomer #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicZoomer.h #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 12 | /*# Licence: LGPL (+ Proprietary) #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #ifndef _AMBISONIC_ZOOMER_H 18 | #define _AMBISONIC_ZOOMER_H 19 | 20 | #include "AmbisonicBase.h" 21 | #include "AmbisonicDecoder.h" 22 | #include "BFormat.h" 23 | 24 | #include 25 | 26 | /// Ambisonic zoomer. 27 | 28 | /** This object is used to apply a zoom effect into BFormat soundfields. */ 29 | 30 | class CAmbisonicZoomer : public CAmbisonicBase 31 | { 32 | public: 33 | CAmbisonicZoomer(); 34 | virtual ~CAmbisonicZoomer() = default; 35 | /** 36 | Re-create the object for the given configuration. Previous data is 37 | lost. The last argument is not used, it is just there to match with 38 | the base class's form. Returns true if successful. 39 | */ 40 | virtual bool Configure(unsigned nOrder, bool b3D, unsigned nBlockSize, unsigned nMisc); 41 | /** 42 | Not implemented. 43 | */ 44 | void Reset(); 45 | /** 46 | Recalculate coefficients. 47 | */ 48 | void Refresh(); 49 | /** 50 | Set zoom factor. This is in a range from -1 to 1, with 0 being no zoom, 51 | 1 full forward zoom, and -1 full forward backwards. 52 | */ 53 | void SetZoom(float fZoom); 54 | /** 55 | Get zoom factor. 56 | */ 57 | float GetZoom(); 58 | /** 59 | Zoom into B-Format stream. 60 | */ 61 | void Process(CBFormat* pBFSrcDst, unsigned nSamples); 62 | /** 63 | Compute factorial of integer 64 | */ 65 | float factorial(unsigned M); 66 | protected: 67 | CAmbisonicDecoder m_AmbDecoderFront; 68 | 69 | std::unique_ptr m_AmbEncoderFront; 70 | std::unique_ptr m_AmbEncoderFront_weighted; 71 | std::unique_ptr a_m; 72 | 73 | float m_fZoom; 74 | float m_fZoomRed; 75 | float m_AmbFrontMic; 76 | float m_fZoomBlend; 77 | }; 78 | 79 | #endif // _AMBISONIC_ZOOMER_H 80 | -------------------------------------------------------------------------------- /include/Ambisonics.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# Copyright © 2007 Aristotel Digenis #*/ 5 | /*# #*/ 6 | /*# Filename: Ambisonics.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 19/05/2007 #*/ 9 | /*# Author(s): Aristotel Digenis #*/ 10 | /*# Licence: MIT #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | 15 | #ifndef _AMBISONICS_H 16 | #define _AMBISONICS_H 17 | 18 | 19 | #include "AmbisonicCommons.h" 20 | #include "AmbisonicBase.h" 21 | #include "BFormat.h" 22 | #include "AmbisonicSource.h" 23 | #include "AmbisonicSpeaker.h" 24 | #include "AmbisonicMicrophone.h" 25 | #include "AmbisonicEncoder.h" 26 | #include "AmbisonicEncoderDist.h" 27 | #include "AmbisonicDecoder.h" 28 | #include "AmbisonicProcessor.h" 29 | #include "AmbisonicBinauralizer.h" 30 | #include "AmbisonicZoomer.h" 31 | #include "AmbisonicDecoderPresets.h" 32 | #include "AmbisonicShelfFilters.h" 33 | 34 | #endif //_AMBISONICS_H 35 | 36 | -------------------------------------------------------------------------------- /include/BFormat.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CBFormat - Ambisonic BFormat #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: BFormat.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _BFORMAT_H 17 | #define _BFORMAT_H 18 | 19 | #include "AmbisonicBase.h" 20 | #include 21 | #include 22 | 23 | /// Storage for BFormat signals. 24 | 25 | /** This object is used to store and transfer BFormat signals. Memory is 26 | allocated for the number of channels needed for the given Ambisonic 27 | configuration (order and 2D/3D) and the number of samples. */ 28 | 29 | class CBFormat : public CAmbisonicBase 30 | { 31 | public: 32 | CBFormat(); 33 | /** 34 | Returns the number of samples. 35 | */ 36 | unsigned GetSampleCount(); 37 | /** 38 | Re-create the buffers needed for the given configuration. Previous 39 | buffer contents are lost. 40 | */ 41 | bool Configure(unsigned nOrder, bool b3D, unsigned nSampleCount); 42 | /** 43 | Fill the buffer with zeros. 44 | */ 45 | void Reset(); 46 | /** 47 | Not implemented. 48 | */ 49 | void Refresh(); 50 | /** 51 | Copy a number of samples to a specific channel of the BFormat. 52 | */ 53 | void InsertStream(float* pfData, unsigned nChannel, unsigned nSamples); 54 | /** 55 | Add a number of samples to a specific channel of the BFormat. 56 | */ 57 | void AddStream(float* pfData, unsigned nChannel, unsigned nSamples, unsigned nOffset = 0); 58 | /** 59 | Copy a number of samples from a specific channel of the BFormat. 60 | */ 61 | void ExtractStream(float* pfData, unsigned nChannel, unsigned nSamples); 62 | 63 | /** 64 | Copy the content of the buffer. It is assumed that the two objects are 65 | of the same configuration. 66 | */ 67 | void operator = (const CBFormat &bf); 68 | /** 69 | Returns true if the configuration of the two objects match. 70 | */ 71 | bool operator == (const CBFormat &bf); 72 | /** 73 | Returns true if the configuration of the two objects don't match. 74 | */ 75 | bool operator != (const CBFormat &bf); 76 | CBFormat& operator += (const CBFormat &bf); 77 | CBFormat& operator -= (const CBFormat &bf); 78 | CBFormat& operator *= (const CBFormat &bf); 79 | CBFormat& operator /= (const CBFormat &bf); 80 | CBFormat& operator += (const float &fValue); 81 | CBFormat& operator -= (const float &fValue); 82 | CBFormat& operator *= (const float &fValue); 83 | CBFormat& operator /= (const float &fValue); 84 | 85 | protected: 86 | unsigned m_nSamples; 87 | unsigned m_nDataLength; 88 | std::vector m_pfData; 89 | std::unique_ptr m_ppfChannels; 90 | 91 | //friend classes cannot be pure abstract type, 92 | //so must list each friend class manually 93 | friend class CAmbisonicEncoder; 94 | friend class CAmbisonicEncoderDist; 95 | friend class CAmbisonicDecoder; 96 | friend class CAmbisonicSpeaker; 97 | friend class CAmbisonicMicrophone; 98 | friend class CAmbisonicProcessor; 99 | friend class CAmbisonicBinauralizer; 100 | friend class CAmbisonicZoomer; 101 | friend class CAmbisonicShelfFilters; 102 | }; 103 | 104 | #endif //_BFORMAT_H 105 | -------------------------------------------------------------------------------- /include/Coordinates.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Cartesian and polar coordinates #*/ 4 | /*# #*/ 5 | /*# #*/ 6 | /*# Filename: Coordinates.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 23/06/2020 #*/ 9 | /*# Author(s): Peter Stitt #*/ 10 | /*# Licence: LGPL + proprietary #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | #pragma once 15 | 16 | struct PolarPosition 17 | { 18 | double azimuth = 0.0; 19 | double elevation = 0.0; 20 | double distance = 1.f; 21 | }; 22 | inline bool operator==(const PolarPosition& lhs, const PolarPosition& rhs) 23 | { 24 | return lhs.azimuth == rhs.azimuth && lhs.elevation == rhs.elevation && lhs.distance == rhs.distance; 25 | } 26 | 27 | struct CartesianPosition 28 | { 29 | double x = 1.0; 30 | double y = 0.0; 31 | double z = 0.0; 32 | }; 33 | inline bool operator==(const CartesianPosition& lhs, const CartesianPosition& rhs) 34 | { 35 | return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z; 36 | } 37 | inline CartesianPosition operator+(const CartesianPosition& lhs, const CartesianPosition& rhs) 38 | { 39 | return CartesianPosition{ lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z }; 40 | } 41 | inline CartesianPosition operator-(const CartesianPosition& lhs, const CartesianPosition& rhs) 42 | { 43 | return CartesianPosition{ lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z }; 44 | } 45 | -------------------------------------------------------------------------------- /include/Decorrelate.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# A decorrelator for loudspeaker arrays #*/ 4 | /*# #*/ 5 | /*# Filename: AdmRenderer.h #*/ 6 | /*# Version: 0.1 #*/ 7 | /*# Date: 23/06/2020 #*/ 8 | /*# Author(s): Peter Stitt #*/ 9 | /*# Licence: LGPL + proprietary #*/ 10 | /*# #*/ 11 | /*############################################################################*/ 12 | 13 | #pragma once 14 | 15 | #include "LoudspeakerLayouts.h" 16 | #include "Tools.h" 17 | #include "kiss_fftr.h" 18 | 19 | /** 20 | This class applied decorrelation to the output speaker layouts. 21 | It allows allows for compensation delay to be applied to the direct signal. 22 | 23 | It is based on Rec. ITU-R BS.2127-0 section 7.4 pg.63 24 | */ 25 | class CDecorrelate 26 | { 27 | public: 28 | /* 29 | layout is the target loudspeaker layout 30 | nSamples is the maximum block size expected 31 | */ 32 | CDecorrelate(); 33 | ~CDecorrelate(); 34 | 35 | /** 36 | Re-create the object for the given configuration. Previous data is 37 | lost. Returns true if successful. 38 | */ 39 | bool Configure(Layout layout, unsigned int nBlockSize); 40 | /** 41 | Not implemented. 42 | */ 43 | void Reset(); 44 | /** 45 | Filter otate B-Format stream. 46 | */ 47 | void Process(std::vector> &ppInDirect, std::vector> &ppInDiffuse, unsigned int nSamples); 48 | 49 | private: 50 | // The time-domain decorrelation filters 51 | std::vector> decorrelationFilters; 52 | // Output layout 53 | Layout m_layout; 54 | // The number of channels in the output array 55 | unsigned int m_nCh; 56 | 57 | // According to Rec. ITU-R BS.2127-0 sec. 7.4 the decorrelation filter length is 512 samples 58 | const unsigned int m_nDecorrelationFilterSamples = 512; 59 | 60 | kiss_fftr_cfg m_pFFT_decor_cfg; 61 | kiss_fftr_cfg m_pIFFT_decor_cfg; 62 | 63 | float* m_pfScratchBufferA; 64 | float** m_pfOverlap; 65 | unsigned m_nFFTSize; 66 | unsigned m_nBlockSize; 67 | unsigned m_nTaps; 68 | unsigned m_nOverlapLength; 69 | unsigned m_nFFTBins; 70 | float m_fFFTScaler; 71 | 72 | kiss_fft_cpx** m_ppcpDecorFilters; 73 | kiss_fft_cpx* m_pcpScratch; 74 | 75 | // Buffers to hold the delayed direct signals 76 | float** m_ppfDirectDelay; 77 | unsigned int m_nDelayLineLength; 78 | int m_nDelay = (m_nDecorrelationFilterSamples - 1) / 2; 79 | int m_nReadPos = 0; 80 | int m_nWritePos = 0; 81 | 82 | /** 83 | Read and write data to delay lines 84 | */ 85 | void WriteToDelayLine(float* pDelayLine, float* pIn, int nWritePos, int nSamples); 86 | void ReadFromDelayLine(float* pDelayLine, float* pOut, int nReadPos, int nSamples); 87 | 88 | /** 89 | Calculate decorrelation filters using the method described in Rec. ITU-R BS.2127-0 sec. 7.4 90 | The filters depend on the layout set at construction 91 | */ 92 | std::vector> CalculateDecorrelationFilterBank(); 93 | 94 | /** 95 | Calculate the time-domain decorrelation filter for the a particular channel index seed 96 | */ 97 | std::vector CalculateDecorrelationFilter(unsigned int seedIndex); 98 | }; -------------------------------------------------------------------------------- /include/GainCalculator.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# A gain calculator with ADM metadata with speaker or HOA output #*/ 4 | /*# #*/ 5 | /*# Filename: GainCalculator.h #*/ 6 | /*# Version: 0.1 #*/ 7 | /*# Date: 28/10/2020 #*/ 8 | /*# Author(s): Peter Stitt #*/ 9 | /*# Licence: LGPL + proprietary #*/ 10 | /*# #*/ 11 | /*############################################################################*/ 12 | 13 | #pragma once 14 | 15 | #include "Coordinates.h" 16 | #include "Tools.h" 17 | #include "AdmMetadata.h" 18 | #include "PolarExtent.h" 19 | #include "AmbisonicEncoder.h" 20 | #include "Screen.h" 21 | 22 | namespace admrender { 23 | 24 | class ChannelLockHandler 25 | { 26 | public: 27 | ChannelLockHandler(Layout layout); 28 | ~ChannelLockHandler(); 29 | 30 | /** 31 | If the Object has a valid channelLock distance then determines the new direction of the object 32 | */ 33 | CartesianPosition handle(ChannelLock channelLock, CartesianPosition position); 34 | 35 | private: 36 | unsigned int m_nCh = 0; 37 | Layout m_layout; 38 | }; 39 | 40 | class ZoneExclusionHandler 41 | { 42 | public: 43 | ZoneExclusionHandler(Layout layout); 44 | ~ZoneExclusionHandler(); 45 | 46 | /** 47 | Calculate the gain vector once the appropriate loudspeakers have been exlcuded 48 | */ 49 | std::vector handle(std::vector exclusionZones, std::vector gains); 50 | 51 | private: 52 | unsigned int m_nCh = 0; 53 | Layout m_layout; 54 | std::vector>> m_downmixMapping; 55 | std::vector> m_downmixMatrix; 56 | 57 | int GetLayerPriority(std::string inputChannelName, std::string outputChannelName); 58 | }; 59 | 60 | class CGainCalculator 61 | { 62 | public: 63 | CGainCalculator(Layout outputLayoutNoLFE); 64 | ~CGainCalculator(); 65 | 66 | /* 67 | Calculate the panning (loudspeaker or HOA) gains to apply to a 68 | mono signal for spatialisation based on the input metadata 69 | */ 70 | void CalculateGains(ObjectMetadata metadata, std::vector& directGains, std::vector& diffuseGains); 71 | 72 | private: 73 | // The output layout 74 | Layout m_outputLayout; 75 | // number of output channels 76 | unsigned int m_nCh; 77 | 78 | CPointSourcePannerGainCalc m_pspGainCalculator; 79 | CPolarExtentHandler m_extentPanner; 80 | CAmbisonicPolarExtentHandler m_ambiExtentPanner; 81 | 82 | CScreenScaleHandler m_screenScale; 83 | CScreenEdgeLock m_screenEdgeLock; 84 | 85 | ChannelLockHandler m_channelLockHandler; 86 | ZoneExclusionHandler m_zoneExclusionHandler; 87 | }; 88 | } // namespace admrenderer 89 | -------------------------------------------------------------------------------- /include/GainInterp.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Apply a vector of gains to a mono input with interpolation #*/ 4 | /*# #*/ 5 | /*# Filename: GainInterp.h #*/ 6 | /*# Version: 0.1 #*/ 7 | /*# Date: 30/10/2020 #*/ 8 | /*# Author(s): Peter Stitt #*/ 9 | /*# Licence: LGPL + proprietary #*/ 10 | /*# #*/ 11 | /*############################################################################*/ 12 | 13 | #pragma once 14 | 15 | #include 16 | 17 | /* 18 | A class to handle the interpolation from one gain vector applied to a mono input over a specified duration 19 | */ 20 | class CGainInterp 21 | { 22 | public: 23 | CGainInterp(); 24 | ~CGainInterp(); 25 | 26 | /* 27 | Set the gain vector target and the time in samples to interpolate to it 28 | */ 29 | void SetGainVector(std::vector newGainVec, unsigned int interpTimeInSamples); 30 | 31 | /* 32 | Apply the gains to the mono input signal and _add_ them to the output buffer 33 | */ 34 | void ProcessAccumul(float* pIn, std::vector>& ppOut, unsigned int nSamples, unsigned int nOffset); 35 | 36 | /* 37 | Resets the gain interpolator by setting the gain vector to the target and making sure there is no interpolation processing 38 | */ 39 | void Reset(); 40 | 41 | private: 42 | // The gain vector and the target gain vector to interpolate towards 43 | std::vector m_gainVec, m_targetGainVec; 44 | 45 | // Flag if the process function has been called. If it has not then any time SetGainVector() is called 46 | // the gain vector will be set to the current and there is no interpolation i.e. first call of ProcessingAccumul() 47 | // is applies a constant gain vector 48 | bool m_isFirstCall = true; 49 | 50 | // The interpolation duration in samples 51 | unsigned int m_interpDurInSamples = 0; 52 | // The number of samples interpolated over 53 | unsigned int m_iInterpCount = 0; 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /include/PointSourcePannerGainCalc.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Calculate the gains required for point source panning. #*/ 4 | /*# CAdmPointSourcePannerGainCalc - ADM Point Source Panner #*/ 5 | /*# Copyright © 2020 Peter Stitt #*/ 6 | /*# #*/ 7 | /*# Filename: AdmPointSourcePannerGainCalc.h #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 23/06/2020 #*/ 10 | /*# Author(s): Peter Stitt #*/ 11 | /*# Licence: LGPL + proprietary #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | #pragma once 16 | 17 | #include "LoudspeakerLayouts.h" 18 | #include "Tools.h" 19 | #include "RegionHandlers.h" 20 | 21 | #include 22 | #include 23 | 24 | class CPointSourcePannerGainCalc 25 | { 26 | public: 27 | CPointSourcePannerGainCalc(Layout layout); 28 | ~CPointSourcePannerGainCalc(); 29 | 30 | /** 31 | Calculate the gains to be applied to a mono signal in order to place it in the target 32 | speaker layout 33 | */ 34 | std::vector CalculateGains(CartesianPosition directionUnitVec); 35 | std::vector CalculateGains(PolarPosition directionUnitVec); 36 | 37 | /** 38 | Get the number of loudspeakers set in the targetLayout 39 | */ 40 | unsigned int getNumChannels(); 41 | 42 | private: 43 | // The loudspeaker layout 44 | Layout m_outputLayout; 45 | // The layout of the extra loudspeakers used to fill in any gaps in the array 46 | Layout m_extraSpeakersLayout; 47 | 48 | // Flag if the output is stereo (0+2+0) and treat as a special case 49 | bool m_isStereo = false; 50 | 51 | std::vector m_downmixMapping; 52 | 53 | // All of the region handlers for the different types 54 | LayoutRegions m_regions; 55 | 56 | /** 57 | Return the extra loudspeakers needed to fill in the gaps in the array. 58 | This currently works for the supported arrays: 0+5+0, 0+4+0, 0+7+0 59 | See Rec. ITU-R BS.2127-0 pg. 27 60 | */ 61 | Layout CalculateExtraSpeakersLayout(Layout layout); 62 | 63 | /** 64 | Calculate the gains for the panning layout. In most cases this will be the same 65 | as the output layout but in the case of 0+2+0 the panning layout is 0+5+0 66 | */ 67 | std::vector _CalculateGains(CartesianPosition directionUnitVec); 68 | }; 69 | -------------------------------------------------------------------------------- /include/PolarExtent.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# A polar extent panner #*/ 4 | /*# #*/ 5 | /*# Filename: PolarExtentPanner.h #*/ 6 | /*# Version: 0.1 #*/ 7 | /*# Date: 21/10/2020 #*/ 8 | /*# Author(s): Peter Stitt #*/ 9 | /*# Licence: LGPL + proprietary #*/ 10 | /*# #*/ 11 | /*############################################################################*/ 12 | 13 | #pragma once 14 | 15 | #include "Coordinates.h" 16 | #include "Tools.h" 17 | #include "PointSourcePannerGainCalc.h" 18 | #include "AmbisonicSource.h" 19 | 20 | /* 21 | All of the class defined here are used for the polar extent panning defined 22 | in Rec. ITU-R BS.2127-0 section 7.3.8 (pg 46). 23 | 24 | It is made up of a PolarExtentHandler, which uses a PolarExtentPanner, which 25 | in turn uses a SpreadPanner and a PointSourcePanner 26 | */ 27 | 28 | /* 29 | The spread panner defines a set of positions of virtual sources on the sphere 30 | and calculates the corresponding point source panning gain vector for each of them. 31 | Then, based on the position, width and height it calculates a set of weights corresponding 32 | to each of the virtual source directions. Finally, the panning gain vectors are weighted 33 | by the weighting function and summed together for each loudspeaker. 34 | */ 35 | class CSpreadPannerBase 36 | { 37 | public: 38 | CSpreadPannerBase(); 39 | ~CSpreadPannerBase(); 40 | 41 | protected: 42 | std::vector m_virtualSourcePositions; 43 | std::vector> m_virtualSourcePanningVectors; 44 | 45 | // The number of virtual source positions 46 | int m_nVirtualSources; 47 | // The weights to be applied to the virtual sources 48 | std::vector m_weights; 49 | 50 | // The last set width and heights 51 | double m_width = 0.; 52 | double m_height = 0.; 53 | // The fade out marge around the "stadium" in degrees 54 | double m_fadeOut = 10.; 55 | // The direction of the source used for the weighting function 56 | CartesianPosition m_weightPosition; 57 | // The rotation matrix to convert the virtual source position basis 58 | std::vector> m_rotMat; 59 | // The coordinates of the circular caps used to define the stadium 60 | CartesianPosition m_circularCapPosition; 61 | double m_circularCapAzimuth = 0.; 62 | 63 | /** 64 | Calculate the weight to be applied to the virtual source gain vector for a virtual 65 | source in with the specified position 66 | Rec. ITU-R BS.2127-0 section 7.3.8.2.3 67 | */ 68 | double CalculateWeights(CartesianPosition position); 69 | 70 | /** 71 | Calculate the rotation matrix and "stadium" for the weighting function for a source 72 | a direction specified by position and the defined width and height. 73 | */ 74 | void ConfigureWeightingFunction(CartesianPosition position, double width, double height); 75 | }; 76 | 77 | /** 78 | A spread panner for loudspeaker array output 79 | */ 80 | class CSpreadPanner : private CSpreadPannerBase 81 | { 82 | public: 83 | CSpreadPanner(CPointSourcePannerGainCalc& psp); 84 | ~CSpreadPanner(); 85 | 86 | /** 87 | Calculate the gains for a source in the defined direction and 88 | with the specified width and height 89 | */ 90 | std::vector CalculateGains(CartesianPosition position, double width, double height); 91 | 92 | private: 93 | CPointSourcePannerGainCalc& m_pointSourcePannerGainCalc; 94 | unsigned int m_nCh = 0; 95 | }; 96 | 97 | /* 98 | A spread panner for Ambisonic sources 99 | */ 100 | class CAmbisonicSpreadPanner : private CSpreadPannerBase 101 | { 102 | public: 103 | CAmbisonicSpreadPanner(unsigned int ambiOrder); 104 | ~CAmbisonicSpreadPanner(); 105 | 106 | /** 107 | Calculate the gains for a source in the defined direction and 108 | with the specified width and height 109 | */ 110 | std::vector CalculateGains(CartesianPosition position, double width, double height); 111 | 112 | /** 113 | Get the order of the HOA encoding for the sources 114 | */ 115 | unsigned int GetAmbisonicOrder(); 116 | 117 | private: 118 | CAmbisonicSource m_ambiSource; 119 | unsigned int m_nCh = 0; 120 | }; 121 | 122 | /** 123 | Class that handles the extent parameters to calculate a gain vector 124 | */ 125 | class CPolarExtentHandlerBase 126 | { 127 | public: 128 | CPolarExtentHandlerBase(); 129 | ~CPolarExtentHandlerBase(); 130 | 131 | protected: 132 | unsigned int m_nCh = 0; 133 | // The minimum extent, defined by the standard at 5 deg 134 | const double m_minExtent = 5.; 135 | 136 | /** 137 | Modifies the extent based on the distance as described in ITU-R BS.2127-0 section 7.3.8.2.1 pg 48 138 | */ 139 | double PolarExtentModification(double distance, double extent); 140 | 141 | /** 142 | Calculate the polar extent gain vector as described in ITU-R BS.2127-0 section 7.3.8.2.2 pg 49 143 | */ 144 | virtual std::vector CalculatePolarExtentGains(CartesianPosition position, double width, double height) = 0; 145 | }; 146 | 147 | 148 | /** 149 | Class that handles the extent parameters to calculate a gain vector 150 | */ 151 | class CPolarExtentHandler : public CPolarExtentHandlerBase 152 | { 153 | public: 154 | CPolarExtentHandler(CPointSourcePannerGainCalc& psp); 155 | ~CPolarExtentHandler(); 156 | 157 | /** 158 | Return a vector of gains for the loudspeakers in the output layout that correspond 159 | to the position, width, height and depth. 160 | 161 | See Rec. ITU-R BS.2127-0 section 7.3.8.2 (pg. 48) for more details on the algorithm 162 | */ 163 | std::vector handle(CartesianPosition position, double width, double height, double depth); 164 | 165 | private: 166 | CPointSourcePannerGainCalc m_pointSourcePannerGainGalc; 167 | CSpreadPanner m_spreadPanner; 168 | 169 | /** 170 | Calculate the polar extent gain vector as described in ITU-R BS.2127-0 section 7.3.8.2.2 pg 49 171 | */ 172 | std::vector CalculatePolarExtentGains(CartesianPosition position, double width, double height) override; 173 | }; 174 | 175 | /** 176 | Class that handles the extent parameters to calculate a gain vector for Ambisonic panning 177 | */ 178 | class CAmbisonicPolarExtentHandler : public CPolarExtentHandlerBase 179 | { 180 | public: 181 | CAmbisonicPolarExtentHandler(unsigned int ambiOrder); 182 | ~CAmbisonicPolarExtentHandler(); 183 | 184 | /** 185 | Return a vector of ambisonic coefficients that correspond 186 | to the position, width, height and depth. 187 | 188 | See Rec. ITU-R BS.2127-0 section 7.3.8.2 (pg. 48) for more details on basis the algorithm. 189 | Note that some changes have been made to account for the fact that ambisonic coefficients should 190 | be summed by power in order to preserve their polarity. 191 | */ 192 | std::vector handle(CartesianPosition position, double width, double height, double depth); 193 | 194 | private: 195 | CAmbisonicSource m_ambiSource; 196 | CAmbisonicSpreadPanner m_ambiSpreadPanner; 197 | 198 | /** 199 | Calculate the HOA extent gain vector. Essentially works as described in ITU-R BS.2127-0 section 7.3.8.2.2 pg 49 200 | except instead of using loudspeaker gains it uses HOA encoding coefficients 201 | */ 202 | std::vector CalculatePolarExtentGains(CartesianPosition position, double width, double height) override; 203 | }; -------------------------------------------------------------------------------- /include/RegionHandlers.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Region handlers for the point source panner #*/ 4 | /*# Copyright © 2020 Peter Stitt #*/ 5 | /*# #*/ 6 | /*# Filename: AdmRegionHandlers.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 23/06/2020 #*/ 9 | /*# Author(s): Peter Stitt #*/ 10 | /*# Licence: LGPL + proprietary #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | #pragma once 15 | 16 | //#include "AdmMetadata.h" 17 | #include "Tools.h" 18 | 19 | #include 20 | #include 21 | 22 | /** 23 | Returns the order of a set of points in an anti-clockwise direction. 24 | The points should be form a Quad or Ngon and be roughly co-planar 25 | */ 26 | static inline std::vector getNgonVectexOrder(std::vector polarPositions, PolarPosition centrePosition) 27 | { 28 | unsigned int nVertices = (unsigned int)polarPositions.size(); 29 | double rotMat[9] = { 0. }; 30 | // Get the rotation matrix that makes the centre the front 31 | getRotationMatrix(-centrePosition.azimuth, centrePosition.elevation, 0., &rotMat[0]); 32 | 33 | std::vector angle(nVertices, 0.); 34 | std::vector vertInds; 35 | for (unsigned int iVert = 0; iVert < nVertices; ++iVert) 36 | { 37 | vertInds.push_back(iVert); 38 | // Rotate the centre position to check it is to the front (0,1,0) 39 | CartesianPosition cartesianPositions = PolarToCartesian(polarPositions[iVert]); 40 | // Unit vector in coordinate system with x-axis to the front and y-axis to the left 41 | std::vector vertexVector = { cartesianPositions.y,-cartesianPositions.x,cartesianPositions.z }; 42 | std::vector vertexRotatedVector(3, 0.); 43 | for (int i = 0; i < 3; ++i) 44 | for (int j = 0; j < 3; ++j) 45 | vertexRotatedVector[i] += rotMat[3 * i + j] * vertexVector[j]; 46 | angle[iVert] = convertToRange360(RAD2DEG * atan2(-vertexRotatedVector[2], vertexRotatedVector[1])); 47 | } 48 | // Sort the angles 49 | int x = 0; 50 | std::iota(vertInds.begin(), vertInds.end(), x++); //Initializing 51 | std::sort(vertInds.begin(), vertInds.end(), [&](int i, int j) {return angle[i] < angle[j]; }); 52 | 53 | return vertInds; 54 | } 55 | 56 | // Holds the output channel indices and their polar coordinates 57 | class RegionHandler 58 | { 59 | public: 60 | RegionHandler(std::vector chanInds, std::vector polPos) 61 | : m_channelInds(chanInds), m_polarPositions(polPos) 62 | { 63 | 64 | } 65 | 66 | std::vector m_channelInds; 67 | std::vector m_polarPositions; 68 | // Tolerance value used to check near zero values 69 | double m_tol = 1e-6; 70 | }; 71 | 72 | /** 73 | Holds a triplet of speaker indices and calculates the gain for a given source direction 74 | */ 75 | class Triplet : public RegionHandler 76 | { 77 | public: 78 | Triplet(std::vector chanInds, std::vector polPos); 79 | 80 | std::vector CalculateGains(std::vector directionUnitVec); 81 | 82 | private: 83 | // Inverse of the matrix holding the triplet unit vectors 84 | std::vector> m_inverseDirections; 85 | }; 86 | 87 | 88 | /** 89 | Holds a VirtualNgon of speaker indices and calculates the gain for a given source direction. 90 | A VirtualNgon has a virtual loudspeaker placed at its centre whose gain is mixed to the real loudspeakers 91 | */ 92 | class VirtualNgon : public RegionHandler 93 | { 94 | public: 95 | VirtualNgon(std::vector chanInds, std::vector polPos, PolarPosition centrePosition); 96 | 97 | std::vector CalculateGains(std::vector directionUnitVec); 98 | 99 | private: 100 | std::vector m_triplets; 101 | double m_downmixCoefficient; 102 | // The number of channels in the Ngon 103 | unsigned int m_nCh = 0; 104 | }; 105 | 106 | /** 107 | Holds a VirtualNgon of speaker indices and calculates the gain for a given source direction. 108 | See Rec. ITU-R BS.2127-0 sec. 6.1.2.3 for full details of the gain calculation 109 | */ 110 | class QuadRegion : public RegionHandler 111 | { 112 | public: 113 | QuadRegion(std::vector chanInds, std::vector polPos); 114 | 115 | double GetPanningValue(std::vector directionUnitVec, std::vector> xprodTerms); 116 | 117 | std::vector CalculateGains(std::vector directionUnitVec); 118 | 119 | private: 120 | std::vector> CalculatePolyXProdTerms(std::vector quadVertices); 121 | 122 | // The coordinates of the vertices in anti-clockwise order start from the bottom left. 123 | std::vector m_quadVertices; 124 | // The ordering of the input speaker coordinates needed to put them in the right order 125 | std::vector m_vertOrder; 126 | // The cross product terms from the final equation in section 6.1.2.3.2 (pg 24) 127 | std::vector> m_polynomialXProdX; 128 | std::vector> m_polynomialXProdY; 129 | }; 130 | 131 | struct LayoutRegions 132 | { 133 | std::vector triplets; 134 | std::vector quadRegions; 135 | std::vector virtualNgons; 136 | }; 137 | -------------------------------------------------------------------------------- /include/Screen.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Screen scaling and screen edge lock handling #*/ 4 | /*# #*/ 5 | /*# #*/ 6 | /*# Filename: Screen.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 30/10/2020 #*/ 9 | /*# Author(s): Peter Stitt #*/ 10 | /*# Licence: LGPL + proprietary #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | #pragma once 15 | 16 | #include "AdmConversions.h" 17 | #include "ScreenCommon.h" 18 | #include "LoudspeakerLayouts.h" 19 | #include "Coordinates.h" 20 | #include "Tools.h" 21 | 22 | /** 23 | In some output layouts when cartesian==true, vertical panning in front of the listener may be 24 | warped. This compensates for that. 25 | 26 | See Rec. ITU-R BS.2127-0 sec. 7.3.2 pg 41 for more details 27 | */ 28 | static inline std::pair CompensatePosition(double az, double el, Layout layout) 29 | { 30 | auto speakerNames = layout.channelNames(); 31 | if (std::find(speakerNames.begin(), speakerNames.end(), std::string("U+045")) != speakerNames.end()) 32 | { 33 | double az_r = interp(el, { -90., 0., 30., 90. }, { 30.,30.,30. * 30. / 45.,30. }); 34 | double azDash = interp(el, { -180., -30., 30., 180. }, { -180., -az_r, az_r, 180. }); 35 | 36 | return { azDash, el }; 37 | } 38 | else 39 | return { az,el }; 40 | 41 | } 42 | 43 | /** 44 | The the position of the source from a position relative to the reference screen to a position 45 | relative to the reproduction screen. 46 | */ 47 | class CScreenScaleHandler { 48 | public: 49 | CScreenScaleHandler(std::vector reproductionScreen, Layout layout); 50 | ~CScreenScaleHandler(); 51 | 52 | /** 53 | Scales a position depending on the reproduction screen and the reference screen 54 | See Rec. ITU-R BS.2127-0 sec. 7.3.3 pg 40 for more details 55 | */ 56 | CartesianPosition handle(CartesianPosition position, bool screenRef, std::vector referenceScreen, bool cartesian); 57 | 58 | private: 59 | Layout m_layout; 60 | // The reproduction screen 61 | Screen m_repScreen, m_refScreen; 62 | // The internal representation of the screens 63 | PolarEdges m_repPolarEdges, m_refPolarEdges; 64 | 65 | bool m_repScreenSet = false; 66 | 67 | /** 68 | Scale the position 69 | */ 70 | CartesianPosition ScalePosition(CartesianPosition); 71 | 72 | /** 73 | Scale the azimuth and elevation 74 | */ 75 | std::pair ScaleAzEl(double az, double el); 76 | }; 77 | 78 | /** 79 | Apply screen edge locking to supplied position based on the reproduction screen and (if cartesian == true) the layout. 80 | */ 81 | class CScreenEdgeLock 82 | { 83 | public: 84 | CScreenEdgeLock(std::vector reproductionScreen, Layout layout); 85 | ~CScreenEdgeLock(); 86 | 87 | /* 88 | Apply screen edge locking to a cartesian position 89 | */ 90 | CartesianPosition HandleVector(CartesianPosition position, admrender::ScreenEdgeLock screenEdgeLock, bool cartesian = false); 91 | 92 | /* 93 | Apply screen edge locking to an azimuth and elevation 94 | */ 95 | std::pair HandleAzEl(double azimuth, double elevation, admrender::ScreenEdgeLock screenEdgeLock); 96 | 97 | 98 | private: 99 | bool m_repScreenSet = false; 100 | Layout m_layout; 101 | Screen m_reproductionScreen; 102 | PolarEdges m_repPolarEdges; 103 | }; 104 | -------------------------------------------------------------------------------- /include/ScreenCommon.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Some common elements related to the screen #*/ 4 | /*# #*/ 5 | /*# #*/ 6 | /*# Filename: ScreenCommon.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 30/10/2020 #*/ 9 | /*# Author(s): Peter Stitt #*/ 10 | /*# Licence: LGPL + proprietary #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | #pragma once 15 | 16 | #include "Coordinates.h" 17 | #include "Tools.h" 18 | 19 | /** 20 | Holds a screen with either cartesian or polar coordinates. If isCartesianScreen is set true then 21 | the centre should be set in centreCartesianPosition. 22 | */ 23 | struct Screen { 24 | // Flag if the screen is cartesian. Only one set of properties is used depending on this flag so make sure they match 25 | bool isCartesianScreen = false; 26 | 27 | double aspectRatio = 1.78; 28 | 29 | // Polar screen properties 30 | PolarPosition centrePolarPosition = PolarPosition{ 0.,0.,1. }; 31 | double widthAzimuth = 58.; 32 | 33 | // Cartesian screen properties 34 | CartesianPosition centreCartesianPosition; 35 | double widthX; 36 | }; 37 | 38 | /** 39 | PolarEdges structure that holds the representation of the screen for 40 | use in the screen edge lock and screen scaling prcocessing classes. 41 | */ 42 | struct PolarEdges { 43 | double leftAzimuth; 44 | double rightAzimuth; 45 | double bottomElevation; 46 | double topElevation; 47 | 48 | /** 49 | Convert from Screen to Polar Edges. 50 | See Rec. ITU-R BS.2127-0 Sec. 7.3.3.1 pg. 40 51 | */ 52 | void fromScreen(Screen screen) 53 | { 54 | CartesianPosition centre; 55 | CartesianPosition v_x, v_z; 56 | if (screen.isCartesianScreen) 57 | { 58 | double w = screen.widthAzimuth; 59 | double a = screen.aspectRatio; 60 | 61 | centre = screen.centreCartesianPosition; 62 | double width = w / 2.; 63 | double height = width / a; 64 | 65 | v_x = CartesianPosition{ width,0.,0. }; 66 | v_z = CartesianPosition{ 0., 0., height }; 67 | } 68 | else 69 | { 70 | double az = screen.centrePolarPosition.azimuth; 71 | double el = screen.centrePolarPosition.elevation; 72 | double d = screen.centrePolarPosition.distance; 73 | double w = screen.widthAzimuth; 74 | double a = screen.aspectRatio; 75 | 76 | centre = PolarToCartesian(screen.centrePolarPosition); 77 | double width = d * std::tan(DEG2RAD * w / 2.); 78 | double height = width / a; 79 | 80 | auto l_xyz = LocalCoordinateSystem(az, el); 81 | v_x = CartesianPosition{ l_xyz[0][0] * width,l_xyz[0][1] * width,l_xyz[0][2] * width }; 82 | v_z = CartesianPosition{ l_xyz[2][0] * height,l_xyz[2][1] * height,l_xyz[2][2] * height }; 83 | } 84 | 85 | leftAzimuth = CartesianToPolar(centre - v_x).azimuth; 86 | rightAzimuth = CartesianToPolar(centre + v_x).azimuth; 87 | bottomElevation = CartesianToPolar(centre - v_z).elevation; 88 | topElevation = CartesianToPolar(centre + v_z).elevation; 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /include/SpeakersBinauralizer.h: -------------------------------------------------------------------------------- 1 | #ifndef BINAURALIZER_H 2 | #define BINAURALIZER_H 3 | 4 | #include 5 | 6 | #include "AmbisonicCommons.h" 7 | #include "BFormat.h" 8 | #include "AmbisonicSpeaker.h" 9 | #include "AmbisonicBinauralizer.h" 10 | 11 | 12 | /** Binaural decoder. */ 13 | 14 | class SpeakersBinauralizer : public CAmbisonicBinauralizer 15 | { 16 | public: 17 | SpeakersBinauralizer(); 18 | bool Configure(unsigned nSampleRate, 19 | unsigned nBlockSize, 20 | CAmbisonicSpeaker *speakers, 21 | unsigned nSpeakers, 22 | unsigned& tailLength, 23 | std::string HRTFPath = ""); 24 | 25 | void Process(float** pBFSrc, float** ppfDst); 26 | 27 | protected: 28 | unsigned m_nSpeakers; 29 | 30 | virtual void AllocateBuffers(); 31 | }; 32 | 33 | #endif // BINAURALIZER_H 34 | -------------------------------------------------------------------------------- /include/config.h.in: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_IN 2 | #define CONFIG_H_IN 3 | 4 | #cmakedefine HAVE_MYSOFA 1 5 | #cmakedefine HAVE_MIT_HRTF 1 6 | 7 | #endif // CONFIG_H_IN 8 | -------------------------------------------------------------------------------- /include/hrtf/hrtf.h: -------------------------------------------------------------------------------- 1 | #ifndef HRTF_H 2 | #define HRTF_H 3 | 4 | 5 | class HRTF 6 | { 7 | public: 8 | HRTF(unsigned i_sampleRate) 9 | : i_sampleRate(i_sampleRate), i_len(0) 10 | { } 11 | virtual ~HRTF() = default; 12 | 13 | virtual bool get(float f_azimuth, float f_elevation, float** pfHRTF) = 0; 14 | 15 | bool isLoaded() { return i_len != 0; } 16 | unsigned getHRTFLen() { return i_len; } 17 | 18 | protected: 19 | unsigned i_sampleRate; 20 | unsigned i_len; 21 | }; 22 | 23 | 24 | #endif // HRTF_H 25 | -------------------------------------------------------------------------------- /include/hrtf/mit_hrtf.h: -------------------------------------------------------------------------------- 1 | #ifndef MIT_HRTF_H 2 | #define MIT_HRTF_H 3 | 4 | #include "hrtf.h" 5 | 6 | #ifdef HAVE_MIT_HRTF 7 | 8 | class MIT_HRTF : public HRTF 9 | { 10 | public: 11 | MIT_HRTF(unsigned i_sampleRate); 12 | bool get(float f_azimuth, float f_elevation, float **pfHRTF); 13 | }; 14 | 15 | #endif 16 | 17 | #endif // MIT_HRTF_H 18 | -------------------------------------------------------------------------------- /include/hrtf/sofa_hrtf.h: -------------------------------------------------------------------------------- 1 | #ifndef SOFA_HRTF_H 2 | #define SOFA_HRTF_H 3 | 4 | #include "config.h" 5 | 6 | #ifdef HAVE_MYSOFA 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include "hrtf.h" 13 | 14 | 15 | class SOFA_HRTF : public HRTF 16 | { 17 | public: 18 | SOFA_HRTF(std::string path, unsigned i_sampleRate); 19 | ~SOFA_HRTF(); 20 | bool get(float f_azimuth, float f_elevation, float **pfHRTF); 21 | 22 | private: 23 | struct MYSOFA_EASY *hrtf; 24 | 25 | unsigned i_filterExtraLength; 26 | int i_internalLength; 27 | }; 28 | 29 | #endif 30 | 31 | #endif // SOFA_HRTF_H 32 | -------------------------------------------------------------------------------- /include/mit_hrtf_filter.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# MIT HRTF C Library #*/ 4 | /*# Copyright © 2007 Aristotel Digenis #*/ 5 | /*# #*/ 6 | /*# Filename: mit_hrtf_filter.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 04/05/2007 #*/ 9 | /*# Author(s): Aristotel Digenis #*/ 10 | /*# Credit: Bill Gardner and Keith Martin #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _MIT_HRTF_FILTER_H 17 | #define _MIT_HRTF_FILTER_H 18 | 19 | #define MIT_HRTF_AZI_POSITIONS_00 37 20 | #define MIT_HRTF_AZI_POSITIONS_10 37 21 | #define MIT_HRTF_AZI_POSITIONS_20 37 22 | #define MIT_HRTF_AZI_POSITIONS_30 31 23 | #define MIT_HRTF_AZI_POSITIONS_40 29 24 | #define MIT_HRTF_AZI_POSITIONS_50 23 25 | #define MIT_HRTF_AZI_POSITIONS_60 19 26 | #define MIT_HRTF_AZI_POSITIONS_70 13 27 | #define MIT_HRTF_AZI_POSITIONS_80 7 28 | #define MIT_HRTF_AZI_POSITIONS_90 1 29 | 30 | #define MIT_HRTF_44_TAPS 128 31 | #define MIT_HRTF_48_TAPS 140 32 | #define MIT_HRTF_88_TAPS 256 33 | #define MIT_HRTF_96_TAPS 279 34 | 35 | typedef struct mit_hrtf_filter_44_str 36 | { 37 | short left[MIT_HRTF_44_TAPS]; 38 | short right[MIT_HRTF_44_TAPS]; 39 | }mit_hrtf_filter_44; 40 | 41 | typedef struct mit_hrtf_filter_48_str 42 | { 43 | short left[MIT_HRTF_48_TAPS]; 44 | short right[MIT_HRTF_48_TAPS]; 45 | }mit_hrtf_filter_48; 46 | 47 | typedef struct mit_hrtf_filter_88_str 48 | { 49 | short left[MIT_HRTF_88_TAPS]; 50 | short right[MIT_HRTF_88_TAPS]; 51 | }mit_hrtf_filter_88; 52 | 53 | typedef struct mit_hrtf_filter_96_str 54 | { 55 | short left[MIT_HRTF_96_TAPS]; 56 | short right[MIT_HRTF_96_TAPS]; 57 | }mit_hrtf_filter_96; 58 | 59 | typedef struct mit_hrtf_filter_set_44_str 60 | { 61 | mit_hrtf_filter_44 e_10[MIT_HRTF_AZI_POSITIONS_10]; 62 | mit_hrtf_filter_44 e_20[MIT_HRTF_AZI_POSITIONS_20]; 63 | mit_hrtf_filter_44 e_30[MIT_HRTF_AZI_POSITIONS_30]; 64 | mit_hrtf_filter_44 e_40[MIT_HRTF_AZI_POSITIONS_40]; 65 | mit_hrtf_filter_44 e00[MIT_HRTF_AZI_POSITIONS_00]; 66 | mit_hrtf_filter_44 e10[MIT_HRTF_AZI_POSITIONS_10]; 67 | mit_hrtf_filter_44 e20[MIT_HRTF_AZI_POSITIONS_20]; 68 | mit_hrtf_filter_44 e30[MIT_HRTF_AZI_POSITIONS_30]; 69 | mit_hrtf_filter_44 e40[MIT_HRTF_AZI_POSITIONS_40]; 70 | mit_hrtf_filter_44 e50[MIT_HRTF_AZI_POSITIONS_50]; 71 | mit_hrtf_filter_44 e60[MIT_HRTF_AZI_POSITIONS_60]; 72 | mit_hrtf_filter_44 e70[MIT_HRTF_AZI_POSITIONS_70]; 73 | mit_hrtf_filter_44 e80[MIT_HRTF_AZI_POSITIONS_80]; 74 | mit_hrtf_filter_44 e90[MIT_HRTF_AZI_POSITIONS_90]; 75 | }mit_hrtf_filter_set_44; 76 | 77 | typedef struct mit_hrtf_filter_set_48_str 78 | { 79 | mit_hrtf_filter_48 e_10[MIT_HRTF_AZI_POSITIONS_10]; 80 | mit_hrtf_filter_48 e_20[MIT_HRTF_AZI_POSITIONS_20]; 81 | mit_hrtf_filter_48 e_30[MIT_HRTF_AZI_POSITIONS_30]; 82 | mit_hrtf_filter_48 e_40[MIT_HRTF_AZI_POSITIONS_40]; 83 | mit_hrtf_filter_48 e00[MIT_HRTF_AZI_POSITIONS_00]; 84 | mit_hrtf_filter_48 e10[MIT_HRTF_AZI_POSITIONS_10]; 85 | mit_hrtf_filter_48 e20[MIT_HRTF_AZI_POSITIONS_20]; 86 | mit_hrtf_filter_48 e30[MIT_HRTF_AZI_POSITIONS_30]; 87 | mit_hrtf_filter_48 e40[MIT_HRTF_AZI_POSITIONS_40]; 88 | mit_hrtf_filter_48 e50[MIT_HRTF_AZI_POSITIONS_50]; 89 | mit_hrtf_filter_48 e60[MIT_HRTF_AZI_POSITIONS_60]; 90 | mit_hrtf_filter_48 e70[MIT_HRTF_AZI_POSITIONS_70]; 91 | mit_hrtf_filter_48 e80[MIT_HRTF_AZI_POSITIONS_80]; 92 | mit_hrtf_filter_48 e90[MIT_HRTF_AZI_POSITIONS_90]; 93 | }mit_hrtf_filter_set_48; 94 | 95 | typedef struct mit_hrtf_filter_set_88_str 96 | { 97 | mit_hrtf_filter_88 e_10[MIT_HRTF_AZI_POSITIONS_10]; 98 | mit_hrtf_filter_88 e_20[MIT_HRTF_AZI_POSITIONS_20]; 99 | mit_hrtf_filter_88 e_30[MIT_HRTF_AZI_POSITIONS_30]; 100 | mit_hrtf_filter_88 e_40[MIT_HRTF_AZI_POSITIONS_40]; 101 | mit_hrtf_filter_88 e00[MIT_HRTF_AZI_POSITIONS_00]; 102 | mit_hrtf_filter_88 e10[MIT_HRTF_AZI_POSITIONS_10]; 103 | mit_hrtf_filter_88 e20[MIT_HRTF_AZI_POSITIONS_20]; 104 | mit_hrtf_filter_88 e30[MIT_HRTF_AZI_POSITIONS_30]; 105 | mit_hrtf_filter_88 e40[MIT_HRTF_AZI_POSITIONS_40]; 106 | mit_hrtf_filter_88 e50[MIT_HRTF_AZI_POSITIONS_50]; 107 | mit_hrtf_filter_88 e60[MIT_HRTF_AZI_POSITIONS_60]; 108 | mit_hrtf_filter_88 e70[MIT_HRTF_AZI_POSITIONS_70]; 109 | mit_hrtf_filter_88 e80[MIT_HRTF_AZI_POSITIONS_80]; 110 | mit_hrtf_filter_88 e90[MIT_HRTF_AZI_POSITIONS_90]; 111 | }mit_hrtf_filter_set_88; 112 | 113 | typedef struct mit_hrtf_filter_set_96_str 114 | { 115 | mit_hrtf_filter_96 e_10[MIT_HRTF_AZI_POSITIONS_10]; 116 | mit_hrtf_filter_96 e_20[MIT_HRTF_AZI_POSITIONS_20]; 117 | mit_hrtf_filter_96 e_30[MIT_HRTF_AZI_POSITIONS_30]; 118 | mit_hrtf_filter_96 e_40[MIT_HRTF_AZI_POSITIONS_40]; 119 | mit_hrtf_filter_96 e00[MIT_HRTF_AZI_POSITIONS_00]; 120 | mit_hrtf_filter_96 e10[MIT_HRTF_AZI_POSITIONS_10]; 121 | mit_hrtf_filter_96 e20[MIT_HRTF_AZI_POSITIONS_20]; 122 | mit_hrtf_filter_96 e30[MIT_HRTF_AZI_POSITIONS_30]; 123 | mit_hrtf_filter_96 e40[MIT_HRTF_AZI_POSITIONS_40]; 124 | mit_hrtf_filter_96 e50[MIT_HRTF_AZI_POSITIONS_50]; 125 | mit_hrtf_filter_96 e60[MIT_HRTF_AZI_POSITIONS_60]; 126 | mit_hrtf_filter_96 e70[MIT_HRTF_AZI_POSITIONS_70]; 127 | mit_hrtf_filter_96 e80[MIT_HRTF_AZI_POSITIONS_80]; 128 | mit_hrtf_filter_96 e90[MIT_HRTF_AZI_POSITIONS_90]; 129 | }mit_hrtf_filter_set_96; 130 | 131 | #endif // _MIT_HRTF_FILTER_H -------------------------------------------------------------------------------- /include/mit_hrtf_lib.h: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# MIT HRTF C Library #*/ 4 | /*# Copyright © 2007 Aristotel Digenis #*/ 5 | /*# #*/ 6 | /*# Filename: mit_hrtf_lib.h #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 04/05/2007 #*/ 9 | /*# Author(s): Aristotel Digenis #*/ 10 | /*# Credit: Bill Gardner and Keith Martin #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #ifndef _MIT_HRTF_LIB_H_ 17 | #define _MIT_HRTF_LIB_H_ 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | #include "mit_hrtf_filter.h" 24 | 25 | 26 | 27 | /** 28 | Checks if an HRTF set is available for the specified settings. 29 | 30 | "azimuth" is in the range of [-180, 180] degrees, where position 0 is the 31 | centre in front of the listener, negative values are to the left of the 32 | listener, and positive values are to the right of the front centre. 33 | 34 | "elevation" is in the range of [-90, 90] degrees, where position 0 is the 35 | centre in front of the listener, negative values are below the listener, 36 | and positive values are above the listener. 37 | 38 | "samplerate" can be one of the following: 44100, 48000, 88200, 96000 39 | 40 | Returns the number of taps needed for the each channel of the available 41 | set. Returns 0 if the requested HRTF set is not available. 42 | */ 43 | unsigned int mit_hrtf_availability(int azimuth, int elevation, unsigned int samplerate); 44 | 45 | 46 | 47 | /** 48 | Copies HRTF taps to given buffers, for specified HRTF set. 49 | 50 | "pAzimuth" is in the range of [-180, 180] degrees, where position 0 is the 51 | centre in front of the listener, negative values are to the left of the 52 | listener, and positive values are to the right of the front centre. The 53 | variable is a pointer, so that once the function returns, the actual 54 | azimuth position used is written to that pointed variable. 55 | 56 | "pElevation" is in the range of [-90, 90] degrees, where position 0 is the 57 | centre in front of the listener, negative values are below the listener, 58 | and positive values are above the listener. The variable is a pointer, so 59 | that once the function returns, the actual azimuth position used is 60 | written to that pointed variable. 61 | 62 | "samplerate" can be one of the following: 44100, 48000, 88200, 96000 63 | 64 | "psLeft" and "psRight" are pointers to buffers allocated (and later 65 | deallocated) by the user based on the return value of the 66 | "mit_hrtf_availability" function. 67 | 68 | Returns the number of taps copied to each of the "psLeft" and "psRight" 69 | buffers. Returns 0 if the requested HRTF set is not available or if there 70 | was an error. 71 | */ 72 | unsigned int mit_hrtf_get(int* pAzimuth, int* pElevation, unsigned int samplerate, short* psLeft, short* psRight); 73 | 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | #endif // _MIT_HRTF_LIB_H_ 80 | -------------------------------------------------------------------------------- /octave/FibonacciSphereSampling.m: -------------------------------------------------------------------------------- 1 | % Test the Fibonacci lattice algorithm on a sphere 2 | % The algorithm and some extra reading can be found here: 3 | % http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/ 4 | clear 5 | close all 6 | clc 7 | 8 | % Number of points to sample on the sphere 9 | nPoints = 1500; 10 | cartCoords = fibonacciSphere(nPoints); 11 | polCoords = cart2sph(cartCoords(:,1),cartCoords(:,2),cartCoords(:,3)); 12 | 13 | figure(1) 14 | subplot(1,2,1) 15 | plot3(cartCoords(:,1),cartCoords(:,2),cartCoords(:,3),'.','MarkerSize',20) 16 | title('Fibonacci') 17 | axis equal 18 | xlabel('x') 19 | ylabel('y') 20 | zlabel('z') 21 | 22 | figure(2) 23 | subplot(1,2,1) 24 | plot(polCoords(:,1)/pi*180,polCoords(:,2)/pi*180,'.','MarkerSize',20) 25 | xlabel('azimuth (degrees)') 26 | ylabel('elevation (degrees)') 27 | axis([-180, 180, -90, 90]) 28 | 29 | %% Compare this to the algorithm used by libear 30 | nRows = 37; 31 | el = linspace(-90,90,nRows); 32 | 33 | perimiter_centre = 2 * pi; 34 | positions = []; 35 | for iEl = 1:length(el) 36 | radius = cos(el(iEl)/180*pi); 37 | perimiter = 2 * pi * radius; 38 | 39 | nPoints = round((perimiter / perimiter_centre) * 2 * (nRows - 1)); 40 | if nPoints == 0 41 | nPoints = 1; 42 | end 43 | 44 | az = linspace(0,360-360/nPoints,nPoints); 45 | for iAz = 1:length(az) 46 | [posTmp(1),posTmp(2),posTmp(3)] = cart([az(iAz), el(iEl), 1.0]); 47 | positions = [positions; posTmp]; 48 | end 49 | end 50 | 51 | [polarPositions(:,1), polarPositions(:,2), polarPositions(:,3)] = pol([positions(:,1),positions(:,2),positions(:,3)]); 52 | 53 | figure(1) 54 | subplot(1,2,2) 55 | plot3(positions(:,1),positions(:,2),positions(:,3),'r.','MarkerSize',20) 56 | title('Libear') 57 | axis equal 58 | xlabel('x') 59 | ylabel('y') 60 | zlabel('z') 61 | 62 | figure(2) 63 | subplot(1,2,2) 64 | plot(polarPositions(:,1),polarPositions(:,2),'.','MarkerSize',20) 65 | xlabel('azimuth (degrees)') 66 | ylabel('elevation (degrees)') 67 | axis([-180, 180, -90, 90]) 68 | -------------------------------------------------------------------------------- /octave/WeightingFunctionTest.m: -------------------------------------------------------------------------------- 1 | % The weighting function of the ADM renderer 2 | clear 3 | close all 4 | clc 5 | 6 | nPoints = 1500; 7 | 8 | % The width and height of the weighting function in degrees 9 | width = 90; 10 | height = 0; 11 | % The fade out distance in degrees 12 | fade_out = 10; 13 | 14 | % virtual source positions 15 | %virtualSourcePos = fibonacciSphere(nPoints); 16 | nRows = 37; 17 | el = linspace(-90,90,nRows); 18 | 19 | perimiter_centre = 2 * pi; 20 | virtualSourcePos = []; 21 | for iEl = 1:length(el) 22 | radius = cos(el(iEl)/180*pi); 23 | perimiter = 2 * pi * radius; 24 | 25 | nPoints = round((perimiter / perimiter_centre) * 2 * (nRows - 1)); 26 | if nPoints == 0 27 | nPoints = 1; 28 | end 29 | 30 | az = linspace(0,360-360/nPoints,nPoints); 31 | for iAz = 1:length(az) 32 | [posTmp(1),posTmp(2),posTmp(3)] = cart([az(iAz), el(iEl), 1.0]); 33 | virtualSourcePos = [virtualSourcePos; posTmp]; 34 | end 35 | end 36 | nPoints = size(virtualSourcePos,1); 37 | 38 | [azOrig,elOrig,~] = pol(virtualSourcePos); 39 | 40 | % panning direction where +ve y is the front and +ve x is the right, as defined in ADM standard 41 | position = [0;1;0]; 42 | position = position/norm(position); 43 | 44 | % Calculate the rotation matrix to go from a position to the panning direction basis 45 | [az,el,d] = pol(position.'); 46 | [rotMat(1,1), rotMat(1,2), rotMat(1,3)] = cart([az - 90;0;1]); 47 | [rotMat(2,1), rotMat(2,2), rotMat(2,3)] = cart([az;el;1]); 48 | [rotMat(3,1), rotMat(3,2), rotMat(3,3)] = cart([az;el+90;1]); 49 | 50 | if height > width 51 | rotMat = flipud(rotMat); 52 | width_ = width; 53 | width = height; 54 | height = width_; 55 | end 56 | 57 | % Handle the case where width > 180 so that they meet at the back 58 | if width > 180 59 | width = 180 + (width - 180)/180*(180+height); 60 | end 61 | 62 | % Get the coordinates of the centre of the cap circles such 63 | % that the edge of the circle is at width/2 when width < 180deg 64 | circleCoordsPol = [width/2 - height/2, 0,1]; 65 | [circleCoordsCart(1,1),circleCoordsCart(1,2),circleCoordsCart(1,3)] = cart([width/2 - height/2, 0,1]); 66 | [circleCoordsCart(2,1),circleCoordsCart(2,2),circleCoordsCart(2,3)] = cart([-(width/2 - height/2), 0,1]); 67 | 68 | % Calculate the weight over all virtual source directions 69 | for iVS = 1:nPoints 70 | % Calculate the direction of the virtual source in the panning basis 71 | positionBasis = rotMat*virtualSourcePos(iVS,:).'; 72 | % Calculate the azimuth and elevation of the source in this basis 73 | [az,el,~] = pol(positionBasis.'); 74 | azBasis(iVS) = az; 75 | elBasis(iVS) = el; 76 | % If the absolute azimuth is less than the circle azimuth coordinates 77 | % then calculate the distance using the elevation angle 78 | if abs(az) < circleCoordsPol(1) 79 | distance = abs(el) - height/2; 80 | else 81 | % Get the circle in the same hemisphere as the virtual source 82 | %if az >= 0 83 | closestCircle = 1; 84 | %elseif az < 0 85 | % closestCircle = 2; 86 | %end 87 | if positionBasis(1) > 0 88 | positionBasis(1) = -positionBasis(1); 89 | end 90 | % If the azimuth is outside this rectangle then calculate its distance 91 | % from the closes of the circles 92 | distance = acos(dot(positionBasis,circleCoordsCart(closestCircle,:))/(norm(positionBasis)*norm(circleCoordsCart(closestCircle,:))))/pi*180 - height/2; 93 | end 94 | w(iVS) = min(max(distance,0),fade_out); 95 | w(iVS) = (1-w(iVS)/fade_out); 96 | end 97 | 98 | %% Plot the weights in the weighting bases 99 | tri=delaunay(azBasis,elBasis); 100 | figure(1) 101 | trisurf(tri,azBasis,elBasis,w,'LineStyle','none') 102 | axis equal 103 | grid on 104 | shading interp 105 | xlabel('azimuth') 106 | ylabel('elevation') 107 | zlabel('weight') 108 | set(gca,'XTick',-180:30:180) 109 | set(gca,'YTick',-90:30:90) 110 | view([0,90]) 111 | 112 | %% Plot the weights in the original coordinate system 113 | tri=delaunay(azOrig,elOrig); 114 | figure(2) 115 | trisurf(tri,azOrig,elOrig,w,'LineStyle','none') 116 | axis equal 117 | grid on 118 | shading interp 119 | xlabel('azimuth') 120 | ylabel('elevation') 121 | zlabel('weight') 122 | set(gca,'XTick',-180:30:180) 123 | set(gca,'YTick',-90:30:90) 124 | view([0,90]) -------------------------------------------------------------------------------- /octave/cart.m: -------------------------------------------------------------------------------- 1 | function [x,y,z] = cart(pol) 2 | % Convert spherical coordinates to ADM defined cartesian 3 | 4 | pol(1:2) = pol(1:2)/180*pi; 5 | 6 | x = sin(-pol(1))*cos(pol(2))*pol(3); 7 | y = cos(-pol(1))*cos(pol(2))*pol(3); 8 | z = sin(pol(2))*pol(3); 9 | 10 | end 11 | -------------------------------------------------------------------------------- /octave/fibonacciSphere.m: -------------------------------------------------------------------------------- 1 | function cartCoords = fibonacciSphere(nPoints) 2 | % Calculate the coordinates of points on a fibonacci sphere. 3 | % % The algorithm and some extra reading can be found here: 4 | % http://extremelearning.com.au/how-to-evenly-distribute-points-on-a-sphere-more-effectively-than-the-canonical-fibonacci-lattice/ 5 | 6 | goldenRatio = (1 + 5^(0.5))/2; 7 | i = linspace(0,nPoints-1,nPoints); 8 | theta = 2 *pi * i / goldenRatio; 9 | phi = acos(1 - 2*(i+0.5)/nPoints); 10 | cartCoords = [cos(theta.') .* sin(phi.'), sin(theta.') .* sin(phi.'), cos(phi.')]; 11 | 12 | end 13 | -------------------------------------------------------------------------------- /octave/pol.m: -------------------------------------------------------------------------------- 1 | function [az,el,r] = pol(cartCoords) 2 | % Convert from ADM cartesian to spherical coordinates 3 | 4 | az = -180/pi*atan2(cartCoords(:,1),cartCoords(:,2)); 5 | el = 180/pi*atan2(cartCoords(:,3),sqrt(cartCoords(:,1).^2 + cartCoords(:,2).^2)); 6 | r = sqrt(cartCoords(:,1).^2 + cartCoords(:,2).^2 + cartCoords(:,3).^2); 7 | 8 | end 9 | -------------------------------------------------------------------------------- /source/AmbisonicBase.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicBase - Ambisonic Base #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicBase.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include "AmbisonicBase.h" 17 | 18 | CAmbisonicBase::CAmbisonicBase() 19 | : m_nOrder(0) 20 | , m_b3D(0) 21 | , m_nChannelCount(0) 22 | { 23 | } 24 | 25 | unsigned CAmbisonicBase::GetOrder() 26 | { 27 | return m_nOrder; 28 | } 29 | 30 | bool CAmbisonicBase::GetHeight() 31 | { 32 | return m_b3D; 33 | } 34 | 35 | unsigned CAmbisonicBase::GetChannelCount() 36 | { 37 | return m_nChannelCount; 38 | } 39 | 40 | bool CAmbisonicBase::Configure(unsigned nOrder, bool b3D, unsigned /*nMisc*/) 41 | { 42 | m_nOrder = nOrder; 43 | m_b3D = b3D; 44 | m_nChannelCount = OrderToComponents(m_nOrder, m_b3D); 45 | 46 | return true; 47 | } 48 | -------------------------------------------------------------------------------- /source/AmbisonicCommons.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# Copyright © 2007 Aristotel Digenis #*/ 5 | /*# #*/ 6 | /*# Filename: AmbisonicCommons.cpp #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 19/05/2007 #*/ 9 | /*# Author(s): Aristotel Digenis #*/ 10 | /*# Licence: MIT #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | 15 | #include "AmbisonicCommons.h" 16 | 17 | float DegreesToRadians(float fDegrees) 18 | { 19 | return fDegrees * (float)M_PI / 180.f; 20 | } 21 | 22 | float RadiansToDegrees(float fRadians) 23 | { 24 | return fRadians * 180.f / (float)M_PI; 25 | } 26 | 27 | unsigned OrderToComponents(unsigned nOrder, bool b3D) 28 | { 29 | if(b3D) 30 | return (unsigned) powf(nOrder + 1.f, 2.f); 31 | else 32 | return nOrder * 2 + 1; 33 | } 34 | 35 | unsigned OrderToComponentPosition(unsigned nOrder, bool b3D) 36 | { 37 | 38 | 39 | unsigned nIndex = 0; 40 | 41 | if(b3D) 42 | { 43 | switch(nOrder) 44 | { 45 | case 0: nIndex = 0; break; 46 | case 1: nIndex = 1; break; 47 | case 2: nIndex = 4; break; 48 | case 3: nIndex = 10;break; 49 | } 50 | } 51 | else 52 | { 53 | switch(nOrder) 54 | { 55 | case 0: nIndex = 0; break; 56 | case 1: nIndex = 1; break; 57 | case 2: nIndex = 3; break; 58 | case 3: nIndex = 5; break; 59 | } 60 | } 61 | 62 | return nIndex; 63 | } 64 | 65 | unsigned OrderToSpeakers(unsigned nOrder, bool b3D) 66 | { 67 | 68 | if(b3D) 69 | return (nOrder * 2 + 2) * 2; 70 | else 71 | return nOrder * 2 + 2; 72 | } 73 | 74 | char ComponentToChannelLabel(unsigned nComponent, bool b3D) 75 | { 76 | 77 | char cLabel = ' '; 78 | if(b3D) 79 | { 80 | switch(nComponent) 81 | { 82 | case 0: cLabel = 'W'; break; 83 | case 1: cLabel = 'Y'; break; 84 | case 2: cLabel = 'Z'; break; 85 | case 3: cLabel = 'X'; break; 86 | case 4: cLabel = 'V'; break; 87 | case 5: cLabel = 'T'; break; 88 | case 6: cLabel = 'R'; break; 89 | case 7: cLabel = 'U'; break; 90 | case 8: cLabel = 'S'; break; 91 | case 9: cLabel = 'Q'; break; 92 | case 10: cLabel = 'O'; break; 93 | case 11: cLabel = 'M'; break; 94 | case 12: cLabel = 'K'; break; 95 | case 13: cLabel = 'L'; break; 96 | case 14: cLabel = 'N'; break; 97 | case 15: cLabel = 'P'; break; 98 | }; 99 | } 100 | else 101 | { 102 | switch(nComponent) 103 | { 104 | case 0: cLabel = 'W'; break; 105 | case 1: cLabel = 'X'; break; 106 | case 2: cLabel = 'Y'; break; 107 | case 3: cLabel = 'U'; break; 108 | case 4: cLabel = 'V'; break; 109 | case 5: cLabel = 'P'; break; 110 | case 6: cLabel = 'Q'; break; 111 | }; 112 | } 113 | 114 | return cLabel; 115 | } 116 | -------------------------------------------------------------------------------- /source/AmbisonicEncoder.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicEncoder - Ambisonic Encoder #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicEncoder.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include "AmbisonicEncoder.h" 17 | 18 | 19 | CAmbisonicEncoder::CAmbisonicEncoder() 20 | { } 21 | 22 | CAmbisonicEncoder::~CAmbisonicEncoder() 23 | { } 24 | 25 | bool CAmbisonicEncoder::Configure(unsigned nOrder, bool b3D, unsigned nMisc) 26 | { 27 | bool success = CAmbisonicSource::Configure(nOrder, b3D, nMisc); 28 | if(!success) 29 | return false; 30 | //SetOrderWeight(0, 1.f / sqrtf(2.f)); // Removed as seems to break SN3D normalisation 31 | 32 | return true; 33 | } 34 | 35 | void CAmbisonicEncoder::Refresh() 36 | { 37 | CAmbisonicSource::Refresh(); 38 | } 39 | 40 | void CAmbisonicEncoder::SetPosition(PolarPoint polPosition, float interpDur) 41 | { 42 | // Set the interpolation duration 43 | m_fInterpDur = interpDur; 44 | // Store the last set coefficients 45 | m_pfCoeffOld = m_pfCoeff; 46 | // Update the coefficients 47 | CAmbisonicSource::SetPosition(polPosition); 48 | Refresh(); 49 | } 50 | 51 | void CAmbisonicEncoder::Process(float* pfSrc, unsigned nSamples, CBFormat* pfDst) 52 | { 53 | unsigned niChannel = 0; 54 | unsigned niSample = 0; 55 | if (m_fInterpDur > 0.f) 56 | { 57 | // Number of samples expected per frame 58 | for (niChannel = 0; niChannel < m_nChannelCount; niChannel++) 59 | { 60 | unsigned int nInterpSamples = (unsigned int)roundf(m_fInterpDur * nSamples); 61 | float deltaCoeff = (m_pfCoeff[niChannel] - m_pfCoeffOld[niChannel]) / ((float)nInterpSamples); 62 | for (niSample = 0; niSample < nInterpSamples; niSample++) 63 | { 64 | float fInterp = niSample * deltaCoeff; 65 | pfDst->m_ppfChannels[niChannel][niSample] = pfSrc[niSample] * (fInterp*m_pfCoeff[niChannel] + (1.f-fInterp)*m_pfCoeffOld[niChannel]); 66 | } 67 | // once interpolation has finished 68 | for (niSample = nInterpSamples; niSample < nSamples; niSample++) 69 | { 70 | pfDst->m_ppfChannels[niChannel][niSample] = pfSrc[niSample] * m_pfCoeff[niChannel]; 71 | } 72 | } 73 | // Set interpolation duration to zero so none is applied on next call 74 | m_fInterpDur = 0.f; 75 | } 76 | else 77 | { 78 | for (niChannel = 0; niChannel < m_nChannelCount; niChannel++) 79 | { 80 | for (niSample = 0; niSample < nSamples; niSample++) 81 | { 82 | pfDst->m_ppfChannels[niChannel][niSample] = pfSrc[niSample] * m_pfCoeff[niChannel]; 83 | } 84 | } 85 | } 86 | } 87 | 88 | void CAmbisonicEncoder::ProcessAccumul(float* pfSrc, unsigned nSamples, CBFormat* pfDst, unsigned int nOffset, float fGain) 89 | { 90 | unsigned niChannel = 0; 91 | unsigned niSample = 0; 92 | if (m_fInterpDur > 0.f) 93 | { 94 | // Number of samples expected per frame 95 | for (niChannel = 0; niChannel < m_nChannelCount; niChannel++) 96 | { 97 | unsigned int nInterpSamples = (int)roundf(m_fInterpDur * nSamples); 98 | float deltaCoeff = 1.f / (float)nInterpSamples; 99 | for (niSample = 0; niSample < nInterpSamples; niSample++) 100 | { 101 | float fInterp = niSample * deltaCoeff; 102 | pfDst->m_ppfChannels[niChannel][niSample + nOffset] += pfSrc[niSample] * (fInterp * m_pfCoeff[niChannel] + (1.f - fInterp) * m_pfCoeffOld[niChannel]) * fGain; 103 | } 104 | // once interpolation has finished 105 | for (niSample = nInterpSamples; niSample < nSamples; niSample++) 106 | { 107 | pfDst->m_ppfChannels[niChannel][niSample + nOffset] += pfSrc[niSample] * m_pfCoeff[niChannel] * fGain; 108 | } 109 | } 110 | // Set interpolation duration to zero so none is applied on next call 111 | m_fInterpDur = 0.f; 112 | } 113 | else 114 | { 115 | for (niChannel = 0; niChannel < m_nChannelCount; niChannel++) 116 | { 117 | for (niSample = 0; niSample < nSamples; niSample++) 118 | { 119 | pfDst->m_ppfChannels[niChannel][niSample + nOffset] += pfSrc[niSample] * m_pfCoeff[niChannel] * fGain; 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /source/AmbisonicEncoderDist.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicEncoderDist - Ambisonic Encoder with distance #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicEncoderDist.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include "AmbisonicEncoderDist.h" 17 | 18 | CAmbisonicEncoderDist::CAmbisonicEncoderDist() 19 | { 20 | m_nSampleRate = 0; 21 | m_fDelay = 0.f; 22 | m_nDelay = 0; 23 | m_nDelayBufferLength = 0; 24 | m_pfDelayBuffer = 0; 25 | m_nIn = 0; 26 | m_nOutA = 0; 27 | m_nOutB = 0; 28 | m_fRoomRadius = 5.f; 29 | m_fInteriorGain = 0.f; 30 | m_fExteriorGain = 0.f; 31 | 32 | Configure(DEFAULT_ORDER, DEFAULT_HEIGHT, DEFAULT_SAMPLERATE); 33 | } 34 | 35 | CAmbisonicEncoderDist::~CAmbisonicEncoderDist() 36 | { 37 | if(m_pfDelayBuffer) 38 | delete [] m_pfDelayBuffer; 39 | } 40 | 41 | bool CAmbisonicEncoderDist::Configure(unsigned nOrder, bool b3D, unsigned nSampleRate) 42 | { 43 | bool success = CAmbisonicEncoder::Configure(nOrder, b3D, 0); 44 | if(!success) 45 | return false; 46 | m_nSampleRate = nSampleRate; 47 | m_nDelayBufferLength = (unsigned)((float)knMaxDistance / knSpeedOfSound * m_nSampleRate + 0.5f); 48 | if(m_pfDelayBuffer) 49 | delete [] m_pfDelayBuffer; 50 | m_pfDelayBuffer = new float[m_nDelayBufferLength]; 51 | Reset(); 52 | 53 | return true; 54 | } 55 | 56 | void CAmbisonicEncoderDist::Reset() 57 | { 58 | memset(m_pfDelayBuffer, 0, m_nDelayBufferLength * sizeof(float)); 59 | m_fDelay = m_polPosition.fDistance / knSpeedOfSound * (float)m_nSampleRate + 0.5f; 60 | m_nDelay = (int)m_fDelay; 61 | m_fDelay -= m_nDelay; 62 | m_nIn = 0; 63 | m_nOutA = (m_nIn - m_nDelay + m_nDelayBufferLength) % m_nDelayBufferLength; 64 | m_nOutB = (m_nOutA + 1) % m_nDelayBufferLength; 65 | } 66 | 67 | void CAmbisonicEncoderDist::Refresh() 68 | { 69 | CAmbisonicEncoder::Refresh(); 70 | 71 | m_fDelay = fabsf(m_polPosition.fDistance) / knSpeedOfSound * (float)m_nSampleRate; //TODO abs() sees float as int! 72 | m_nDelay = (int)m_fDelay; 73 | m_fDelay -= m_nDelay; 74 | m_nOutA = (m_nIn - m_nDelay + m_nDelayBufferLength) % m_nDelayBufferLength; 75 | m_nOutB = (m_nOutA + 1) % m_nDelayBufferLength; 76 | 77 | //Source is outside speaker array 78 | if(fabs(m_polPosition.fDistance) >= m_fRoomRadius) 79 | { 80 | m_fInteriorGain = (m_fRoomRadius / fabsf(m_polPosition.fDistance)) / 2.f; 81 | m_fExteriorGain = m_fInteriorGain; 82 | } 83 | else 84 | { 85 | m_fInteriorGain = (2.f - fabsf(m_polPosition.fDistance) / m_fRoomRadius) / 2.f; 86 | m_fExteriorGain = (fabsf(m_polPosition.fDistance) / m_fRoomRadius) / 2.f; 87 | } 88 | } 89 | 90 | void CAmbisonicEncoderDist::Process(float* pfSrc, unsigned nSamples, CBFormat* pfDst) 91 | { 92 | unsigned niChannel = 0; 93 | unsigned niSample = 0; 94 | float fSrcSample = 0; 95 | 96 | for(niSample = 0; niSample < nSamples; niSample++) 97 | { 98 | //Store 99 | m_pfDelayBuffer[m_nIn] = pfSrc[niSample]; 100 | //Read 101 | fSrcSample = m_pfDelayBuffer[m_nOutA] * (1.f - m_fDelay) 102 | + m_pfDelayBuffer[m_nOutB] * m_fDelay; 103 | 104 | pfDst->m_ppfChannels[kW][niSample] = fSrcSample * m_fInteriorGain * m_pfCoeff[kW]; 105 | 106 | fSrcSample *= m_fExteriorGain; 107 | for(niChannel = 1; niChannel < m_nChannelCount; niChannel++) 108 | { 109 | pfDst->m_ppfChannels[niChannel][niSample] = fSrcSample * m_pfCoeff[niChannel]; 110 | } 111 | 112 | m_nIn = (m_nIn + 1) % m_nDelayBufferLength; 113 | m_nOutA = (m_nOutA + 1) % m_nDelayBufferLength; 114 | m_nOutB = (m_nOutB + 1) % m_nDelayBufferLength; 115 | } 116 | } 117 | 118 | void CAmbisonicEncoderDist::SetRoomRadius(float fRoomRadius) 119 | { 120 | m_fRoomRadius = fRoomRadius; 121 | } 122 | 123 | float CAmbisonicEncoderDist::GetRoomRadius() 124 | { 125 | return m_fRoomRadius; 126 | } 127 | -------------------------------------------------------------------------------- /source/AmbisonicMicrophone.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicMicrophone - Ambisonic Microphone #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicMicrophone.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include "AmbisonicMicrophone.h" 17 | 18 | CAmbisonicMicrophone::CAmbisonicMicrophone() 19 | { 20 | m_fDirectivity = 1.f; 21 | } 22 | 23 | CAmbisonicMicrophone::~CAmbisonicMicrophone() 24 | { } 25 | 26 | void CAmbisonicMicrophone::Refresh() 27 | { 28 | CAmbisonicSource::Refresh(); 29 | 30 | m_pfCoeff[0] *= (2.f - m_fDirectivity) * sqrtf(2.f); 31 | } 32 | 33 | void CAmbisonicMicrophone::Process(CBFormat* pBFSrc, unsigned nSamples, float* pfDst) 34 | { 35 | unsigned niChannel = 0; 36 | unsigned niSample = 0; 37 | float fTempA = 0; 38 | float fTempB = 0; 39 | for(niSample = 0; niSample < nSamples; niSample++) 40 | { 41 | fTempA = pBFSrc->m_ppfChannels[0][niSample] * m_pfCoeff[0]; 42 | fTempB = 0; 43 | for(niChannel = 1; niChannel < m_nChannelCount; niChannel++) 44 | { 45 | fTempB += pBFSrc->m_ppfChannels[niChannel][niSample] * m_pfCoeff[niChannel]; 46 | } 47 | pfDst[niSample] = 0.5f * (fTempA + fTempB * m_fDirectivity); 48 | } 49 | } 50 | 51 | void CAmbisonicMicrophone::SetDirectivity(float fDirectivity) 52 | { 53 | m_fDirectivity = fDirectivity; 54 | } 55 | 56 | float CAmbisonicMicrophone::GetDirectivity() 57 | { 58 | return m_fDirectivity; 59 | } 60 | -------------------------------------------------------------------------------- /source/AmbisonicSource.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicSource - Ambisonic Source #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicSource.cpp #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis #*/ 12 | /*# Licence: LGPL #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #include "AmbisonicSource.h" 18 | 19 | #define fSqrt32 sqrtf(3.f)/2.f 20 | #define fSqrt58 sqrtf(5.f/8.f) 21 | #define fSqrt152 sqrtf(15.f)/2.f 22 | #define fSqrt38 sqrtf(3.f/8.f) 23 | 24 | CAmbisonicSource::CAmbisonicSource() 25 | { 26 | m_polPosition.fAzimuth = 0.f; 27 | m_polPosition.fElevation = 0.f; 28 | m_polPosition.fDistance = 1.f; 29 | m_fGain = 1.f; 30 | } 31 | 32 | bool CAmbisonicSource::Configure(unsigned nOrder, bool b3D, unsigned nMisc) 33 | { 34 | bool success = CAmbisonicBase::Configure(nOrder, b3D, nMisc); 35 | if(!success) 36 | return false; 37 | 38 | m_pfCoeff.resize( m_nChannelCount, 0 ); 39 | // for a Basic Ambisonics decoder all of the gains are set to 1.f 40 | m_pfOrderWeights.resize( m_nOrder + 1, 1.f ); 41 | 42 | return true; 43 | } 44 | 45 | void CAmbisonicSource::Reset() 46 | { 47 | //memset(m_pfCoeff, 0, m_nChannelCount * sizeof(float)); 48 | } 49 | 50 | void CAmbisonicSource::Refresh() 51 | { 52 | float fCosAzim = cosf(m_polPosition.fAzimuth); 53 | float fSinAzim = sinf(m_polPosition.fAzimuth); 54 | float fCosElev = cosf(m_polPosition.fElevation); 55 | float fSinElev = sinf(m_polPosition.fElevation); 56 | 57 | float fCos2Azim = cosf(2.f * m_polPosition.fAzimuth); 58 | float fSin2Azim = sinf(2.f * m_polPosition.fAzimuth); 59 | float fSin2Elev = sinf(2.f * m_polPosition.fElevation); 60 | 61 | if(m_b3D) 62 | { 63 | // Uses ACN channel ordering and SN3D normalization scheme (AmbiX format) 64 | /* (m_nOrder >= 0) (always true) */ 65 | m_pfCoeff[0] = 1.f * m_pfOrderWeights[0]; // W 66 | 67 | if(m_nOrder >= 1) 68 | { 69 | m_pfCoeff[1] = (fSinAzim * fCosElev) * m_pfOrderWeights[1]; // Y 70 | m_pfCoeff[2] = (fSinElev) * m_pfOrderWeights[1]; // Z 71 | m_pfCoeff[3] = (fCosAzim * fCosElev) * m_pfOrderWeights[1]; // X 72 | } 73 | if(m_nOrder >= 2) 74 | { 75 | m_pfCoeff[4] = fSqrt32*(fSin2Azim * powf(fCosElev, 2.f)) * m_pfOrderWeights[2]; // V 76 | m_pfCoeff[5] = fSqrt32*(fSinAzim * fSin2Elev) * m_pfOrderWeights[2]; // T 77 | m_pfCoeff[6] = (1.5f * powf(fSinElev, 2.f) - 0.5f) * m_pfOrderWeights[2]; // R 78 | m_pfCoeff[7] = fSqrt32*(fCosAzim * fSin2Elev) * m_pfOrderWeights[2]; // S 79 | m_pfCoeff[8] = fSqrt32*(fCos2Azim * powf(fCosElev, 2.f)) * m_pfOrderWeights[2]; // U 80 | } 81 | if(m_nOrder >= 3) 82 | { 83 | m_pfCoeff[9] = fSqrt58*(sinf(3.f * m_polPosition.fAzimuth) * powf(fCosElev, 3.f)) * m_pfOrderWeights[3]; // Q 84 | m_pfCoeff[10] = fSqrt152*(fSin2Azim * fSinElev * powf(fCosElev, 2.f)) * m_pfOrderWeights[3]; // O 85 | m_pfCoeff[11] = fSqrt38*(fSinAzim * fCosElev * (5.f * powf(fSinElev, 2.f) - 1.f)) * m_pfOrderWeights[3]; // M 86 | m_pfCoeff[12] = (fSinElev * (5.f * powf(fSinElev, 2.f) - 3.f) * 0.5f) * m_pfOrderWeights[3]; // K 87 | m_pfCoeff[13] = fSqrt38*(fCosAzim * fCosElev * (5.f * powf(fSinElev, 2.f) - 1.f)) * m_pfOrderWeights[3]; // L 88 | m_pfCoeff[14] = fSqrt152*(fCos2Azim * fSinElev * powf(fCosElev, 2.f)) * m_pfOrderWeights[3]; // N 89 | m_pfCoeff[15] = fSqrt58*(cosf(3.f * m_polPosition.fAzimuth) * powf(fCosElev, 3.f)) * m_pfOrderWeights[3]; // P 90 | 91 | } 92 | } 93 | else 94 | { 95 | /* (m_nOrder >= 0) (always true) */ 96 | m_pfCoeff[0] = m_pfOrderWeights[0]; 97 | 98 | if(m_nOrder >= 1) 99 | { 100 | m_pfCoeff[1] = (fCosAzim * fCosElev) * m_pfOrderWeights[1]; 101 | m_pfCoeff[2] = (fSinAzim * fCosElev) * m_pfOrderWeights[1]; 102 | } 103 | if(m_nOrder >= 2) 104 | { 105 | m_pfCoeff[3] = (fCos2Azim * powf(fCosElev, 2)) * m_pfOrderWeights[2]; 106 | m_pfCoeff[4] = (fSin2Azim * powf(fCosElev, 2)) * m_pfOrderWeights[2]; 107 | } 108 | if(m_nOrder >= 3) 109 | { 110 | m_pfCoeff[5] = (cosf(3.f * m_polPosition.fAzimuth) * powf(fCosElev, 3.f)) * m_pfOrderWeights[3]; 111 | m_pfCoeff[6] = (sinf(3.f * m_polPosition.fAzimuth) * powf(fCosElev, 3.f)) * m_pfOrderWeights[3]; 112 | } 113 | } 114 | 115 | for(unsigned ni = 0; ni < m_nChannelCount; ni++) 116 | m_pfCoeff[ni] *= m_fGain; 117 | } 118 | 119 | void CAmbisonicSource::SetPosition(PolarPoint polPosition) 120 | { 121 | m_polPosition = polPosition; 122 | } 123 | 124 | PolarPoint CAmbisonicSource::GetPosition() 125 | { 126 | return m_polPosition; 127 | } 128 | 129 | void CAmbisonicSource::SetOrderWeight(unsigned nOrder, float fWeight) 130 | { 131 | m_pfOrderWeights[nOrder] = fWeight; 132 | } 133 | 134 | void CAmbisonicSource::SetOrderWeightAll(float fWeight) 135 | { 136 | for(unsigned niOrder = 0; niOrder < m_nOrder + 1; niOrder++) 137 | { 138 | m_pfOrderWeights[niOrder] = fWeight; 139 | } 140 | } 141 | 142 | void CAmbisonicSource::SetCoefficient(unsigned nChannel, float fCoeff) 143 | { 144 | m_pfCoeff[nChannel] = fCoeff; 145 | } 146 | 147 | float CAmbisonicSource::GetOrderWeight(unsigned nOrder) 148 | { 149 | return m_pfOrderWeights[nOrder]; 150 | } 151 | 152 | float CAmbisonicSource::GetCoefficient(unsigned nChannel) 153 | { 154 | return m_pfCoeff[nChannel]; 155 | } 156 | 157 | std::vector CAmbisonicSource::GetCoefficients() 158 | { 159 | return m_pfCoeff; 160 | } 161 | 162 | void CAmbisonicSource::SetGain(float fGain) 163 | { 164 | m_fGain = fGain; 165 | } 166 | 167 | float CAmbisonicSource::GetGain() 168 | { 169 | return m_fGain; 170 | } 171 | -------------------------------------------------------------------------------- /source/AmbisonicSpeaker.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicSpeaker - Ambisonic Speaker #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicSpeaker.cpp #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 12 | /*# Licence: LGPL (+ Proprietary) #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #include "AmbisonicSpeaker.h" 18 | 19 | 20 | CAmbisonicSpeaker::CAmbisonicSpeaker() 21 | { } 22 | 23 | CAmbisonicSpeaker::~CAmbisonicSpeaker() 24 | { } 25 | 26 | bool CAmbisonicSpeaker::Configure(unsigned nOrder, bool b3D, unsigned nMisc) 27 | { 28 | bool success = CAmbisonicSource::Configure(nOrder, b3D, nMisc); 29 | if(!success) 30 | return false; 31 | 32 | return true; 33 | } 34 | 35 | void CAmbisonicSpeaker::Refresh() 36 | { 37 | CAmbisonicSource::Refresh(); 38 | } 39 | 40 | void CAmbisonicSpeaker::Process(CBFormat* pBFSrc, unsigned nSamples, float* pfDst) 41 | { 42 | unsigned niChannel = 0; 43 | unsigned niSample = 0; 44 | memset(pfDst, 0, nSamples * sizeof(float)); 45 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 46 | { 47 | float *in = pBFSrc->m_ppfChannels[niChannel]; 48 | float *out = pfDst; 49 | 50 | if(m_b3D){ /* Decode to a 3D loudspeaker array */ 51 | // The spherical harmonic coefficients are multiplied by (2*order + 1) to provide the correct decoder 52 | // for SN3D normalised Ambisonic inputs. 53 | const float coeff = m_pfCoeff[niChannel] * (2.f*floorf(sqrtf((float)niChannel)) + 1.f); 54 | for(niSample = 0; niSample < nSamples; niSample++) 55 | *out++ += (*in++) * coeff; 56 | } 57 | else 58 | { /* Decode to a 2D loudspeaker array */ 59 | // The spherical harmonic coefficients are multiplied by 2 to provide the correct decoder 60 | // for SN3D normalised Ambisonic inputs decoded to a horizontal loudspeaker array 61 | const float coeff = m_pfCoeff[niChannel] * 2.f; 62 | for(niSample = 0; niSample < nSamples; niSample++) 63 | *out++ += (*in++) * coeff; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /source/AmbisonicZoomer.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicZoomer - Ambisonic Zoomer #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# Copyright © 2017 Videolabs #*/ 7 | /*# #*/ 8 | /*# Filename: AmbisonicZoomer.cpp #*/ 9 | /*# Version: 0.2 #*/ 10 | /*# Date: 19/05/2007 #*/ 11 | /*# Author(s): Aristotel Digenis, Peter Stitt #*/ 12 | /*# Licence: LGPL (+ Proprietary) #*/ 13 | /*# #*/ 14 | /*############################################################################*/ 15 | 16 | 17 | #include "AmbisonicZoomer.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | CAmbisonicZoomer::CAmbisonicZoomer() 25 | { 26 | m_fZoom = 0; 27 | } 28 | 29 | bool CAmbisonicZoomer::Configure(unsigned nOrder, bool b3D, unsigned nBlockSize, unsigned nMisc) 30 | { 31 | bool success = CAmbisonicBase::Configure(nOrder, b3D, nMisc); 32 | if(!success) 33 | return false; 34 | 35 | m_AmbDecoderFront.Configure(m_nOrder, 1, nBlockSize, kAmblib_Mono, 1); 36 | 37 | //Calculate all the speaker coefficients 38 | m_AmbDecoderFront.Refresh(); 39 | 40 | m_fZoomRed = 0.f; 41 | 42 | m_AmbEncoderFront.reset(new float[m_nChannelCount]); 43 | m_AmbEncoderFront_weighted.reset(new float[m_nChannelCount]); 44 | a_m.reset(new float[m_nOrder + 1]); 45 | 46 | // These weights a_m are applied to the channels of a corresponding order within the Ambisonics signals. 47 | // When applied to the encoded channels and decoded to a particular loudspeaker direction they will create a 48 | // virtual microphone pattern with no rear lobes. 49 | // When used for decoding this is known as in-phase decoding. 50 | for(unsigned iOrder = 0; iOrder < m_nOrder + 1; iOrder++) 51 | a_m[iOrder] = (2*iOrder+1)*factorial(m_nOrder)*factorial(m_nOrder+1) / (factorial(m_nOrder+iOrder+1)*factorial(m_nOrder-iOrder)); 52 | 53 | unsigned iDegree=0; 54 | for(unsigned iChannel = 0; iChannelm_ppfChannels[iChannel][niSample]; 98 | } 99 | for(unsigned iChannel=0; iChannel1e-6) 102 | { 103 | // Blend original channel with the virtual microphone pointed directly to the front 104 | // Only do this for Ambisonics components that aren't zero for an encoded frontal source 105 | pBFSrcDst->m_ppfChannels[iChannel][niSample] = (m_fZoomBlend * pBFSrcDst->m_ppfChannels[iChannel][niSample] 106 | + m_AmbEncoderFront[iChannel]*m_fZoom*fMic) / (m_fZoomBlend + std::fabs(m_fZoom)*m_AmbFrontMic); 107 | } 108 | else{ 109 | // reduce the level of the Ambisonic components that are zero for a frontal source 110 | pBFSrcDst->m_ppfChannels[iChannel][niSample] = pBFSrcDst->m_ppfChannels[iChannel][niSample] * m_fZoomRed; 111 | } 112 | } 113 | } 114 | } 115 | 116 | float CAmbisonicZoomer::factorial(unsigned M) 117 | { 118 | unsigned ret = 1; 119 | for(unsigned int i = 1; i <= M; ++i) 120 | ret *= i; 121 | return (float)ret; 122 | } 123 | -------------------------------------------------------------------------------- /source/BFormat.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CBFormat - Ambisonic BFormat #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: BFormat.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include "BFormat.h" 17 | 18 | CBFormat::CBFormat() 19 | { 20 | m_nSamples = 0; 21 | m_nDataLength = 0; 22 | } 23 | 24 | unsigned CBFormat::GetSampleCount() 25 | { 26 | return m_nSamples; 27 | } 28 | 29 | bool CBFormat::Configure(unsigned nOrder, bool b3D, unsigned nSampleCount) 30 | { 31 | bool success = CAmbisonicBase::Configure(nOrder, b3D, nSampleCount); 32 | if(!success) 33 | return false; 34 | 35 | m_nSamples = nSampleCount; 36 | m_nDataLength = m_nSamples * m_nChannelCount; 37 | 38 | m_pfData.resize(m_nDataLength); 39 | memset(m_pfData.data(), 0, m_nDataLength * sizeof(float)); 40 | m_ppfChannels.reset(new float*[m_nChannelCount]); 41 | 42 | for(unsigned niChannel = 0; niChannel < m_nChannelCount; niChannel++) 43 | { 44 | m_ppfChannels[niChannel] = &m_pfData[niChannel * m_nSamples]; 45 | } 46 | 47 | return true; 48 | } 49 | 50 | void CBFormat::Reset() 51 | { 52 | memset(m_pfData.data(), 0, m_nDataLength * sizeof(float)); 53 | } 54 | 55 | void CBFormat::Refresh() 56 | { 57 | 58 | } 59 | 60 | void CBFormat::InsertStream(float* pfData, unsigned nChannel, unsigned nSamples) 61 | { 62 | memcpy(m_ppfChannels[nChannel], pfData, nSamples * sizeof(float)); 63 | } 64 | 65 | void CBFormat::AddStream(float* pfData, unsigned nChannel, unsigned nSamples, unsigned nOffset) 66 | { 67 | unsigned niSample = 0; 68 | 69 | for (niSample = 0; niSample < nSamples; niSample++) 70 | { 71 | m_ppfChannels[nChannel][niSample + nOffset] += pfData[niSample]; 72 | } 73 | } 74 | 75 | void CBFormat::ExtractStream(float* pfData, unsigned nChannel, unsigned nSamples) 76 | { 77 | memcpy(pfData, m_ppfChannels[nChannel], nSamples * sizeof(float)); 78 | } 79 | 80 | void CBFormat::operator = (const CBFormat &bf) 81 | { 82 | memcpy(m_pfData.data(), bf.m_pfData.data(), m_nDataLength * sizeof(float)); 83 | } 84 | 85 | bool CBFormat::operator == (const CBFormat &bf) 86 | { 87 | if(m_b3D == bf.m_b3D && m_nOrder == bf.m_nOrder && m_nDataLength == bf.m_nDataLength) 88 | return true; 89 | else 90 | return false; 91 | } 92 | 93 | bool CBFormat::operator != (const CBFormat &bf) 94 | { 95 | if(m_b3D != bf.m_b3D || m_nOrder != bf.m_nOrder || m_nDataLength != bf.m_nDataLength) 96 | return true; 97 | else 98 | return false; 99 | } 100 | 101 | CBFormat& CBFormat::operator += (const CBFormat &bf) 102 | { 103 | unsigned niChannel = 0; 104 | unsigned niSample = 0; 105 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 106 | { 107 | for(niSample = 0; niSample < m_nSamples; niSample++) 108 | { 109 | m_ppfChannels[niChannel][niSample] += bf.m_ppfChannels[niChannel][niSample]; 110 | } 111 | } 112 | 113 | return *this; 114 | } 115 | 116 | CBFormat& CBFormat::operator -= (const CBFormat &bf) 117 | { 118 | unsigned niChannel = 0; 119 | unsigned niSample = 0; 120 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 121 | { 122 | for(niSample = 0; niSample < m_nSamples; niSample++) 123 | { 124 | m_ppfChannels[niChannel][niSample] -= bf.m_ppfChannels[niChannel][niSample]; 125 | } 126 | } 127 | 128 | return *this; 129 | } 130 | 131 | CBFormat& CBFormat::operator *= (const CBFormat &bf) 132 | { 133 | unsigned niChannel = 0; 134 | unsigned niSample = 0; 135 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 136 | { 137 | for(niSample = 0; niSample < m_nSamples; niSample++) 138 | { 139 | m_ppfChannels[niChannel][niSample] *= bf.m_ppfChannels[niChannel][niSample]; 140 | } 141 | } 142 | 143 | return *this; 144 | } 145 | 146 | CBFormat& CBFormat::operator /= (const CBFormat &bf) 147 | { 148 | unsigned niChannel = 0; 149 | unsigned niSample = 0; 150 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 151 | { 152 | for(niSample = 0; niSample < m_nSamples; niSample++) 153 | { 154 | m_ppfChannels[niChannel][niSample] /= bf.m_ppfChannels[niChannel][niSample]; 155 | } 156 | } 157 | 158 | return *this; 159 | } 160 | 161 | CBFormat& CBFormat::operator += (const float &fValue) 162 | { 163 | unsigned niChannel = 0; 164 | unsigned niSample = 0; 165 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 166 | { 167 | for(niSample = 0; niSample < m_nSamples; niSample++) 168 | { 169 | m_ppfChannels[niChannel][niSample] += fValue; 170 | } 171 | } 172 | 173 | return *this; 174 | } 175 | 176 | CBFormat& CBFormat::operator -= (const float &fValue) 177 | { 178 | unsigned niChannel = 0; 179 | unsigned niSample = 0; 180 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 181 | { 182 | for(niSample = 0; niSample < m_nSamples; niSample++) 183 | { 184 | m_ppfChannels[niChannel][niSample] -= fValue; 185 | } 186 | } 187 | 188 | return *this; 189 | } 190 | 191 | CBFormat& CBFormat::operator *= (const float &fValue) 192 | { 193 | unsigned niChannel = 0; 194 | unsigned niSample = 0; 195 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 196 | { 197 | for(niSample = 0; niSample < m_nSamples; niSample++) 198 | { 199 | m_ppfChannels[niChannel][niSample] *= fValue; 200 | } 201 | } 202 | 203 | return *this; 204 | } 205 | 206 | CBFormat& CBFormat::operator /= (const float &fValue) 207 | { 208 | unsigned niChannel = 0; 209 | unsigned niSample = 0; 210 | for(niChannel = 0; niChannel < m_nChannelCount; niChannel++) 211 | { 212 | for(niSample = 0; niSample < m_nSamples; niSample++) 213 | { 214 | m_ppfChannels[niChannel][niSample] /= fValue; 215 | } 216 | } 217 | 218 | return *this; 219 | } 220 | -------------------------------------------------------------------------------- /source/GainInterp.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Apply a vector of gains to a mono input with interpolation #*/ 4 | /*# #*/ 5 | /*# Filename: GainInterp.cpp #*/ 6 | /*# Version: 0.1 #*/ 7 | /*# Date: 30/10/2020 #*/ 8 | /*# Author(s): Peter Stitt #*/ 9 | /*# Licence: LGPL + proprietary #*/ 10 | /*# #*/ 11 | /*############################################################################*/ 12 | 13 | #include "GainInterp.h" 14 | 15 | CGainInterp::CGainInterp() 16 | { 17 | } 18 | 19 | CGainInterp::~CGainInterp() 20 | { 21 | } 22 | 23 | void CGainInterp::SetGainVector(std::vector newGainVec, unsigned int interpTimeInSamples) 24 | { 25 | if (m_targetGainVec != newGainVec) 26 | { 27 | if (!m_isFirstCall) 28 | { 29 | m_gainVec = m_targetGainVec; 30 | m_targetGainVec = newGainVec; 31 | m_interpDurInSamples = interpTimeInSamples; 32 | // Reset the interpolation counter to start interpolation 33 | m_iInterpCount = 0; 34 | } 35 | else if (m_isFirstCall) 36 | { 37 | m_gainVec = newGainVec; 38 | m_targetGainVec = newGainVec; 39 | m_interpDurInSamples = 0; 40 | m_iInterpCount = 0; 41 | } 42 | } 43 | } 44 | 45 | void CGainInterp::ProcessAccumul(float* pIn, std::vector>& ppOut, unsigned int nSamples, unsigned int nOffset) 46 | { 47 | unsigned int nCh = (unsigned int)m_targetGainVec.size(); 48 | // The number of samples to interpolate over in this block 49 | unsigned int nInterpSamples = std::max(std::min(m_interpDurInSamples - m_iInterpCount, nSamples), (unsigned int)0); 50 | 51 | float deltaCoeff = 1.f / m_interpDurInSamples; 52 | 53 | if (nInterpSamples > 0) 54 | { 55 | for (unsigned int i = 0; i < nInterpSamples; ++i) 56 | { 57 | float fInterp = (float)(i + m_iInterpCount) * deltaCoeff; 58 | float fOneMinusInterp = 1.f - fInterp; 59 | for (unsigned int iCh = 0; iCh < nCh; ++iCh) 60 | ppOut[iCh][i + nOffset] += pIn[i] * (fOneMinusInterp * (float)m_gainVec[iCh] + fInterp * (float)m_targetGainVec[iCh]); 61 | } 62 | 63 | m_iInterpCount += nInterpSamples; 64 | // If interpolation ends then set the m_gainVec to equal the target 65 | if (m_iInterpCount >= m_interpDurInSamples) 66 | m_gainVec = m_targetGainVec; 67 | } 68 | 69 | for (unsigned int iCh = 0; iCh < nCh; ++iCh) 70 | for (unsigned int i = nInterpSamples; i < nSamples; ++i) 71 | ppOut[iCh][i + nOffset] += pIn[i] * (float)m_gainVec[iCh]; 72 | 73 | m_isFirstCall = false; 74 | } 75 | 76 | void CGainInterp::Reset() 77 | { 78 | m_iInterpCount = m_interpDurInSamples; 79 | m_gainVec = m_targetGainVec; 80 | m_isFirstCall = true; 81 | } 82 | -------------------------------------------------------------------------------- /source/Screen.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Screen scaling and screen edge lock handling #*/ 4 | /*# #*/ 5 | /*# #*/ 6 | /*# Filename: Screen.cpp #*/ 7 | /*# Version: 0.1 #*/ 8 | /*# Date: 30/10/2020 #*/ 9 | /*# Author(s): Peter Stitt #*/ 10 | /*# Licence: LGPL + proprietary #*/ 11 | /*# #*/ 12 | /*############################################################################*/ 13 | 14 | #include "Screen.h" 15 | 16 | CScreenScaleHandler::CScreenScaleHandler(std::vector reproductionScreen, Layout layout) : m_layout(layout) 17 | { 18 | if (reproductionScreen.size() > 0) 19 | { 20 | m_repScreen = reproductionScreen[0]; 21 | m_repPolarEdges.fromScreen(m_repScreen); 22 | m_repScreenSet = true; 23 | } 24 | } 25 | 26 | CScreenScaleHandler::~CScreenScaleHandler() 27 | { 28 | 29 | } 30 | 31 | CartesianPosition CScreenScaleHandler::handle(CartesianPosition position, bool screenRef, std::vector referenceScreen, bool cartesian) 32 | { 33 | if (screenRef && m_repScreenSet) 34 | { 35 | if (referenceScreen.size() > 0) 36 | m_refScreen = referenceScreen[0]; 37 | else 38 | m_refScreen = Screen(); // default screen 39 | m_refPolarEdges.fromScreen(m_refScreen); 40 | 41 | if (cartesian) 42 | { 43 | auto polarPosition = admrender::PointCartToPolar(position); 44 | auto AzEl_s = ScaleAzEl(polarPosition.azimuth, polarPosition.elevation); 45 | auto AzEl_sc = CompensatePosition(AzEl_s.first, AzEl_s.second, m_layout); 46 | return admrender::PointPolarToCart({ AzEl_sc.first,AzEl_sc.second,polarPosition.distance }); 47 | } 48 | else 49 | return ScalePosition(position); 50 | } 51 | else 52 | return position; // return unmodified 53 | } 54 | 55 | CartesianPosition CScreenScaleHandler::ScalePosition(CartesianPosition position) 56 | { 57 | PolarPosition polarPosition = CartesianToPolar(position); 58 | auto AzEl_s = ScaleAzEl(polarPosition.azimuth, polarPosition.elevation); 59 | return PolarToCartesian(PolarPosition{ AzEl_s.first,AzEl_s.second,polarPosition.distance }); 60 | } 61 | 62 | std::pair CScreenScaleHandler::ScaleAzEl(double az, double el) 63 | { 64 | double azScaled = interp(az, { -180., m_refPolarEdges.rightAzimuth,m_refPolarEdges.leftAzimuth, 180. }, 65 | { -180., m_repPolarEdges.rightAzimuth,m_repPolarEdges.leftAzimuth, 180. }); 66 | double elScaled = interp(el, { -90., m_refPolarEdges.bottomElevation,m_refPolarEdges.topElevation, 90. }, 67 | { -90., m_repPolarEdges.bottomElevation,m_repPolarEdges.topElevation, 90. }); 68 | 69 | return { azScaled,elScaled }; 70 | } 71 | 72 | 73 | // CScreenEdgeLock ========================================================================================================== 74 | CScreenEdgeLock::CScreenEdgeLock(std::vector reproductionScreen, Layout layout) : m_layout(layout) 75 | { 76 | if (reproductionScreen.size() > 0) 77 | { 78 | m_reproductionScreen = reproductionScreen[0]; 79 | m_repPolarEdges.fromScreen(m_reproductionScreen); 80 | m_repScreenSet = true; 81 | } 82 | } 83 | 84 | CScreenEdgeLock::~CScreenEdgeLock() 85 | { 86 | 87 | } 88 | 89 | CartesianPosition CScreenEdgeLock::HandleVector(CartesianPosition position, admrender::ScreenEdgeLock screenEdgeLock, bool cartesian) 90 | { 91 | if (m_repScreenSet) 92 | { 93 | if (cartesian) 94 | { 95 | auto polarPosition = admrender::PointCartToPolar(position); 96 | auto AzEl_s = HandleAzEl(polarPosition.azimuth, polarPosition.elevation, screenEdgeLock); 97 | auto AzEl_sc = CompensatePosition(AzEl_s.first, AzEl_s.second, m_layout); 98 | return admrender::PointPolarToCart({ AzEl_sc.first,AzEl_sc.second,polarPosition.distance }); 99 | } 100 | else 101 | { 102 | PolarPosition polarPosition = CartesianToPolar(position); 103 | auto AzEl_s = HandleAzEl(polarPosition.azimuth, polarPosition.elevation, screenEdgeLock); 104 | return PolarToCartesian(PolarPosition{ AzEl_s.first,AzEl_s.second,polarPosition.distance }); 105 | } 106 | } 107 | else 108 | return position; 109 | } 110 | 111 | std::pair CScreenEdgeLock::HandleAzEl(double az, double el, admrender::ScreenEdgeLock screenEdgeLock) 112 | { 113 | if (m_repScreenSet) 114 | { 115 | if (screenEdgeLock.horizontal == admrender::ScreenEdgeLock::LEFT) 116 | az = m_repPolarEdges.leftAzimuth; 117 | if (screenEdgeLock.horizontal == admrender::ScreenEdgeLock::RIGHT) 118 | az = m_repPolarEdges.rightAzimuth; 119 | 120 | if (screenEdgeLock.vertical == admrender::ScreenEdgeLock::TOP) 121 | el = m_repPolarEdges.topElevation; 122 | if (screenEdgeLock.vertical == admrender::ScreenEdgeLock::BOTTOM) 123 | el = m_repPolarEdges.bottomElevation; 124 | } 125 | 126 | return { az, el }; 127 | } 128 | -------------------------------------------------------------------------------- /source/SpeakersBinauralizer.cpp: -------------------------------------------------------------------------------- 1 | /*############################################################################*/ 2 | /*# #*/ 3 | /*# Ambisonic C++ Library #*/ 4 | /*# CAmbisonicBinauralizer - Ambisonic Binauralizer #*/ 5 | /*# Copyright © 2007 Aristotel Digenis #*/ 6 | /*# #*/ 7 | /*# Filename: AmbisonicBinauralizer.cpp #*/ 8 | /*# Version: 0.1 #*/ 9 | /*# Date: 19/05/2007 #*/ 10 | /*# Author(s): Aristotel Digenis #*/ 11 | /*# Licence: MIT #*/ 12 | /*# #*/ 13 | /*############################################################################*/ 14 | 15 | 16 | #include "SpeakersBinauralizer.h" 17 | 18 | 19 | SpeakersBinauralizer::SpeakersBinauralizer() 20 | : m_nSpeakers(0) 21 | { 22 | } 23 | 24 | bool SpeakersBinauralizer::Configure(unsigned nSampleRate, 25 | unsigned nBlockSize, 26 | CAmbisonicSpeaker *speakers, 27 | unsigned nSpeakers, 28 | unsigned& tailLength, 29 | std::string HRTFPath) 30 | { 31 | //Iterators 32 | unsigned niEar = 0; 33 | unsigned niTap = 0; 34 | 35 | HRTF *p_hrtf = getHRTF(nSampleRate, HRTFPath); 36 | if (p_hrtf == nullptr) 37 | return false; 38 | 39 | m_nTaps = tailLength = p_hrtf->getHRTFLen(); 40 | m_nBlockSize = nBlockSize; 41 | 42 | //What will the overlap size be? 43 | m_nOverlapLength = m_nBlockSize < m_nTaps ? m_nBlockSize - 1 : m_nTaps - 1; 44 | //How large does the FFT need to be 45 | m_nFFTSize = 1; 46 | while(m_nFFTSize < (m_nBlockSize + m_nTaps + m_nOverlapLength)) 47 | m_nFFTSize <<= 1; 48 | //How many bins is that 49 | m_nFFTBins = m_nFFTSize / 2 + 1; 50 | //What do we need to scale the result of the iFFT by 51 | m_fFFTScaler = 1.f / m_nFFTSize; 52 | 53 | m_nSpeakers = nSpeakers; 54 | 55 | //Allocate buffers with new settings 56 | AllocateBuffers(); 57 | 58 | //Allocate temporary buffers for retrieving taps from mit_hrtf_lib 59 | float* pfHRTF[2]; 60 | for(niEar = 0; niEar < 2; niEar++) 61 | pfHRTF[niEar] = new float[m_nTaps]; 62 | 63 | //Allocate buffers for HRTF accumulators 64 | float** ppfAccumulator[2]; 65 | for(niEar = 0; niEar < 2; niEar++) 66 | { 67 | ppfAccumulator[niEar] = new float*[nSpeakers]; 68 | for (unsigned niChannel = 0; niChannel < nSpeakers; niChannel++) 69 | { 70 | ppfAccumulator[niEar][niChannel] = new float[m_nTaps]; 71 | memset(ppfAccumulator[niEar][niChannel], 0, m_nTaps * sizeof(float)); 72 | } 73 | } 74 | 75 | for(unsigned niChannel = 0; niChannel < nSpeakers; niChannel++) 76 | { 77 | PolarPoint position = speakers[niChannel].GetPosition(); 78 | 79 | bool b_found = p_hrtf->get(position.fAzimuth, position.fElevation, pfHRTF); 80 | if (!b_found) 81 | return false; 82 | 83 | //Accumulate channel/component HRTF 84 | for(niTap = 0; niTap < m_nTaps; niTap++) 85 | { 86 | ppfAccumulator[0][niChannel][niTap] += pfHRTF[0][niTap]; 87 | ppfAccumulator[1][niChannel][niTap] += pfHRTF[1][niTap]; 88 | } 89 | } 90 | delete p_hrtf; 91 | 92 | //Find the maximum tap 93 | float fMax = 0; 94 | for(niEar = 0; niEar < 2; niEar++) 95 | { 96 | for (unsigned niChannel = 0; niChannel < nSpeakers; niChannel++) 97 | { 98 | for(niTap = 0; niTap < m_nTaps; niTap++) 99 | { 100 | fMax = fabsf(ppfAccumulator[niEar][niChannel][niTap]) > fMax ? 101 | fabsf(ppfAccumulator[niEar][niChannel][niTap]) : fMax; 102 | } 103 | } 104 | } 105 | 106 | //Normalize to pre-defined value 107 | float fUpperSample = 1.f; 108 | float fScaler = fUpperSample / fMax; 109 | fScaler *= 0.35f; 110 | for(niEar = 0; niEar < 2; niEar++) 111 | { 112 | for (unsigned niChannel = 0; niChannel < nSpeakers; niChannel++) 113 | { 114 | for(niTap = 0; niTap < m_nTaps; niTap++) 115 | { 116 | ppfAccumulator[niEar][niChannel][niTap] *= fScaler; 117 | } 118 | } 119 | } 120 | 121 | //Convert frequency domain filters 122 | for(niEar = 0; niEar < 2; niEar++) 123 | { 124 | for (unsigned niChannel = 0; niChannel < nSpeakers; niChannel++) 125 | { 126 | memcpy(m_pfScratchBufferA.data(), ppfAccumulator[niEar][niChannel], m_nTaps * sizeof(float)); 127 | memset(&m_pfScratchBufferA[m_nTaps], 0, (m_nFFTSize - m_nTaps) * sizeof(float)); 128 | kiss_fftr(m_pFFT_cfg.get(), m_pfScratchBufferA.data(), m_ppcpFilters[niEar][niChannel].get()); 129 | } 130 | } 131 | 132 | for(niEar = 0; niEar < 2; niEar++) 133 | delete [] pfHRTF[niEar]; 134 | 135 | for(niEar = 0; niEar < 2; niEar++) 136 | { 137 | for(unsigned niChannel = 0; niChannel < nSpeakers; niChannel++) 138 | delete [] ppfAccumulator[niEar][niChannel]; 139 | delete [] ppfAccumulator[niEar]; 140 | } 141 | 142 | return true; 143 | } 144 | 145 | 146 | void SpeakersBinauralizer::Process(float** pBFSrc, float** ppfDst) 147 | { 148 | kiss_fft_cpx cpTemp; 149 | 150 | for (unsigned niEar = 0; niEar < 2; niEar++) 151 | { 152 | memset(m_pfScratchBufferA.data(), 0, m_nFFTSize * sizeof(float)); 153 | for (unsigned niChannel = 0; niChannel < m_nSpeakers; niChannel++) 154 | { 155 | memcpy(m_pfScratchBufferB.data(), pBFSrc[niChannel], m_nBlockSize * sizeof(float)); 156 | memset(&m_pfScratchBufferB[m_nBlockSize], 0, (m_nFFTSize - m_nBlockSize) * sizeof(float)); 157 | kiss_fftr(m_pFFT_cfg.get(), m_pfScratchBufferB.data(), m_pcpScratch.get()); 158 | for(unsigned ni = 0; ni < m_nFFTBins; ni++) 159 | { 160 | cpTemp.r = m_pcpScratch[ni].r * m_ppcpFilters[niEar][niChannel][ni].r 161 | - m_pcpScratch[ni].i * m_ppcpFilters[niEar][niChannel][ni].i; 162 | cpTemp.i = m_pcpScratch[ni].r * m_ppcpFilters[niEar][niChannel][ni].i 163 | + m_pcpScratch[ni].i * m_ppcpFilters[niEar][niChannel][ni].r; 164 | m_pcpScratch[ni] = cpTemp; 165 | } 166 | kiss_fftri(m_pIFFT_cfg.get(), m_pcpScratch.get(), m_pfScratchBufferB.data()); 167 | for (unsigned ni = 0; ni < m_nFFTSize; ni++) 168 | m_pfScratchBufferA[ni] += m_pfScratchBufferB[ni]; 169 | } 170 | 171 | for (unsigned ni = 0; ni < m_nFFTSize; ni++) 172 | m_pfScratchBufferA[ni] *= m_fFFTScaler; 173 | memcpy(ppfDst[niEar], m_pfScratchBufferA.data(), m_nBlockSize * sizeof(float)); 174 | for (unsigned ni = 0; ni < m_nOverlapLength; ni++) 175 | ppfDst[niEar][ni] += m_pfOverlap[niEar][ni]; 176 | memcpy(m_pfOverlap[niEar].data(), &m_pfScratchBufferA[m_nBlockSize], m_nOverlapLength * sizeof(float)); 177 | } 178 | } 179 | 180 | 181 | void SpeakersBinauralizer::AllocateBuffers() 182 | { 183 | CAmbisonicBinauralizer::AllocateBuffers(); 184 | 185 | //Allocate the FFTBins for each channel, for each ear 186 | for(unsigned niEar = 0; niEar < 2; niEar++) 187 | { 188 | m_ppcpFilters[niEar].resize(m_nSpeakers); 189 | for(unsigned niChannel = 0; niChannel < m_nSpeakers; niChannel++) 190 | m_ppcpFilters[niEar][niChannel].reset(new kiss_fft_cpx[m_nFFTBins]); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /source/hrtf/mit_hrtf.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | #ifdef HAVE_MIT_HRTF 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | MIT_HRTF::MIT_HRTF(unsigned i_sampleRate) 13 | : HRTF(i_sampleRate) 14 | { 15 | i_len = mit_hrtf_availability(0, 0, i_sampleRate); 16 | } 17 | 18 | 19 | bool MIT_HRTF::get(float f_azimuth, float f_elevation, float** pfHRTF) 20 | { 21 | int nAzimuth = (int)RadiansToDegrees(-f_azimuth); 22 | if(nAzimuth > 180) 23 | nAzimuth -= 360; 24 | int nElevation = (int)RadiansToDegrees(f_elevation); 25 | //Get HRTFs for given position 26 | std::vector psHRTF[2] = {std::vector(i_len), std::vector(i_len)}; 27 | unsigned ret = mit_hrtf_get(&nAzimuth, &nElevation, i_sampleRate, psHRTF[0].data(), psHRTF[1].data()); 28 | if (ret == 0) 29 | return false; 30 | 31 | //Convert from short to float representation 32 | for (unsigned t = 0; t < i_len; t++) 33 | { 34 | pfHRTF[0][t] = psHRTF[0][t] / 32767.f; 35 | pfHRTF[1][t] = psHRTF[1][t] / 32767.f; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /source/hrtf/sofa_hrtf.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | 6 | #ifdef HAVE_MYSOFA 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | SOFA_HRTF::SOFA_HRTF(std::string path, unsigned i_sampleRate) 14 | : HRTF(i_sampleRate), hrtf(nullptr) 15 | { 16 | int err; 17 | 18 | hrtf = mysofa_open(path.c_str(), i_sampleRate, &i_internalLength, &err); 19 | if (hrtf == nullptr) 20 | { 21 | std::cout << "Could not load the SOFA HRTF." << std::endl; 22 | return; 23 | } 24 | 25 | i_filterExtraLength = i_internalLength / 2; 26 | i_len = i_internalLength + i_filterExtraLength; 27 | } 28 | 29 | 30 | SOFA_HRTF::~SOFA_HRTF() 31 | { 32 | if (hrtf != nullptr) 33 | mysofa_close(hrtf); 34 | } 35 | 36 | 37 | bool SOFA_HRTF::get(float f_azimuth, float f_elevation, float** pfHRTF) 38 | { 39 | float delaysSec[2]; // unit is second. 40 | unsigned delaysSamples[2]; // unit is samples. 41 | std::vector pfHRTFNotDelayed[2]; 42 | pfHRTFNotDelayed[0].resize( i_internalLength, 0.f ); 43 | pfHRTFNotDelayed[1].resize( i_internalLength, 0.f ); 44 | 45 | float p[3] = {RadiansToDegrees(f_azimuth), RadiansToDegrees(f_elevation), 1.f}; 46 | mysofa_s2c(p); 47 | 48 | mysofa_getfilter_float(hrtf, p[0], p[1], p[2], 49 | pfHRTFNotDelayed[0].data(), pfHRTFNotDelayed[1].data(), &delaysSec[0], &delaysSec[1]); 50 | delaysSamples[0] = std::roundf(delaysSec[0] * i_sampleRate); 51 | delaysSamples[1] = std::roundf(delaysSec[1] * i_sampleRate); 52 | 53 | if (delaysSamples[0] > i_filterExtraLength 54 | || delaysSamples[1] > i_filterExtraLength) 55 | { 56 | std::cout << "Too big HRTF delay for the buffer length." << std::endl; 57 | return false; 58 | } 59 | 60 | std::fill(pfHRTF[0], pfHRTF[0] + i_len, 0); 61 | std::fill(pfHRTF[1], pfHRTF[1] + i_len, 0); 62 | 63 | std::copy(pfHRTFNotDelayed[0].begin(), pfHRTFNotDelayed[0].end(), pfHRTF[0] + delaysSamples[0]); 64 | std::copy(pfHRTFNotDelayed[1].begin(), pfHRTFNotDelayed[1].end(), pfHRTF[1] + delaysSamples[1]); 65 | 66 | return true; 67 | } 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /source/kiss_fft/_kiss_fft_guts.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2010, Mark Borgerding 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | */ 14 | 15 | /* kiss_fft.h 16 | defines kiss_fft_scalar as either short or a float type 17 | and defines 18 | typedef struct { kiss_fft_scalar r; kiss_fft_scalar i; }kiss_fft_cpx; */ 19 | #include "kiss_fft.h" 20 | #include 21 | 22 | #define MAXFACTORS 32 23 | /* e.g. an fft of length 128 has 4 factors 24 | as far as kissfft is concerned 25 | 4*4*4*2 26 | */ 27 | 28 | struct kiss_fft_state{ 29 | int nfft; 30 | int inverse; 31 | int factors[2*MAXFACTORS]; 32 | kiss_fft_cpx twiddles[1]; 33 | }; 34 | 35 | /* 36 | Explanation of macros dealing with complex math: 37 | 38 | C_MUL(m,a,b) : m = a*b 39 | C_FIXDIV( c , div ) : if a fixed point impl., c /= div. noop otherwise 40 | C_SUB( res, a,b) : res = a - b 41 | C_SUBFROM( res , a) : res -= a 42 | C_ADDTO( res , a) : res += a 43 | * */ 44 | #ifdef FIXED_POINT 45 | #if (FIXED_POINT==32) 46 | # define FRACBITS 31 47 | # define SAMPPROD int64_t 48 | #define SAMP_MAX 2147483647 49 | #else 50 | # define FRACBITS 15 51 | # define SAMPPROD int32_t 52 | #define SAMP_MAX 32767 53 | #endif 54 | 55 | #define SAMP_MIN -SAMP_MAX 56 | 57 | #if defined(CHECK_OVERFLOW) 58 | # define CHECK_OVERFLOW_OP(a,op,b) \ 59 | if ( (SAMPPROD)(a) op (SAMPPROD)(b) > SAMP_MAX || (SAMPPROD)(a) op (SAMPPROD)(b) < SAMP_MIN ) { \ 60 | fprintf(stderr,"WARNING:overflow @ " __FILE__ "(%d): (%d " #op" %d) = %ld\n",__LINE__,(a),(b),(SAMPPROD)(a) op (SAMPPROD)(b) ); } 61 | #endif 62 | 63 | 64 | # define smul(a,b) ( (SAMPPROD)(a)*(b) ) 65 | # define sround( x ) (kiss_fft_scalar)( ( (x) + (1<<(FRACBITS-1)) ) >> FRACBITS ) 66 | 67 | # define S_MUL(a,b) sround( smul(a,b) ) 68 | 69 | # define C_MUL(m,a,b) \ 70 | do{ (m).r = sround( smul((a).r,(b).r) - smul((a).i,(b).i) ); \ 71 | (m).i = sround( smul((a).r,(b).i) + smul((a).i,(b).r) ); }while(0) 72 | 73 | # define DIVSCALAR(x,k) \ 74 | (x) = sround( smul( x, SAMP_MAX/k ) ) 75 | 76 | # define C_FIXDIV(c,div) \ 77 | do { DIVSCALAR( (c).r , div); \ 78 | DIVSCALAR( (c).i , div); }while (0) 79 | 80 | # define C_MULBYSCALAR( c, s ) \ 81 | do{ (c).r = sround( smul( (c).r , s ) ) ;\ 82 | (c).i = sround( smul( (c).i , s ) ) ; }while(0) 83 | 84 | #else /* not FIXED_POINT*/ 85 | 86 | # define S_MUL(a,b) ( (a)*(b) ) 87 | #define C_MUL(m,a,b) \ 88 | do{ (m).r = (a).r*(b).r - (a).i*(b).i;\ 89 | (m).i = (a).r*(b).i + (a).i*(b).r; }while(0) 90 | # define C_FIXDIV(c,div) /* NOOP */ 91 | # define C_MULBYSCALAR( c, s ) \ 92 | do{ (c).r *= (s);\ 93 | (c).i *= (s); }while(0) 94 | #endif 95 | 96 | #ifndef CHECK_OVERFLOW_OP 97 | # define CHECK_OVERFLOW_OP(a,op,b) /* noop */ 98 | #endif 99 | 100 | #define C_ADD( res, a,b)\ 101 | do { \ 102 | CHECK_OVERFLOW_OP((a).r,+,(b).r)\ 103 | CHECK_OVERFLOW_OP((a).i,+,(b).i)\ 104 | (res).r=(a).r+(b).r; (res).i=(a).i+(b).i; \ 105 | }while(0) 106 | #define C_SUB( res, a,b)\ 107 | do { \ 108 | CHECK_OVERFLOW_OP((a).r,-,(b).r)\ 109 | CHECK_OVERFLOW_OP((a).i,-,(b).i)\ 110 | (res).r=(a).r-(b).r; (res).i=(a).i-(b).i; \ 111 | }while(0) 112 | #define C_ADDTO( res , a)\ 113 | do { \ 114 | CHECK_OVERFLOW_OP((res).r,+,(a).r)\ 115 | CHECK_OVERFLOW_OP((res).i,+,(a).i)\ 116 | (res).r += (a).r; (res).i += (a).i;\ 117 | }while(0) 118 | 119 | #define C_SUBFROM( res , a)\ 120 | do {\ 121 | CHECK_OVERFLOW_OP((res).r,-,(a).r)\ 122 | CHECK_OVERFLOW_OP((res).i,-,(a).i)\ 123 | (res).r -= (a).r; (res).i -= (a).i; \ 124 | }while(0) 125 | 126 | 127 | #ifdef FIXED_POINT 128 | # define KISS_FFT_COS(phase) floor(.5+SAMP_MAX * cos (phase)) 129 | # define KISS_FFT_SIN(phase) floor(.5+SAMP_MAX * sin (phase)) 130 | # define HALF_OF(x) ((x)>>1) 131 | #elif defined(USE_SIMD) 132 | # define KISS_FFT_COS(phase) _mm_set1_ps( cos(phase) ) 133 | # define KISS_FFT_SIN(phase) _mm_set1_ps( sin(phase) ) 134 | # define HALF_OF(x) ((x)*_mm_set1_ps(.5)) 135 | #else 136 | # define KISS_FFT_COS(phase) (kiss_fft_scalar) cos(phase) 137 | # define KISS_FFT_SIN(phase) (kiss_fft_scalar) sin(phase) 138 | # define HALF_OF(x) ((x)*.5) 139 | #endif 140 | 141 | #define kf_cexp(x,phase) \ 142 | do{ \ 143 | (x)->r = KISS_FFT_COS(phase);\ 144 | (x)->i = KISS_FFT_SIN(phase);\ 145 | }while(0) 146 | 147 | 148 | /* a debugging function */ 149 | #define pcpx(c)\ 150 | fprintf(stderr,"%g + %gi\n",(double)((c)->r),(double)((c)->i) ) 151 | 152 | 153 | #ifdef KISS_FFT_USE_ALLOCA 154 | // define this to allow use of alloca instead of malloc for temporary buffers 155 | // Temporary buffers are used in two case: 156 | // 1. FFT sizes that have "bad" factors. i.e. not 2,3 and 5 157 | // 2. "in-place" FFTs. Notice the quotes, since kissfft does not really do an in-place transform. 158 | #include 159 | #define KISS_FFT_TMP_ALLOC(nbytes) alloca(nbytes) 160 | #define KISS_FFT_TMP_FREE(ptr) 161 | #else 162 | #define KISS_FFT_TMP_ALLOC(nbytes) KISS_FFT_MALLOC(nbytes) 163 | #define KISS_FFT_TMP_FREE(ptr) KISS_FFT_FREE(ptr) 164 | #endif 165 | -------------------------------------------------------------------------------- /source/kiss_fft/kiss_fft.h: -------------------------------------------------------------------------------- 1 | #ifndef KISS_FFT_H 2 | #define KISS_FFT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | /* 14 | ATTENTION! 15 | If you would like a : 16 | -- a utility that will handle the caching of fft objects 17 | -- real-only (no imaginary time component ) FFT 18 | -- a multi-dimensional FFT 19 | -- a command-line utility to perform ffts 20 | -- a command-line utility to perform fast-convolution filtering 21 | 22 | Then see kfc.h kiss_fftr.h kiss_fftnd.h fftutil.c kiss_fastfir.c 23 | in the tools/ directory. 24 | */ 25 | 26 | #ifdef USE_SIMD 27 | # include 28 | # define kiss_fft_scalar __m128 29 | #define KISS_FFT_MALLOC(nbytes) _mm_malloc(nbytes,16) 30 | #define KISS_FFT_FREE _mm_free 31 | #else 32 | #define KISS_FFT_MALLOC malloc 33 | #define KISS_FFT_FREE free 34 | #endif 35 | 36 | 37 | #ifdef FIXED_POINT 38 | #include 39 | # if (FIXED_POINT == 32) 40 | # define kiss_fft_scalar int32_t 41 | # else 42 | # define kiss_fft_scalar int16_t 43 | # endif 44 | #else 45 | # ifndef kiss_fft_scalar 46 | /* default is float */ 47 | # define kiss_fft_scalar float 48 | # endif 49 | #endif 50 | 51 | typedef struct { 52 | kiss_fft_scalar r; 53 | kiss_fft_scalar i; 54 | }kiss_fft_cpx; 55 | 56 | typedef struct kiss_fft_state* kiss_fft_cfg; 57 | 58 | /* 59 | * kiss_fft_alloc 60 | * 61 | * Initialize a FFT (or IFFT) algorithm's cfg/state buffer. 62 | * 63 | * typical usage: kiss_fft_cfg mycfg=kiss_fft_alloc(1024,0,NULL,NULL); 64 | * 65 | * The return value from fft_alloc is a cfg buffer used internally 66 | * by the fft routine or NULL. 67 | * 68 | * If lenmem is NULL, then kiss_fft_alloc will allocate a cfg buffer using malloc. 69 | * The returned value should be free()d when done to avoid memory leaks. 70 | * 71 | * The state can be placed in a user supplied buffer 'mem': 72 | * If lenmem is not NULL and mem is not NULL and *lenmem is large enough, 73 | * then the function places the cfg in mem and the size used in *lenmem 74 | * and returns mem. 75 | * 76 | * If lenmem is not NULL and ( mem is NULL or *lenmem is not large enough), 77 | * then the function returns NULL and places the minimum cfg 78 | * buffer size in *lenmem. 79 | * */ 80 | 81 | kiss_fft_cfg kiss_fft_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem); 82 | 83 | /* 84 | * kiss_fft(cfg,in_out_buf) 85 | * 86 | * Perform an FFT on a complex input buffer. 87 | * for a forward FFT, 88 | * fin should be f[0] , f[1] , ... ,f[nfft-1] 89 | * fout will be F[0] , F[1] , ... ,F[nfft-1] 90 | * Note that each element is complex and can be accessed like 91 | f[k].r and f[k].i 92 | * */ 93 | void kiss_fft(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout); 94 | 95 | /* 96 | A more generic version of the above function. It reads its input from every Nth sample. 97 | * */ 98 | void kiss_fft_stride(kiss_fft_cfg cfg,const kiss_fft_cpx *fin,kiss_fft_cpx *fout,int fin_stride); 99 | 100 | /* If kiss_fft_alloc allocated a buffer, it is one contiguous 101 | buffer and can be simply free()d when no longer needed*/ 102 | #define kiss_fft_free free 103 | 104 | /* 105 | Cleans up some memory that gets managed internally. Not necessary to call, but it might clean up 106 | your compiler output to call this before you exit. 107 | */ 108 | void kiss_fft_cleanup(void); 109 | 110 | 111 | /* 112 | * Returns the smallest integer k, such that k>=n and k has only "fast" factors (2,3,5) 113 | */ 114 | int kiss_fft_next_fast_size(int n); 115 | 116 | /* for real ffts, we need an even size */ 117 | #define kiss_fftr_next_fast_size_real(n) \ 118 | (kiss_fft_next_fast_size( ((n)+1)>>1)<<1) 119 | 120 | #ifdef __cplusplus 121 | } 122 | #endif 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /source/kiss_fft/kiss_fftr.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2003-2004, Mark Borgerding 3 | 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 10 | * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | */ 14 | 15 | #include "kiss_fftr.h" 16 | #include "_kiss_fft_guts.h" 17 | 18 | struct kiss_fftr_state{ 19 | kiss_fft_cfg substate; 20 | kiss_fft_cpx * tmpbuf; 21 | kiss_fft_cpx * super_twiddles; 22 | #ifdef USE_SIMD 23 | void * pad; 24 | #endif 25 | }; 26 | 27 | kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem,size_t * lenmem) 28 | { 29 | int i; 30 | kiss_fftr_cfg st = NULL; 31 | size_t subsize, memneeded; 32 | 33 | if (nfft & 1) { 34 | fprintf(stderr,"Real FFT optimization must be even.\n"); 35 | return NULL; 36 | } 37 | nfft >>= 1; 38 | 39 | kiss_fft_alloc (nfft, inverse_fft, NULL, &subsize); 40 | memneeded = sizeof(struct kiss_fftr_state) + subsize + sizeof(kiss_fft_cpx) * ( nfft * 3 / 2); 41 | 42 | if (lenmem == NULL) { 43 | st = (kiss_fftr_cfg) KISS_FFT_MALLOC (memneeded); 44 | } else { 45 | if (*lenmem >= memneeded) 46 | st = (kiss_fftr_cfg) mem; 47 | *lenmem = memneeded; 48 | } 49 | if (!st) 50 | return NULL; 51 | 52 | st->substate = (kiss_fft_cfg) (st + 1); /*just beyond kiss_fftr_state struct */ 53 | st->tmpbuf = (kiss_fft_cpx *) (((char *) st->substate) + subsize); 54 | st->super_twiddles = st->tmpbuf + nfft; 55 | kiss_fft_alloc(nfft, inverse_fft, st->substate, &subsize); 56 | 57 | for (i = 0; i < nfft/2; ++i) { 58 | double phase = 59 | -3.14159265358979323846264338327 * ((double) (i+1) / nfft + .5); 60 | if (inverse_fft) 61 | phase *= -1; 62 | kf_cexp (st->super_twiddles+i,phase); 63 | } 64 | return st; 65 | } 66 | 67 | void kiss_fftr(kiss_fftr_cfg st,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata) 68 | { 69 | /* input buffer timedata is stored row-wise */ 70 | int k,ncfft; 71 | kiss_fft_cpx fpnk,fpk,f1k,f2k,tw,tdc; 72 | 73 | if ( st->substate->inverse) { 74 | fprintf(stderr,"kiss fft usage error: improper alloc\n"); 75 | exit(1); 76 | } 77 | 78 | ncfft = st->substate->nfft; 79 | 80 | /*perform the parallel fft of two real signals packed in real,imag*/ 81 | kiss_fft( st->substate , (const kiss_fft_cpx*)timedata, st->tmpbuf ); 82 | /* The real part of the DC element of the frequency spectrum in st->tmpbuf 83 | * contains the sum of the even-numbered elements of the input time sequence 84 | * The imag part is the sum of the odd-numbered elements 85 | * 86 | * The sum of tdc.r and tdc.i is the sum of the input time sequence. 87 | * yielding DC of input time sequence 88 | * The difference of tdc.r - tdc.i is the sum of the input (dot product) [1,-1,1,-1... 89 | * yielding Nyquist bin of input time sequence 90 | */ 91 | 92 | tdc.r = st->tmpbuf[0].r; 93 | tdc.i = st->tmpbuf[0].i; 94 | C_FIXDIV(tdc,2); 95 | CHECK_OVERFLOW_OP(tdc.r ,+, tdc.i); 96 | CHECK_OVERFLOW_OP(tdc.r ,-, tdc.i); 97 | freqdata[0].r = tdc.r + tdc.i; 98 | freqdata[ncfft].r = tdc.r - tdc.i; 99 | #ifdef USE_SIMD 100 | freqdata[ncfft].i = freqdata[0].i = _mm_set1_ps(0); 101 | #else 102 | freqdata[ncfft].i = freqdata[0].i = 0; 103 | #endif 104 | 105 | for ( k=1;k <= ncfft/2 ; ++k ) { 106 | fpk = st->tmpbuf[k]; 107 | fpnk.r = st->tmpbuf[ncfft-k].r; 108 | fpnk.i = - st->tmpbuf[ncfft-k].i; 109 | C_FIXDIV(fpk,2); 110 | C_FIXDIV(fpnk,2); 111 | 112 | C_ADD( f1k, fpk , fpnk ); 113 | C_SUB( f2k, fpk , fpnk ); 114 | C_MUL( tw , f2k , st->super_twiddles[k-1]); 115 | 116 | freqdata[k].r = HALF_OF(f1k.r + tw.r); 117 | freqdata[k].i = HALF_OF(f1k.i + tw.i); 118 | freqdata[ncfft-k].r = HALF_OF(f1k.r - tw.r); 119 | freqdata[ncfft-k].i = HALF_OF(tw.i - f1k.i); 120 | } 121 | } 122 | 123 | void kiss_fftri(kiss_fftr_cfg st,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata) 124 | { 125 | /* input buffer timedata is stored row-wise */ 126 | int k, ncfft; 127 | 128 | if (st->substate->inverse == 0) { 129 | fprintf (stderr, "kiss fft usage error: improper alloc\n"); 130 | exit (1); 131 | } 132 | 133 | ncfft = st->substate->nfft; 134 | 135 | st->tmpbuf[0].r = freqdata[0].r + freqdata[ncfft].r; 136 | st->tmpbuf[0].i = freqdata[0].r - freqdata[ncfft].r; 137 | C_FIXDIV(st->tmpbuf[0],2); 138 | 139 | for (k = 1; k <= ncfft / 2; ++k) { 140 | kiss_fft_cpx fk, fnkc, fek, fok, tmp; 141 | fk = freqdata[k]; 142 | fnkc.r = freqdata[ncfft - k].r; 143 | fnkc.i = -freqdata[ncfft - k].i; 144 | C_FIXDIV( fk , 2 ); 145 | C_FIXDIV( fnkc , 2 ); 146 | 147 | C_ADD (fek, fk, fnkc); 148 | C_SUB (tmp, fk, fnkc); 149 | C_MUL (fok, tmp, st->super_twiddles[k-1]); 150 | C_ADD (st->tmpbuf[k], fek, fok); 151 | C_SUB (st->tmpbuf[ncfft - k], fek, fok); 152 | #ifdef USE_SIMD 153 | st->tmpbuf[ncfft - k].i *= _mm_set1_ps(-1.0); 154 | #else 155 | st->tmpbuf[ncfft - k].i *= -1; 156 | #endif 157 | } 158 | kiss_fft (st->substate, st->tmpbuf, (kiss_fft_cpx *) timedata); 159 | } 160 | -------------------------------------------------------------------------------- /source/kiss_fft/kiss_fftr.h: -------------------------------------------------------------------------------- 1 | #ifndef KISS_FTR_H 2 | #define KISS_FTR_H 3 | 4 | #include "kiss_fft.h" 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | 10 | /* 11 | 12 | Real optimized version can save about 45% cpu time vs. complex fft of a real seq. 13 | 14 | 15 | 16 | */ 17 | 18 | typedef struct kiss_fftr_state *kiss_fftr_cfg; 19 | 20 | 21 | kiss_fftr_cfg kiss_fftr_alloc(int nfft,int inverse_fft,void * mem, size_t * lenmem); 22 | /* 23 | nfft must be even 24 | 25 | If you don't care to allocate space, use mem = lenmem = NULL 26 | */ 27 | 28 | 29 | void kiss_fftr(kiss_fftr_cfg cfg,const kiss_fft_scalar *timedata,kiss_fft_cpx *freqdata); 30 | /* 31 | input timedata has nfft scalar points 32 | output freqdata has nfft/2+1 complex points 33 | */ 34 | 35 | void kiss_fftri(kiss_fftr_cfg cfg,const kiss_fft_cpx *freqdata,kiss_fft_scalar *timedata); 36 | /* 37 | input freqdata has nfft/2+1 complex points 38 | output timedata has nfft scalar points 39 | */ 40 | 41 | #define kiss_fftr_free free 42 | 43 | #ifdef __cplusplus 44 | } 45 | #endif 46 | #endif 47 | --------------------------------------------------------------------------------