├── example1 ├── click.wav └── example1.blend ├── example3 ├── plop.wav └── example3.blend ├── testbench └── RT60.blend ├── example2 ├── door │ └── door.wav ├── example2.blend ├── steps │ ├── step-01.wav │ ├── step-02.wav │ ├── step-03.wav │ ├── step-04.wav │ ├── step-05.wav │ ├── step-06.wav │ ├── step-07.wav │ ├── step-08.wav │ └── step-09.wav └── bach-bwv999 │ ├── bach-bwv999.wav │ ├── bach-bwv999-high.wav │ ├── bach-bwv999-low.wav │ └── bach-bwv999-mid.wav ├── win ├── EAR.sln └── EAR.vcproj ├── cmake └── CMakeLists.txt ├── src ├── Animated.cpp ├── Material.h ├── Triangle.h ├── HelperFunctions.h ├── MonoRecorder.h ├── Settings.h ├── Triangle.cpp ├── SceneContext.h ├── StereoRecorder.h ├── HelperFunctions.cpp ├── Material.cpp ├── Datatype.h ├── Distributions.h ├── Mesh.h ├── Animated.h ├── Settings.cpp ├── SoundFile.h ├── Scene.h ├── MonoRecorder.cpp ├── Datatype.cpp ├── Mesh.cpp ├── StereoRecorder.cpp ├── SoundFile.cpp ├── Scene.cpp ├── Recorder.h ├── Recorder.cpp └── EAR.cpp ├── lib ├── equalizer │ ├── Equalizer.h │ └── Equalizer.cpp └── wave │ ├── WaveFile.h │ └── WaveFile.cpp ├── README.md └── blender └── render_EAR ├── materials.py ├── sines.py ├── storyboard.py └── glyphs.py /example1/click.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example1/click.wav -------------------------------------------------------------------------------- /example3/plop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example3/plop.wav -------------------------------------------------------------------------------- /testbench/RT60.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/testbench/RT60.blend -------------------------------------------------------------------------------- /example1/example1.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example1/example1.blend -------------------------------------------------------------------------------- /example2/door/door.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/door/door.wav -------------------------------------------------------------------------------- /example2/example2.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/example2.blend -------------------------------------------------------------------------------- /example3/example3.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example3/example3.blend -------------------------------------------------------------------------------- /example2/steps/step-01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-01.wav -------------------------------------------------------------------------------- /example2/steps/step-02.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-02.wav -------------------------------------------------------------------------------- /example2/steps/step-03.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-03.wav -------------------------------------------------------------------------------- /example2/steps/step-04.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-04.wav -------------------------------------------------------------------------------- /example2/steps/step-05.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-05.wav -------------------------------------------------------------------------------- /example2/steps/step-06.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-06.wav -------------------------------------------------------------------------------- /example2/steps/step-07.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-07.wav -------------------------------------------------------------------------------- /example2/steps/step-08.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-08.wav -------------------------------------------------------------------------------- /example2/steps/step-09.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/steps/step-09.wav -------------------------------------------------------------------------------- /example2/bach-bwv999/bach-bwv999.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/bach-bwv999/bach-bwv999.wav -------------------------------------------------------------------------------- /example2/bach-bwv999/bach-bwv999-high.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/bach-bwv999/bach-bwv999-high.wav -------------------------------------------------------------------------------- /example2/bach-bwv999/bach-bwv999-low.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/bach-bwv999/bach-bwv999-low.wav -------------------------------------------------------------------------------- /example2/bach-bwv999/bach-bwv999-mid.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aothms/ear/HEAD/example2/bach-bwv999/bach-bwv999-mid.wav -------------------------------------------------------------------------------- /win/EAR.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 10.00 3 | # Visual C++ Express 2008 4 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EAR", "EAR.vcproj", "{302FB7AC-B8A0-49BF-8D93-6829F66DCFC7}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Win32 = Debug|Win32 9 | Release|Win32 = Release|Win32 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {302FB7AC-B8A0-49BF-8D93-6829F66DCFC7}.Debug|Win32.ActiveCfg = Debug|Win32 13 | {302FB7AC-B8A0-49BF-8D93-6829F66DCFC7}.Debug|Win32.Build.0 = Debug|Win32 14 | {302FB7AC-B8A0-49BF-8D93-6829F66DCFC7}.Release|Win32.ActiveCfg = Release|Win32 15 | {302FB7AC-B8A0-49BF-8D93-6829F66DCFC7}.Release|Win32.Build.0 = Release|Win32 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /cmake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.6) 2 | project (EAR) 3 | 4 | # find_package(Threads) 5 | # set(Boost_USE_STATIC_LIBS ON) 6 | find_package(Boost COMPONENTS thread REQUIRED) 7 | 8 | find_file(fftw3_h "fftw3.h") 9 | if(fftw3_h) 10 | add_definitions(-DUSE_FFTW) 11 | set(FFTW_LIB fftw3f) 12 | message(STATUS "Found FFTW using DFTs for audio convolution") 13 | endif(fftw3_h) 14 | 15 | if(NOT CMAKE_BUILD_TYPE) 16 | set(CMAKE_BUILD_TYPE "Release") 17 | message(STATUS "Defaulting to release build") 18 | endif(NOT CMAKE_BUILD_TYPE) 19 | 20 | file(GLOB ear_sources "../src/*.cpp") 21 | 22 | set(libs "../lib") 23 | set(lib_sources "${libs}/wave/WaveFile.cpp" "${libs}/equalizer/Equalizer.cpp") 24 | 25 | include_directories("${libs}/wave" "${libs}/equalizer") 26 | 27 | add_executable(EAR ${ear_sources} ${lib_sources}) 28 | target_link_libraries (EAR ${Boost_THREAD_LIBRARY} ${FFTW_LIB}) 29 | #target_link_libraries (EAR ${Boost_THREAD_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) 30 | 31 | install(TARGETS EAR DESTINATION bin) 32 | -------------------------------------------------------------------------------- /src/Animated.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include "Animated.h" 21 | Keyframes* Keyframes::p = 0; -------------------------------------------------------------------------------- /src/Material.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef MATERIAL_H 21 | #define MATERIAL_H 22 | 23 | #include 24 | 25 | #include "Datatype.h" 26 | 27 | enum BounceType { REFLECT, REFRACT, ABSORB }; 28 | 29 | /// This class represents the material of mesh surfaces and defines how 30 | /// sound rays bounce off of the material. 31 | class Material : public Datatype { 32 | public: 33 | std::string name; 34 | std::map reflection_coefficient; 35 | std::map refraction_coefficient; 36 | std::map absorption_coefficient; 37 | std::map specularity_coefficient; 38 | std::map::iterator it; 39 | 40 | Material(); 41 | bool isTransparent(); 42 | BounceType Bounce(int band); 43 | }; 44 | 45 | #endif -------------------------------------------------------------------------------- /src/Triangle.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef TRIANGLE_H_ 21 | #define TRIANGLE_H_ 22 | 23 | #include 24 | 25 | #include "Datatype.h" 26 | #include "Material.h" 27 | 28 | /// A simple extention to the gmtl Trif class to also store the triangle 29 | /// area and normal and add a function to sample a point on the triangle 30 | /// using a uniform distribution, the latter is used when a mesh is used 31 | /// as an emitting surface for a sound source. 32 | class Triangle : public gmtl::Trif, public Datatype { 33 | private: 34 | void calcArea(); 35 | public: 36 | gmtl::Vec3f normal; 37 | float area; 38 | Material* m; 39 | Triangle(const gmtl::Point3f& a,const gmtl::Point3f& b,const gmtl::Point3f& c); 40 | Triangle(); 41 | void SamplePoint(gmtl::Point3f& p); 42 | float SignedVolume() const; 43 | }; 44 | 45 | #endif -------------------------------------------------------------------------------- /lib/equalizer/Equalizer.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | // 4th order Linkwitz-Riley filter, adapted from: 21 | // http://www.musicdsp.org/archive.php?classid=3#266 22 | 23 | #ifndef EQUALIZER_H 24 | #define EQUALIZER_H 25 | 26 | class Equalizer { 27 | private: 28 | class Pass { 29 | protected: 30 | float b1, b2, b3, b4; 31 | float a0, a1, a2, a3, a4; 32 | float xm1, xm2, xm3, xm4; 33 | float ym1, ym2, ym3, ym4; 34 | float wc4, k4, a_tmp; 35 | Pass(float fc); 36 | public: 37 | void Process(const float in, float &out); 38 | }; 39 | class LowPass : public Pass { 40 | public: 41 | LowPass(float fc); 42 | }; 43 | class HighPass : public Pass { 44 | public: 45 | HighPass(float fc); 46 | }; 47 | public: 48 | static void Split(float* data, float* low, float* mid, float* high, unsigned int length, float f1, float f2, float f3); 49 | }; 50 | 51 | #endif -------------------------------------------------------------------------------- /src/HelperFunctions.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef HELPERFUNCTIONS_H 21 | #define HELPERFUNCTIONS_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | /// Splits a string into a part before and after the first space encountered 30 | bool Split(const std::string& str, std::string& a, std::string& b); 31 | 32 | /// Returns the directory name of a path 33 | std::string DirName(const std::string& str); 34 | 35 | /// Returns the filename of a path 36 | std::string FileName(const std::string& str); 37 | 38 | /// Sets the number of segments for the progress bar in case the maximum 39 | /// number of threads is larger than the pool of tasks 40 | void SetProgressBarSegments(int n); 41 | /// Signals work on the first batch of tasks is done and moves to the next 42 | void NextProgressBarSegment(); 43 | /// Draw a progress bar to stdout using appropriate locking for threads 44 | void DrawProgressBar(const int& done, const int& total); 45 | 46 | /// Draw a string to stdout using appropriate locking for threads 47 | void DrawString(const std::string & s); 48 | 49 | #ifdef _MSC_VER 50 | #define DIR_SEPERATOR "\\" 51 | #else 52 | #define DIR_SEPERATOR "/" 53 | #endif 54 | 55 | #endif -------------------------------------------------------------------------------- /src/MonoRecorder.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef MONORECORDER_H 21 | #define MONORECORDER_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "boost/thread/thread.hpp" 28 | 29 | #include "../lib/wave/WaveFile.h" 30 | 31 | #include "SoundFile.h" 32 | #include "Recorder.h" 33 | 34 | /// This class represents a listener approximated by a single cartesian 35 | /// point. It therefore has no orientation and captures sound from every 36 | /// direction. 37 | class MonoRecorder : public Datatype, public Recorder { 38 | private: 39 | static boost::mutex mutex; 40 | public: 41 | gmtl::Point3f location; 42 | std::string filename; 43 | Animated* animation; 44 | gmtl::Point3f getLocation(float t); 45 | void setLocation(gmtl::Point3f& loc); 46 | std::string getFilename(); 47 | void setFilename(const std::string& s); 48 | int trackCount(); 49 | 50 | MonoRecorder(bool fromFile = true); 51 | Recorder* getBlankCopy(int secs = -1); 52 | bool Save(const std::string& fn, bool norm = true, float norm_max = 1.0f); 53 | bool Save(); 54 | inline void _Sample(int i, float v); 55 | void Record(const gmtl::Vec3f& dir, float ampl, float t, float dist, int band, int kf); 56 | void setLocation(gmtl::Point3f p); 57 | bool isAnimated(); 58 | //gmtl::Point3f getLocation(); 59 | const gmtl::Point3f& getLocation(int i = -1) const; 60 | float getSegmentLength(int i); 61 | std::string toString(); 62 | Animated* getAnimationData(); 63 | }; 64 | 65 | #endif -------------------------------------------------------------------------------- /lib/wave/WaveFile.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | // Adapted from: 21 | // http://www.codeproject.com/KB/audio-video/wave_class_for_playing_and_recording.aspx 22 | 23 | #ifndef _WAVE_H 24 | #define _WAVE_H 25 | 26 | #pragma pack(push) 27 | #pragma pack(1) 28 | typedef struct 29 | { 30 | char riff[4]; 31 | unsigned int size; 32 | char wave[4]; 33 | 34 | } wavedescr; 35 | typedef struct 36 | { 37 | char id[4]; 38 | unsigned int size; 39 | short format; 40 | short channels; 41 | unsigned int sampleRate; 42 | unsigned int byteRate; 43 | short blockAlign; 44 | short bitsPerSample; 45 | 46 | } wavefmt; 47 | #pragma pack(pop) 48 | 49 | class WaveFile 50 | { 51 | private: 52 | wavedescr desc; 53 | wavefmt fmt; 54 | char* data; 55 | unsigned int size; 56 | unsigned int sample_size; 57 | void Init(); 58 | public: 59 | WaveFile(); 60 | WaveFile(const char* fn); 61 | ~WaveFile(); 62 | bool Load(const char* fn); 63 | bool Save(const char* fn); 64 | float* ToFloat(); 65 | bool FromFloat(const float*, int size, bool norm = false, float norm_max = -1.0f); 66 | bool FromFloat(const float* left, const float* right, int size1, int size2, bool norm = false); 67 | 68 | bool HasData() {return data != 0; } 69 | void* GetData() {return data;} 70 | unsigned int GetSize() {return size;} 71 | unsigned int GetSampleSize() {return sample_size;} 72 | short GetChannels() {return fmt.channels;} 73 | unsigned int GetSampleRate() {return fmt.sampleRate;} 74 | short GetBitsPerSample() {return fmt.bitsPerSample;} 75 | }; 76 | 77 | #endif -------------------------------------------------------------------------------- /src/Settings.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef SETTINGS_H 21 | #define SETTINGS_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "boost/thread/thread.hpp" 28 | #include "boost/thread/mutex.hpp" 29 | 30 | #include "Datatype.h" 31 | 32 | /// The class provides static access to the SET datatype block in the .EAR 33 | /// file. 34 | /// NOTE Accessing data from the settings block might NOT thread safe! 35 | class Settings : public Datatype { 36 | private: 37 | enum { SETTING_NOTFOUND_IGNORE, SETTING_NOTFOUND_WARN, SETTING_NOTFOUND_THROW }; 38 | static std::map settings; 39 | static std::vector errors; 40 | static boost::mutex m_mutex; 41 | static Datatype* getsetting(const std::string& s, int warn= SETTING_NOTFOUND_WARN); 42 | public: 43 | /// Initializes the settings with the block found in the file. 44 | static void init(Datatype* d); 45 | /// Gets a settings as an integer. 46 | static int GetInt(const std::string& s); 47 | /// Gets a settings as a boolean, which is an integer > 0. 48 | static bool GetBool(const std::string& s); 49 | /// Checks if a settings is defined in the file. 50 | static bool IsSet(const std::string& s); 51 | /// Gets a settings as a floating point numeral. 52 | static float GetFloat(const std::string& s); 53 | /// Gets a settings as a float triplet vector. 54 | static gmtl::Vec3f GetVec(const std::string& s); 55 | /// Gets a settings as a float triplet point. 56 | static std::string GetString(const std::string& s); 57 | }; 58 | 59 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | E.A.R: Evaluation of Acoustics using Ray-tracing 2 | ================================================ 3 | 4 | E.A.R is an application that uses Ray-tracing to conceive an impression of the acoustics of a space that has been modeled in 3d. It is a stand-alone application written in C++, but comes with an exporter interface for [Blender](http://www.blender.org). Originally, it was created to aid architects in designing the auditory experience of their buildings, but it has since then evolved into a more all-round tool, which can find its use in filmmaking and special effects as well. E.A.R is free and open source for your enjoyment. 5 | 6 | Installation 7 | ------------ 8 | Grab the Blender E.A.R add-on from the [downloads section](https://github.com/aothms/ear/downloads). The Blender E.A.R add-on allows you to model your scene, which will get rendered in an included binary application. The addon package comes with binaries for most operating systems, but you can also compile the binary yourself. If you have not installed a recent version of Blender on your system, this would be a good time to do so, please get it from the [Blender website](http://www.blender.org/download/get-blender/). 9 | 10 | Place the downloaded E.A.R Add-on, which is named render_EAR, in your Blender addons folder. Now you are good to go! 11 | 12 | Building E.A.R from source 13 | -------------------------- 14 | This guide is directed at **Ubuntu** users, but with minor modifications the concepts apply to other *nix operating systems as well. Windows users can compile the binary using the included Visual Studio project. 15 | 16 | Install some prerequisites (don't copy the dollar sign, it simply represents the prompt): 17 | 18 | $ sudo apt-get install build-essential cmake libboost-thread-dev 19 | 20 | Create a directory in your home folder for E.A.R: 21 | 22 | $ cd ~ 23 | $ mkdir EAR 24 | $ cd EAR 25 | 26 | Get the [Graphics Math Template Library](http://ggt.sourceforge.net/): 27 | 28 | $ wget -O gmtl-0.6.1.zip http://sourceforge.net/projects/ggt/files/Generic%20Math%20Template%20Library/0.6.1/gmtl-0.6.1.zip/download 29 | $ unzip gmtl-0.6.1.zip 30 | $ sudo mv gmtl-0.6.1/gmtl /usr/include/ 31 | 32 | Now download, build and install E.A.R: 33 | 34 | $ wget -O EAR.zip https://github.com/aothms/ear/zipball/master 35 | $ unzip EAR.zip 36 | $ cd aothms-ear* 37 | $ mkdir build 38 | $ cd build 39 | $ cmake ../cmake 40 | $ sudo make install 41 | 42 | To test whether everything is installed successfully you can invoke E.A.R from the command line by issuing: 43 | 44 | $ EAR 45 | 46 | If everything went well, you should be issued the following welcome message 47 | 48 | ______ _____ 49 | | ____| /\ | __ \ 50 | | |__ / \ | |__) | 51 | | __| / /\ \ | _ / 52 | | |____ / ____ \ | | \ \ 53 | |______| (_) /_/ \_\ (_) |_| \_\ 54 | Evaluation of Acoustics using Ray-tracing 55 | version 0.1.4b 56 | 57 | Usage: 58 | EAR render 59 | EAR calc T60 60 | -------------------------------------------------------------------------------- /src/Triangle.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | 22 | #include "Datatype.h" 23 | #include "Material.h" 24 | #include "Triangle.h" 25 | 26 | void Triangle::calcArea() { 27 | gmtl::Vec3f result; 28 | gmtl::cross(result,edge(0),edge(1)); 29 | area = gmtl::length(result) / 2.0f; 30 | } 31 | Triangle::Triangle(const gmtl::Point3f& a,const gmtl::Point3f& b,const gmtl::Point3f& c) : gmtl::Trif(a,b,c) { 32 | normal = gmtl::normal(*this); 33 | calcArea(); 34 | } 35 | Triangle::Triangle() : gmtl::Trif() { 36 | Read(false); 37 | assertid("tri "); 38 | mVerts[0] = ReadVec(); 39 | mVerts[1] = ReadVec(); 40 | mVerts[2] = ReadVec(); 41 | normal = gmtl::normal(*this); 42 | calcArea(); 43 | } 44 | void Triangle::SamplePoint(gmtl::Point3f& P) { 45 | // http://math.stackexchange.com/questions/18686/uniform-random-point-in-triangle 46 | const float r1 = gmtl::Math::unitRandom(); 47 | const float r2 = gmtl::Math::unitRandom(); 48 | const float sr1 = gmtl::Math::sqrt(r1); 49 | const gmtl::Point3f& A = (*this)[0]; 50 | const gmtl::Point3f& B = (*this)[1]; 51 | const gmtl::Point3f& C = (*this)[2]; 52 | P = (1.0f - sr1) * A + (sr1 * (1.0f - r2)) * B + (sr1 * r2) * C; 53 | } 54 | 55 | float Triangle::SignedVolume() const { 56 | // http://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up 57 | const gmtl::Point3f& p1 = mVerts[0]; 58 | const gmtl::Point3f& p2 = mVerts[1]; 59 | const gmtl::Point3f& p3 = mVerts[2]; 60 | const float v321 = p3[0]*p2[1]*p1[2]; 61 | const float v231 = p2[0]*p3[1]*p1[2]; 62 | const float v312 = p3[0]*p1[1]*p2[2]; 63 | const float v132 = p1[0]*p3[1]*p2[2]; 64 | const float v213 = p2[0]*p1[1]*p3[2]; 65 | const float v123 = p1[0]*p2[1]*p3[2]; 66 | return (1.0f/6.0f)*(-v321 + v231 + v312 - v132 - v213 + v123); 67 | } -------------------------------------------------------------------------------- /blender/render_EAR/materials.py: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # # 3 | # This file is part of EAR: Evaluation of Acoustics using Ray-tracing. # 4 | # # 5 | # EAR is free software: you can redistribute it and/or modify # 6 | # it under the terms of the GNU General Public License as published by # 7 | # the Free Software Foundation, either version 3 of the License, or # 8 | # (at your option) any later version. # 9 | # # 10 | # EAR is distributed in the hope that it will be useful, # 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 13 | # GNU General Public License for more details. # 14 | # # 15 | # You should have received a copy of the GNU General Public License # 16 | # along with EAR. If not, see . # 17 | # # 18 | ######################################################################## 19 | 20 | ######################################################################## 21 | # # 22 | # Sample material definitions, inspired by the Odeon Material.Li8 file # 23 | # # 24 | ######################################################################## 25 | 26 | mats = """ 27 | 100% Transparent 28 | 0.00000 0.00000 0.00000 29 | 1.00000 1.00000 1.00000 30 | 1.00000 1.00000 1.00000 31 | 32 | 100% Absorbent 33 | 0.00000 0.00000 0.00000 34 | 0.00000 0.00000 0.00000 35 | 0.50000 0.50000 0.50000 36 | 37 | 100% Reflecting 38 | 1.00000 1.00000 1.00000 39 | 0.00000 0.00000 0.00000 40 | 0.20000 0.40000 0.80000 41 | 42 | Rough concrete 43 | 0.98000 0.96000 0.93000 44 | 0.00000 0.00000 0.00000 45 | 0.10000 0.20000 0.40000 46 | 47 | Smooth concrete 48 | 0.99000 0.98000 0.95000 49 | 0.00000 0.00000 0.00000 50 | 0.30000 0.60000 0.90000 51 | 52 | Wooden floor 53 | 0.80000 0.90000 0.95000 54 | 0.15000 0.08000 0.04000 55 | 0.30000 0.40000 0.50000 56 | 57 | Glazing 58 | 0.60000 0.70000 0.80000 59 | 0.39000 0.24000 0.17000 60 | 0.60000 0.70000 0.90000 61 | """ 62 | 63 | definitions = [] 64 | 65 | def materials(): 66 | global mats, definitions 67 | names = ["-- Custom --"] 68 | definitions.append(None) 69 | data = [] 70 | for l in [x for x in mats.split('\n') if x != '' and x[0] != '#']: 71 | if len(data) == 3: 72 | definitions.append(data) 73 | data = [] 74 | names.append(l) 75 | elif len(names)>len(definitions): 76 | data.append([float(x) for x in l.split()]) 77 | else: 78 | names.append(l) 79 | definitions.append(data) 80 | return list(zip([str(x) for x in range(len(names))],names,names)) 81 | 82 | def lookup(str): 83 | global definitions 84 | return definitions[int(str)] -------------------------------------------------------------------------------- /src/SceneContext.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include "Recorder.h" 21 | #include "SoundFile.h" 22 | 23 | /// This class holds all data that is needed to render an impulse response. 24 | /// The class is executable and can therefore be used as a context for a 25 | /// thread. 26 | class SceneContext { 27 | public: 28 | std::vector recorders; 29 | Scene* scene; 30 | int band; 31 | int soundfile_id; 32 | int keyframe_id; 33 | int samples; 34 | float absorption; 35 | float dry_level; 36 | void assignRecorders(Scene* s) { 37 | for ( std::vector::const_iterator it = s->listeners.begin(); it != s->listeners.end(); ++ it ) { 38 | recorders.push_back((*it)->getBlankCopy(4)); 39 | } 40 | } 41 | SceneContext(Scene* scn,int b,int sf, int s, float ab, float dr, int kf=-1) : 42 | scene(scn), band(b), soundfile_id(sf), keyframe_id(kf), 43 | samples(s), absorption(ab), dry_level(dr) { 44 | assignRecorders(scn); 45 | } 46 | void operator()() { 47 | scene->Render(band,soundfile_id,absorption,samples,dry_level,recorders,keyframe_id); 48 | } 49 | std::string toString() const { 50 | std::stringstream ss; 51 | ss << "s:" << soundfile_id << " b:" << band << " k:" << keyframe_id; 52 | return ss.str(); 53 | } 54 | }; 55 | 56 | /// This class holds all data that is needed to convolute a sound file by 57 | /// an impulse response. The class is executable and can therefore be used 58 | /// as a context for a thread. 59 | class RecorderContext { 60 | public: 61 | SoundFile* soundfile; 62 | Recorder* recorder1; 63 | Recorder* recorder2; 64 | float offset; 65 | float length; 66 | RecorderContext(SoundFile* s, Recorder* r1, float o = 0.0f, Recorder* r2 = 0, float l = 0.0f) : 67 | soundfile(s), recorder1(r1), offset(o), recorder2(r2), length(l) {} 68 | void operator()() { 69 | if ( recorder2 ) { 70 | recorder1->Process(soundfile,recorder2,offset,length); 71 | } else { 72 | recorder1->Process(soundfile,offset); 73 | } 74 | } 75 | 76 | }; -------------------------------------------------------------------------------- /src/StereoRecorder.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef STEREORECORDER_H 21 | #define STEREORECORDER_H 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | #include "Recorder.h" 31 | 32 | /// This class represents a listener approximated by a single cartesian 33 | /// point and a direction vector of the right ear. Both can be animated. 34 | /// The ear vector is used to add directivity to the final impulse 35 | /// responses by taking the dot product of the ear vector and the 36 | /// ray direction of the sample. The interaural time and -intensity 37 | /// differences are based on this dot product. The intensity difference 38 | /// also depend on the frequency bands, because the human head blocks 39 | /// high frequencies more than low frequencies. 40 | class StereoRecorder : public Datatype, public Recorder { 41 | private: 42 | gmtl::Point3f location; 43 | gmtl::Vec3f right_ear; 44 | gmtl::Vec3f head_absorption; 45 | float head_size; 46 | Animated* animation; 47 | Animated* right_ear_animation; 48 | public: 49 | std::string filename; 50 | //gmtl::Point3f getLocation(float t); 51 | void setLocation(gmtl::Point3f& loc); 52 | std::string getFilename(); 53 | void setFilename(const std::string& s); 54 | int trackCount(); 55 | 56 | StereoRecorder(bool fromFile = true); 57 | Recorder* getBlankCopy(int secs = -1); 58 | bool Save(const std::string& fn, bool norm = true, float norm_max = 1.0f); 59 | bool Save(); 60 | inline void _Sample(int i, float v, int channel); 61 | void Record(const gmtl::Vec3f& dir, float ampl, float t, float dist, int band, int kf); 62 | void setLocation(gmtl::Point3f p); 63 | bool isAnimated(); 64 | const gmtl::Point3f& getLocation(int i=-1) const; 65 | const gmtl::Vec3f& getRightEar(int i=-1); 66 | float getSegmentLength(int i); 67 | std::string toString(); 68 | Animated* getAnimationData(); 69 | }; 70 | 71 | #endif -------------------------------------------------------------------------------- /src/HelperFunctions.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include 30 | 31 | #include "HelperFunctions.h" 32 | 33 | bool Split(const std::string& str, std::string& a, std::string& b) { 34 | const std::string& delimiters = " "; 35 | std::string::size_type pos = str.find_first_of(delimiters); 36 | if ( pos == std::string::npos ) return false; 37 | a = str.substr(0,pos); 38 | b = str.substr(pos+1); 39 | return true; 40 | } 41 | 42 | std::string DirName(const std::string& str) { 43 | const std::string& delimiters = "\\/"; 44 | std::string::size_type pos = str.find_last_of(delimiters); 45 | if ( pos == std::string::npos ) throw std::runtime_error("Failed to interpret file path"); 46 | return str.substr(0,pos+1); 47 | } 48 | 49 | std::string FileName(const std::string& str) { 50 | const std::string& delimiters = "\\/"; 51 | std::string::size_type pos = str.find_last_of(delimiters); 52 | if ( pos == std::string::npos ) throw std::runtime_error("Failed to interpret file path"); 53 | return str.substr(pos+1); 54 | } 55 | 56 | boost::mutex cout_mutex; 57 | static int nsegments = 1; 58 | static int segment = 0; 59 | void SetProgressBarSegments(int n) { nsegments = n; segment = 0; } 60 | void NextProgressBarSegment() { ++ segment; } 61 | void DrawProgressBar(const int& done, const int& total) { 62 | const int progress_update_interval = total / 50; 63 | if ( ! (done % progress_update_interval) ) { 64 | boost::mutex::scoped_lock lock(cout_mutex); 65 | std::cout << "\r["; 66 | for ( int _x = 0; _x < total - progress_update_interval; _x += progress_update_interval ) 67 | std::cout << ((nsegments*_x. * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "Material.h" 25 | 26 | Material::Material() { 27 | Read(false); 28 | assertid("MAT "); 29 | name = ReadString(); 30 | std::cout << "Material '" << name << "'" << std::endl; 31 | std::cout << " +- refl: ["; 32 | for( int i = 0; i < 3; i ++ ) 33 | absorption_coefficient[i] = 1.0f; 34 | for( int i = 0; i < 3; i ++ ) { 35 | const float f = ReadFloat(); 36 | reflection_coefficient[i] = f; 37 | std::cout << f; 38 | if ( i < 2 ) std::cout << ", "; 39 | absorption_coefficient[i] -= f - 1e-9f; 40 | } 41 | std::cout << "]" << std::endl; 42 | if ( Datatype::PeakId() == "flt4" ) { 43 | std::cout << " +- trans: ["; 44 | for( int i = 0; i < 3; i ++ ) { 45 | const float f = ReadFloat(); 46 | refraction_coefficient[i] = f; 47 | std::cout << f; 48 | if ( i < 2 ) std::cout << ", "; 49 | absorption_coefficient[i] -= f - 1e-9f; 50 | } 51 | std::cout << "]" << std::endl; 52 | } 53 | std::cout << " +- absorp: ["; 54 | for( int i = 0; i < 3; i ++ ) { 55 | const float f = absorption_coefficient[i]; 56 | if ( f < 0.0f ) throw DatatypeException("Invalid material settings"); 57 | std::cout << f; 58 | if ( i < 2 ) std::cout << ", "; 59 | absorption_coefficient[i] = 1.0f-f; 60 | } 61 | std::cout << "]" << std::endl; 62 | if ( Datatype::PeakId() == "flt4" ) { 63 | std::cout << " +- spec: ["; 64 | for( int i = 0; i < 3; i ++ ) { 65 | const float f = ReadFloat(); 66 | specularity_coefficient[i] = f; 67 | std::cout << f; 68 | if ( i < 2 ) std::cout << ", "; 69 | } 70 | std::cout << "]" << std::endl; 71 | } 72 | } 73 | bool Material::isTransparent() { 74 | return refraction_coefficient.size() == 3; 75 | } 76 | BounceType Material::Bounce(int band) { 77 | const float fl = reflection_coefficient[band]; 78 | const float fr = refraction_coefficient[band]; 79 | if ( fl < 0.0001 && fr < 0.0001 ) return REFLECT; 80 | const float ab = 1.0f - fl - fr; 81 | const float fl2 = fl / (fl+fr); 82 | return ( gmtl::Math::unitRandom() <= fl2 ) ? REFLECT : REFRACT; 83 | } -------------------------------------------------------------------------------- /src/Datatype.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef DATATYPE_H 21 | #define DATATYPE_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | class DatatypeException : public std::exception { 32 | private: 33 | std::string error_message; 34 | public: 35 | DatatypeException(const std::string& msg) { 36 | error_message = msg; 37 | } 38 | ~DatatypeException() throw() {} 39 | virtual const char* what() const throw() { 40 | return error_message.c_str(); 41 | } 42 | }; 43 | 44 | /// A structure to keep track of the file cursor, which can be pushed on a 45 | /// stack in case nested datatypes are being read in. 46 | struct readpos { 47 | char* input; 48 | int input_length; 49 | }; 50 | 51 | /// The is the global class for all datatypes that can be serialized to the 52 | /// .EAR file format. The class reads all binary data from the file and can 53 | /// then be sequentially queried for the different blocks it contains. 54 | class Datatype { 55 | public: 56 | int* id; 57 | char* data; 58 | int* length; 59 | static char* buffer; 60 | static char* input; 61 | static int input_length; 62 | static bool scanning; 63 | static std::vector readstack; 64 | static std::string prefix; 65 | static std::string stringblock; 66 | 67 | Datatype(); 68 | void assertid(const char* c); 69 | bool isInt(); 70 | bool isFloat(); 71 | bool isString(); 72 | bool isVec(); 73 | bool isTri(); 74 | static void push(Datatype* d); 75 | static void pop(); 76 | static std::string PeakId(); 77 | static bool SetInput(std::string filename); 78 | static void Dispose(); 79 | static Datatype* Read(bool r=true); 80 | static float ReadFloat(); 81 | static gmtl::Vec3f ReadVec(); 82 | template 83 | static T ReadTriplet() { 84 | Datatype* d = Read(false); 85 | d->assertid("vecf"); 86 | float x = ReadFloat(); 87 | float y = ReadFloat(); 88 | float z = ReadFloat(); 89 | return T(x,y,z); 90 | } 91 | static gmtl::Point3f ReadPoint(); 92 | static std::string ReadString(); 93 | static Datatype* Scan(std::string a); 94 | }; 95 | 96 | #endif -------------------------------------------------------------------------------- /lib/equalizer/Equalizer.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | // 4th order Linkwitz-Riley filter, adapted from: 21 | // http://www.musicdsp.org/archive.php?classid=3#266 22 | 23 | #include 24 | 25 | #include "Equalizer.h" 26 | 27 | void Equalizer::Pass::Process(const float in, float &out) { 28 | out = a0 * in + a1 * xm1 + a2 * xm2 + a3 * xm3 + a4 * xm4 - 29 | b1 * ym1 - b2 * ym2 - b3 * ym3 - b4 * ym4; 30 | xm4 = xm3; xm3 = xm2; xm2 = xm1; xm1 = in; 31 | ym4 = ym3; ym3 = ym2; ym2 = ym1; ym1 = out; 32 | } 33 | Equalizer::Pass::Pass(float fc) { 34 | //fc -> cutoff frequency 35 | //srate -> sample rate 36 | 37 | const float srate = 44100.0f; 38 | const float pi = 3.14285714285714f; 39 | 40 | // shared for both lp, hp; optimizations here 41 | float wc = 2.0f * pi * srate; 42 | float wc2 = wc * wc; 43 | float wc3 = wc2 * wc; 44 | wc4 = wc2 * wc2; 45 | float k = wc / tan(pi * fc / srate); 46 | float k2 = k * k; 47 | float k3 = k2 * k; 48 | k4 = k2 * k2; 49 | float sqrt2 = sqrtf(2.0f); 50 | float sq_tmp1 = sqrt2 * wc3 * k; 51 | float sq_tmp2 = sqrt2 * wc * k3; 52 | a_tmp = 4.0f * wc2 * k2 + 2.0f * sq_tmp1 + k4 + 53 | 2.0f * sq_tmp2 + wc4; 54 | 55 | b1 = (4.0f * (wc4 + sq_tmp1 - k4 - sq_tmp2)) / a_tmp; 56 | b2 = (6.0f * wc4 - 8.0f * wc2 * k2 + 6.0f * k4) / a_tmp; 57 | b3 = (4.0f * (wc4 - sq_tmp1 + sq_tmp2 - k4)) / a_tmp; 58 | b4 = (k4 - 2.0f * sq_tmp1 + wc4 - 2.0f * sq_tmp2 + 59 | 4.0f * wc2 * k2) / a_tmp; 60 | 61 | xm1 = xm2 = xm3 = xm4 = 0.0f; 62 | ym1 = ym2 = ym3 = ym4 = 0.0f; 63 | } 64 | 65 | Equalizer::LowPass::LowPass(float fc) : Equalizer::Pass(fc) { 66 | a0 = wc4 / a_tmp; 67 | a1 = 4.0f * wc4 / a_tmp; 68 | a2 = 6.0f * wc4 / a_tmp; 69 | a3 = a1; 70 | a4 = a0; 71 | } 72 | 73 | Equalizer::HighPass::HighPass(float fc) : Equalizer::Pass(fc) { 74 | a0 = k4 / a_tmp; 75 | a1 = -4.0f * k4 / a_tmp; 76 | a2 = 6.0f * k4 / a_tmp; 77 | a3 = a1; 78 | a4 = a0; 79 | } 80 | 81 | void Equalizer::Split(float* data, float* low, float* mid, 82 | float* high, unsigned int length, 83 | float f1, float f2, float f3) { 84 | const float fc1 = (f1+f2)/2.0f; 85 | const float fc2 = (f2+f3)/2.0f; 86 | HighPass hi_pass1 = fc1; 87 | HighPass hi_pass2 = fc2; 88 | LowPass lo_pass1 = fc1; 89 | LowPass lo_pass2 = fc2; 90 | for ( unsigned int i = 0; i < length; ++ i ) { 91 | hi_pass2.Process(data[i], high[i]); 92 | lo_pass1.Process(data[i], low[i]); 93 | lo_pass2.Process(data[i], mid[i]); 94 | hi_pass1.Process(mid[i], mid[i]); 95 | } 96 | } -------------------------------------------------------------------------------- /src/Distributions.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | /************************************************************************ 21 | * * 22 | * This file contains some formulas and functions that are related to * 23 | * statistical and geometrical distrubutions. Implementations are sub- * 24 | * optimal in terms of performance and efficiency. * 25 | * * 26 | ************************************************************************/ 27 | 28 | #ifndef DISTRIBUTIONS_H 29 | #define DISTRIBUTIONS_H 30 | 31 | #include 32 | #include 33 | 34 | #ifdef PI 35 | #undef PI 36 | #endif 37 | #define PI 3.14159265f 38 | #define SHERE_SURFACE(R) (4.0f*PI*(R)*(R)) 39 | #define SPHERE_VOLUME(R) (4.0f/3.0f*PI*(R)*(R)*(R)) 40 | #define INV_SPHERE (1.0f / SHERE_SURFACE(1.0f)) 41 | #define INV_HEMI (2.0f / SHERE_SURFACE(1.0f)) 42 | #define INV_SPHERE_2(R) (1.0f / SHERE_SURFACE(R)) 43 | #define INV_HEMI_2(R) (2.0f / SHERE_SURFACE(R)) 44 | 45 | /// Samples a vector on a sphere. The implementation used here is very 46 | /// inefficient. A point in a unit cube is sampled and discarded if 47 | /// it falls outside a sphere with radius one. 48 | inline void Sample_Sphere(gmtl::Vec3f& v) { 49 | const float f1 = gmtl::Math::unitRandom() * 2.0f - 1.0f; 50 | const float f2 = gmtl::Math::unitRandom() * 2.0f - 1.0f; 51 | const float f3 = gmtl::Math::unitRandom() * 2.0f - 1.0f; 52 | v = gmtl::Vec3f(f1,f2,f3); 53 | const float l = gmtl::lengthSquared(v); 54 | if ( l < 0.001f || l > 1.0f ) { 55 | return Sample_Sphere(v); 56 | } 57 | v /= sqrt(l); 58 | } 59 | /// Samples a vector on a hemisphere aligned by normal vector n, by 60 | /// first sampling a sphere and discarding the sample if the dot 61 | /// product with the normal vector is negative. 62 | inline void Sample_Hemi(gmtl::Vec3f& v, const gmtl::Vec3f& n) { 63 | Sample_Sphere(v); 64 | if ( gmtl::dot(n,v) < 0.0f ) { 65 | return Sample_Hemi(v,n); 66 | } 67 | } 68 | /// Samples a vector on a hemisphere aligned by normal vector n, but 69 | /// factors in a reflection vector as well, to account for a specular 70 | /// reflection component. 71 | inline void Sample_Hemi(gmtl::Vec3f& v, const gmtl::Vec3f& surface_normal,const gmtl::Vec3f& reflection,float factor) { 72 | Sample_Hemi(v,surface_normal); 73 | v = v * (1.0f - factor) + reflection * factor; 74 | gmtl::normalize(v); 75 | } 76 | #endif -------------------------------------------------------------------------------- /src/Mesh.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef MESH_H 21 | #define MESH_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include "HelperFunctions.h" 31 | #include "Triangle.h" 32 | #include "Material.h" 33 | 34 | /// This class defines a set of triangles that together make an object 35 | /// that reflects sound rays. The volume does not need to be closed and 36 | /// triangles are defined two-sides, which means that they reflect sound 37 | /// regardless of whether the dot product of the ray direction and the 38 | /// triangle normal is greater or larger than zero. Only a single material 39 | /// can be assigned to a mesh. A mesh can also be used as an emitting 40 | /// volume for area sound sources. 41 | class Mesh : public Datatype { 42 | private: 43 | bool has_boundingbox; 44 | float total_area; 45 | float total_weighted_area; 46 | public: 47 | std::vector tris; 48 | Material* material; 49 | 50 | static std::map materials; 51 | 52 | float xmin,ymin,zmin,xmax,ymax,zmax; 53 | 54 | bool RayIntersection(gmtl::Rayf* r,gmtl::Point3f* &p, gmtl::Vec3f* &n, Material* &mat) ; 55 | bool LineIntersection(gmtl::LineSegf* l); 56 | void SamplePoint(gmtl::Point3f& p, gmtl::Vec3f& n); 57 | Mesh(bool from_file = true); 58 | ~Mesh(); 59 | static Mesh* Empty(); 60 | void Combine(Mesh* m); 61 | void BoundingBox(); 62 | /// Returns the surface area of the mesh, useful for example to determine the T60 63 | /// reverberation time using Sabine, Eyring or Millington-Sette. 64 | float Area() const; 65 | /// Returns the surface area of the mesh times the average absorption of the 66 | /// surfaces, commonly called the Total Absorption measured in Sabins. Useful 67 | /// for example to determine the T60 reverberation time using Sabine, Eyring 68 | /// or Millington-Sette. 69 | float TotalAbsorption() const; 70 | /// Calculates the internal volume of the mesh. In case of a non-manifold or open 71 | /// mesh, this function returns wrong results. For example to determine the T60 72 | /// reverberation time using Sabine, Eyring or Millington-Sette. 73 | float Volume() const; 74 | /// Calculates the internal volume of the mesh. In case of a non-manifold or open 75 | /// mesh, this function returns wrong results. For example to determine the T60 76 | /// reverberation time using Sabine, Eyring or Millington-Sette. 77 | float AverageAbsorption() const; 78 | }; 79 | 80 | #endif -------------------------------------------------------------------------------- /src/Animated.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef ANIMATED_H 21 | #define ANIMATED_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "Datatype.h" 29 | 30 | /// This class represents the time offsets at which keyframes are placed. 31 | /// Contrary to convention, keyframes times can not be individually specified 32 | /// for different listeners or sources, but rather, their time offsets 33 | /// are defined on a per-file basis, dictating the keyframe time coordinates 34 | /// for all animated entities in the file. 35 | class Keyframes: public Datatype { 36 | private: 37 | static Keyframes* p; 38 | public: 39 | std::vector keys; 40 | Keyframes() { 41 | Read(false); 42 | assertid("KEYS"); 43 | int l = *length; 44 | while( l ) { 45 | const float f = Datatype::ReadFloat(); 46 | keys.push_back(f); 47 | // 2 * 4 bytes are read for the datatype 48 | // |flt4|xxxx| 49 | l -= 2 * 4; 50 | } 51 | } 52 | static void Init() { 53 | p = new Keyframes(); 54 | } 55 | static void Dispose() { 56 | delete p; 57 | } 58 | static Keyframes* Get() { 59 | return p; 60 | } 61 | }; 62 | 63 | /// This class represents the movements of listeners or sound sources if 64 | /// they are set to be animated. The number of frames need to be equal to 65 | /// the number of keyframe time offsets as read by the Keyframes class. 66 | template 67 | class Animated : public Datatype { 68 | public: 69 | std::vector frames; 70 | 71 | Animated() { 72 | Read(false); 73 | assertid("anim"); 74 | int l = *length; 75 | Keyframes* keys = Keyframes::Get(); 76 | if ( !keys || keys->keys.empty() ) { 77 | throw DatatypeException("Keyframe data not read"); 78 | } 79 | while( l ) { 80 | frames.push_back(Datatype::ReadTriplet()); 81 | // 7 * 4 bytes are read for the datatype 82 | // |vec3|flt4|xxxx|flt4|xxxx|flt4|xxxx| 83 | l -= 7 * 4; 84 | } 85 | if ( frames.size() != keys->keys.size() ) { 86 | throw DatatypeException("Keyframe count does not match"); 87 | } 88 | } 89 | const T& Evaluate(int i){ 90 | return frames[i]; 91 | } 92 | float SegmentLength(int i){ 93 | Keyframes* keys = Keyframes::Get(); 94 | if ( i + 1 < (int)keys->keys.size() ) { 95 | return keys->keys[i+1] - keys->keys[1]; 96 | } else { 97 | return -1.0f; 98 | } 99 | } 100 | std::string toString(){ 101 | const T& f = frames.front(); 102 | const T& b = frames.back(); 103 | std::stringstream ss; 104 | ss << std::setprecision(std::cout.precision()) << std::fixed; 105 | ss << "< Animated (" << f.mData[0] << ", " << f.mData[1] << ", " << f.mData[2] << ") -> (" << b.mData[0] << ", " << b.mData[1] << ", " << b.mData[2] << ") >"; 106 | return ss.str(); 107 | } 108 | }; 109 | 110 | #endif -------------------------------------------------------------------------------- /blender/render_EAR/sines.py: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # # 3 | # This file is part of EAR: Evaluation of Acoustics using Ray-tracing. # 4 | # # 5 | # EAR is free software: you can redistribute it and/or modify # 6 | # it under the terms of the GNU General Public License as published by # 7 | # the Free Software Foundation, either version 3 of the License, or # 8 | # (at your option) any later version. # 9 | # # 10 | # EAR is distributed in the hope that it will be useful, # 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 13 | # GNU General Public License for more details. # 14 | # # 15 | # You should have received a copy of the GNU General Public License # 16 | # along with EAR. If not, see . # 17 | # # 18 | ######################################################################## 19 | 20 | ######################################################################## 21 | # # 22 | # A module to create output a sine complex based on either a base # 23 | # frequency and several overtones or based on a list of absolute # 24 | # frequencies. # 25 | # # 26 | ######################################################################## 27 | 28 | import wave 29 | import array 30 | from math import sin,pi 31 | try: from functools import reduce 32 | except: pass 33 | 34 | # A function in the form of f(x) = a*sin(2pi*f) 35 | class Sine: 36 | rate = 22050.0 / pi 37 | def __init__(self,p,a): 38 | self.p = p 39 | self.a = a 40 | self.l = [self] 41 | def __neg__(self): 42 | return Sine(self.p,-self.a) 43 | def __add__(self,b): 44 | a = Sine(self.p,self.a) 45 | a.l = self.l[:] 46 | a.l.append(b) 47 | return a 48 | def __str__(self): 49 | return "+".join("%.2f*sin(%d*2pi)"%(x.a,x.p) for x in self.l) 50 | def __sub__(self,b): 51 | return self + (-b) 52 | def __getitem__(self,k): 53 | if type(k) == int: 54 | return sum([sin(float(s.p*k)/self.rate)*self.a for s in self.l]) 55 | elif type(k) == slice: 56 | return [self[i] for i in range(*k.indices(k.stop))] 57 | 58 | # A function in the form of: 59 | # | (x/a)^ae for x <= a 60 | # f(x) = | ((x-a)/d)^de for x > a 61 | # To specify the envelope, attack and decay of the sine complex 62 | class Envelope: 63 | def __init__(self,a,ae,d,de): 64 | self.a = a * 44100.0 65 | self.d = d * 44100.0 66 | self.ae, self.de = ae,de 67 | def __iter__(self): 68 | return iter([(x/self.a)**self.ae for x in range(int(self.a))]+\ 69 | [(x/self.d)**self.de for x in range(int(self.d)-1,0,-1)]) 70 | 71 | # A class to write the samples to file 72 | class Wave: 73 | def __init__(self,fn,s): 74 | maxh = 2**15-1 75 | d = array.array('h',[maxh if r >= 1.0 else -maxh if r <-1.0 else int(r*maxh) for r in s]) 76 | w = wave.open(fn,'wb') 77 | w.setparams((1,2,44100,0,'NONE','not compressed')) 78 | w.writeframes(d.tostring()) 79 | 80 | # Outputs a sine complex based on a list of absolute frequencies 81 | def write_compound(filename,attack,attack_exp,decay,decay_exp,frequencies): 82 | envelope = list(Envelope(attack,attack_exp,decay,decay_exp)) 83 | sines = reduce(Sine.__add__,[Sine(a,b) for a,b in frequencies]) 84 | sine_data = sines[0:len(envelope)] 85 | wave_data = [a*b for a,b in zip(envelope,sine_data)] 86 | Wave(filename,wave_data) 87 | 88 | # Outputs a sine complex based on a base frequency and several overtones 89 | def write_compound_overtones(filename,attack,attack_exp,decay,decay_exp,base,overtones): 90 | write_compound(filename,attack,attack_exp,decay,decay_exp,[(base*2.0**a,b) for a,b in overtones]) 91 | -------------------------------------------------------------------------------- /src/Settings.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include "Settings.h" 21 | #include "Datatype.h" 22 | 23 | void Settings::init(Datatype* d) { 24 | Datatype::scanning = true; 25 | char* old_input = Datatype::input; 26 | int old_input_length = Datatype::input_length; 27 | Datatype::input = d->data; 28 | std::cout << "Settings" << std::endl; 29 | while( Datatype::PeakId() == "str " ) { 30 | std::string str = Datatype::ReadString(); 31 | std::cout << " +- " << str << ": "; 32 | Datatype* D = Datatype::Read(); 33 | settings[str] = D; 34 | if ( D->isFloat() ) std::cout << *((float*)D->data); 35 | else if ( D->isInt() ) std::cout << *((int*)D->data); 36 | else if ( D->isVec() ) { 37 | float* f = (float*)D->data; 38 | std::cout << "[" << *(f+1) << ", " << *(f+3) << ", " << *(f+5) << "]"; 39 | } else if ( D->isString() ) { 40 | int j; 41 | for( int i = 0;; i ++ ) { 42 | if ( D->data[i] == 0 ) { j = i; break; } 43 | } 44 | std::string s = std::string(D->data,j); 45 | std::cout << s; 46 | } 47 | std::cout << std::endl; 48 | } 49 | Datatype::input = old_input; 50 | Datatype::input_length = old_input_length; 51 | Datatype::scanning = false; 52 | } 53 | 54 | 55 | Datatype* Settings::getsetting(const std::string& s, int warn) { 56 | boost::mutex::scoped_lock lock(m_mutex); 57 | std::map::const_iterator it=settings.find(s); 58 | if ( it == settings.end() ) { 59 | if ( warn != SETTING_NOTFOUND_IGNORE ) { 60 | std::stringstream ss; 61 | ss << "Setting '" << s << "' not found"; 62 | if ( warn == SETTING_NOTFOUND_WARN && std::find ( errors.begin(), errors.end(), s ) == errors.end() ) { 63 | std::cout << ss.str() << std::endl; 64 | errors.push_back(s); 65 | } else if ( warn == SETTING_NOTFOUND_THROW ) { 66 | throw DatatypeException(ss.str()); 67 | } 68 | } 69 | return 0; 70 | } 71 | return it->second; 72 | } 73 | 74 | int Settings::GetInt(const std::string& s) { 75 | Datatype* d = getsetting(s, SETTING_NOTFOUND_THROW); 76 | d->assertid("int4"); 77 | return *((int*) d->data); 78 | } 79 | bool Settings::GetBool(const std::string& s) { 80 | return GetInt(s) > 0; 81 | } 82 | bool Settings::IsSet(const std::string& s) { 83 | return !!getsetting(s, SETTING_NOTFOUND_IGNORE); 84 | } 85 | float Settings::GetFloat(const std::string& s) { 86 | Datatype* d = getsetting(s); 87 | d->assertid("flt4"); 88 | return * ((float*) d->data); 89 | } 90 | gmtl::Vec3f Settings::GetVec(const std::string& s) { 91 | Datatype* d = getsetting(s, SETTING_NOTFOUND_THROW); 92 | Datatype::push(d); 93 | gmtl::Vec3f v = Datatype::ReadVec(); 94 | Datatype::pop(); 95 | return v; 96 | } 97 | std::string Settings::GetString(const std::string& s) { 98 | Datatype* d = getsetting(s, SETTING_NOTFOUND_THROW); 99 | Datatype::push(d); 100 | std::string v = Datatype::ReadString(); 101 | Datatype::pop(); 102 | return v; 103 | } 104 | 105 | std::map Settings::settings; 106 | std::vector Settings::errors; 107 | boost::mutex Settings::m_mutex; 108 | -------------------------------------------------------------------------------- /src/SoundFile.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef SOUNDFILE_H 21 | #define SOUNDFILE_H 22 | 23 | #include 24 | 25 | #include "../lib/wave/WaveFile.h" 26 | 27 | #include "Mesh.h" 28 | #include "Animated.h" 29 | #include "HelperFunctions.h" 30 | 31 | class SoundFile; 32 | 33 | /// This is the abstract base class of all sound files. It outlines methods 34 | /// related to the location/animation of the sound source, the sample data 35 | /// of the wave file and functionality to generate a ray form the origin point 36 | /// or mesh if the latter is defined. 37 | class AbstractSoundFile : public Datatype { 38 | protected: 39 | Mesh* mesh; 40 | float gain; 41 | bool sample_owner; 42 | SoundFile* soundfiles[3]; 43 | public: 44 | float *data, *data_low, *data_mid, *data_high; 45 | unsigned int sample_length; 46 | unsigned int offset; 47 | 48 | gmtl::Point3f location; 49 | 50 | Animated* animation; 51 | void setLocation(gmtl::Point3f p); 52 | bool isAnimated(); 53 | gmtl::Point3f getLocation(); 54 | gmtl::Point3f getLocation(int i); 55 | 56 | gmtl::Rayf* SoundRay(int keyframeID = -1); 57 | bool isMeshSource(); 58 | float getGain(); 59 | 60 | /// Returns only the corresponding frequency band of the file. Regardless of 61 | /// the exact instantiation class, it always returns a SoundFile*. 62 | virtual SoundFile* Band(int I) = 0; 63 | virtual std::string toString() = 0; 64 | virtual ~AbstractSoundFile() {}; 65 | }; 66 | 67 | /// This class inherits from AbstractSoundFile and instantiates a sound source 68 | /// based on a single source .WAVE file. To split the file into the three 69 | /// frequency bands an equalizer algorithm is used. 70 | class SoundFile : public AbstractSoundFile { 71 | private: 72 | std::string filename; 73 | public: 74 | static float f1; 75 | static float f2; 76 | static float f3; 77 | SoundFile(); 78 | ~SoundFile(); 79 | SoundFile(float* data,int length, unsigned int offset, bool is_owner); 80 | SoundFile* Band(int I); 81 | /// Returns a section of the sound file. 82 | /// NOTE: No data is copied on the pointer to the first element is increased. 83 | SoundFile* Section(unsigned int start, unsigned int length); 84 | /// Returns a section of the sound file. 85 | /// NOTE: No data is copied on the pointer to the first element is increased. 86 | SoundFile* Section(float start, float length = -1.0f); 87 | std::string toString(); 88 | /// Sets the center frequencies of the three frequency bands used by the 89 | /// equalizer algorithm. 90 | static void SetEqBands(float f1,float f2,float f3); 91 | }; 92 | 93 | /// This class inherits from AbstractSoundFile and instantiates a sound source 94 | /// based on three source .WAVE file. Therefore the equalizer algorithm 95 | /// does not need to be used. 96 | class TripleBandSoundFile : public AbstractSoundFile { 97 | private: 98 | std::string filename[3]; 99 | public: 100 | TripleBandSoundFile(); 101 | ~TripleBandSoundFile(); 102 | SoundFile* Band(int I); 103 | std::string toString(); 104 | }; 105 | 106 | #endif -------------------------------------------------------------------------------- /src/Scene.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef SCENE_H 21 | #define SCENE_H 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #include "Material.h" 34 | #include "Recorder.h" 35 | #include "Distributions.h" 36 | 37 | /// This class encapsulates all datatypes in the .EAR file format and provides 38 | /// methods to tracing the rays from the sound sources bouncing off of the 39 | /// meshes into the recorders. Ideally this class would also create a reference 40 | /// to some sort of grid acceleration structure to speed up the triangle-ray 41 | /// intersection tests, but this is currently not the case. 42 | class Scene { 43 | private: 44 | /// Intersects the ray in sound_ray with the triangles of the scene's meshes. 45 | /// In case no hit is found (for example between there is no geometry in that 46 | /// direction) 0 is returned. The arguments that are passed by reference return 47 | /// information about the triangle normal on which the ray is reflected, the 48 | /// path length of the previous origin to the point of intersection, the material 49 | /// at the hit point and the type of bounce which is to be processed, meaning 50 | /// whether the ray is reflected or refracted (through a transparent material) 51 | inline gmtl::Rayf* Bounce(int band, gmtl::Rayf* sound_ray, gmtl::Vec3f*& surface_normal, float& l, Material*& mat, BounceType& bt); 52 | /// Sees whether there is a free line of sight between the point p and point x. 53 | /// This is done by testing all triangles in the scene for intersection with the 54 | /// line segment between p and x until an intersection is found. So this is a 55 | /// timeconsuming operation that could be sped up by using a grid acceleration 56 | /// structure 57 | inline gmtl::LineSegf* Connect(const gmtl::Point3f* p, const gmtl::Point3f& x); 58 | public: 59 | std::vector listeners; 60 | std::vector sources; 61 | std::vector meshes; 62 | /// Adds a listener to the scene. 63 | void addListener(Recorder* l); 64 | /// Adds a sound source to the scene. 65 | void addSoundSource(AbstractSoundFile* s); 66 | /// Adds a mesh to the scene. Under the hood all triangles are stored inside a 67 | /// single mesh object. 68 | void addMesh(Mesh* m); 69 | /// Adds a material definition to the scene. 70 | void addMaterial(Material* m); 71 | /// Renders an impulse response for the sound file in sound (an index in the 72 | /// sources vector) for the frequency band specified in band. Multiple recorders 73 | /// are supported to be rendered simultaneously in which case for every ray-triangle 74 | /// intersection a connection is sought between the intersection point and 75 | /// every recorder location. This is more efficient than rendering each recorder 76 | /// separately, but does come for free either. 77 | void Render(int band, int sound, float absorbtion_factor, int num_samples, float dry, const std::vector& rec, int keyframeID = -1); 78 | ~Scene(); 79 | }; 80 | 81 | #endif -------------------------------------------------------------------------------- /src/MonoRecorder.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "boost/thread/thread.hpp" 25 | 26 | #include "../lib/wave/WaveFile.h" 27 | 28 | #include "SoundFile.h" 29 | #include "MonoRecorder.h" 30 | 31 | #define USE_FILTER 32 | 33 | void MonoRecorder::setLocation(gmtl::Point3f& loc) { location = loc; } 34 | std::string MonoRecorder::getFilename() { return filename; } 35 | void MonoRecorder::setFilename(const std::string& s) { filename = s; } 36 | int MonoRecorder::trackCount() { return 1; } 37 | 38 | MonoRecorder::MonoRecorder(bool fromFile) { 39 | is_truncated = is_processed = false; 40 | if ( fromFile ) { 41 | stamped_offset = 0; 42 | Read(false); 43 | assertid("OUT1"); 44 | filename = ReadString(); 45 | float t = ReadFloat(); 46 | if ( Datatype::PeakId() == "anim" ) { 47 | animation = new Animated(); 48 | } else { 49 | setLocation(ReadVec()); 50 | animation = 0; 51 | } 52 | std::cout << this->toString(); 53 | } else { 54 | animation = 0; 55 | filename = ""; 56 | } 57 | has_samples = save_processed = false; 58 | tracks.push_back(new RecorderTrack()); 59 | } 60 | Recorder* MonoRecorder::getBlankCopy(int secs) { 61 | MonoRecorder* r = new MonoRecorder(false); 62 | r->stamped_offset = 0; 63 | r->filename = filename; 64 | r->location = location; 65 | r->animation = animation; 66 | r->save_processed = save_processed; 67 | return r; 68 | } 69 | bool MonoRecorder::Save(const std::string& fn, bool norm, float norm_max) { 70 | WaveFile w; 71 | const RecorderTrack& to_save = *(save_processed ? processed_tracks[0] : tracks[0]); 72 | w.FromFloat(&to_save[0],to_save.getLength(),norm,norm_max); 73 | w.Save(fn.c_str()); 74 | return true; 75 | } 76 | bool MonoRecorder::Save() { 77 | return Save(filename,false); 78 | } 79 | inline void MonoRecorder::_Sample(int i, float v) { 80 | this->tracks[0]->operator [](i) += v; 81 | has_samples = true; 82 | } 83 | void MonoRecorder::Record(const gmtl::Vec3f& dir, float a, float t, float dist, int band, int kf) { 84 | const int s = (int) (t*44100.0); 85 | #ifdef USE_FILTER 86 | const float width = sqrt(dist); 87 | float ampl = 2.0f * a / width; 88 | const int w = (int) ceil(width); 89 | const float step = ampl / w; 90 | for ( int i = 0; i < w; ++ i ) { 91 | _Sample(i+s,ampl); 92 | ampl -= step; 93 | } 94 | #else 95 | _Sample(s,a); 96 | #endif 97 | } 98 | void MonoRecorder::setLocation(gmtl::Point3f p) { 99 | location = p; 100 | } 101 | bool MonoRecorder::isAnimated() { return animation > 0; } 102 | const gmtl::Point3f& MonoRecorder::getLocation(int i) const { 103 | if ( i >= 0 && animation > 0 ) { 104 | return animation->Evaluate(i); 105 | } else { 106 | return location; 107 | } 108 | } 109 | float MonoRecorder::getSegmentLength(int i) { 110 | return animation->SegmentLength(i); 111 | } 112 | std::string MonoRecorder::toString() { 113 | std::string loc; 114 | if ( this->isAnimated() ) { 115 | loc = this->animation->toString(); 116 | } else { 117 | std::stringstream ss; 118 | ss << std::setprecision(std::cout.precision()) << std::fixed; 119 | ss << this->location; 120 | loc = ss.str(); 121 | } 122 | return "Recorder\n +- mono\n +- location: " + loc + "\n"; 123 | } 124 | Animated* MonoRecorder::getAnimationData() { 125 | return animation; 126 | } -------------------------------------------------------------------------------- /src/Datatype.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | 22 | #include "Datatype.h" 23 | #include "Settings.h" 24 | 25 | Datatype::Datatype() { 26 | if ( ! input ) return; 27 | id = (int*)input; 28 | data = input + 4; 29 | if ( isInt() || isFloat() || isVec() || isString() || isTri() ) { 30 | length = 0; 31 | } else { 32 | length = (int*)(input+4); 33 | data += 4; 34 | } 35 | char* aid = (char*)id; 36 | if ( !scanning && Settings::GetBool("debug") ) 37 | std::cout << "Reading '" << aid[0] << aid[1] << aid[2] << aid[3] << "' block of " << *length << " bytes" << std::endl; 38 | } 39 | void Datatype::assertid(const char* c) { 40 | if ( *((int*)c) != *id ) { 41 | char* aid = (char*)id; 42 | std::stringstream ss; 43 | ss << "Found '"; 44 | ss.write(aid,4); 45 | ss << "' while expecting '" << c << "'"; 46 | DatatypeException(ss.str()); 47 | } 48 | } 49 | bool Datatype::isInt() { 50 | return *id == *((int*)"int4"); 51 | } 52 | bool Datatype::isFloat() { 53 | return *id == *((int*)"flt4"); 54 | } 55 | bool Datatype::isString() { 56 | return *id == *((int*)"str "); 57 | } 58 | bool Datatype::isVec() { 59 | return *id == *((int*)"vec3"); 60 | } 61 | bool Datatype::isTri() { 62 | return *id == *((int*)"tri "); 63 | } 64 | void Datatype::push(Datatype* d) { 65 | readpos r; 66 | r.input = input; 67 | r.input_length = input_length; 68 | input = d->data - (d->length ? 8 : 4); 69 | input_length = d->length ? (*d->length + 8) : 0; 70 | readstack.push_back(r); 71 | } 72 | void Datatype::pop() { 73 | readpos r = readstack.back(); 74 | readstack.pop_back(); 75 | input = r.input; 76 | input_length = r.input_length; 77 | } 78 | std::string Datatype::PeakId() { 79 | return std::string (input,4); 80 | } 81 | bool Datatype::SetInput(std::string filename) { 82 | std::ifstream f (filename.c_str(), std::ios_base::binary); 83 | if ( ! f.good() ) return false; 84 | f.seekg(0,std::ios_base::end); 85 | input_length = f.tellg(); 86 | f.seekg(0,std::ios_base::beg); 87 | buffer = input = new char[input_length]; 88 | f.read(input,input_length); 89 | if ( Datatype::PeakId() != ".EAR" ) return false; 90 | input += 4;input_length -= 4; 91 | return true; 92 | } 93 | void Datatype::Dispose() { 94 | delete[] buffer; 95 | } 96 | Datatype* Datatype::Read(bool r) { 97 | Datatype* d = new Datatype(); 98 | const int header_size = d->length ? 8 : 4; 99 | int length = 0; 100 | if ( d->length ) length = *d->length; 101 | else if ( d->isFloat() || d->isInt() ) length = 4; 102 | else if ( d->isVec() ) length = 24; 103 | else if ( d->isTri() ) length = 24*3; 104 | else if ( d->isString() ) { 105 | char* temp = d->data; 106 | int max_length = input_length - header_size + 4; 107 | while ( *(temp++) && (max_length--) ) length ++; 108 | length += 4 - (length%4); 109 | } 110 | input += header_size;input_length -= header_size; 111 | if ( r ) { input += length; input_length -= length; } 112 | return d; 113 | } 114 | float Datatype::ReadFloat() { 115 | Datatype* d = Read(); 116 | d->assertid("flt4"); 117 | float f = *((float*)d->data); 118 | delete d; 119 | return f; 120 | } 121 | gmtl::Vec3f Datatype::ReadVec() { 122 | Datatype* d = Read(false); 123 | d->assertid("vec3"); 124 | float x = ReadFloat(); 125 | float y = ReadFloat(); 126 | float z = ReadFloat(); 127 | return gmtl::Vec3f(x,y,z); 128 | } 129 | gmtl::Point3f Datatype::ReadPoint() { 130 | Datatype* d = Read(false); 131 | d->assertid("vec3"); 132 | float x = ReadFloat(); 133 | float y = ReadFloat(); 134 | float z = ReadFloat(); 135 | return gmtl::Point3f(x,y,z); 136 | } 137 | std::string Datatype::ReadString() { 138 | Datatype* d = Read(); 139 | d->assertid("str "); 140 | int length = 0; 141 | char* temp = d->data; 142 | int max_length = input_length + 4; 143 | while ( *(temp++) ) length ++; 144 | std::string s = std::string(d->data,length); 145 | delete d; 146 | return s; 147 | } 148 | Datatype* Datatype::Scan(std::string a) { 149 | scanning = true; 150 | char* old_input = input; 151 | int old_input_length = input_length; 152 | Datatype* ret = 0; 153 | while( Datatype::input_length ) { 154 | const bool b = Datatype::PeakId() == a; 155 | Datatype* d = Datatype::Read(); 156 | if ( b ) { ret = d; break; } 157 | delete d; 158 | } 159 | input = old_input; 160 | input_length = old_input_length; 161 | scanning = false; 162 | return ret; 163 | } 164 | char* Datatype::input = 0; 165 | char* Datatype::buffer = 0; 166 | std::string Datatype::prefix = ""; 167 | std::string Datatype::stringblock = ""; 168 | int Datatype::input_length = 0; 169 | bool Datatype::scanning = false; 170 | std::vector Datatype::readstack; 171 | -------------------------------------------------------------------------------- /src/Mesh.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include "Mesh.h" 29 | #include "HelperFunctions.h" 30 | #include "Triangle.h" 31 | #include "Material.h" 32 | 33 | bool Mesh::RayIntersection(gmtl::Rayf* r,gmtl::Point3f* &p, gmtl::Vec3f* &n, Material* &mat) { 34 | float d = 1000000; 35 | float u,v,t; 36 | bool x = false; 37 | Triangle* tri; 38 | std::vector::const_iterator ti; 39 | for(ti=tris.begin(); ti!=tris.end(); ++ti){ 40 | if ( gmtl::intersectDoubleSided(**ti,*r,u,v,t) && t > 0.001f && t < d ) { 41 | d = t; 42 | tri = *ti; 43 | x = true; 44 | } 45 | } 46 | if ( !x ) return false; 47 | p = new gmtl::Point3f(); 48 | *p = r->mOrigin + r->mDir * d; 49 | if ( gmtl::dot(tri->normal,r->mDir) > 0.0 ) { 50 | n = new gmtl::Vec3f(tri->normal * -1.0f); 51 | } else { 52 | n = new gmtl::Vec3f(tri->normal); 53 | } 54 | mat = tri->m; 55 | return x; 56 | } 57 | 58 | bool Mesh::LineIntersection(gmtl::LineSegf* l) { 59 | float u,v,t; 60 | std::vector::const_iterator ti; 61 | for(ti=tris.begin(); ti!=tris.end(); ++ti){ 62 | // intersectDoubleSided is only defined for the gmtl::Ray type, therefore 63 | // we need to check ourselves if t is within (0,1], which is the parametric 64 | // range for the gmtl::LineSeg type. 65 | if ( gmtl::intersectDoubleSided(**ti,*l,u,v,t) && t > 1e-5f && t < 1.0f ) { 66 | return true; 67 | } 68 | 69 | } 70 | return false; 71 | } 72 | 73 | Mesh* Mesh::Empty() { 74 | return new Mesh(false); 75 | } 76 | 77 | Mesh::Mesh(bool from_file) { 78 | total_area = 0; 79 | total_weighted_area = 0; 80 | if ( ! from_file ) return; 81 | Read(false); 82 | assertid("MESH"); 83 | std::string m = ReadString(); 84 | material = materials[m]; 85 | const float absorption = 1.0f - material->absorption_coefficient[1]; 86 | while ( Datatype::PeakId() == "tri " ) { 87 | Triangle* tri = new Triangle(); 88 | tri->m = material; 89 | tris.push_back(tri); 90 | total_area += tri->area; 91 | total_weighted_area += tri->area * absorption; 92 | } 93 | has_boundingbox = false; 94 | BoundingBox(); 95 | std::stringstream ss; 96 | ss << std::setprecision(std::cout.precision()) << std::fixed; 97 | std::string indent = std::string(" ",Datatype::prefix.size()); 98 | ss << Datatype::prefix << "Mesh \r\n" << indent << " +- faces: " << tris.size() << std::endl << indent << " +- material: '" << material->name << "'" << std::endl; 99 | ss << indent << " +- bounds: (" << xmin << ", " << ymin << ", " << zmin << ") - (" << xmax << ", " << ymax << ", " << zmax << ")" << std::endl; 100 | ss << indent << " +- surface area: " << total_area << std::endl; 101 | ss << indent << " +- total absorption: " << total_weighted_area << std::endl; 102 | ss << indent << " +- volume: " << Volume() << std::endl; 103 | if ( indent.size() ) { 104 | Datatype::stringblock = ss.str(); 105 | } else { 106 | std::cout << ss.str(); 107 | } 108 | } 109 | 110 | Mesh::~Mesh() { 111 | for ( std::vector::const_iterator it = tris.begin(); it != tris.end(); ++ it ) { 112 | delete *it; 113 | } 114 | } 115 | 116 | void Mesh::Combine(Mesh* m) { 117 | std::vector::const_iterator ti; 118 | for( ti = m->tris.begin(); ti != m->tris.end(); ++ ti ) { 119 | tris.push_back(*ti); 120 | } 121 | total_area += m->total_area; 122 | BoundingBox(); 123 | } 124 | 125 | void Mesh::BoundingBox() { 126 | xmin = ymin = zmin = 1e9; 127 | xmax = ymax = zmax = -1e9; 128 | std::vector::const_iterator it; 129 | for( it = tris.begin(); it != tris.end(); it ++ ) { 130 | Triangle* t = *it; 131 | for ( int i = 0; i < 3; i ++ ) { 132 | if ( t->mVerts[i][0] < xmin ) xmin = t->mVerts[i][0]; 133 | if ( t->mVerts[i][1] < ymin ) ymin = t->mVerts[i][1]; 134 | if ( t->mVerts[i][2] < zmin ) zmin = t->mVerts[i][2]; 135 | if ( t->mVerts[i][0] > xmax ) xmax = t->mVerts[i][0]; 136 | if ( t->mVerts[i][1] > ymax ) ymax = t->mVerts[i][1]; 137 | if ( t->mVerts[i][2] > zmax ) zmax = t->mVerts[i][2]; 138 | } 139 | } 140 | has_boundingbox = true; 141 | } 142 | 143 | void Mesh::SamplePoint(gmtl::Point3f& p, gmtl::Vec3f& n) { 144 | float x = gmtl::Math::rangeRandom(0,total_area); 145 | std::vector::const_iterator it; 146 | for( it = tris.begin(); it != tris.end(); it ++ ) { 147 | x -= (*it)->area; 148 | if ( x < 0 ) { 149 | (*it)->SamplePoint(p); 150 | n = (*it)->normal; 151 | break; 152 | } 153 | } 154 | } 155 | 156 | float Mesh::Area() const { 157 | return total_area; 158 | } 159 | 160 | float Mesh::TotalAbsorption() const { 161 | return total_weighted_area; 162 | } 163 | 164 | float Mesh::Volume() const { 165 | // http://stackoverflow.com/questions/1406029/how-to-calculate-the-volume-of-a-3d-mesh-object-the-surface-of-which-is-made-up 166 | float volume = 0.0f; 167 | std::vector::const_iterator it; 168 | for( it = tris.begin(); it != tris.end(); it ++ ) { 169 | volume += (*it)->SignedVolume(); 170 | } 171 | return volume; 172 | } 173 | 174 | float Mesh::AverageAbsorption() const { 175 | return total_weighted_area / total_area; 176 | } 177 | 178 | std::map Mesh::materials; 179 | -------------------------------------------------------------------------------- /src/StereoRecorder.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "../lib/wave/WaveFile.h" 25 | #include "SoundFile.h" 26 | #include "StereoRecorder.h" 27 | 28 | void StereoRecorder::setLocation(gmtl::Point3f& loc) { location = loc; } 29 | std::string StereoRecorder::getFilename() { return filename; } 30 | void StereoRecorder::setFilename(const std::string& s) { filename = s; } 31 | int StereoRecorder::trackCount() { return 2; } 32 | 33 | StereoRecorder::StereoRecorder(bool fromFile) { 34 | is_truncated = is_processed = false; 35 | if ( fromFile ) { 36 | stamped_offset = 0; 37 | Read(false); 38 | assertid("OUT2"); 39 | filename = ReadString(); 40 | float t = ReadFloat(); 41 | if ( Datatype::PeakId() == "anim" ) { 42 | animation = new Animated(); 43 | } else { 44 | setLocation(ReadPoint()); 45 | animation = 0; 46 | } 47 | if ( Datatype::PeakId() == "anim" ) { 48 | right_ear_animation = new Animated(); 49 | } else { 50 | right_ear = ReadVec(); 51 | right_ear_animation = 0; 52 | } 53 | head_size = ReadFloat(); 54 | head_absorption = ReadVec(); 55 | for ( unsigned int i = 0; i < 3; ++ i ) { 56 | head_absorption[i] = std::max(0.0f,powf(1.0f-head_absorption[i],4)); 57 | } 58 | std::cout << this->toString(); 59 | } else { 60 | animation = 0; 61 | right_ear_animation = 0; 62 | filename = ""; 63 | } 64 | has_samples = save_processed = false; 65 | tracks.push_back(new RecorderTrack()); 66 | tracks.push_back(new RecorderTrack()); 67 | } 68 | Recorder* StereoRecorder::getBlankCopy(int secs) { 69 | StereoRecorder* r = new StereoRecorder(false); 70 | r->stamped_offset = 0; 71 | r->filename = filename; 72 | r->location = location; 73 | r->animation = animation; 74 | r->save_processed = save_processed; 75 | r->right_ear = right_ear; 76 | r->right_ear_animation = right_ear_animation; 77 | r->head_size = head_size; 78 | r->head_absorption = head_absorption; 79 | return r; 80 | } 81 | bool StereoRecorder::Save(const std::string& fn, bool norm, float norm_max) { 82 | WaveFile w; 83 | const RecorderTrack& left = *(save_processed ? processed_tracks[0] : tracks[0]); 84 | const RecorderTrack& right = *(save_processed ? processed_tracks[1] : tracks[1]); 85 | w.FromFloat(&left[0],&right[0],left.getLength(),right.getLength(),norm); 86 | w.Save(fn.c_str()); 87 | return true; 88 | } 89 | bool StereoRecorder::Save() { 90 | return Save(filename,false); 91 | } 92 | inline void StereoRecorder::_Sample(int i, float v, int channel) { 93 | if ( i < 0 ) return; 94 | this->tracks[channel]->operator [](i) += v; 95 | has_samples = true; 96 | } 97 | void StereoRecorder::Record(const gmtl::Vec3f& dir, float a, float t, float dist, int band, int kf) { 98 | const float dot = gmtl::dot(dir,getRightEar(kf)); 99 | const float time_difference = head_size / 343.0f; 100 | 101 | const int s_right = (int) ((t-(dot*time_difference))*44100.0); 102 | const int s_left = (int) ((t+(dot*time_difference))*44100.0); 103 | 104 | const float width = sqrt(dist); 105 | const float ampl = 2.0f * a / width; 106 | 107 | float ampl_left = ampl; 108 | float ampl_right = ampl; 109 | 110 | const float intensity_difference = fabs(dot); 111 | 112 | // Interaural intensity differences are higher for the high frequency bands 113 | const float factor = powf(head_absorption[band],intensity_difference*head_size); 114 | if ( dot < 0 ) { 115 | ampl_right *= factor*factor; 116 | } else { 117 | ampl_left *= factor*factor; 118 | } 119 | 120 | const int w = (int) ceil(width); 121 | const float step_left = ampl_left / w; 122 | const float step_right = ampl_right / w; 123 | 124 | for ( int i = 0; i < w; ++ i ) { 125 | _Sample(i+s_left,ampl_left,0); 126 | ampl_left -= step_left; 127 | _Sample(i+s_right,ampl_right,1); 128 | ampl_right -= step_right; 129 | } 130 | } 131 | void StereoRecorder::setLocation(gmtl::Point3f p) { 132 | location = p; 133 | } 134 | bool StereoRecorder::isAnimated() { return animation > 0; } 135 | const gmtl::Point3f& StereoRecorder::getLocation(int i) const { 136 | if ( i >= 0 && animation > 0 ) { 137 | return animation->Evaluate(i); 138 | } else { 139 | return location; 140 | } 141 | } 142 | const gmtl::Vec3f& StereoRecorder::getRightEar(int i) { 143 | if ( i >= 0 && right_ear_animation > 0 ) { 144 | return right_ear_animation->Evaluate(i); 145 | } else { 146 | return right_ear; 147 | } 148 | } 149 | float StereoRecorder::getSegmentLength(int i) { 150 | return animation->SegmentLength(i); 151 | } 152 | std::string StereoRecorder::toString() { 153 | std::stringstream ss; 154 | ss << std::setprecision(std::cout.precision()) << std::fixed; 155 | ss << "Recorder" << std::endl << " +- stereo" << std::endl << " +- location: "; 156 | if ( this->animation ) { 157 | ss << this->animation->toString(); 158 | } else { 159 | ss << this->location; 160 | } 161 | ss << std::endl << " +- right: "; 162 | if ( this->right_ear_animation ) { 163 | ss << this->right_ear_animation->toString(); 164 | } else { 165 | ss << this->right_ear; 166 | } 167 | ss << std::endl << " +- head size: " << head_size << std::endl << " +- head absorption: " << head_absorption << std::endl; 168 | return ss.str(); 169 | } 170 | Animated* StereoRecorder::getAnimationData() { 171 | return animation; 172 | } -------------------------------------------------------------------------------- /win/EAR.vcproj: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 27 | 30 | 33 | 36 | 39 | 42 | 51 | 54 | 57 | 60 | 68 | 71 | 74 | 77 | 80 | 83 | 86 | 89 | 90 | 99 | 102 | 105 | 108 | 111 | 114 | 129 | 132 | 135 | 138 | 146 | 149 | 152 | 155 | 158 | 161 | 164 | 167 | 168 | 169 | 170 | 175 | 180 | 185 | 186 | 187 | 192 | 195 | 196 | 199 | 200 | 203 | 204 | 207 | 208 | 211 | 212 | 215 | 216 | 219 | 220 | 223 | 224 | 227 | 228 | 231 | 232 | 235 | 236 | 239 | 240 | 243 | 244 | 247 | 248 | 251 | 252 | 253 | 258 | 261 | 262 | 265 | 266 | 269 | 270 | 273 | 274 | 277 | 278 | 281 | 282 | 285 | 286 | 289 | 290 | 293 | 294 | 297 | 298 | 301 | 302 | 305 | 306 | 309 | 310 | 313 | 314 | 317 | 318 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | -------------------------------------------------------------------------------- /lib/wave/WaveFile.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | // Adapted from: 21 | // http://www.codeproject.com/KB/audio-video/wave_class_for_playing_and_recording.aspx 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #ifdef _MSC_VER 30 | #include 31 | #endif 32 | 33 | #include "WaveFile.h" 34 | 35 | void WaveFile::Init() { 36 | memset(&desc, 0, sizeof(wavedescr)); 37 | memset(&fmt, 0, sizeof(wavefmt)); 38 | data = 0; 39 | size = 0; 40 | } 41 | 42 | WaveFile::WaveFile() { 43 | Init(); 44 | } 45 | 46 | WaveFile::WaveFile(const char* fn) { 47 | Init(); 48 | Load(fn); 49 | } 50 | 51 | WaveFile::~WaveFile() 52 | { 53 | free(data); 54 | } 55 | 56 | bool WaveFile::Load(const char* fn) 57 | { 58 | #ifdef _MSC_VER 59 | int buffer_size = MultiByteToWideChar(CP_UTF8,0,fn,-1,0,0); 60 | wchar_t* longname = new wchar_t[buffer_size]; 61 | MultiByteToWideChar(CP_UTF8,0,fn,-1,longname,buffer_size); 62 | FILE* file = _wfopen(longname,TEXT("rb")); 63 | delete[] longname; 64 | #else 65 | FILE* file = fopen(fn,"rb"); 66 | #endif 67 | if (file) { 68 | // Read .WAV descriptor 69 | bool read_success = fread(&desc, sizeof(wavedescr), 1, file) == 1; 70 | 71 | // Check for valid .WAV file 72 | if (read_success && strncmp(desc.wave, "WAVE", 4) == 0) 73 | { 74 | // Read .WAV format 75 | read_success = fread(&fmt, sizeof(wavefmt), 1, file) == 1; 76 | 77 | // Check for valid .WAV file 78 | if (read_success && (strncmp(fmt.id, "fmt", 3) == 0) && (fmt.format == 1)) 79 | { 80 | // Read next chunk 81 | char id[4]; 82 | unsigned int block_size; 83 | read_success = fread(id, 1, 4, file) == 4 && 84 | fread(&block_size, 4, 1, file) == 1; 85 | unsigned int offset = ftell(file); 86 | 87 | // Read .WAV data 88 | while (read_success && (offset < desc.size)) 89 | { 90 | // Check for .WAV data chunk 91 | if (strncmp(id, "data", 4) == 0) 92 | { 93 | data = (char*)realloc(data, (size+block_size)); 94 | read_success = fread(data+size, 1, block_size, file) == block_size; 95 | size += block_size; 96 | } else { 97 | read_success = fseek(file,block_size,SEEK_CUR) == 0; 98 | } 99 | 100 | if ( !read_success ) break; 101 | 102 | // Read next chunk 103 | read_success = fread(id, 1, 4, file) == 4 && 104 | fread(&size, 4, 1, file) == 1; 105 | offset = ftell(file); 106 | } 107 | } 108 | } 109 | 110 | // Close .WAV file 111 | fclose(file); 112 | } 113 | 114 | if ( size ) { 115 | const int bytes_per_sample = fmt.bitsPerSample >> 3; 116 | sample_size = size / bytes_per_sample / fmt.channels; 117 | } else { 118 | sample_size = 0; 119 | } 120 | 121 | return true; 122 | } 123 | 124 | bool WaveFile::Save(const char* fn) 125 | { 126 | #ifdef _MSC_VER 127 | int buffer_size = MultiByteToWideChar(CP_UTF8,0,fn,-1,0,0); 128 | wchar_t* longname = new wchar_t[buffer_size]; 129 | MultiByteToWideChar(CP_UTF8,0,fn,-1,longname,buffer_size); 130 | FILE* file = _wfopen(longname,TEXT("wb")); 131 | delete[] longname; 132 | #else 133 | FILE* file = fopen(fn,"wb"); 134 | #endif 135 | if (file) 136 | { 137 | // Save .WAV descriptor 138 | // The size of the data buffer plus some header bytes 139 | desc.size = size + 36; 140 | fwrite(&desc, sizeof(wavedescr), 1, file); 141 | 142 | // Save .WAV format 143 | fwrite(&fmt, sizeof(wavefmt), 1, file); 144 | 145 | // Write .WAV data 146 | fwrite("data", 1, 4, file); 147 | fwrite(&size, 4, 1, file); 148 | fwrite(data, 1, size, file); 149 | 150 | // Close .WAV file 151 | fclose(file); 152 | } 153 | 154 | return true; 155 | } 156 | 157 | float* WaveFile::ToFloat() { 158 | // Return 0 if format is not understood or the data is empty 159 | if ( fmt.bitsPerSample != 8 && fmt.bitsPerSample != 16 && 160 | fmt.bitsPerSample != 24 ) 161 | return 0; 162 | if ( ! size || ! data ) return 0; 163 | 164 | const int bytes_per_sample = fmt.bitsPerSample >> 3; 165 | const float max_sample = (float) (2<<(fmt.bitsPerSample-2)); 166 | float* f = new float[sample_size]; 167 | char* ptr = data; 168 | for ( unsigned int i = 0; i < sample_size; ++ i) { 169 | f[i] = 0.0f; 170 | for ( int c = 0; c < fmt.channels; ++ c ) { 171 | float current_sample = 0.f; 172 | if ( bytes_per_sample == 1) { 173 | current_sample = *((unsigned char*)ptr)-128.0f; 174 | } else if ( bytes_per_sample == 2 ) { 175 | current_sample = *(short*)ptr; 176 | } else if ( bytes_per_sample == 3 ) { 177 | const unsigned char c1 = *((unsigned char*)(ptr+0)); 178 | const unsigned char c2 = *((unsigned char*)(ptr+1)); 179 | const unsigned char c3 = *((unsigned char*)(ptr+2)); 180 | current_sample = (float)(c1|(c2<<8)|(c3<<16)|((c3&0x80)?(0xff<<24):(0))); 181 | } 182 | ptr += bytes_per_sample; 183 | f[i] += (float) current_sample / max_sample; 184 | } 185 | f[i] /= fmt.channels; 186 | } 187 | return f; 188 | } 189 | 190 | bool WaveFile::FromFloat(const float* f, int length, bool norm, float max) { 191 | data = new char[length*2]; 192 | size = length * 2; 193 | short* s = (short*) data; 194 | if ( norm ) { 195 | if ( max < 0 ) { 196 | max = -1e9; 197 | for (long i=0; i max ) max = fi; 200 | } 201 | max /= 0.8f; 202 | } else { 203 | max /= 0.95f; 204 | } 205 | } else { 206 | max = 1.0f; 207 | } 208 | for (long i=0; i max ) max = left[i]; 233 | } 234 | for (long i=0; i max ) max = right[i]; 236 | } 237 | max /= 0.8f; 238 | } else { 239 | max = 1.0f; 240 | } 241 | for (long i=0; i. * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #include "../lib/wave/WaveFile.h" 26 | #include "../lib/equalizer/Equalizer.h" 27 | 28 | #include "Animated.h" 29 | #include "HelperFunctions.h" 30 | #include "SoundFile.h" 31 | #include "Distributions.h" 32 | 33 | SoundFile::SoundFile() { 34 | Read(false); 35 | assertid("SSRC"); 36 | filename = ReadString(); 37 | WaveFile w(filename.c_str()); 38 | data = w.ToFloat(); 39 | sample_owner = true; 40 | sample_length = w.GetSampleSize(); 41 | if ( !data || !sample_length ) { 42 | throw DatatypeException("Failed to open sound file " + filename); 43 | } 44 | soundfiles[0] = soundfiles[1] = soundfiles[2] = 0; 45 | data_low = data_mid = data_high = 0; 46 | mesh = 0; 47 | animation = 0; 48 | if ( Datatype::PeakId() == "anim" ) { 49 | animation = new Animated(); 50 | } else if ( Datatype::PeakId() == "mesh" ) { 51 | Datatype::prefix = " +- " + Datatype::prefix; 52 | mesh = new Mesh(); 53 | Datatype::prefix = Datatype::prefix.substr(4); 54 | } else { 55 | setLocation(ReadPoint()); 56 | } 57 | if ( Datatype::PeakId() == "flt4" ) { 58 | gain = Datatype::ReadFloat(); 59 | } else { 60 | gain = 1.0f; 61 | } 62 | if ( Datatype::PeakId() == "flt4" ) { 63 | offset = (unsigned int) (Datatype::ReadFloat() * 44100.0f); 64 | } else { 65 | offset = 0; 66 | } 67 | std::cout << this->toString(); 68 | std::cout << Datatype::stringblock; 69 | Datatype::stringblock = ""; 70 | } 71 | SoundFile::~SoundFile() { 72 | if ( sample_owner ) { 73 | delete mesh; 74 | delete animation; 75 | delete[] data; 76 | delete[] data_low; 77 | delete[] data_mid; 78 | delete[] data_high; 79 | delete soundfiles[0]; 80 | delete soundfiles[1]; 81 | delete soundfiles[2]; 82 | } 83 | } 84 | TripleBandSoundFile::TripleBandSoundFile() { 85 | Read(false); 86 | assertid("3SRC"); 87 | for ( int i = 0; i < 3; ++ i ) { 88 | std::string fn = ReadString(); 89 | WaveFile w(fn.c_str()); 90 | soundfiles[i] = new SoundFile(w.ToFloat(),w.GetSampleSize(),0,true); 91 | filename[i] = fn; 92 | } 93 | mesh = 0; 94 | animation = 0; 95 | if ( Datatype::PeakId() == "anim" ) { 96 | animation = new Animated(); 97 | } else if ( Datatype::PeakId() == "mesh" ) { 98 | Datatype::prefix = " +- " + Datatype::prefix; 99 | mesh = new Mesh(); 100 | Datatype::prefix = Datatype::prefix.substr(4); 101 | } else { 102 | setLocation(ReadPoint()); 103 | } 104 | if ( Datatype::PeakId() == "flt4" ) { 105 | gain = Datatype::ReadFloat(); 106 | } else { 107 | gain = 1.0f; 108 | } 109 | if ( Datatype::PeakId() == "flt4" ) { 110 | offset = (unsigned int) (Datatype::ReadFloat() * 44100.0f); 111 | } else { 112 | offset = 0; 113 | } 114 | std::cout << this->toString(); 115 | std::cout << Datatype::stringblock; 116 | Datatype::stringblock = ""; 117 | } 118 | TripleBandSoundFile::~TripleBandSoundFile() { 119 | for ( int i = 0; i < 3; ++ i ) { 120 | delete soundfiles[i]; 121 | } 122 | delete animation; 123 | delete mesh; 124 | } 125 | SoundFile::SoundFile(float* d,int length, unsigned int o, bool is_owner) { 126 | data = d; 127 | sample_owner = is_owner; 128 | sample_length = length; 129 | offset = o; 130 | mesh = 0; 131 | animation = 0; 132 | data_low = data_mid = data_high = 0; 133 | soundfiles[0] = soundfiles[1] = soundfiles[2] = 0; 134 | } 135 | void AbstractSoundFile::setLocation(gmtl::Point3f p) { 136 | location = p; 137 | } 138 | bool AbstractSoundFile::isAnimated() { return animation > 0; } 139 | gmtl::Point3f AbstractSoundFile::getLocation() { 140 | return location; 141 | } 142 | gmtl::Point3f AbstractSoundFile::getLocation(int i) { 143 | if ( i >= 0 && animation > 0 ) { 144 | return animation->Evaluate(i); 145 | } else { 146 | return location; 147 | } 148 | } 149 | SoundFile* SoundFile::Band(int I) { 150 | if ( !soundfiles[0] ) { 151 | data_low = new float[sample_length]; 152 | data_mid = new float[sample_length]; 153 | data_high = new float[sample_length]; 154 | Equalizer::Split(data,data_low,data_mid,data_high,sample_length,f1*1000.0f,f2*1000.0f,f3*1000.0f); 155 | soundfiles[0] = new SoundFile(data_low,sample_length,0,false); 156 | soundfiles[1] = new SoundFile(data_mid,sample_length,0,false); 157 | soundfiles[2] = new SoundFile(data_high,sample_length,0,false); 158 | } 159 | return soundfiles[I]; 160 | } 161 | SoundFile* TripleBandSoundFile::Band(int I) { 162 | return soundfiles[I]; 163 | } 164 | SoundFile* SoundFile::Section(unsigned int start, unsigned int length) { 165 | if ( start >= sample_length ) 166 | return new SoundFile(0,0,0,false); 167 | else return new SoundFile(data+start,std::min(length,sample_length-start),offset+start,false); 168 | } 169 | SoundFile* SoundFile::Section(float start, float length) { 170 | const unsigned int int_start = (int) (start * 44100.0f); 171 | const unsigned int int_length = length < 0 ? sample_length - int_start : (int) (length * 44100.0f); 172 | return Section(int_start,int_length); 173 | } 174 | std::string SoundFile::toString() { 175 | std::string loc; 176 | if ( ! mesh ) { 177 | loc = " +- location: "; 178 | if ( this->isAnimated() ) { 179 | loc += this->animation->toString(); 180 | } else { 181 | std::stringstream ss; 182 | ss << std::setprecision(std::cout.precision()) << std::fixed; 183 | ss << this->location; 184 | loc += ss.str(); 185 | } 186 | loc += "\r\n"; 187 | } 188 | std::stringstream ss; 189 | ss << "Sound source\r\n" << loc << " +- data: " << FileName(filename) << " [" << sample_length << " samples]" << std::endl << " +- offset: " << offset << std::endl; 190 | return ss.str(); 191 | } 192 | std::string TripleBandSoundFile::toString() { 193 | std::string loc; 194 | if ( ! mesh ) { 195 | loc = " +- location: "; 196 | if ( this->isAnimated() ) { 197 | loc += this->animation->toString(); 198 | } else { 199 | std::stringstream ss; 200 | ss << std::setprecision(std::cout.precision()) << std::fixed; 201 | ss << this->location; 202 | loc += ss.str(); 203 | } 204 | loc += "\r\n"; 205 | } 206 | std::stringstream ss; 207 | ss << "Sound source\r\n" << loc; 208 | for ( int i = 0; i < 3; ++ i ) { 209 | ss << " +- data" << (i+1) << ": " << FileName(filename[i]) << " [" << soundfiles[i]->sample_length << " samples]" << std::endl; 210 | } 211 | ss << " +- offset: " << offset << std::endl; 212 | return ss.str(); 213 | } 214 | 215 | gmtl::Rayf* AbstractSoundFile::SoundRay(int keyframeID) { 216 | if ( mesh > 0 ) { 217 | gmtl::Point3f p; 218 | gmtl::Vec3f n,d; 219 | mesh->SamplePoint(p,n); 220 | Sample_Hemi(d,n); 221 | return new gmtl::Rayf(p,d); 222 | } else { 223 | gmtl::Point3f p = getLocation(keyframeID); 224 | gmtl::Vec3f d; 225 | Sample_Sphere(d); 226 | return new gmtl::Rayf(p,d); 227 | } 228 | } 229 | 230 | bool AbstractSoundFile::isMeshSource() { 231 | return mesh > 0; 232 | } 233 | 234 | void SoundFile::SetEqBands(float F1, float F2, float F3) { 235 | f1 = F1; f2 = F2; f3 = F3; 236 | } 237 | 238 | float AbstractSoundFile::getGain() { return gain; } 239 | 240 | float SoundFile::f1 = 0.2f; 241 | float SoundFile::f2 = 1.0f; 242 | float SoundFile::f3 = 2.0f; -------------------------------------------------------------------------------- /blender/render_EAR/storyboard.py: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # # 3 | # This file is part of EAR: Evaluation of Acoustics using Ray-tracing. # 4 | # # 5 | # EAR is free software: you can redistribute it and/or modify # 6 | # it under the terms of the GNU General Public License as published by # 7 | # the Free Software Foundation, either version 3 of the License, or # 8 | # (at your option) any later version. # 9 | # # 10 | # EAR is distributed in the hope that it will be useful, # 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 13 | # GNU General Public License for more details. # 14 | # # 15 | # You should have received a copy of the GNU General Public License # 16 | # along with EAR. If not, see . # 17 | # # 18 | ######################################################################## 19 | 20 | ######################################################################## 21 | # # 22 | # Maps the animated location of a blender object to a soundtrack # 23 | # # 24 | ######################################################################## 25 | 26 | import bpy 27 | import wave 28 | import mathutils 29 | import glob 30 | import random 31 | import struct 32 | import math 33 | 34 | # Note: ideally these classes should be updated to use the Python array type 35 | 36 | # A class to read the samples from a .wav file 37 | class SoundFile(): 38 | def __init__(self,filename='in.wav'): 39 | self.filename = bpy.path.abspath(filename) 40 | self.wavefile = wave.open(self.filename,'rb') 41 | self.nchannels, self.sampwidth,\ 42 | self.framerate, self.nframes,\ 43 | self.comptype, self.compname = self.wavefile.getparams() 44 | self.samples = [stof(i) for i in struct.unpack("%dh"%self.nframes, self.wavefile.readframes(self.nframes))] 45 | self.wavefile.close() 46 | 47 | # A class that allocated a list of floats to store a sequence of sonic events 48 | class Recorder(): 49 | def __init__(self): 50 | self.samples = [] 51 | self.framerate = 44100 52 | def stretch(self,l): 53 | if l > len(self.samples): 54 | self.samples += [0.0] * (l-len(self.samples)+1) 55 | def add(self,sf,offset=0.0): 56 | t1 = int(offset*self.framerate) 57 | t2 = t1 + len(sf.samples) 58 | self.stretch(t2) 59 | for i in range(len(sf.samples)): 60 | self.samples[i+t1] += sf.samples[i] 61 | def write(self,fn): 62 | wavefile = wave.open(fn,'wb') 63 | wavefile.setparams((1,2,self.framerate,0,'NONE','not compressed')) 64 | data = [iclamp(stoi(f)) for f in self.samples] 65 | _data = b''.join((struct.pack('h',d) for d in data)) 66 | wavefile.writeframes(_data) 67 | wavefile.close() 68 | def __len__(self): 69 | return len(self.samples) 70 | 71 | # Clamps an integer between a min and a max value 72 | def iclamp(i,min=-32768,max=32767): 73 | return min if i < min else i if i < max else max 74 | 75 | # Maps a floating point value to an unsigned short integer 76 | def stoi(f): 77 | return int((f)*2.0**15.0) 78 | 79 | # Maps an unsigned short integer to a floating point value 80 | def stof(i): 81 | return float(i)/2.0**15.0 82 | 83 | # Sees if the object passes through a portal mesh by 84 | # seeing if the sign of the dot project with a movement 85 | # vector and a face normal flips. And whether a line segment 86 | # from previous location to location intersects with the mesh. 87 | def changed_orientation(loc,object,face,previous,previous_loc): 88 | location = object.matrix_world.inverted() * loc 89 | difference = (location-face.center).normalized() 90 | dot = difference.dot(face.normal) 91 | facing = dot < 0.0 92 | if previous is None: 93 | return False,facing,-1 94 | if facing != previous: 95 | previous_location = object.matrix_world.inverted() * previous_loc 96 | hit_location, normal, face_index = object.ray_cast(previous_location,location) 97 | if face_index != -1: 98 | return True,facing,face_index 99 | return False,facing,-1 100 | 101 | 102 | # Generates a .wav file that contains the sonic events that 103 | # an object encounters along its animated path. When distance 104 | # of step_length is traversed a footstep event is added to, 105 | # when a portal object is passes its corresponding sound is 106 | # added. 107 | def generate(biped, step_length=0.65): 108 | scn = bpy.context.scene 109 | 110 | major,minor = bpy.app.version[0:2] 111 | transpose_matrices = minor >= 62 112 | if transpose_matrices: 113 | location = mathutils.Vector([r[3] for r in biped.matrix_world[0:3]]) 114 | else: 115 | location = mathutils.Vector(biped.matrix_world[3][0:3]) 116 | 117 | old_location = None 118 | 119 | rec = Recorder() 120 | 121 | fcurves = biped.animation_data.action.fcurves 122 | 123 | frame_resolution = 10.0 124 | 125 | steps = [] 126 | 127 | portals = [] 128 | for mesh in [m for m in bpy.data.objects if m.is_portal]: 129 | portals.extend(zip([mesh]*len(mesh.data.faces),list(mesh.data.faces))) 130 | portal_orientations = [None]*len(portals) 131 | 132 | for fr in range(scn.frame_start*int(frame_resolution),(scn.frame_end+1)*int(frame_resolution)): 133 | frame = fr / frame_resolution 134 | for crv in [c for c in fcurves if c.data_path == 'location']: 135 | location[crv.array_index] = crv.evaluate(frame) 136 | if not old_location: 137 | old_location = location.copy() 138 | continue 139 | t = frame/float(scn.render.fps) 140 | portal_index = 0 141 | for portal_object,portal_face in portals: 142 | changed,facing,face_index = changed_orientation(location,portal_object,portal_face,portal_orientations[portal_index],old_location) 143 | portal_orientations[portal_index] = facing 144 | portal_index += 1 145 | if changed: 146 | sound_file = SoundFile(portal_object.filename) 147 | rec.add(sound_file,t) 148 | distance = (location-old_location).length 149 | if distance > step_length: 150 | old_location = location.copy() 151 | steps.append((t,location.copy())) 152 | 153 | meshes = [m for m in scn.objects if m.type == 'MESH'] 154 | 155 | step_locations = [] 156 | 157 | for t,ear_location in steps: 158 | closest_distance = float('+inf') 159 | closest_object = None 160 | closest_location = None 161 | face_index = -1 162 | for m in meshes: 163 | ray_start = ear_location * m.matrix_world.inverted() 164 | ray_end = ray_start + mathutils.Vector([0,0,-2]) 165 | hit_location, normal, mi = m.ray_cast(ray_start,ray_end) 166 | if mi != -1: 167 | face_index = mi 168 | hit_location_global = hit_location * m.matrix_world 169 | distance = (ear_location-hit_location_global).length 170 | if distance < closest_distance: 171 | closest_distance = distance 172 | closest_object = m 173 | closest_location = hit_location_global + mathutils.Vector([0,0,0.01]) 174 | if closest_object: 175 | step_locations.append(closest_location) 176 | material_index = closest_object.data.faces[face_index].material_index 177 | material = closest_object.material_slots[material_index].material 178 | steps_dir = material.stepdir 179 | step_files = glob.glob("%s/*.wav"%bpy.path.abspath(steps_dir)) 180 | if not len(step_files): continue 181 | sound = step_files[random.randrange(0,len(step_files))] 182 | sound_file = SoundFile(sound) 183 | rec.add(sound_file,t) 184 | 185 | if len(rec): 186 | num_keyframes = int(math.ceil(scn.frame_end-scn.frame_start)/scn.frame_step) 187 | key_spacing = len(steps) // (num_keyframes) 188 | locs = [] 189 | for i in range(0,num_keyframes): 190 | locs.append(step_locations[i*key_spacing]) 191 | locs.append(step_locations[-1]) 192 | out_file = bpy.path.abspath('//%s.%d-%d.wav'%(biped.name,scn.frame_start,scn.frame_end)) 193 | rec.write(out_file) 194 | return out_file,locs 195 | else: return None, None -------------------------------------------------------------------------------- /src/Scene.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "Material.h" 31 | #include "MonoRecorder.h" 32 | #include "Distributions.h" 33 | #include "Scene.h" 34 | 35 | // Contributions that are negative, zero, denormal, NaN or infinite are to be discarded 36 | #ifdef _MSC_VER 37 | #define INVALID_FLOAT(x) (_fpclass(x)==2) 38 | #else 39 | #include 40 | #define INVALID_FLOAT(x) (std::fpclassify(x)!=FP_NORMAL) 41 | #endif 42 | 43 | #define DO_PHASE_INVERSION 44 | 45 | // The exponent for specular reflections 46 | #define EXP 1000.0f 47 | #define EXP_INT (EXP + 1.0f) 48 | 49 | gmtl::Rayf* Scene::Bounce(int band, gmtl::Rayf* sound_ray, 50 | gmtl::Vec3f*& surface_normal, float& l, 51 | Material*& mat, BounceType& bt) { 52 | gmtl::Point3f* p = 0; 53 | gmtl::Rayf* old_sound_ray = 0; 54 | 55 | // Only testing intersections with meshes[0] because it 56 | // contains a combination of all meshes added to the scene 57 | if ( meshes[0]->RayIntersection( 58 | sound_ray,p,surface_normal,mat) ) { 59 | 60 | bt = mat->Bounce(band); 61 | gmtl::Vec3f v; 62 | 63 | const float spec = mat->specularity_coefficient[band]; 64 | 65 | if ( bt == REFRACT ) { 66 | gmtl::Vec3f* n2 = new gmtl::Vec3f(-*surface_normal); 67 | delete surface_normal; 68 | surface_normal = n2; 69 | Sample_Hemi(v, *surface_normal, sound_ray->mDir, spec); 70 | } else { 71 | gmtl::Vec3f refl = gmtl::reflect( 72 | refl, sound_ray->mDir, *surface_normal); 73 | Sample_Hemi(v, *surface_normal, refl, spec); 74 | } 75 | 76 | old_sound_ray = new gmtl::Rayf(*p,v); 77 | const gmtl::Vec3f dist = *p - sound_ray->mOrigin; 78 | l = gmtl::length(dist); 79 | } 80 | delete p; 81 | return old_sound_ray; 82 | } 83 | 84 | gmtl::LineSegf* Scene::Connect(const gmtl::Point3f* p, 85 | const gmtl::Point3f& x) { 86 | // Create a line segment and test for line-triangle intersections 87 | gmtl::LineSegf* ls = new gmtl::LineSegf(*p,x); 88 | // Only testing intersections with meshes[0] because it contains 89 | // a combination of all meshes added to the scene 90 | if ( meshes[0]->LineIntersection(ls) ) { 91 | delete ls; 92 | return 0; 93 | } else { 94 | return ls; 95 | } 96 | } 97 | void Scene::addListener(Recorder* l) { 98 | listeners.push_back(l); 99 | } 100 | void Scene::addSoundSource(AbstractSoundFile* s) { 101 | sources.push_back(s); 102 | } 103 | void Scene::addMesh(Mesh* m) { 104 | if ( meshes.empty() ) meshes.push_back(m); 105 | else meshes[0]->Combine(m); 106 | } 107 | void Scene::addMaterial(Material* m) { 108 | Mesh::materials[m->name] = m; 109 | } 110 | 111 | void Scene::Render(int band, int sound, float absorbtion_factor, 112 | int num_samples, float dry, 113 | const std::vector& recs, 114 | int keyframeID) { 115 | 116 | gmtl::Math::seedRandom((int)time(NULL)); 117 | 118 | AbstractSoundFile* currentSound = sources[sound]; 119 | const gmtl::Point3f sfloc = 120 | currentSound->getLocation(keyframeID); 121 | 122 | float amount = 0; 123 | 124 | for( int sample_count=0; sample_count < num_samples; 125 | sample_count ++ ) { 126 | 127 | amount += 1.0f; 128 | 129 | DrawProgressBar(sample_count,num_samples); 130 | 131 | float sample_intensity = 1.0; 132 | gmtl::Rayf* sound_ray = 0; 133 | gmtl::Rayf* old_sound_ray = 0; 134 | gmtl::Vec3f* surface_normal = 0; 135 | float total_path_length = 0.0f; 136 | 137 | gmtl::Vec3f prev_ray_dir = gmtl::Vec3f(); 138 | 139 | float segment_length = 0.0f; 140 | Material* mat = 0; 141 | BounceType bt; 142 | 143 | for( int num_bounces = 0; num_bounces < 1000; 144 | num_bounces ++ ) { 145 | 146 | surface_normal = 0; 147 | mat = 0; 148 | if ( ! sound_ray ) { 149 | sound_ray = currentSound->SoundRay(keyframeID); 150 | } else { 151 | old_sound_ray = Bounce(band,sound_ray, 152 | surface_normal,segment_length,mat,bt); 153 | 154 | sample_intensity *= pow(absorbtion_factor,segment_length); 155 | 156 | total_path_length += segment_length; 157 | delete sound_ray; 158 | sound_ray = old_sound_ray; 159 | } 160 | 161 | const float spec_coef = (mat > 0) 162 | ? mat->specularity_coefficient[band] 163 | : 0; 164 | 165 | // Failed to generate valid bounce, terminate path 166 | if ( sound_ray == 0 ) break; 167 | 168 | // Account for energy loss by absorbtion: 169 | if ( num_bounces > 0 ) { 170 | sample_intensity *= mat->absorption_coefficient[band]; 171 | } 172 | 173 | const float sample_intensity_before_bounce = sample_intensity; 174 | 175 | if ( INVALID_FLOAT(sample_intensity) ) { 176 | break; 177 | } 178 | 179 | // Direct sound is added in a separate step in the end, 180 | // because in future versions it might be stored 181 | // separately, for example to be able to reproduce 182 | // phenomena like Doppler effect. In case the sound 183 | // source emits from a mesh, the direct sound is 184 | // sampled regardless. 185 | if ( num_bounces || currentSound->isMeshSource() ) { 186 | 187 | // For every recorder in the scene.. 188 | for ( std::vector::const_iterator 189 | it = recs.begin(); it != recs.end(); ++ it ) { 190 | Recorder* rec = *it; 191 | 192 | // See if the intersection point of the ray is 193 | // 'visible' from the recorder location 194 | gmtl::LineSegf* ls = Connect(&sound_ray->mOrigin, 195 | rec->getLocation(keyframeID)); 196 | 197 | if ( ls ) { 198 | 199 | // Because triangles in EAR are two-sided we might need 200 | // to re-orient the surface normal of the triangle based 201 | // on its dot product with the linesegment direction 202 | const gmtl::Vec3f lsdir = gmtl::makeNormal(ls->mDir); 203 | bool valid = true; 204 | 205 | const float dot = num_bounces 206 | ? gmtl::dot(lsdir,*surface_normal) 207 | : 1.0f; 208 | 209 | if ( dot > 0 ) { 210 | float this_sample_intensity = 211 | sample_intensity_before_bounce; 212 | 213 | // A valid path from the intersection point to the 214 | // listener location has been found, now we need to 215 | // determine the intensity of the contribution of 216 | // the ray. 217 | if ( num_bounces) { 218 | 219 | if ( bt == REFLECT ) { 220 | gmtl::Vec3f refl_vector; 221 | gmtl::reflect(refl_vector, 222 | prev_ray_dir,*surface_normal); 223 | 224 | const float diff_factor = 225 | -gmtl::dot(*surface_normal, 226 | prev_ray_dir); 227 | 228 | const float spec_factor = (std::max)(0.0f, 229 | gmtl::dot(refl_vector,lsdir)); 230 | 231 | const float factor = spec_coef * 232 | EXP_INT * pow(spec_factor,EXP) + 233 | (1.0f - spec_coef) * diff_factor; 234 | 235 | this_sample_intensity *= factor; 236 | } else { 237 | const float diff_factor = gmtl::dot( 238 | *surface_normal,prev_ray_dir); 239 | const float spec_factor = (std::max)(0.0f, 240 | gmtl::dot(prev_ray_dir,lsdir)); 241 | 242 | const float factor = spec_coef * 243 | EXP_INT * pow(spec_factor,EXP) + 244 | (1.0f - spec_coef) * diff_factor; 245 | 246 | this_sample_intensity *= factor; 247 | } 248 | } 249 | 250 | const float l = ls->getLength(); 251 | this_sample_intensity *= pow(absorbtion_factor,l); 252 | this_sample_intensity *= INV_HEMI_2(l); 253 | 254 | if ( !INVALID_FLOAT( 255 | this_sample_intensity) ) { 256 | #ifdef DO_PHASE_INVERSION 257 | if ( num_bounces % 2 ) this_sample_intensity *= -1.0f; 258 | #endif 259 | rec->Record(lsdir,this_sample_intensity, 260 | (total_path_length+l)/343.0f, 261 | total_path_length+l,band,keyframeID); 262 | } 263 | } 264 | } 265 | 266 | delete ls; 267 | ls = 0; 268 | } 269 | 270 | } 271 | 272 | // Arbitrary constant, ideally this would be determined 273 | // based on some heuristics or previously collected 274 | // samples. 275 | if ( sample_intensity < 0.00000001 ) break; 276 | 277 | prev_ray_dir = gmtl::makeNormal(sound_ray->mDir); 278 | 279 | delete surface_normal; 280 | surface_normal = 0; 281 | } 282 | delete surface_normal; 283 | delete sound_ray; 284 | } 285 | 286 | // For every recorder in the scene... 287 | std::vector::const_iterator it = recs.begin(); 288 | for ( ; it != recs.end(); ++ it ) { 289 | Recorder* rec = *it; 290 | 291 | rec->Multiply(1.0f / amount); 292 | 293 | // The direct sound lobe is added... 294 | // Ideally this lobe would be stored separately from the rest 295 | // of the samples, it could then be subject of dopler-effect 296 | // calculations for example and would ease the calculation of 297 | // some of the statistical properties of the rendered impulse 298 | // response. 299 | if ( !currentSound->isMeshSource() ) { 300 | 301 | const gmtl::Point3f listener_location = 302 | rec->getLocation(keyframeID); 303 | gmtl::LineSegf* ls = Connect(&listener_location,sfloc); 304 | if ( ls ) { 305 | const gmtl::Vec3f dist = listener_location - sfloc; 306 | const float len = gmtl::length(dist); 307 | const gmtl::Vec3f dir = gmtl::makeNormal(dist); 308 | rec->Record(dir,INV_SPHERE_2(len)*pow(absorbtion_factor, 309 | len)*dry,len/343.0f,len, band, keyframeID); 310 | } 311 | } 312 | 313 | const float gain = currentSound->getGain(); 314 | rec->Multiply(gain*gain); 315 | 316 | } 317 | 318 | } 319 | Scene::~Scene() { 320 | {std::vector::const_iterator it = listeners.begin(); 321 | for ( ; it != listeners.end(); ++ it ) { 322 | delete *it; 323 | }} 324 | {std::vector::const_iterator it = 325 | sources.begin(); 326 | for ( ; it != sources.end(); ++ it ) { 327 | delete *it; 328 | }} 329 | {std::vector::const_iterator it = meshes.begin(); 330 | for ( ; it != meshes.end(); ++ it ) { 331 | delete *it; 332 | }} 333 | } -------------------------------------------------------------------------------- /src/Recorder.h: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #ifndef RECORDER_H 21 | #define RECORDER_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include "Animated.h" 33 | #include "SoundFile.h" 34 | 35 | #define SAMPLE_RATE (44100) 36 | #define INITIAL_BUFFER_SIZE (3*SAMPLE_RATE) 37 | #define INCREMENTAL_BUFFER_SIZE (1*SAMPLE_RATE) 38 | 39 | /// Whether to use fftw to process the convolution in the frequency domain 40 | // #define USE_FFTW 41 | 42 | #ifdef USE_FFTW 43 | #ifdef _MSC_VER 44 | #define FFTW_DLL 45 | #endif 46 | #include 47 | #endif 48 | 49 | /// This class behaves as a dynamic array of floating point numbers. 50 | /// NOTE: The behaviour of this class differs whether it is a constant 51 | /// or non-constant copy. In case of a non-constant instance, the 52 | /// element access operater automatically resizes the array in case 53 | /// of accessing an element outside of the array bounds, whereas a constant 54 | /// instance in that case would simply return 0.0. 55 | class FloatBuffer { 56 | private: 57 | float zero; 58 | float* data; 59 | unsigned int length; 60 | void resizeArray(const unsigned int l); 61 | public: 62 | unsigned int first_sample; 63 | unsigned int real_length; 64 | FloatBuffer(); 65 | ~FloatBuffer(); 66 | float& operator[] (const unsigned int i); 67 | const float& operator[] (const unsigned int i) const; 68 | /// Returns the Root Mean Square (or quadratic mean) of the 69 | /// data in the array. Any leading or trailing zero's are not 70 | /// included in the calculation. 71 | float RootMeanSquare() const; 72 | /// Returns the maximum value in the array. 73 | float Maximum() const; 74 | /// Multiplies all data in the array by a constant factor 75 | void Multiply(float f); 76 | /// Normalizes the data in the array. The first parameter defines 77 | /// the resulting maximum value in the buffer. The second parameter 78 | /// defines the original value that gets mapped to the value in 79 | /// the first parameter. 80 | void Normalize(float M = 1.0f, float MAX = -1.0f); 81 | /// Truncates (or matches) the buffer to this length. 82 | void Truncate(unsigned int l); 83 | /// Raises the data in the buffer to the power specified in a. The 84 | /// default of 0.67 is attributed to Stevens' power law: 85 | /// http://en.wikipedia.org/wiki/Stevens%27_power_law 86 | void Power(float a = 0.67f); 87 | /// Returns the length of the buffer incorporating a treshold 88 | /// that signals values under this treshold to be neglected. 89 | unsigned int getLength(float tresh = -1.0f) const; 90 | void Write(const std::string& fn) const; 91 | void Read(const std::string& fn); 92 | }; 93 | 94 | /// This class represents a single impulse response of a listener. The main 95 | /// use of this class is to provide a way to convolute sound files with this 96 | /// impulse response. 97 | class RecorderTrack : public FloatBuffer { 98 | public: 99 | #ifdef USE_FFTW 100 | typedef enum { CONSTANT, FADE_IN, FADE_OUT } Fade; 101 | /// Processes a sound file to include the response in the recorder track. 102 | /// The response is not interpolated with a successive response. 103 | /// A Fast Fourier Transform is used to transfer both the dry signal 104 | /// and the impulse response to the frequency domain to reduce the 105 | /// complexity (= speed up) of the convolution operation. 106 | /// An additional argument specifies whether to optionally fade in 107 | /// or fade out the input signal to help with the interpolation 108 | /// of successive key-frames. 109 | RecorderTrack* Process(SoundFile* const sound_file, Fade fade= CONSTANT ) const; 110 | /// Processes a sound file to include the response in the recorder track. 111 | /// The response is interpolated with another response to suggest the 112 | /// perception of movement from one location to the other. 113 | /// A Fast Fourier Transform is used to transfer both the dry signal 114 | /// and the impulse response to the frequency domain to reduce the 115 | /// complexity (= speed up) of the convolution operation. 116 | /// Contrary to the direct convolution mode, the FFT convolution mode 117 | /// does not allow the impulse response to be linearly interpolated 118 | /// hence the dry signal is faded in and out respectively before 119 | /// convolution occurs with the impulse response. 120 | RecorderTrack* Process(RecorderTrack* const other, SoundFile* const sound_file) const; 121 | #else 122 | /// Processes a sound file to include the response in the recorder track. 123 | /// The response is not interpolated with a successive response. 124 | RecorderTrack* Process(SoundFile* const sound_file) const; 125 | /// Processes a sound file to include the response in the recorder track. 126 | /// The response is interpolated with another response to suggest the 127 | /// perception of movement from one location to the other. 128 | RecorderTrack* Process(RecorderTrack* const other, SoundFile* const sound_file) const; 129 | #endif 130 | /// Linearly adds the data from the other recorder track to this one. 131 | void Add(const RecorderTrack* other); 132 | /// Returns the T60 reverberation time for the samples stored in this recorder track. 133 | /// From http://en.wikipedia.org/wiki/Reverberation 134 | /// T60 is the time required for reflections of a direct sound to decay by 60 dB below 135 | /// the level of the direct sound. 136 | float T60() const; 137 | /// TODO: It would be great to implement other statistics as well. For example EDT 138 | /// (Early Decay Time) & STI (Speach Transmission Index). The StereoRecorder class 139 | /// could for example implement the IACC (Inter Aural Cross Correlation). 140 | }; 141 | 142 | /// This class is the abstract base class for all classes of listeners. It defines 143 | /// methods to record rendered samples and to use the data in the recorder for 144 | /// convoluting sound files to include the rendered response in the final result. 145 | class Recorder { 146 | public: 147 | bool save_processed; 148 | bool is_processed; 149 | bool is_truncated; 150 | bool has_samples; 151 | int stamped_offset; 152 | typedef std::vector Tracks; 153 | typedef std::vector::const_iterator TrackIt; 154 | Tracks tracks; 155 | Tracks processed_tracks; 156 | 157 | /// Returns the number of tracks in the recorder. E.g. 1 for mono, 2 for stereo. 158 | virtual int trackCount() = 0; 159 | /// Returns the location of the recorder for a certain keyframe index. 160 | virtual const gmtl::Point3f& getLocation(int i = -1) const = 0; 161 | /// Sets the constant location of the listener. 162 | virtual void setLocation(gmtl::Point3f& loc) = 0; 163 | /// Returns the filename of to where the final result will be written. 164 | virtual std::string getFilename() = 0; 165 | /// Gets a blank copy of a recorder with the same amount of tracks. 166 | virtual Recorder* getBlankCopy(int secs = -1) = 0; 167 | /// Returns the animated location of the recorder in case it is defined. 168 | virtual Animated* getAnimationData() = 0; 169 | /// Returns whether the recorder is animated. 170 | virtual bool isAnimated() = 0; 171 | /// Records a sample to one or all of the recorder tracks. The direction of the sample 172 | /// can be used to simulate stereo recording or use Head Related Transfer Functions. 173 | /// The amplitude is the intensity of the sample. Time is the total path length of the 174 | /// sample divided by the speed of sound. The distance is used to splat the sample over 175 | /// the buffer using a filter. The band is used to incorporate properties that differ per 176 | /// frequency, such as the filter or potentially the HRTF. 177 | virtual void Record(const gmtl::Vec3f& dir, float ampl, float t, float dist, int band, int kf) = 0; 178 | /// Saves the data in the recorder to the specified filename. In case the the recorder contains 179 | /// processed data, the member save_processed dictates whether the convoluted sound file 180 | /// or the raw impulse resonse is written to file. 181 | virtual bool Save(const std::string& s, bool norm = true, float norm_max = -1.0f) = 0; 182 | /// Saves the data in the recorder to the specified filename. In case the the recorder contains 183 | /// processed data, the member save_processed dictates whether the convoluted sound file 184 | /// or the raw impulse resonse is written to file. 185 | virtual bool Save() = 0; 186 | /// Processes a sound file to include the responses in the tracks of the recorder. 187 | /// The responses are not interpolated with a successive responses. 188 | void Process(SoundFile* const sf, float offset = 0.0f); 189 | /// Processes a sound file to include the responses in the tracks of the recorder. 190 | /// The responses are interpolated with another recorder to suggest the 191 | /// perception of movement from one location to the other. 192 | void Process(SoundFile* const sf, Recorder* r, float offset, float length); 193 | /// Multiplies all tracks in the recorder by a constant factor 194 | void Multiply(const float factor); 195 | /// Raises the tracks in the recorder to the power specified in a. The 196 | /// default of 0.67 is attributed to Stevens' power law: 197 | /// http://en.wikipedia.org/wiki/Stevens%27_power_law 198 | void Power(float a = 0.67f); 199 | /// Returns the Root Mean Square (or quadratic mean) of the recorder tracks. 200 | float RootMeanSquare(); 201 | /// Matches the length of the tracks in this recorder and two other recorders. 202 | /// To prevent the processing of long tails that do not contribute to the 203 | /// final outcome due to their low intensity, a treshold value is calculated 204 | /// based of the Root Mean Square of the tracks, after which values are 205 | /// discarded. 206 | void Truncate(Recorder* r2, Recorder* r3); 207 | /// Truncates (or matches) the tracks in the recorder to this length. 208 | void Truncate(int len); 209 | /// Returns a const float array of the one of the tracks in the recorder. 210 | /// Use getLength() to determine the length of the returned array. 211 | const float* getSamples(unsigned int channel = 0); 212 | /// Returns maximum length of all tracks in this recorder incorporating 213 | /// a treshold that signals values under this treshold to be neglected. 214 | unsigned int getLength(float tresh = -1.0f); 215 | /// Linearly adds the tracks from the other recorder to this one. 216 | void Add(Recorder* r); 217 | /// Normalizes the tracks in this recorder. The parameter defines 218 | /// the resulting maximum value in the buffers. 219 | void Normalize(float M = 1.0f); 220 | ~Recorder(); 221 | }; 222 | 223 | #endif -------------------------------------------------------------------------------- /src/Recorder.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "Recorder.h" 30 | #include "Animated.h" 31 | #include "SoundFile.h" 32 | 33 | void FloatBuffer::resizeArray(const unsigned int l) { 34 | if ( l <= length ) return; 35 | const float* old_data = data; 36 | data = new float[l]; 37 | memcpy(data,old_data,length*sizeof(data[0])); 38 | memset(data+length,0,(l-length)*sizeof(data[0])); 39 | length = l; 40 | delete[] old_data; 41 | } 42 | 43 | FloatBuffer::FloatBuffer() { 44 | data = 0; 45 | real_length = length = 0; 46 | first_sample = INITIAL_BUFFER_SIZE -1; 47 | this->resizeArray(INITIAL_BUFFER_SIZE); 48 | } 49 | FloatBuffer::~FloatBuffer() { 50 | delete data; 51 | } 52 | float& FloatBuffer::operator[] (const unsigned int i) { 53 | if ( i >= length ) { 54 | resizeArray(i+INCREMENTAL_BUFFER_SIZE); 55 | } 56 | if ( i > real_length ) real_length = i; 57 | if ( i < first_sample ) first_sample = i; 58 | return data[i]; 59 | } 60 | const float& FloatBuffer::operator[] (const unsigned int i) const { 61 | if ( i >= length ) { 62 | return zero; 63 | } 64 | return data[i]; 65 | } 66 | 67 | float FloatBuffer::RootMeanSquare() const { 68 | if ( ! real_length ) return 0; 69 | float x = 0.0; 70 | for ( unsigned int i = first_sample; i < real_length; i ++ ) { 71 | x += data[i]*data[i]; 72 | } 73 | return sqrt(x/(float)real_length); 74 | } 75 | 76 | float FloatBuffer::Maximum() const { 77 | float x = 0.0; 78 | for ( unsigned int i = first_sample; i < real_length; i ++ ) { 79 | const float a = fabs(data[i]); 80 | if ( a > x ) x = a; 81 | } 82 | return x; 83 | } 84 | 85 | void FloatBuffer::Multiply(float f) { 86 | for ( unsigned int i = first_sample; i < real_length; i ++ ) { 87 | data[i] *= f; 88 | } 89 | } 90 | 91 | void FloatBuffer::Normalize(float M, float MAX) { 92 | Multiply( M / (MAX < 0 ? Maximum() : MAX) ); 93 | } 94 | 95 | void FloatBuffer::Truncate(unsigned int l) { 96 | if ( l == 0 ) l = 1; 97 | if ( l >= length ) { 98 | resizeArray(l); 99 | } 100 | real_length = l; 101 | } 102 | 103 | void FloatBuffer::Power(float a) { 104 | for ( unsigned int i = first_sample; i < real_length; i ++ ) { 105 | const float f = pow(abs(data[i]),a); 106 | data[i] = data[i] < 0 ? (f*-1.0f) : f; 107 | } 108 | } 109 | 110 | unsigned int FloatBuffer::getLength(float tresh) const { 111 | if ( tresh < 0.0f ) 112 | return real_length; 113 | unsigned int max = 0; 114 | for ( unsigned int i = first_sample; i < length; ++ i ) { 115 | if ( abs(data[i]) >= tresh ) max = i; 116 | } 117 | return max + 1; 118 | } 119 | 120 | void FloatBuffer::Write(const std::string& fn) const { 121 | std::ofstream f(fn.c_str(),std::ios_base::binary); 122 | f.write((char*)data,sizeof(float)*(real_length+1)); 123 | } 124 | 125 | void FloatBuffer::Read(const std::string& fn) { 126 | std::ifstream f(fn.c_str(),std::ios_base::binary); 127 | f.seekg(0,std::ios_base::end); 128 | std::streamsize stream_size = f.tellg(); 129 | f.seekg(0,std::ios_base::beg); 130 | resizeArray(stream_size/4); 131 | f.read((char*)data,stream_size); 132 | first_sample = 0; 133 | real_length = stream_size / 4; 134 | } 135 | 136 | #ifdef USE_FFTW 137 | // Processes a sound file to include the response in the recorder 138 | // track. The response is not interpolated with a successive 139 | // response. A Fast Fourier Transform is used to transfer both the 140 | // dry signal and the impulse response to the frequency domain to 141 | // reduce the complexity (= speed up) of the convolution operation. 142 | // An additional argument specifies whether to optionally fade in 143 | // or fade out the input signal to help with the interpolation 144 | // of successive key-frames. 145 | RecorderTrack* RecorderTrack::Process(SoundFile* const sound_file, 146 | Fade fade) const { 147 | 148 | const RecorderTrack& _this = *this; 149 | const unsigned int M = this->getLength(); 150 | const unsigned int N = sound_file->sample_length; 151 | const unsigned int MN = M+N+1; 152 | const unsigned int MNh = MN/2+1; 153 | 154 | float* a = (float*) fftwf_malloc(sizeof (float) * MN); 155 | memset(a,0,sizeof(float)*MN); 156 | memcpy(a,&_this[0],sizeof(float)*M); 157 | 158 | fftwf_complex* A = (fftwf_complex *) fftwf_malloc ( 159 | sizeof (fftwf_complex) * MNh); 160 | memset(A,0,sizeof (fftwf_complex) * MNh); 161 | fftwf_plan fft_plan1 = fftwf_plan_dft_r2c_1d( 162 | MN,a,A,FFTW_ESTIMATE); 163 | fftwf_execute(fft_plan1); 164 | fftwf_free(a); 165 | 166 | float* b = (float*) fftwf_malloc(sizeof (float) * MN); 167 | memset(b,0,sizeof(float)*MN); 168 | memcpy(b,sound_file->data,sizeof(float)*N); 169 | if ( fade != CONSTANT ) { 170 | float factor = fade == FADE_OUT ? 1.0f : 0.0f; 171 | float df = (fade == FADE_OUT ? -1.0f : 1.0f) / 172 | (float)sound_file->sample_length; 173 | for ( unsigned int i = 0; 174 | i < sound_file->sample_length; 175 | ++ i ) { 176 | b[i] *= factor; 177 | factor += df; 178 | } 179 | } 180 | fftwf_complex* B = (fftwf_complex *) fftwf_malloc ( 181 | sizeof (fftwf_complex) * MNh); 182 | memset(B,0,sizeof (fftwf_complex) * MNh); 183 | fftwf_plan fft_plan2 = fftwf_plan_dft_r2c_1d( 184 | MN,b,B,FFTW_ESTIMATE); 185 | fftwf_execute(fft_plan2); 186 | fftwf_free(b); 187 | 188 | float scale = 1.0f / (float)MN; 189 | for ( unsigned int i = 0; i < MNh; ++ i ) { 190 | float re = (A[i][0] * B[i][0] - A[i][1] * B[i][1]) * scale; 191 | float im = (A[i][0] * B[i][1] + A[i][1] * B[i][0]) * scale; 192 | A[i][0] = re; 193 | A[i][1] = im; 194 | } 195 | 196 | fftwf_free(B); 197 | 198 | float* c = (float*) fftwf_malloc(sizeof (float) * MN); 199 | memset(c,0,sizeof(float)*MN); 200 | fftwf_plan inv_fft_plan = fftwf_plan_dft_c2r_1d( 201 | MN,A,c,FFTW_ESTIMATE); 202 | 203 | fftwf_execute(inv_fft_plan); 204 | 205 | fftwf_free(A); 206 | 207 | RecorderTrack* result = new RecorderTrack(); 208 | RecorderTrack& _result = *result; 209 | 210 | for ( unsigned int i = 0; i < MN; ++ i ) { 211 | _result[i+sound_file->offset] = c[i]; 212 | } 213 | 214 | fftwf_free(c); 215 | 216 | fftwf_destroy_plan (fft_plan1); 217 | fftwf_destroy_plan (fft_plan2); 218 | fftwf_destroy_plan (inv_fft_plan); 219 | 220 | return result; 221 | } 222 | 223 | // Processes a sound file to include the response in the recorder 224 | // track. The response is interpolated with another response to 225 | // suggest the perception of movement from one location to the other. 226 | // A Fast Fourier Transform is used to transfer both the dry signal 227 | // and the impulse response to the frequency domain to reduce the 228 | // complexity (= speed up) of the convolution operation. 229 | // Contrary to the direct convolution mode, the FFT convolution mode 230 | // does not allow the impulse response to be linearly interpolated 231 | // hence the dry signal is faded in and out respectively before 232 | // convolution occurs with the impulse response. 233 | RecorderTrack* RecorderTrack::Process(RecorderTrack* const other, 234 | SoundFile* const sound_file) 235 | const { 236 | RecorderTrack* a = this->Process(sound_file, FADE_OUT); 237 | RecorderTrack* b = other->Process(sound_file, FADE_IN); 238 | 239 | a->Add(b); 240 | 241 | delete b; 242 | return a; 243 | } 244 | #else 245 | // Processes a sound file to include the response in the recorder 246 | // track. The response is not interpolated with a successive response 247 | RecorderTrack* RecorderTrack::Process(SoundFile* const sound_file) 248 | const { 249 | RecorderTrack* result = new RecorderTrack(); 250 | RecorderTrack& _result = *result; 251 | const RecorderTrack& _this = *this; 252 | const unsigned int len = this->real_length; 253 | const unsigned int sound_length = sound_file->sample_length; 254 | for ( unsigned int i = 0; i < sound_length; ++ i ) { 255 | const float sfs = sound_file->data[i]; 256 | int index = i + sound_file->offset + first_sample; 257 | for ( unsigned int j = first_sample; j < len; ++ j ) { 258 | _result[index++] += sfs * _this[j]; 259 | } 260 | DrawProgressBar(i,sound_length); 261 | } 262 | return result; 263 | } 264 | // Processes a sound file to include the response in the recorder 265 | // track. The response is interpolated with another response to 266 | // suggest the perception of movement from one location to the other 267 | RecorderTrack* RecorderTrack::Process(RecorderTrack* const other, 268 | SoundFile* const sound_file) 269 | const { 270 | RecorderTrack* result = new RecorderTrack(); 271 | RecorderTrack& _result = *result; 272 | const RecorderTrack& _this = *this; 273 | const RecorderTrack& _other = *other; 274 | const unsigned int sound_length = sound_file->sample_length; 275 | const float flt_samples = 1.0f / (float) sound_length; 276 | const unsigned int len = (std::max)( 277 | real_length,other->real_length); 278 | const unsigned int first = (std::min)( 279 | first_sample,other->first_sample); 280 | for( unsigned int i = 0; i < sound_length; i ++ ) { 281 | const float i1 = i * flt_samples; 282 | const float i2 = 1.0f - i1; 283 | const float sfs = sound_file->data[i]; 284 | int index = i + sound_file->offset + first; 285 | for ( unsigned int j = first; j < len; j ++ ) { 286 | const float p = i2 * _this[j] + i1 * _other[j]; 287 | _result[index++] += sfs * p; 288 | } 289 | DrawProgressBar(i,sound_length); 290 | } 291 | return result; 292 | } 293 | #endif 294 | void RecorderTrack::Add(const RecorderTrack* other) { 295 | FloatBuffer& _this = *this; 296 | const RecorderTrack& _other = *other; 297 | const unsigned int len = other->getLength(0.0f); 298 | for ( unsigned int i = 0; i < len; ++ i ) { 299 | _this[i] += _other[i]; 300 | } 301 | } 302 | 303 | float RecorderTrack::T60() const { 304 | const float attenuation_db = 60.0f; 305 | const float attenuation_gain = powf(10.0f,attenuation_db/20.0f); 306 | 307 | float direct_intensity = 0.0f; 308 | float min_gain = 0.0f; 309 | int last_significant_offset; 310 | int direct_sound_offset; 311 | 312 | const RecorderTrack& _this = *this; 313 | 314 | float previous_sample = -1.0f; 315 | bool inside_indirect_lobe = false; 316 | for ( unsigned int j = first_sample; j < real_length; ++ j ) { 317 | const float sample = _this[j]; 318 | if ( inside_indirect_lobe ) { 319 | if ( sample > min_gain ) { 320 | last_significant_offset = j; 321 | } 322 | } else if ( sample < previous_sample ) { 323 | // This is a rather silly way to determine the end of the 324 | // direct sound field, for it may not even been present 325 | // in this track and it is explicitely calculated 326 | // seperately from the reflections anyway in the 327 | // rendering function, but this information is no longer 328 | // available at this stage. 329 | inside_indirect_lobe = true; 330 | direct_intensity = previous_sample; 331 | min_gain = direct_intensity / attenuation_gain; 332 | direct_sound_offset = j; 333 | } 334 | previous_sample = sample; 335 | } 336 | 337 | const int reverberation_length = last_significant_offset - 338 | direct_sound_offset; 339 | return (float)reverberation_length/44100.0f; 340 | } 341 | 342 | 343 | void Recorder::Process(SoundFile* const sf, float offset) { 344 | for ( TrackIt it = tracks.begin(); it != tracks.end(); ++ it ) { 345 | SoundFile* section = sf->Section(offset); 346 | processed_tracks.push_back((*it)->Process(section)); 347 | delete section; 348 | } 349 | is_processed = true; 350 | } 351 | 352 | void Recorder::Process(SoundFile* const sf, Recorder* r, 353 | float offset, float length) { 354 | unsigned int track_id = 0; 355 | for ( TrackIt it = tracks.begin(); 356 | it != tracks.end(); ++ it, ++ track_id ) { 357 | SoundFile* section = sf->Section(offset,length); 358 | processed_tracks.push_back( 359 | (*it)->Process(r->tracks[track_id],section)); 360 | delete section; 361 | } 362 | is_processed = true; 363 | } 364 | 365 | void Recorder::Multiply(const float factor) { 366 | for ( TrackIt it = tracks.begin(); it != tracks.end(); ++ it ) { 367 | (*it)->Multiply(factor); 368 | } 369 | } 370 | 371 | void Recorder::Power(float a) { 372 | for ( TrackIt it = tracks.begin(); it != tracks.end(); ++ it ) { 373 | (*it)->Power(a); 374 | } 375 | } 376 | 377 | float Recorder::RootMeanSquare() { 378 | float acc = 0; 379 | for ( TrackIt it = tracks.begin(); it != tracks.end(); ++ it ) { 380 | const float track_rms = (*it)->RootMeanSquare(); 381 | acc += track_rms * track_rms; 382 | } 383 | return sqrt(acc); 384 | } 385 | 386 | void Recorder::Truncate(Recorder* r2, Recorder* r3) { 387 | const float rms1 = this->RootMeanSquare() / 10000.0f; 388 | const float rms2 = r2->RootMeanSquare() / 10000.0f; 389 | const float rms3 = r3->RootMeanSquare() / 10000.0f; 390 | const int l1 = this->getLength(rms1); 391 | const int l2 = r2->getLength(rms2); 392 | const int l3 = r3->getLength(rms3); 393 | const int l = (std::max)(l1,(std::max)(l2,l3)); 394 | this->Truncate(l); 395 | r2->Truncate(l); 396 | r3->Truncate(l); 397 | } 398 | 399 | void Recorder::Truncate(int len) { 400 | is_truncated = true; 401 | for ( TrackIt it = tracks.begin(); it != tracks.end(); ++ it ) { 402 | (*it)->Truncate(len); 403 | } 404 | } 405 | 406 | const float* Recorder::getSamples(unsigned int channel) { 407 | if ( channel >= tracks.size() ) return 0; 408 | const RecorderTrack& track = *tracks[channel]; 409 | return &track[0]; 410 | } 411 | 412 | unsigned int Recorder::getLength(float tresh) { 413 | if ( ! is_processed && ! has_samples ) return 0; 414 | unsigned int length = 0; 415 | if ( is_processed ) { 416 | for ( TrackIt it = processed_tracks.begin(); 417 | it != processed_tracks.end(); ++ it ) { 418 | const unsigned int track_length = (*it)->getLength(); 419 | if ( track_length > length ) length = track_length; 420 | } 421 | } else { 422 | for ( TrackIt it = tracks.begin(); 423 | it != tracks.end(); ++ it ) { 424 | const unsigned int track_length = 425 | (*it)->getLength(tresh); 426 | if ( track_length > length ) length = track_length; 427 | } 428 | } 429 | return length; 430 | } 431 | 432 | void Recorder::Add(Recorder* r) { 433 | if ( trackCount() != r->trackCount() ) throw; 434 | for ( unsigned int i = 0; 435 | i < r->processed_tracks.size(); ++ i ) { 436 | if ( i == processed_tracks.size() ) 437 | processed_tracks.push_back(new RecorderTrack()); 438 | processed_tracks[i]->Add(r->processed_tracks[i]); 439 | } 440 | is_processed = true; 441 | } 442 | 443 | void Recorder::Normalize(float M) { 444 | float max = -1e9; 445 | TrackIt begin,end; 446 | if ( save_processed ) { 447 | begin = processed_tracks.begin(); 448 | end = processed_tracks.end(); 449 | } else { 450 | begin = tracks.begin(); 451 | end = tracks.end(); 452 | } 453 | for ( TrackIt it = begin; it != end; ++ it ) { 454 | const float track_max = (*it)->Maximum(); 455 | if ( track_max > max ) max = track_max; 456 | } 457 | for ( TrackIt it = begin; it != end; ++ it ) { 458 | (*it)->Normalize(M,max); 459 | } 460 | } 461 | 462 | Recorder::~Recorder() { 463 | for ( TrackIt it = processed_tracks.begin(); 464 | it != processed_tracks.end(); ++ it ) { 465 | delete *it; 466 | } 467 | for ( TrackIt it = tracks.begin(); it != tracks.end(); ++ it ) { 468 | delete *it; 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /src/EAR.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************ 2 | * * 3 | * This file is part of EAR: Evaluation of Acoustics using Ray-tracing. * 4 | * * 5 | * EAR is free software: you can redistribute it and/or modify * 6 | * it under the terms of the GNU General Public License as published by * 7 | * the Free Software Foundation, either version 3 of the License, or * 8 | * (at your option) any later version. * 9 | * * 10 | * EAR is distributed in the hope that it will be useful, * 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 13 | * GNU General Public License for more details. * 14 | * * 15 | * You should have received a copy of the GNU General Public License * 16 | * along with EAR. If not, see . * 17 | * * 18 | ************************************************************************/ 19 | 20 | #define BANNER " ______ _____ \n | ____| /\\ | __ \\ \n | |__ / \\ | |__) | \n | __| / /\\ \\ | _ / \n | |____ / ____ \\ | | \\ \\ \n |______| (_) /_/ \\_\\ (_) |_| \\_\\" 21 | #define PRODUCT "Evaluation of Acoustics using Ray-tracing" 22 | #define VERSION "0.1.8b" 23 | #define HEADER BANNER "\n " PRODUCT "\n version " VERSION 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | 39 | #include "../lib/wave/WaveFile.h" 40 | #include "../lib/equalizer/Equalizer.h" 41 | 42 | #include "Settings.h" 43 | #include "Mesh.h" 44 | #include "SoundFile.h" 45 | #include "MonoRecorder.h" 46 | #include "StereoRecorder.h" 47 | #include "Recorder.h" 48 | #include "HelperFunctions.h" 49 | #include "Scene.h" 50 | #include "Material.h" 51 | #include "SceneContext.h" 52 | 53 | using boost::thread; 54 | 55 | int Render(std::string filename, float* calc_T60=0, float* T60_Sabine=0, float* T60_Eyring=0) { 56 | 57 | // Init RNG, scene, and file input 58 | gmtl::Math::seedRandom((unsigned int) time(0)); 59 | Scene* scene = new Scene(); 60 | bool valid_file = Datatype::SetInput(filename); 61 | if ( ! valid_file ) { 62 | std::cout << "Failed to read file" << std::endl; 63 | return 1; 64 | } 65 | 66 | // Read the settings from file 67 | Datatype* settings = Datatype::Scan("SET "); 68 | if ( settings ) { 69 | Settings::init(settings); 70 | delete settings; 71 | } else { 72 | std::cout << "No settings block found in file" << std::endl; 73 | return 1; 74 | } 75 | 76 | gmtl::Vec3f absorption = Settings::GetVec("absorption"); 77 | float dry_level = Settings::GetFloat("drylevel"); 78 | #ifdef _DEBUG 79 | int num_samples = Settings::GetInt("samples") / 1000; 80 | #else 81 | int num_samples = Settings::GetInt("samples") / 10; 82 | #endif 83 | int max_threads = Settings::IsSet("maxthreads") ? 84 | Settings::GetInt("maxthreads") : -1; 85 | 86 | if ( max_threads < 1 ) max_threads = -1; 87 | 88 | std::string debugdir; 89 | bool has_debugdir = false; 90 | 91 | // Read rest of input file 92 | while ( Datatype::input_length ) { 93 | std::string peak = Datatype::PeakId(); 94 | if ( peak == "OUT1" ) { 95 | Recorder* r = new MonoRecorder(); 96 | scene->addListener(r); 97 | } 98 | else if ( peak == "OUT2" ) { 99 | Recorder* r = new StereoRecorder(); 100 | scene->addListener(r); 101 | } 102 | else if ( peak == "SSRC" ) scene->addSoundSource(new SoundFile()); 103 | else if ( peak == "3SRC" ) scene->addSoundSource(new TripleBandSoundFile()); 104 | else if ( peak == "MESH" ) scene->addMesh(new Mesh()); 105 | else if ( peak == "MAT " ) scene->addMaterial(new Material()); 106 | else if ( peak == "SET " ) {delete Datatype::Read();} 107 | else if ( peak == "VRSN" ) {delete Datatype::Read();} 108 | else if ( peak == "KEYS" ) {Keyframes::Init();} 109 | else if ( peak == "FREQ" ) { 110 | Datatype::Read(false); 111 | const float f1 = Datatype::ReadFloat(); 112 | const float f2 = Datatype::ReadFloat(); 113 | const float f3 = Datatype::ReadFloat(); 114 | SoundFile::SetEqBands(f1,f2,f3); 115 | } else { 116 | std::cout << "Unknown block '" << peak << "'" << std::endl; 117 | Datatype::Read(); 118 | } 119 | } 120 | 121 | const char* lomihi[] = {"low","mid","high"}; 122 | 123 | if ( Settings::IsSet("debugdir") ) { 124 | has_debugdir = true; 125 | debugdir = Settings::GetString("debugdir") + DIR_SEPERATOR; 126 | 127 | // Save equalizer output for debugging purposes 128 | int sf_id = 0; 129 | for ( std::vector::const_iterator it = scene->sources.begin(); it != scene->sources.end(); ++ it ) { 130 | for ( int band_id = 0; band_id < 3; ++ band_id ) { 131 | // If we are only here to calculate the T60 reverberation time 132 | // we are only going to render the mid frequency range. 133 | if ( calc_T60 && band_id != 1 ) continue; 134 | SoundFile* band = (*it)->Band(band_id); 135 | WaveFile w; 136 | w.FromFloat(band->data,band->sample_length); 137 | std::stringstream ss; 138 | ss << debugdir << "sound-" << sf_id << ".band-" << band_id << lomihi[band_id] << ".wav"; 139 | w.Save(ss.str().c_str()); 140 | // bands are now owned by the parent sound file so do not delete them 141 | // delete band; 142 | } 143 | // If we are only here to calculate the T60 reverberation time 144 | // we are only going to render the first sound file encountered. 145 | if ( calc_T60 ) break; 146 | sf_id ++; 147 | } 148 | } 149 | 150 | if ( scene->sources.empty() ) { 151 | std::cout << std::endl << "No sound sources defined" << std::endl << std::endl; 152 | return 1; 153 | } 154 | 155 | if ( scene->listeners.empty() ) { 156 | std::cout << std::endl << "No listeners defined" << std::endl << std::endl; 157 | return 1; 158 | } 159 | 160 | if ( scene->meshes.empty() ) { 161 | std::cout << std::endl << "Warning: no reflective geometry" << std::endl << std::endl; 162 | scene->addMesh(Mesh::Empty()); 163 | } 164 | 165 | Keyframes* keys = Keyframes::Get(); 166 | 167 | std::cout << "Rendering..." << std::endl; 168 | 169 | // Create impules responses for sounds x keyframes x bands 170 | std::vector scs; 171 | for( unsigned int sound_id = 0; sound_id < scene->sources.size(); sound_id ++ ) { 172 | AbstractSoundFile* sf = scene->sources[sound_id]; 173 | // This for loop iterates over all keyframes. If the scene contains a static 174 | // configuration and no keyframes are present, keyframe_id is assigned -1. 175 | for( int keyframe_id = keys?0:-1; keyframe_id < (int)(keys?keys->keys.size():0); keyframe_id ++ ) { 176 | for( int band_id = 0; band_id < 3; band_id ++ ) { 177 | // If we are only here to calculate the T60 reverberation time 178 | // we are only going to render the mid frequency range. 179 | if ( calc_T60 && band_id != 1 ) continue; 180 | const float absorption_factor = 1.0f-absorption[band_id]; 181 | SceneContext s(scene,band_id,sound_id,num_samples,absorption_factor,dry_level,keyframe_id); 182 | scs.push_back(s); 183 | } 184 | // If we are only here to calculate the T60 reverberation time 185 | // we are only going to render the first keyframe. 186 | if ( calc_T60 ) break; 187 | } 188 | // If we are only here to calculate the T60 reverberation time 189 | // we are only going to render the first sound file encountered. 190 | if ( calc_T60 ) break; 191 | } 192 | 193 | if ( max_threads > 0 ) 194 | SetProgressBarSegments((int)ceil((float)scs.size()/(float)max_threads)); 195 | 196 | {std::vector::const_iterator it = scs.begin(); 197 | while( true ) { 198 | boost::thread_group group; 199 | for ( int i = 0; max_threads < 0 || i < max_threads; i ++ ) { 200 | group.create_thread(*it); 201 | *it ++; 202 | if ( it == scs.end() ) break; 203 | } 204 | group.join_all(); 205 | if ( max_threads > 0 ) NextProgressBarSegment(); 206 | if ( it == scs.end() ) break; 207 | }} 208 | 209 | // Calculate max response 210 | float max = 0.0f; 211 | for( std::vector::const_iterator it = scs.begin(); it != scs.end(); ++it ) { 212 | for ( std::vector::const_iterator rit = it->recorders.begin(); rit != it->recorders.end(); ++ rit ) { 213 | Recorder* r1 = *rit; 214 | r1->Power(0.335f); 215 | for ( int i = 0; i < r1->trackCount(); ++ i ) { 216 | const float m = r1->tracks[i]->Maximum(); 217 | if ( m > max ) max = m; 218 | } 219 | } 220 | } 221 | 222 | const float treshold = max / 256.0f; 223 | 224 | for( std::vector::iterator it = scs.begin(); it != scs.end(); ++it ) { 225 | int rec_id = 0; 226 | for ( std::vector::const_iterator rit = it->recorders.begin(); rit != it->recorders.end(); ++ rit ) { 227 | Recorder* r1 = *rit; 228 | r1->Truncate(r1->getLength(treshold)); 229 | if ( has_debugdir ) { 230 | std::stringstream ss; 231 | const int sf_id = it->soundfile_id; 232 | const int band_id = it->band; 233 | const int kf_id = it->keyframe_id; 234 | ss << debugdir << "response-" << rec_id << ".sound-" << sf_id; 235 | if ( kf_id != -1 ) { 236 | ss << ".frame-" << std::setw(2) << std::setfill('0') << kf_id; 237 | } 238 | ss << ".band-" << band_id << lomihi[band_id]; 239 | r1->Save(ss.str() + ".wav",true,max); 240 | r1->tracks[0]->Write(ss.str() + ".bin"); 241 | } 242 | rec_id ++; 243 | } 244 | } 245 | 246 | const bool noprocess = Settings::IsSet("noprocessing") && Settings::GetBool("noprocessing"); 247 | if ( noprocess || calc_T60 ) { 248 | std::cout << std::endl << "Not processing data" << std::endl; 249 | 250 | if ( calc_T60 ) { 251 | 252 | // If we are only here to calculate the T60 reverberation time the rendered result 253 | // does not need to be convoluted. Instead, the T60 is determined based on the 254 | // rendered impulse response, as well as by the two well-known formulas Sabine 255 | // and Norris-Eyring. These deal with the prediction of reverberation time on a 256 | // statistical level. For a 'conventional' setup, the T60 that is calculated from 257 | // the impulse response should not deviate too much from the statistical prediction. 258 | 259 | const SceneContext sc = *scs.begin(); 260 | const Recorder* rec = *sc.recorders.begin(); 261 | const RecorderTrack* track = *rec->tracks.begin(); 262 | *calc_T60 = track->T60(); 263 | 264 | const Mesh* mesh = scene->meshes[0]; 265 | const float V = mesh->Volume(); 266 | const float A = mesh->TotalAbsorption(); 267 | const float S = mesh->Area(); 268 | const float a = mesh->AverageAbsorption(); 269 | const float m = absorption[1]; 270 | 271 | // Sabine: 272 | // 0.1611 V 273 | // T = -------- 274 | // A + 4mV 275 | // 276 | // Norris-Eyring: 277 | // 0.1611 V 278 | // T = ---------------- 279 | // -S ln(1-a) + 4mV 280 | 281 | if ( T60_Sabine ) 282 | *T60_Sabine = 0.1611f*V/(A+4.0f*m*V); 283 | if ( T60_Eyring ) 284 | *T60_Eyring = 0.1611f*V/(-S*log(1.0f-a)+4.0f*m*V); 285 | } 286 | 287 | Datatype::Dispose(); 288 | Keyframes::Dispose(); 289 | delete scene; 290 | return 0; 291 | } 292 | 293 | std::cout << std::endl << "Processing data..." << std::endl; 294 | 295 | 296 | // Multiply impulses responses by sound file 297 | std::vector rcs; 298 | for( std::vector::iterator it = scs.begin(); it != scs.end(); it ++ ) { 299 | const SceneContext sc = *it; 300 | const int band = sc.band; 301 | AbstractSoundFile* sf = scene->sources[sc.soundfile_id]; 302 | if ( keys ) { 303 | const float offset = keys->keys[sc.keyframe_id]; 304 | const int lastkey = keys->keys.size() - 1; 305 | if ( sc.keyframe_id == lastkey ) { 306 | for ( std::vector::const_iterator rit = sc.recorders.begin(); rit != sc.recorders.end(); ++ rit ) { 307 | Recorder* r1 = *rit; 308 | rcs.push_back(RecorderContext(sf->Band(band),r1,offset)); 309 | } 310 | } else { 311 | const SceneContext sc2 = *(it+3); 312 | std::vector::const_iterator rit2 = sc2.recorders.begin(); 313 | for ( std::vector::const_iterator rit = sc.recorders.begin(); rit != sc.recorders.end(); ++ rit ) { 314 | Recorder* r1 = *rit; 315 | Recorder* r2 = *rit2; 316 | const float length = keys->keys[sc.keyframe_id+1] - offset; 317 | rcs.push_back(RecorderContext(sf->Band(band),r1,offset,r2,length)); 318 | ++ rit2; 319 | } 320 | } 321 | } else { 322 | for ( std::vector::const_iterator rit = sc.recorders.begin(); rit != sc.recorders.end(); ++ rit ) { 323 | Recorder* r1 = *rit; 324 | rcs.push_back(RecorderContext(sf->Band(band),r1)); 325 | } 326 | } 327 | 328 | } 329 | 330 | if ( max_threads > 0 ) 331 | SetProgressBarSegments((int)ceil((float)rcs.size()/(float)max_threads)); 332 | 333 | #ifdef USE_FFTW 334 | // The creation of fftw plans is not thread safe, therefore fft convolution occurs 335 | // sequentially. Note that the actual execution of plans is thread safe, hence 336 | // fft convolution could be sped up by separating creation and execution of plans 337 | // or using a mutex somewhere in the RecorderTrack class. 338 | for ( std::vector::iterator it = rcs.begin(); it != rcs.end(); ++ it ) { 339 | (*it)(); 340 | } 341 | #else 342 | {std::vector::const_iterator it = rcs.begin(); 343 | while( true ) { 344 | boost::thread_group group; 345 | for ( int i = 0; max_threads < 0 || i < max_threads; i ++ ) { 346 | group.create_thread(*it); 347 | *it ++; 348 | if ( it == rcs.end() ) break; 349 | } 350 | group.join_all(); 351 | if ( max_threads > 0 ) NextProgressBarSegment(); 352 | if ( it == rcs.end() ) break; 353 | }} 354 | std::cout << std::endl; 355 | #endif 356 | 357 | std::cout << "Merging result..." << std::endl; 358 | 359 | // Add buffers and save 360 | int rec_id = 0; 361 | for ( std::vector::const_iterator it = scene->listeners.begin(); it != scene->listeners.end(); ++ it ) { 362 | Recorder* r0 = *it; 363 | Recorder* total = r0->getBlankCopy(); 364 | int scene_id = 0; 365 | for( std::vector::iterator it = scs.begin(); it != scs.end(); it ++ ) { 366 | SceneContext sc = *it; 367 | Recorder* other = sc.recorders[rec_id]; 368 | other->save_processed = true; 369 | if ( has_debugdir ) { 370 | std::stringstream ss; 371 | ss << debugdir << "rec-" << rec_id << ".sound-" << sc.soundfile_id; 372 | if (sc.keyframe_id != -1) { 373 | ss << ".frame-" << std::setw(2) << std::setfill('0') << sc.keyframe_id; 374 | } 375 | ss << ".band-" << sc.band << ".wav"; 376 | other->Save(ss.str()); 377 | } 378 | total->Add(other); 379 | scene_id ++; 380 | } 381 | total->save_processed = true; 382 | total->Normalize(0.8f); 383 | total->Truncate(total->getLength(1e-6f)); 384 | total->Save(); 385 | rec_id ++; 386 | } 387 | 388 | Datatype::Dispose(); 389 | Keyframes::Dispose(); 390 | delete scene; 391 | 392 | return 0; 393 | } 394 | 395 | int main(int argc, char** argv) { 396 | std::cout << HEADER << std::endl << std::endl << std::endl; 397 | std::cout << std::setprecision(3) << std::fixed; 398 | for ( int i = 1; i < argc; i ++ ) { 399 | const std::string cmd(argv[i]); 400 | const std::string arg1 = ((i+1)" << std::endl 430 | << " EAR calc T60 " << std::endl; 431 | } 432 | 433 | boost::mutex MonoRecorder::mutex; 434 | 435 | -------------------------------------------------------------------------------- /blender/render_EAR/glyphs.py: -------------------------------------------------------------------------------- 1 | ######################################################################## 2 | # # 3 | # This file is part of EAR: Evaluation of Acoustics using Ray-tracing. # 4 | # # 5 | # EAR is free software: you can redistribute it and/or modify # 6 | # it under the terms of the GNU General Public License as published by # 7 | # the Free Software Foundation, either version 3 of the License, or # 8 | # (at your option) any later version. # 9 | # # 10 | # EAR is distributed in the hope that it will be useful, # 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of # 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 13 | # GNU General Public License for more details. # 14 | # # 15 | # You should have received a copy of the GNU General Public License # 16 | # along with EAR. If not, see . # 17 | # # 18 | ######################################################################## 19 | 20 | ######################################################################## 21 | # # 22 | # Several two dimensional line drawings for graphical anotations. # 23 | # # 24 | ######################################################################## 25 | 26 | glyphs = { 27 | 'loudspeaker':[[[-0.14580124616622925, 8.1557159423828125], [0.28187063336372375, 7.4722042083740234]], [[0.28187063336372375, 7.4722042083740234], [0.59118324518203735, 6.7707405090332031]], [[0.59118324518203735, 6.7707405090332031], [0.78179508447647095, 6.0708723068237305]], [[0.78179508447647095, 6.0708723068237305], [0.85334873199462891, 5.3922057151794434]], [[0.85334873199462891, 5.3922057151794434], [0.80547511577606201, 4.7543735504150391]], [[0.80547511577606201, 4.7543735504150391], [0.63784605264663696, 4.1769542694091797]], [[0.63784605264663696, 4.1769542694091797], [0.35009071230888367, 3.6795516014099121]], [[0.35009071230888367, 3.6795516014099121], [0.16106048226356506, 3.4669983386993408]], [[0.16106048226356506, 3.4669983386993408], [-0.058146640658378601, 3.2817726135253906]], [[-0.058146640658378601, 3.2817726135253906], [-0.30160415172576904, 3.1298584938049316]], [[-0.30160415172576904, 3.1298584938049316], [-0.8365931510925293, 2.9365115165710449]], [[-0.8365931510925293, 2.9365115165710449], [-1.4202249050140381, 2.8852181434631348]], [[-1.4202249050140381, 2.8852181434631348], [-2.0339775085449219, 2.9696238040924072]], [[-2.0339775085449219, 2.9696238040924072], [-2.6592423915863037, 3.1833176612854004]], [[-2.6592423915863037, 3.1833176612854004], [-3.2775118350982666, 3.5200870037078857]], [[-3.2775118350982666, 3.5200870037078857], [-3.870248556137085, 3.9734907150268555]], [[-3.870248556137085, 3.9734907150268555], [-4.4188442230224609, 4.537203311920166]], [[-4.4188442230224609, 4.537203311920166], [-4.8994331359863281, 5.19677734375]], [[-4.8994331359863281, 5.19677734375], [-5.2679758071899414, 5.8917450904846191]], [[-5.2679758071899414, 5.8917450904846191], [-5.5179877281188965, 6.5948619842529297]], [[-5.5179877281188965, 6.5948619842529297], [-5.6491131782531738, 7.2865509986877441]], [[-5.6491131782531738, 7.2865509986877441], [-5.6609959602355957, 7.947237491607666]], [[-5.6609959602355957, 7.947237491607666], [-5.5532798767089844, 8.5573129653930664]], [[-5.5532798767089844, 8.5573129653930664], [-5.3256230354309082, 9.0971460342407227]], [[-5.3256230354309082, 9.0971460342407227], [-4.977684497833252, 9.5471878051757812]], [[-4.977684497833252, 9.5471878051757812], [-4.758491039276123, 9.7324142456054687]], [[-4.758491039276123, 9.7324142456054687], [-4.5150332450866699, 9.8843269348144531]], [[-4.5150332450866699, 9.8843269348144531], [-3.9800443649291992, 10.077675819396973]], [[-3.9800443649291992, 10.077675819396973], [-3.3963980674743652, 10.12896728515625]], [[-3.3963980674743652, 10.12896728515625], [-2.7826595306396484, 10.044575691223145]], [[-2.7826595306396484, 10.044575691223145], [-2.1573946475982666, 9.8308544158935547]], [[-2.1573946475982666, 9.8308544158935547], [-1.5391260385513306, 9.4941272735595703]], [[-1.5391260385513306, 9.4941272735595703], [-0.94638955593109131, 9.0406951904296875]], [[0.33303606510162354, 12.974333763122559], [0.55205780267715454, 12.289853096008301]], [[0.55205780267715454, 12.289853096008301], [0.5376962423324585, 11.633228302001953]], [[0.5376962423324585, 11.633228302001953], [0.44766175746917725, 11.338263511657715]], [[0.44766175746917725, 11.338263511657715], [0.304881751537323, 11.077722549438477]], [[0.304881751537323, 11.077722549438477], [0.11123526841402054, 10.860795021057129]], [[0.11123526841402054, 10.860795021057129], [-0.13142472505569458, 10.696612358093262]], [[-0.13142472505569458, 10.696612358093262], [-0.40751081705093384, 10.598556518554688]], [[-0.40751081705093384, 10.598556518554688], [-0.69701898097991943, 10.571259498596191]], [[-0.69701898097991943, 10.571259498596191], [-0.99147075414657593, 10.610782623291016]], [[-0.99147075414657593, 10.610782623291016], [-1.2823742628097534, 10.71318531036377]], [[-1.2823742628097534, 10.71318531036377], [-1.8196287155151367, 11.090945243835449]], [[-1.8196287155151367, 11.090945243835449], [-2.240917444229126, 11.673165321350098]], [[-2.240917444229126, 11.673165321350098], [-2.4599399566650391, 12.357631683349609]], [[-2.4599399566650391, 12.357631683349609], [-2.4455776214599609, 13.014257431030273]], [[-2.4455776214599609, 13.014257431030273], [-2.3555440902709961, 13.309235572814941]], [[-2.3555440902709961, 13.309235572814941], [-2.2127630710601807, 13.569747924804688]], [[-2.2127630710601807, 13.569747924804688], [-2.0191166400909424, 13.786691665649414]], [[-2.0191166400909424, 13.786691665649414], [-1.7764567136764526, 13.950858116149902]], [[-1.7764567136764526, 13.950858116149902], [-1.5003705024719238, 14.048928260803223]], [[-1.5003705024719238, 14.048928260803223], [-1.2108632326126099, 14.076241493225098]], [[-1.2108632326126099, 14.076241493225098], [-0.91641139984130859, 14.036731719970703]], [[-0.91641139984130859, 14.036731719970703], [-0.62550711631774902, 13.934329986572266]], [[-0.62550711631774902, 13.934329986572266], [-0.088253326714038849, 13.556554794311523]], [[-7.5109167098999023, 17.738580703735352], [-6.1857690811157227, 2.9390764236450195]], [[7.1394248008728027, 4.7016839981079102], [7.5109152793884277, 18.938102722167969]], [[7.5109152793884277, 18.938102722167969], [-1.0373919010162354, 20.0]], [[-7.5109167098999023, 17.738580703735352], [-1.0373919010162354, 20.0]], [[2.1059019565582275, 5.9604644775390625e-07], [7.1394248008728027, 4.7016839981079102]], [[2.1059019565582275, 5.9604644775390625e-07], [-6.1857690811157227, 2.9390764236450195]], [[1.7570796012878418, 16.169050216674805], [7.5109152793884277, 18.938102722167969]], [[1.7570796012878418, 16.169050216674805], [-7.5109167098999023, 17.738580703735352]], [[2.1059019565582275, 5.9604644775390625e-07], [1.7570796012878418, 16.169050216674805]], [[0.33303606510162354, 12.974333763122559], [-0.088253326714038849, 13.556554794311523]], [[-0.14580124616622925, 8.1557159423828125], [-0.94638955593109131, 9.0406951904296875]]], 28 | 'ear':[[[-4.9982008934020996, 12.961938858032227], [-4.9594221115112305, 12.873139381408691]], [[-4.9594221115112305, 12.873139381408691], [-4.8250946998596191, 12.66634464263916]], [[-4.8250946998596191, 12.66634464263916], [-4.3572406768798828, 12.158697128295898]], [[-4.3572406768798828, 12.158697128295898], [-2.8462193012237549, 10.948786735534668]], [[-2.8462193012237549, 10.948786735534668], [-0.99047398567199707, 9.82550048828125]], [[-0.99047398567199707, 9.82550048828125], [-0.091699674725532532, 9.4261531829833984]], [[-0.091699674725532532, 9.4261531829833984], [0.70324832201004028, 9.2041730880737305]], [[0.70324832201004028, 9.2041730880737305], [0.8783257007598877, 9.1824960708618164]], [[-2.8768911361694336, 3.7023978233337402], [-3.0796387195587158, 3.6306734085083008]], [[-3.0796387195587158, 3.6306734085083008], [-3.2777822017669678, 3.5989289283752441]], [[-3.2777822017669678, 3.5989289283752441], [-3.4707210063934326, 3.6034183502197266]], [[-3.4707210063934326, 3.6034183502197266], [-3.6577966213226318, 3.6403393745422363]], [[-3.6577966213226318, 3.6403393745422363], [-3.8384237289428711, 3.7059731483459473]], [[-3.8384237289428711, 3.7059731483459473], [-4.0119590759277344, 3.7964856624603271]], [[-4.0119590759277344, 3.7964856624603271], [-4.3352923393249512, 4.037198543548584]], [[-4.3352923393249512, 4.037198543548584], [-4.622833251953125, 4.3323616981506348]], [[-4.622833251953125, 4.3323616981506348], [-5.0706267356872559, 4.965385913848877]], [[-5.0706267356872559, 4.965385913848877], [-5.2209396362304687, 5.2430181503295898]], [[-2.9829626083374023, 13.341122627258301], [-0.69592541456222534, 12.13295841217041]], [[-0.69592541456222534, 12.13295841217041], [0.53856378793716431, 11.223502159118652]], [[0.53856378793716431, 11.223502159118652], [0.76527655124664307, 10.989696502685547]], [[0.76527655124664307, 10.989696502685547], [0.94234579801559448, 10.757991790771484]], [[0.94234579801559448, 10.757991790771484], [1.0613292455673218, 10.530749320983887]], [[1.0613292455673218, 10.530749320983887], [1.0964188575744629, 10.419530868530273]], [[1.0964188575744629, 10.419530868530273], [1.1219862699508667, 9.997004508972168]], [[1.1219862699508667, 9.997004508972168], [1.0844489336013794, 9.6138458251953125]], [[1.0844489336013794, 9.6138458251953125], [0.99622303247451782, 9.2702360153198242]], [[0.99622303247451782, 9.2702360153198242], [0.8624260425567627, 8.9661493301391602]], [[0.8624260425567627, 8.9661493301391602], [0.68820667266845703, 8.7016162872314453]], [[0.68820667266845703, 8.7016162872314453], [0.47867938876152039, 8.4765748977661133]], [[0.47867938876152039, 8.4765748977661133], [0.23895494639873505, 8.2910575866699219]], [[0.23895494639873505, 8.2910575866699219], [-0.54312378168106079, 7.8701186180114746]], [[-0.54312378168106079, 7.8701186180114746], [-0.76431864500045776, 7.691922664642334]], [[-0.76431864500045776, 7.691922664642334], [-0.96416389942169189, 7.4842982292175293]], [[-0.96416389942169189, 7.4842982292175293], [-1.312034010887146, 6.9758772850036621]], [[-1.312034010887146, 6.9758772850036621], [-1.8839854001998901, 5.7399425506591797]], [[-1.8839854001998901, 5.7399425506591797], [-2.0174832344055176, 5.5533676147460938]], [[-2.0174832344055176, 5.5533676147460938], [-2.0852317810058594, 5.482844352722168]], [[-2.0852317810058594, 5.482844352722168], [-2.1542963981628418, 5.4254188537597656]], [[-2.1542963981628418, 5.4254188537597656], [-2.2251644134521484, 5.3794898986816406]], [[-2.2251644134521484, 5.3794898986816406], [-2.298332691192627, 5.3435134887695313]], [[-2.298332691192627, 5.3435134887695313], [-2.453493595123291, 5.295039176940918]], [[-2.453493595123291, 5.295039176940918], [-2.8636500835418701, 5.2430181503295898]], [[0.85654866695404053, 10.681491851806641], [0.041711032390594482, 10.772662162780762]], [[0.041711032390594482, 10.772662162780762], [-0.77188217639923096, 10.981745719909668]], [[-0.77188217639923096, 10.981745719909668], [-1.6740732192993164, 11.384410858154297]], [[-1.6740732192993164, 11.384410858154297], [-2.1156885623931885, 11.678143501281738]], [[-2.1156885623931885, 11.678143501281738], [-2.5282201766967773, 12.044058799743652]], [[-2.5282201766967773, 12.044058799743652], [-2.894564151763916, 12.490079879760742]], [[-2.894564151763916, 12.490079879760742], [-3.19769287109375, 13.024125099182129]], [[-3.19769287109375, 13.024125099182129], [-3.4204742908477783, 13.654131889343262]], [[-3.4204742908477783, 13.654131889343262], [-3.5458495616912842, 14.388022422790527]], [[-3.5458495616912842, 14.388022422790527], [-3.5567302703857422, 15.233731269836426]], [[-3.5567302703857422, 15.233731269836426], [-3.4360604286193848, 16.199182510375977]], [[-3.4360604286193848, 16.199182510375977], [-3.3332633972167969, 16.563583374023437]], [[-3.3332633972167969, 16.563583374023437], [-3.1732132434844971, 16.901628494262695]], [[-3.1732132434844971, 16.901628494262695], [-2.967104434967041, 17.211734771728516]], [[-2.967104434967041, 17.211734771728516], [-2.7260782718658447, 17.492271423339844]], [[-2.7260782718658447, 17.492271423339844], [-2.183896541595459, 17.958181381225586]], [[-2.183896541595459, 17.958181381225586], [-1.6359231472015381, 18.286417007446289]], [[-1.6359231472015381, 18.286417007446289], [-1.2544358968734741, 18.428251266479492]], [[-1.2544358968734741, 18.428251266479492], [-0.85886353254318237, 18.493139266967773]], [[-0.85886353254318237, 18.493139266967773], [-0.45405399799346924, 18.487621307373047]], [[-0.45405399799346924, 18.487621307373047], [-0.044811177998781204, 18.418283462524414]], [[-0.044811177998781204, 18.418283462524414], [0.3640437126159668, 18.291681289672852]], [[0.3640437126159668, 18.291681289672852], [1.1612368822097778, 17.892961502075195]], [[1.1612368822097778, 17.892961502075195], [1.8989444971084595, 17.344032287597656]], [[1.8989444971084595, 17.344032287597656], [2.5385749340057373, 16.697423934936523]], [[2.5385749340057373, 16.697423934936523], [3.0414917469024658, 16.005685806274414]], [[3.0414917469024658, 16.005685806274414], [3.2296538352966309, 15.659317016601563]], [[3.2296538352966309, 15.659317016601563], [3.3691573143005371, 15.321357727050781]], [[3.3691573143005371, 15.321357727050781], [3.4551949501037598, 14.998395919799805]], [[3.4551949501037598, 14.998395919799805], [3.5352253913879395, 12.995941162109375]], [[3.5352253913879395, 12.995941162109375], [3.784975528717041, 11.902626037597656]], [[3.784975528717041, 11.902626037597656], [3.8083422183990479, 11.231494903564453]], [[3.8083422183990479, 11.231494903564453], [3.7082762718200684, 10.443282127380371]], [[3.7082762718200684, 10.443282127380371], [3.2224054336547852, 8.736332893371582]], [[3.2224054336547852, 8.736332893371582], [2.4963922500610352, 7.2232532501220703]], [[2.4963922500610352, 7.2232532501220703], [2.0961728096008301, 6.6774249076843262]], [[2.0961728096008301, 6.6774249076843262], [1.2421826124191284, 5.9903788566589355]], [[1.2421826124191284, 5.9903788566589355], [-1.0766113996505737, 4.5802521705627441]], [[-1.0766113996505737, 4.5802521705627441], [-1.1374696493148804, 4.5296907424926758]], [[-5.4225010871887207, 15.793843269348145], [-5.3365468978881836, 16.316291809082031]], [[-5.3365468978881836, 16.316291809082031], [-5.1778554916381836, 16.829231262207031]], [[-5.1778554916381836, 16.829231262207031], [-4.9520153999328613, 17.326143264770508]], [[-4.9520153999328613, 17.326143264770508], [-4.6645584106445313, 17.800487518310547]], [[-4.6645584106445313, 17.800487518310547], [-4.3210930824279785, 18.245750427246094]], [[-4.3210930824279785, 18.245750427246094], [-3.9271640777587891, 18.655393600463867]], [[-3.9271640777587891, 18.655393600463867], [-3.4883527755737305, 19.022895812988281]], [[-3.4883527755737305, 19.022895812988281], [-3.0102167129516602, 19.341737747192383]], [[-3.0102167129516602, 19.341737747192383], [-2.4983348846435547, 19.605401992797852]], [[-2.4983348846435547, 19.605401992797852], [-1.9582842588424683, 19.807348251342773]], [[-1.9582842588424683, 19.807348251342773], [-1.3956254720687866, 19.941061019897461]], [[-1.3956254720687866, 19.941061019897461], [-0.81592381000518799, 20.0]], [[-0.81592381000518799, 20.0], [-0.22475256025791168, 19.977651596069336]], [[-0.22475256025791168, 19.977651596069336], [0.37230867147445679, 19.867504119873047]], [[0.37230867147445679, 19.867504119873047], [0.96971189975738525, 19.662998199462891]], [[0.96971189975738525, 19.662998199462891], [1.5618711709976196, 19.357641220092773]], [[1.5618711709976196, 19.357641220092773], [2.6488912105560303, 18.540027618408203]], [[2.6488912105560303, 18.540027618408203], [3.5588488578796387, 17.545907974243164]], [[3.5588488578796387, 17.545907974243164], [4.2911796569824219, 16.415573120117187]], [[4.2911796569824219, 16.415573120117187], [4.8453292846679687, 15.189261436462402]], [[4.8453292846679687, 15.189261436462402], [5.2207541465759277, 13.907284736633301]], [[5.2207541465759277, 13.907284736633301], [5.4168548583984375, 12.609891891479492]], [[5.4168548583984375, 12.609891891479492], [5.4330964088439941, 11.33735179901123]], [[5.4330964088439941, 11.33735179901123], [5.2689437866210938, 10.129945755004883]], [[5.2689437866210938, 10.129945755004883], [4.5738754272460937, 7.9436731338500977]], [[4.5738754272460937, 7.9436731338500977], [3.6063516139984131, 6.0835514068603516]], [[3.6063516139984131, 6.0835514068603516], [2.5155248641967773, 4.6291260719299316]], [[2.5155248641967773, 4.6291260719299316], [1.9704980850219727, 4.078923225402832]], [[1.9704980850219727, 4.078923225402832], [0.97474688291549683, 3.2762539386749268]], [[0.97474688291549683, 3.2762539386749268], [-1.4068796634674072, 0.61194717884063721]], [[-1.4068796634674072, 0.61194717884063721], [-1.837470531463623, 0.30826151371002197]], [[-1.837470531463623, 0.30826151371002197], [-2.2937283515930176, 0.10286867618560791]], [[-2.2937283515930176, 0.10286867618560791], [-2.5168232917785645, 0.042755007743835449]], [[-2.5168232917785645, 0.042755007743835449], [-2.7352440357208252, 0.0089514255523681641]], [[-2.7352440357208252, 0.0089514255523681641], [-2.9479730129241943, 5.9604644775390625e-07]], [[-2.9479730129241943, 5.9604644775390625e-07], [-3.1540088653564453, 0.014300346374511719]], [[-3.1540088653564453, 0.014300346374511719], [-3.352367639541626, 0.050418972969055176]], [[-3.352367639541626, 0.050418972969055176], [-3.5420446395874023, 0.1067584753036499]], [[-3.5420446395874023, 0.1067584753036499], [-3.8913605213165283, 0.274086594581604]], [[-3.8913605213165283, 0.274086594581604], [-4.1939587593078613, 0.50416111946105957]], [[-4.1939587593078613, 0.50416111946105957], [-4.4418339729309082, 0.78479528427124023]], [[-4.4418339729309082, 0.78479528427124023], [-4.6269941329956055, 1.1037814617156982]], [[-4.6269941329956055, 1.1037814617156982], [-5.3036313056945801, 3.1146163940429687]], [[-5.3036313056945801, 3.1146163940429687], [-5.3396220207214355, 3.1801633834838867]], [[-5.3396220207214355, 3.1801633834838867], [-5.3821773529052734, 3.2379031181335449]], [[-5.3821773529052734, 3.2379031181335449], [-5.4330964088439941, 3.2887232303619385]]], 29 | 'feet':[[[5.6736354827880859, 1.9343870878219604], [5.4205331802368164, 1.2580138444900513]], [[5.4205331802368164, 1.2580138444900513], [5.2548675537109375, 0.96095144748687744]], [[5.2548675537109375, 0.96095144748687744], [5.0587148666381836, 0.69653570652008057]], [[5.0587148666381836, 0.69653570652008057], [4.8288969993591309, 0.46860814094543457]], [[4.8288969993591309, 0.46860814094543457], [4.562079906463623, 0.2810978889465332]], [[4.562079906463623, 0.2810978889465332], [4.2550563812255859, 0.13790786266326904]], [[4.2550563812255859, 0.13790786266326904], [3.9045205116271973, 0.042907595634460449]], [[3.9045205116271973, 0.042907595634460449], [3.507206916809082, -1.1920928955078125e-06]], [[3.507206916809082, -1.1920928955078125e-06], [2.638974666595459, 0.074774622917175293]], [[2.638974666595459, 0.074774622917175293], [2.2676167488098145, 0.17847716808319092]], [[2.2676167488098145, 0.17847716808319092], [1.9432495832443237, 0.32208681106567383]], [[1.9432495832443237, 0.32208681106567383], [1.6634078025817871, 0.50341486930847168]], [[1.6634078025817871, 0.50341486930847168], [1.4256095886230469, 0.72036087512969971]], [[1.4256095886230469, 0.72036087512969971], [1.2273564338684082, 0.97070395946502686]], [[1.2273564338684082, 0.97070395946502686], [1.0661031007766724, 1.2523734569549561]], [[1.0661031007766724, 1.2523734569549561], [0.84477484226226807, 1.9010204076766968]], [[0.84477484226226807, 1.9010204076766968], [0.74161297082901001, 2.6491379737854004]], [[0.74161297082901001, 2.6491379737854004], [0.86971056461334229, 4.8420310020446777]], [[0.86971056461334229, 4.8420310020446777], [6.0416345596313477, 4.3411960601806641]], [[0.93503522872924805, 7.1895465850830078], [0.8777843713760376, 10.392948150634766]], [[0.8777843713760376, 10.392948150634766], [0.9686129093170166, 12.64793872833252]], [[0.9686129093170166, 12.64793872833252], [1.2076138257980347, 14.985715866088867]], [[1.2076138257980347, 14.985715866088867], [1.3986935615539551, 16.102958679199219]], [[1.3986935615539551, 16.102958679199219], [1.646124005317688, 17.141687393188477]], [[1.646124005317688, 17.141687393188477], [1.956270694732666, 18.068824768066406]], [[1.956270694732666, 18.068824768066406], [2.3355796337127686, 18.851284027099609]], [[2.3355796337127686, 18.851284027099609], [2.7904465198516846, 19.456016540527344]], [[2.7904465198516846, 19.456016540527344], [3.3273506164550781, 19.849939346313477]], [[3.3273506164550781, 19.849939346313477], [3.9526810646057129, 20.0]], [[3.9526810646057129, 20.0], [4.6728634834289551, 19.873104095458984]], [[4.6728634834289551, 19.873104095458984], [5.2378230094909668, 19.602790832519531]], [[5.2378230094909668, 19.602790832519531], [5.7317266464233398, 19.227922439575195]], [[5.7317266464233398, 19.227922439575195], [6.1567678451538086, 18.75505256652832]], [[6.1567678451538086, 18.75505256652832], [6.5150446891784668, 18.190710067749023]], [[6.5150446891784668, 18.190710067749023], [6.8087148666381836, 17.541431427001953]], [[6.8087148666381836, 17.541431427001953], [7.0399141311645508, 16.81373405456543]], [[7.0399141311645508, 16.81373405456543], [7.2107696533203125, 16.014154434204102]], [[7.2107696533203125, 16.014154434204102], [7.3234744071960449, 15.149238586425781]], [[7.3234744071960449, 15.149238586425781], [7.3801565170288086, 14.225506782531738]], [[7.3801565170288086, 14.225506782531738], [7.3339767456054687, 12.227745056152344]], [[7.3339767456054687, 12.227745056152344], [7.089423656463623, 10.073141098022461]], [[-5.6736354827880859, 1.9343870878219604], [-5.4205470085144043, 1.2580138444900513]], [[-5.4205470085144043, 1.2580138444900513], [-5.2548675537109375, 0.96095144748687744]], [[-5.2548675537109375, 0.96095144748687744], [-5.0587315559387207, 0.69653570652008057]], [[-5.0587315559387207, 0.69653570652008057], [-4.8288969993591309, 0.46860814094543457]], [[-4.8288969993591309, 0.46860814094543457], [-4.5620965957641602, 0.2810978889465332]], [[-4.5620965957641602, 0.2810978889465332], [-4.2550563812255859, 0.13790786266326904]], [[-4.2550563812255859, 0.13790786266326904], [-3.9045205116271973, 0.042907595634460449]], [[-3.9045205116271973, 0.042907595634460449], [-3.50722336769104, -1.1920928955078125e-06]], [[-3.50722336769104, -1.1920928955078125e-06], [-2.6389882564544678, 0.074774622917175293]], [[-2.6389882564544678, 0.074774622917175293], [-2.2676167488098145, 0.17847716808319092]], [[-2.2676167488098145, 0.17847716808319092], [-1.9432628154754639, 0.32208681106567383]], [[-1.9432628154754639, 0.32208681106567383], [-1.6634244918823242, 0.50341486930847168]], [[-1.6634244918823242, 0.50341486930847168], [-1.4256229400634766, 0.72036087512969971]], [[-1.4256229400634766, 0.72036087512969971], [-1.2273564338684082, 0.97070395946502686]], [[-1.2273564338684082, 0.97070395946502686], [-1.0661330223083496, 1.2523734569549561]], [[-1.0661330223083496, 1.2523734569549561], [-0.8447914719581604, 1.9010204076766968]], [[-0.8447914719581604, 1.9010204076766968], [-0.74164295196533203, 2.6491379737854004]], [[-0.74164295196533203, 2.6491379737854004], [-0.86972719430923462, 4.8420310020446777]], [[-0.86972719430923462, 4.8420310020446777], [-6.0416479110717773, 4.3411960601806641]], [[-6.3871264457702637, 6.6507234573364258], [-0.93503522872924805, 7.1895465850830078]], [[-0.93503522872924805, 7.1895465850830078], [-0.87779766321182251, 10.392948150634766]], [[-0.87779766321182251, 10.392948150634766], [-0.96862620115280151, 12.64793872833252]], [[-0.96862620115280151, 12.64793872833252], [-1.2076104879379272, 14.985715866088867]], [[-1.2076104879379272, 14.985715866088867], [-1.3987070322036743, 16.102958679199219]], [[-1.3987070322036743, 16.102958679199219], [-1.646124005317688, 17.141687393188477]], [[-1.646124005317688, 17.141687393188477], [-1.9562839269638062, 18.068824768066406]], [[-1.9562839269638062, 18.068824768066406], [-2.3355963230133057, 18.851284027099609]], [[-2.3355963230133057, 18.851284027099609], [-2.7904763221740723, 19.456016540527344]], [[-2.7904763221740723, 19.456016540527344], [-3.3273639678955078, 19.849939346313477]], [[-3.3273639678955078, 19.849939346313477], [-3.9526975154876709, 20.0]], [[-3.9526975154876709, 20.0], [-4.6728768348693848, 19.873104095458984]], [[-4.6728768348693848, 19.873104095458984], [-5.2378368377685547, 19.602790832519531]], [[-5.2378368377685547, 19.602790832519531], [-5.7317566871643066, 19.227922439575195]], [[-5.7317566871643066, 19.227922439575195], [-6.1567807197570801, 18.75505256652832]], [[-6.1567807197570801, 18.75505256652832], [-6.5150575637817383, 18.190710067749023]], [[-6.5150575637817383, 18.190710067749023], [-6.8087148666381836, 17.541431427001953]], [[-6.8087148666381836, 17.541431427001953], [-7.0399141311645508, 16.81373405456543]], [[-7.0399141311645508, 16.81373405456543], [-7.2107834815979004, 16.014154434204102]], [[-7.2107834815979004, 16.014154434204102], [-7.3234872817993164, 15.149238586425781]], [[-7.3234872817993164, 15.149238586425781], [-7.3801536560058594, 14.225506782531738]], [[-7.3801536560058594, 14.225506782531738], [-7.3339896202087402, 12.227745056152344]], [[-7.3339896202087402, 12.227745056152344], [-7.0894403457641602, 10.073141098022461]], [[-6.3871264457702637, 6.6507234573364258], [-7.0894403457641602, 10.073141098022461]], [[-5.6736354827880859, 1.9343870878219604], [-6.0416479110717773, 4.3411960601806641]], [[7.089423656463623, 10.073141098022461], [6.3871264457702637, 6.6507234573364258]], [[0.93503522872924805, 7.1895465850830078], [6.3871264457702637, 6.6507234573364258]], [[5.6736354827880859, 1.9343870878219604], [6.0416345596313477, 4.3411960601806641]]] 30 | } --------------------------------------------------------------------------------