├── Automation └── CI │ ├── JenkinsBuildCILinux.sh │ ├── JenkinsBuildCIOSX.sh │ └── JenkinsBuildCIWindows.bat ├── Documentation ├── HowThePhaseVocoderWorks.md ├── Images │ ├── BassDrumEntireWaveform.png │ ├── BassDrumExpansionExample.png │ ├── BassDrumSpectrogram.png │ ├── BassDrumWaveform.png │ ├── C1PianoNoteWaveform.png │ ├── C2PianoNoteWaveform.png │ ├── C3PianoNoteWaveform.png │ ├── TransientAudioExample.png │ ├── TransientAudioExampleMax256Annotated.png │ ├── TransientAudioExampleMax32Annotated.png │ ├── TransientAudioExampleMax512.png │ ├── TransientAudioExampleMax512Annotated.png │ ├── TransientAudioExampleTransientFound.png │ └── TransientQualification.png └── TransientDetection.md ├── Doxygen └── Doxyfile ├── README.md └── Source ├── AudioData ├── AudioData.h ├── CMakeLists.txt ├── Source │ └── AudioData.cpp └── UT │ ├── AudioData-UT.cpp │ └── CMakeLists.txt ├── CMakeLists.txt ├── CMakeSupport ├── CMakeLists.CompilerSettings.txt ├── CMakeLists.Externals.txt └── CMakeLists.GoogleTest.txt ├── Signal ├── CMakeLists.txt ├── Fourier.h ├── FrequencyDomain.h ├── LowPassFilter.h ├── PeakFrequencyDetection.h ├── PeakProfile.h ├── PhaseVocoder.h ├── Resampler.h ├── SignalConversion.h ├── Source │ ├── Fourier.cpp │ ├── FrequencyDomain.cpp │ ├── LowPassFilter.cpp │ ├── PeakFrequencyDetection.cpp │ ├── PeakProfile.cpp │ ├── PhaseVocoder.cpp │ ├── Resampler.cpp │ ├── SignalConversion.cpp │ ├── TransientDetector.cpp │ ├── TransientPeakAndValley.cpp │ ├── WindowedSincValues.cpp │ ├── WindowedSincValues.h │ └── Windowing.cpp ├── TransientDetector.h ├── TransientPeakAndValley.h ├── UT │ ├── CMakeLists.txt │ ├── Fourier-UT.cpp │ ├── LowPassFilter-UT.cpp │ ├── PeakFrequencyDetection-UT.cpp │ ├── PeakProfile-UT.cpp │ ├── PhaseVocoder-UT.cpp │ ├── Resampler-UT.cpp │ ├── SignalConversion-UT.cpp │ ├── TestAudio │ │ ├── 100HzSineWaveAt32768Hz.wav │ │ ├── 222HzSineAnd19000HzSine.wav │ │ ├── 400HzSineAnd2121HzSine.wav │ │ ├── 5000HzSineAnd9797HzSine.wav │ │ ├── 808BassDrum.wav │ │ ├── 808RimShot616SamplesLong.wav │ │ ├── 808Snare1024SamplesLong.wav │ │ ├── 808Snare2615SamplesLong.wav │ │ ├── 808Snare4096SamplesLong.wav │ │ ├── 808Snare4097SamplesLong.wav │ │ ├── 808Snare4100SamplesLong.wav │ │ ├── AcousticGuitarDualStringPluck.wav │ │ ├── AcousticGuitarStringPluck.wav │ │ ├── BuiltToSpillBeat32123HzSampleRate.wav │ │ ├── BuiltToSpillBeatBeginningWithSilence.wav │ │ ├── PeakAt6000And10000.wav │ │ ├── SinglePianoKey.wav │ │ ├── SweetEmotion.wav │ │ └── TenSamples.wav │ ├── TestAudioResults │ │ ├── 100HzSineWaveAt32768HzCompressed.wav │ │ ├── 100HzSineWaveAt32768HzResampled.wav │ │ ├── 100HzSineWaveAt32768HzStretched.wav │ │ ├── 222HzSineAnd19000HzSineLowPassFilteredAt8000Hz.wav │ │ ├── 222HzSineAnd19000HzSineResampled.wav │ │ ├── 400HzSineAnd2121HzSineLowPassFilteredAt1000Hz.wav │ │ ├── 400HzSineAnd2121HzSineResampled.wav │ │ ├── 5000HzSineAnd9797HzSineLowPassFilteredAt6000Hz.wav │ │ ├── 5000HzSineAnd9797HzSineResampled.wav │ │ ├── 808BassDrumCompressed.wav │ │ ├── 808BassDrumStretched.wav │ │ ├── 808RimShot616SamplesLongCompressed.wav │ │ ├── 808RimShot616SamplesLongStretched.wav │ │ ├── 808Snare1024SamplesLongCompressed.wav │ │ ├── 808Snare1024SamplesLongOutputStretched.wav │ │ ├── 808Snare2615SamplesLongCompressed.wav │ │ ├── 808Snare2615SamplesLongStretched.wav │ │ ├── 808Snare4096SamplesLongCompressed.wav │ │ ├── 808Snare4096SamplesLongStretched.wav │ │ ├── 808Snare4097SamplesLongCompressed.wav │ │ ├── 808Snare4097SamplesLongStretched.wav │ │ ├── 808Snare4100SamplesLongCompressed.wav │ │ ├── 808Snare4100SamplesLongStretched.wav │ │ ├── AcousticGuitarStringPluckCompressed.wav │ │ ├── AcousticGuitarStringPluckStretched.wav │ │ ├── SinglePianoKeyCompressed.wav │ │ ├── SinglePianoKeyResampled.wav │ │ ├── SinglePianoKeyStretched.wav │ │ ├── TenSamplesOutputCompressed.wav │ │ └── TenSamplesOutputStretched.wav │ ├── TransientDetector-UT.cpp │ ├── TransientPeakAndValley-UT.cpp │ ├── Windowing-UT.cpp │ └── main.cpp └── Windowing.h ├── ThreadSafeAudioFile ├── CMakeLists.txt ├── Reader.h ├── Source │ ├── Reader.cpp │ └── Writer.cpp ├── UT │ ├── AudioFileDataHelper.cpp │ ├── AudioFileDataHelper.h │ ├── CMakeLists.txt │ ├── Reader-UT.cpp │ └── Writer-UT.cpp └── Writer.h ├── Utilities ├── CMakeLists.txt ├── Exception.h ├── File.h ├── Source │ ├── Exception.cpp │ ├── File.cpp │ ├── Stringify.cpp │ └── Timer.cpp ├── Stringify.h ├── Timer.h └── UT │ ├── CMakeLists.txt │ ├── Exception-UT.cpp │ ├── File-UT.cpp │ ├── Stringify-UT.cpp │ ├── TestFiles │ ├── TestFileA.txt │ └── TestFileB.txt │ └── Timer-UT.cpp └── WaveFile ├── CMakeLists.txt ├── Source ├── WaveFileReader.cpp └── WaveFileWriter.cpp ├── UT ├── CMakeLists.txt ├── TestWaveFile.wav ├── TestWaveFileMono.wav ├── TestWaveFileStereo.wav ├── WaveFileReader-UT.cpp └── WaveFileWriter-UT.cpp ├── WaveFileDefines.h ├── WaveFileHeader.h ├── WaveFileReader.h └── WaveFileWriter.h /Automation/CI/JenkinsBuildCILinux.sh: -------------------------------------------------------------------------------- 1 | # Do a debug build 2 | mkdir DebugBuild 3 | cd DebugBuild 4 | cmake -DCMAKE_BUILD_TYPE=Debug -G "Unix Makefiles" ../Source 5 | cmake --build . 6 | 7 | cd .. 8 | 9 | # Do a release build 10 | mkdir ReleaseBuild 11 | cd ReleaseBuild 12 | cmake -DCMAKE_BUILD_TYPE=Release -G "Unix Makefiles" ../Source 13 | cmake --build . 14 | -------------------------------------------------------------------------------- /Automation/CI/JenkinsBuildCIOSX.sh: -------------------------------------------------------------------------------- 1 | # Do a debug build 2 | mkdir DebugBuild 3 | cd DebugBuild 4 | /usr/local/bin/cmake -G "Xcode" -DCMAKE_BUILD_TYPE=Debug ../Source 5 | xcodebuild -project AudioLib.xcodeproj -configuration Debug 6 | 7 | cd .. 8 | 9 | # Do a releaase build 10 | mkdir ReleaseBuild 11 | cd ReleaseBuild 12 | /usr/local/bin/cmake -G "Xcode" -DCMAKE_BUILD_TYPE=Release ../Source 13 | xcodebuild -project AudioLib.xcodeproj -configuration Release 14 | -------------------------------------------------------------------------------- /Automation/CI/JenkinsBuildCIWindows.bat: -------------------------------------------------------------------------------- 1 | call "%VisualStudioPath%\vcvarsall.bat" amd64 2 | cd %WORKSPACE% 3 | mkdir AudioLibBuilt 4 | cd AudioLibBuilt 5 | cmake -G "%CMakeGeneratorString%" ../Source 6 | devenv AudioLib.sln /Build Debug 7 | devenv AudioLib.sln /Build Release 8 | -------------------------------------------------------------------------------- /Documentation/HowThePhaseVocoderWorks.md: -------------------------------------------------------------------------------- 1 | How the PhaseVocoder Works 2 | ========================== 3 | 4 | The PhaseVocoder allows for time expansion/compression and pitch shifting of audio. The general process involves analyzing input audio and placing it into two different categories: transient components and sinusoidal components. An audio transient is usually a higher amplitude, short-duration percusive sound occurring at the onset of audio such as a drum hit or musical note (more info on transient detection [here](TransientDetection.md)). Frequently, audio with more sinusoidal components follows the transient. The PhaseVocoder works by preserving the transient and stretching or compressing the phase of the sinusoidal components of the input audio. 5 | 6 | 7 |   8 | 9 | **Time Expansion Example** 10 | 11 | The waveform and spectrogram below is that of the first 70 milliseconds of a bass drum. A wave file of this audio can be found [here](https://github.com/tmdarwen/PhaseVocoder/blob/master/Source/Signal/UT/TestAudio/808BassDrum.wav). In the waveform, note how the first few milliseconds of audio has an irregular shape and then the waveform follows a sinusoidal shape. 12 | 13 |

14 | 15 | Similarly, in the spectrogram below, note how the first few milliseconds of audio has strong amounts of energy across nearly the entire spectrum of 0 to 8,000 Hz before concentrating at specific lower frequencies. 16 | 17 |

18 | 19 | The entire waveform of the bass drum can be seen below. After the initial transient, the audio continues uninterrupted for the entire 1.66 seconds. In other words, no other transients occur aside from the initial transient. 20 | 21 |

22 | 23 | In order to apply time expansion or compression to the input audio, without degrading audio quality, the PhaseVocoder first preserves the transient section. The PhaseVocoder then takes small windows of the audio following the transient, adjusts the phase of the window in the frequency domain, before taking the inverse Fourier transform and writing the window to the output audio. The following is an oversimplification but should give you a general idea of how the process works: 24 | 25 |

26 | 27 |   28 | 29 | **Pitch Shifting** 30 | 31 | Shifting the pitch of audio without changing its length is done using a resampler in conjunction with the phase vocoding technique described above. 32 | 33 | The key to understanding pitch shifting is to understand that playing audio back at different sample rates adjusts the pitch of the audio. For example, if you have audio of a C2 piano note (65.41 Hz) recorded at 44.1 KHz, playing back the audio at twice the sample rate (88.2 KHz) will result in a C3 piano note (130.81 Hz), effectively adjusting the pitch up by an octave. 34 | 35 | Similarly, playing back the audio at half the sample rate (22.05 KHz) will result in a C1 piano note (32.70 Hz). These examples are shown below. Take note of the values above the waveform on the x-axis. These are in units of seconds. 36 | 37 | A C2 piano note recorded at 44.1 KHz two seconds in durations: 38 | 39 |

40 | 41 | The C2 piano note recorded at 44.1 KHz but played back at 88.2 KHz results in a pitch shift up by one octave to C3. Note the duration of the audio is cut in half to one second: 42 | 43 |

44 | 45 | The C2 piano note recorded at 44.1 KHz but played back at 22.05 KHz results in a pitch shift down one octave to C1. Note the duration of the audio is doubled to four seconds: 46 | 47 |

48 | 49 | As shown above, pitch shifting can be accomplished by adjusting sample rates. However, this results in the length of the input audio being extended or reduced. This is where the PhaseVocoder's phase vocoding abilities come in. Often in audio editing and production you'll want the note to remain a constant time length as the pitch is adjusted. The same phase vocoding technique explained earlier in this document is applied to the resampled audio to accomplish this. 50 | -------------------------------------------------------------------------------- /Documentation/Images/BassDrumEntireWaveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/BassDrumEntireWaveform.png -------------------------------------------------------------------------------- /Documentation/Images/BassDrumExpansionExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/BassDrumExpansionExample.png -------------------------------------------------------------------------------- /Documentation/Images/BassDrumSpectrogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/BassDrumSpectrogram.png -------------------------------------------------------------------------------- /Documentation/Images/BassDrumWaveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/BassDrumWaveform.png -------------------------------------------------------------------------------- /Documentation/Images/C1PianoNoteWaveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/C1PianoNoteWaveform.png -------------------------------------------------------------------------------- /Documentation/Images/C2PianoNoteWaveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/C2PianoNoteWaveform.png -------------------------------------------------------------------------------- /Documentation/Images/C3PianoNoteWaveform.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/C3PianoNoteWaveform.png -------------------------------------------------------------------------------- /Documentation/Images/TransientAudioExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientAudioExample.png -------------------------------------------------------------------------------- /Documentation/Images/TransientAudioExampleMax256Annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientAudioExampleMax256Annotated.png -------------------------------------------------------------------------------- /Documentation/Images/TransientAudioExampleMax32Annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientAudioExampleMax32Annotated.png -------------------------------------------------------------------------------- /Documentation/Images/TransientAudioExampleMax512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientAudioExampleMax512.png -------------------------------------------------------------------------------- /Documentation/Images/TransientAudioExampleMax512Annotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientAudioExampleMax512Annotated.png -------------------------------------------------------------------------------- /Documentation/Images/TransientAudioExampleTransientFound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientAudioExampleTransientFound.png -------------------------------------------------------------------------------- /Documentation/Images/TransientQualification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Documentation/Images/TransientQualification.png -------------------------------------------------------------------------------- /Documentation/TransientDetection.md: -------------------------------------------------------------------------------- 1 | Transient Detection 2 | =================== 3 | 4 | A transient is usually a higher amplitude, short-duration percusive sound occurring at the onset of audio such as a drum hit or musical note. For the sake of sound quality, it's key to preserve transients when performing the phase vocoding process. 5 | 6 | What qualifies audio as being a transient is debatable, and therefore can be difficult to auto-detect. What I explain below is a transient detection algorithm I've developed. One important item to note about this algorithm is that it's imperative to make the transient detection point occur _immediately before_ the transient begins. If the PhaseVocoder does not preserve the entire transient the audio quality will suffer. 7 | 8 | 9 |   10 | 11 | **Transient Detection By Example** 12 | 13 | In the audio snippet pictured, it's clear we have three transients: One at the very beginning of the audio, one between 0.20 and 0.30 and another one between 0.40 and 0.50. 14 | 15 |

16 | 17 | When first analyzing audio input, detecting the first transient is a special case. We simply consider the first audio that occurs above a nominal audio output threshold to be the transient. Because of this, it's more useful here to explain how the second transient (between 0.20 and 0.30) is detected. 18 | 19 | The first step we perform in transient detection is to reduce the input signal down to roughly twelve millisecond sections, plotting a point that is the max sample value in this twelve millisecond section as shown below. 20 | 21 |

22 | 23 | We then take note of the "peak" and "valley" points in this graph as shown below. 24 | 25 |

26 | 27 | We then perform the exact same process between the valley and peak sample positions but at a resolution of about six milliseconds, resulting in the following: 28 | 29 |

30 | 31 | And then one additional time at a resolution of about one millisecond: 32 | 33 |

34 | 35 | We use this final valley point to calculate the beginning of the transient. For this example, this results in finding the transient position _immediately before_ the transient occurs as shown below. 36 | 37 |

38 | 39 | Another way of considering this process is that we're recursively "zooming in" on the point where the audio transitions from low intensity to high intensity to pinpoint the start of the audio transient. 40 | 41 | 42 |   43 | 44 | **Transient Qualification** 45 | 46 | Of course, not every peak/valley combination is a transient. In order for a peak/valley combination to be considered a transient the peak value must be at least 1.5 times greater than the valley. Note the image below. The circled peak and valley combination is not considered a transient since the growth from the valley point to the peak point is less than a ratio of 1.5. 47 | 48 |

49 | 50 | When using the PhaseVocoder, this ratio can be configured by the user through the `--valleypeakratio`. This option allows the user to set a specific ratio. The default ratio is 1.5. Setting a lower ratio will result in the software more liberally detecting audio transients, setting a higher ratio will cause the software to be more strict when considering if a peak/value combination is a transient. 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AudioLib 2 | ======== 3 | 4 | This is a library I've made to support some of my audio related projects. It's currently used in my [PhaseVocoder](https://github.com/tmdarwen/PhaseVocoder) project and my [AudioAnalysisTool](https://github.com/tmdarwen/AudioAnalysisTool) project. Feel free to use this library in your own projects. It's licensed under the permissable [MIT license](https://en.wikipedia.org/wiki/MIT_License). 5 | 6 |   7 | 8 | **Main Components** 9 | 10 | - AudioData - A class allowing for easy transportation and access of digital audio data. 11 | 12 | - Signal - Various digital signal processing utilities, such as the [PhaseVocder](Documentation/HowThePhaseVocoderWorks.md) and [TransientDetector](Documentation/TransientDetection.md). 13 | 14 | - ThreadSafeAudioFile - Provides threadsafe reading and writing of wave audio files. 15 | 16 | - Utilities - Basic utilities supporting application development. 17 | 18 | - WaveFile - Simple classes for reading and writing wave audio files. 19 | 20 |   21 | 22 | **Doxygen Documentation** 23 | 24 | Doxygen documentation can easily be generated by simply running Doxygen using the config file located in the [Doxygen](Doxygen) directory. 25 | 26 |   27 | 28 | **Unit Test Coverage** 29 | 30 | Unit test coverage is extensive. You'll notice every component within the source directory has a UT directory which contains unit tests. These automatically build and run as part of the build process. 31 | 32 |   33 | 34 | **Build Dependencies** 35 | 36 | - Building this project requires [CMake](https://cmake.org) version 3.0 or later. 37 | 38 | - Building this project requires a C++14 compliant compiler. This project is routinely built in my continous integration system which uses MS Visual Studio 2017, GCC 5.3.1 and Apple LLVM version 7.3.0 (clang 703.0.31). 39 | 40 | - [GoogleTest](https://github.com/google/googletest) is currently the only external dependency. You do *not* need to clone or install this manually. The GoogleTest GitHub repo will be cloned automatically when CMake runs. 41 | 42 |   43 | 44 | **Steps for Building** 45 | 46 | 1. Clone this repo. 47 | 48 | 2. Create a new directory at the parallel level as the cloned repo. This directory will hold the project files CMake creates. 49 | 50 | 3. cd into this new directory. 51 | 52 | 4. From the command line, run _cmake -G YourDesiredGeneratorType ../AudioLib/Source_ 53 | 54 | 5. Run make or open the project file in an IDE and build. 55 | 56 |   57 | 58 | 59 | **Continuous Integration** 60 | 61 | The [Automation/CI directory](/Automation/CI) contains scripts that can be used with [Jenkins](https://jenkins.io/) to setup continuous integration in Linux, MacOS and Windows. For more information on how to setup Jenkins to use these scripts please see [this document](https://github.com/tmdarwen/PhaseVocoder/tree/master/Documentation/JenkinsSetup.md). 62 | 63 |   64 | 65 | 66 | **Licensing** 67 | 68 | The MIT License applies to this software and its supporting documentation: 69 | 70 | *Copyright (c) 2017-2018 - Terence M. Darwen - tmdarwen.com* 71 | 72 | *Permission is hereby granted, free of charge, to any person obtaining a copy of 73 | this software and associated documentation files (the "Software"), to deal in 74 | the Software without restriction, including without limitation the rights to 75 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 76 | the Software, and to permit persons to whom the Software is furnished to do so, 77 | subject to the following conditions:* 78 | 79 | *The above copyright notice and this permission notice shall be included in all 80 | copies or substantial portions of the Software.* 81 | 82 | *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 84 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 85 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 86 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 87 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.* 88 | -------------------------------------------------------------------------------- /Source/AudioData/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp "Source/[^.]*.h" "Source/[^.]*.cpp") 3 | add_library(AudioData ${source_files}) 4 | 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | 7 | add_subdirectory(UT) 8 | 9 | set_target_properties(AudioData PROPERTIES FOLDER Libs) 10 | 11 | -------------------------------------------------------------------------------- /Source/AudioData/UT/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | #set(project_name AudioData-UT) 2 | include_directories("${PROJECT_SOURCE_DIR}/External/googletest/googletest/include") 3 | file(GLOB source_files [^.]*.h [^.]*.cpp "../Source/[^.]*.h" "../Source/[^.]*.cpp") 4 | add_executable(AudioData-UT ${source_files}) 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | target_link_libraries(AudioData-UT Utilities gtest) 7 | add_custom_command(TARGET AudioData-UT POST_BUILD COMMAND AudioData-UT --output-on-failure) 8 | 9 | set_target_properties(AudioData-UT PROPERTIES FOLDER Libs) 10 | 11 | -------------------------------------------------------------------------------- /Source/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 4 | 5 | project(AudioLib) 6 | 7 | if(DEBUG_BUILD) 8 | add_definitions(-DDEBUG_BUILD) 9 | endif(DEBUG_BUILD) 10 | 11 | if(DEBUG_TO_LOG_FILE) 12 | add_definitions(-DDEBUG_BUILD) 13 | add_definitions(-DDEBUG_TO_LOG_FILE) 14 | endif(DEBUG_TO_LOG_FILE) 15 | 16 | option(INCLUDE_GOOGLE_TEST "Pulldown GoogleTest and include it in the project" ON) 17 | 18 | option(MSVC_MULTI_THREADED_DLL_RUNTIME_LIB "Allows for setting the runtime lib for MSVC compiler to multi-threaded DLL" OFF) 19 | 20 | # "Externals" consists of GoogleTest 21 | if(INCLUDE_GOOGLE_TEST) 22 | include(${CMAKE_CURRENT_SOURCE_DIR}/CMakeSupport/CMakeLists.Externals.txt) 23 | include_externals() 24 | endif() 25 | 26 | add_subdirectory(AudioData) 27 | add_subdirectory(Signal) 28 | add_subdirectory(ThreadSafeAudioFile) 29 | add_subdirectory(Utilities) 30 | add_subdirectory(WaveFile) 31 | -------------------------------------------------------------------------------- /Source/CMakeSupport/CMakeLists.CompilerSettings.txt: -------------------------------------------------------------------------------- 1 | # Specific compiler settings we want to set in various projects 2 | 3 | option(MSVCMT "Pulldown GoogleTest and include it in the project" ON) 4 | 5 | if(MSVC) 6 | set(CMAKE_CXX_FLAGS "/Zi /EHsc") 7 | if(MSVC_MULTI_THREADED_DLL_RUNTIME_LIB) 8 | set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd") 9 | set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD") 10 | else() 11 | set(CMAKE_CXX_FLAGS_DEBUG "/Od /MTd") 12 | set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MT") 13 | endif(MSVC_MULTI_THREADED_DLL_RUNTIME_LIB) 14 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /INCREMENTAL:NO /OPT:REF /OPT:ICF") #Generate PDBs for release build 15 | elseif(APPLE) 16 | add_definitions(-DTARGET_MAC) 17 | set(CMAKE_CXX_FLAGS "-std=c++14") 18 | elseif(UNIX) 19 | add_definitions(-DTARGET_LINUX) 20 | set(CMAKE_CXX_FLAGS "-std=c++14 -g") 21 | endif(MSVC) 22 | 23 | -------------------------------------------------------------------------------- /Source/CMakeSupport/CMakeLists.Externals.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | function(include_external cmake_file project_name) 4 | configure_file(${cmake_file} ${project_name}-download/CMakeLists.txt) 5 | execute_process(COMMAND "${CMAKE_COMMAND}" -G "${CMAKE_GENERATOR}" . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/${project_name}-download") 6 | execute_process(COMMAND "${CMAKE_COMMAND}" --build . WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/${project_name}-download" ) 7 | add_subdirectory("${CMAKE_BINARY_DIR}/${project_name}-src" "${CMAKE_BINARY_DIR}/${project_name}-build") 8 | endfunction() 9 | 10 | function(include_externals) 11 | # Pulldown GoogleTest 12 | if(MSVC) 13 | # The following is needed for compiling with MSVC C++ compiler v15.5 b/c of deprecation of std::tr1 namespace 14 | add_definitions(-D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING) 15 | if(MSVC_MULTI_THREADED_DLL_RUNTIME_LIB) 16 | set(CMAKE_CXX_FLAGS_RELEASE "/MD") 17 | set(CMAKE_CXX_FLAGS_DEBUG "/MDd") 18 | else() 19 | set(CMAKE_CXX_FLAGS_DEBUG "/Od /MTd") 20 | set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MT") 21 | endif(MSVC_MULTI_THREADED_DLL_RUNTIME_LIB) 22 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) # Prevent GoogleTest from overriding options when building with MSVC 23 | endif(MSVC) 24 | include_external(CMakeSupport/CMakeLists.GoogleTest.txt googletest) 25 | endfunction() 26 | -------------------------------------------------------------------------------- /Source/CMakeSupport/CMakeLists.GoogleTest.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(googletest-download NONE) 4 | 5 | include(ExternalProject) 6 | ExternalProject_Add(googletest 7 | GIT_REPOSITORY https://github.com/google/googletest.git 8 | GIT_TAG release-1.8.0 9 | SOURCE_DIR "${CMAKE_BINARY_DIR}/googletest-src" 10 | BINARY_DIR "${CMAKE_BINARY_DIR}/googletest-build" 11 | CONFIGURE_COMMAND "" 12 | BUILD_COMMAND "" 13 | INSTALL_COMMAND "" 14 | TEST_COMMAND "" 15 | ) 16 | -------------------------------------------------------------------------------- /Source/Signal/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp "Source/[^.]*.h" "Source/[^.]*.cpp") 3 | add_library(Signal ${source_files}) 4 | 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | 7 | add_subdirectory(UT) 8 | 9 | set_target_properties(Signal PROPERTIES FOLDER Libs) 10 | 11 | -------------------------------------------------------------------------------- /Source/Signal/Fourier.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file Fourier.h 28 | //! @brief Functions allowing for Fourier transformations. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Signal { 37 | 38 | 39 | namespace Fourier 40 | { 41 | //! Returns true if the given number is a power of two; false otherwise. 42 | bool IsPowerOfTwo(std::size_t number); 43 | 44 | //! Applies the Discrete Fourier Transform to the given audio data. 45 | Signal::FrequencyDomain ApplyDFT(const AudioData& timeDomainSignal); 46 | 47 | //! Applies the Inverse Discrete Fourier Transform to the given frequency data. 48 | AudioData ApplyInverseDFT(const Signal::FrequencyDomain& frequencyDomainData); 49 | 50 | //! Applies the Fast Fourier Transform to the given audio data. 51 | // 52 | //! Note that the length of the given audio must be a power of two. 53 | Signal::FrequencyDomain ApplyFFT(const AudioData& timeDomainSignal); 54 | 55 | //! Applies the Inverse Fast Fourier Transform to the given audio data. 56 | // 57 | //! Note that the length of the given frequency domain data must be a power of two. 58 | AudioData ApplyInverseFFT(const Signal::FrequencyDomain& frequencyDomainData); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Source/Signal/FrequencyDomain.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file FrequencyDomain.h 28 | //! @brief Class to hold frequency domain information for a signal. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | 35 | namespace Signal { 36 | 37 | //! Simple structure to hold a single frequency bin's frequency domain data. 38 | 39 | struct FrequencyBin 40 | { 41 | //! Instantiates frequency bin data with zeroes for real and imaginary values. 42 | FrequencyBin() : reX_(0.0), imX_(0.0) { } 43 | 44 | //! Instantiates frequency bin data with the given values. 45 | FrequencyBin(double reX, double imX) : reX_(reX), imX_(imX) { } 46 | 47 | //! The real component of the frequency. 48 | double reX_; 49 | 50 | //! The imaginary component of the frequency. 51 | double imX_; 52 | }; 53 | 54 | 55 | //! Class to hold frequency domain information for a signal. 56 | 57 | class FrequencyDomain 58 | { 59 | public: 60 | 61 | //! Constructs object with no frequency data. 62 | FrequencyDomain(); 63 | 64 | //! Constructs object with the given frequency domain data. 65 | FrequencyDomain(std::vector FrequencyBin); 66 | 67 | //! Add the given frequency bin data. 68 | void PushFrequencyBin(Signal::FrequencyBin FrequencyBin); 69 | 70 | //! Get the number of frequency bins in this frequency domain data. 71 | std::size_t GetSize() const; 72 | 73 | //! Get frequency bin data for the given frequency bin. 74 | const FrequencyBin& GetBin(std::size_t binNumber) const; 75 | 76 | //! Get the magnitudes for all frequency bins. 77 | const std::vector& GetMagnitudes(); 78 | 79 | //! Get the wrapped phase values for all frequency bins. 80 | const std::vector& GetWrappedPhases(); 81 | 82 | //! Get the real components of all frequency bins. 83 | const std::vector& GetRealComponent(); 84 | 85 | //! Get the imaginary components of all frequency bins. 86 | const std::vector& GetImaginaryComponent(); 87 | 88 | //! Get the data for all frequency bins. 89 | std::vector GetRectangularFrequencyData() const; 90 | 91 | private: 92 | double CalculateArcTangent(double imaginary, double real); 93 | enum Quadrant 94 | { 95 | QUADRANT1, 96 | QUADRANT2, 97 | QUADRANT3, 98 | QUADRANT4, 99 | BETWEEN_QUADRANT1_AND_QUADRANT2, 100 | BETWEEN_QUADRANT2_AND_QUADRANT3, 101 | BETWEEN_QUADRANT3_AND_QUADRANT4, 102 | BETWEEN_QUADRANT4_AND_QUADRANT1 103 | }; 104 | 105 | Quadrant GetQuadrant(double reX, double imX); 106 | double GetWrappedPhase(double reX, double imX); 107 | 108 | std::vector data_; 109 | 110 | // Cached data, lazy initialized 111 | std::vector magnitudes_; 112 | std::vector wrappedPhases_; 113 | std::vector realComponent_; 114 | std::vector imaginaryComponent_; 115 | }; 116 | 117 | } 118 | -------------------------------------------------------------------------------- /Source/Signal/LowPassFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file LowPassFilter.h 28 | //! @brief Implementation of a low pass filter. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | 35 | namespace Signal { 36 | 37 | //! An implementation of a low pass filter. 38 | // 39 | //! This is an implementation of equation 16-4 (Windowed Sinc Filter) from the 40 | //! book "The Scientist and Engineer's Guide to Digital Signal Processing" 2nd 41 | //! edition by Steven W. Smith. 42 | 43 | class LowPassFilter 44 | { 45 | public: 46 | 47 | //! Instatiate the LowPassFilter. 48 | // 49 | //! The cutoffRatio should be calculated as follows: 50 | //! cutoffRatio = OutputSampleRate/InputSampleRate * 0.5 51 | //! signal sample rate. For example, if you're input is a 44100Hz signal and 52 | //! you want to filter out everything above 32000Hz you would use a ratio of 53 | //! 0.3628. 54 | LowPassFilter(double cutoffRatio, std::size_t filterLength=100); 55 | 56 | //! Clears internal buffers and counters to restart processing fresh. 57 | void Reset(); 58 | 59 | //! Submit audio data for the filter to process. 60 | void SubmitAudioData(const AudioData& audioData); 61 | 62 | //! Method to retrieve output after processed by the low pass filter. 63 | AudioData GetAudioData(uint64_t samples); 64 | 65 | //! Returns the number of output samples currently available. 66 | std::size_t OutputSamplesAvailable(); 67 | 68 | //! At the end of processing, this can be called to get any and all remaining output samples. 69 | AudioData FlushAudioData(); 70 | 71 | //! Returns the minimum input samples needed for processing. This is the same as the filter length. 72 | std::size_t MinimumSamplesNeededForProcessing(); 73 | 74 | 75 | private: 76 | void CalculateFilterKernel(); 77 | void Process(); 78 | 79 | double cutoffRatio_; 80 | std::size_t filterLength_; 81 | std::vector filterKernel_; 82 | 83 | AudioData audioInput_; 84 | AudioData audioOutput_; 85 | 86 | std::mutex mutex_; 87 | 88 | const double minCutoffRatioRange_{0.0001}; 89 | const double maxCutoffRatioRange_{0.5000}; 90 | }; 91 | 92 | } 93 | -------------------------------------------------------------------------------- /Source/Signal/PeakFrequencyDetection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file PeakFrequencyDetection.h 28 | //! @brief Pinpoint signal frequency from Fourier information. 29 | 30 | #pragma once 31 | 32 | #include 33 | 34 | namespace Signal { 35 | 36 | //! Allows for generating audio data of the given sine wave at the given sample rate, frequency, etc. 37 | std::vector GenerateSineWave(double sampleRate, std::size_t lengthInSamples, double signalFrequency, double phase=0.0); 38 | 39 | // ******************************************************************************************************* 40 | // ******************* USE THE "QUINN" ALGORITHM AND AVOID THE CORRELATION ONE BELOW ********************* 41 | // ******************************************************************************************************* 42 | // I got this algorithm from: http://dspguru.com/dsp/howtos/how-to-interpolate-fft-peak it's called "Quinn's Second Estimator". 43 | // If there's any question about how well it performs compared to correlation, see the UT titled "TestPeakBinFrequencyAccuracy" 44 | 45 | //! Given a time domain signal, and the frequency bin, this will ascertain what the frequency of the signal is. 46 | double GetPeakFrequencyByQuinn(std::size_t peakBin, const std::vector& timeDomainSignal, double inputSignalSampleRate); 47 | 48 | //! Given a frequency domain information of a signal, this will ascertain what the frequency of the signal is. 49 | double GetPeakFrequencyByQuinn(std::size_t peakBin, std::size_t fourierSize, const std::vector& real, const std::vector& imaginary, double inputSignalSampleRate); 50 | 51 | //! Attempts to use correlation to pinpoint the frequency of the signal for the given frequency bin. 52 | double GetPeakFrequencyByCorrelation(std::size_t peakBin, const std::vector& timeDomainSignal, double inputSignalSampleRate); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /Source/Signal/PeakProfile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file PeakProfile.h 28 | //! @brief Identifies peak frequency bins in an audio signal. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Signal { 37 | 38 | //! Given a frequency domain signal, this will find all the "peak" bins. 39 | 40 | class PeakProfile 41 | { 42 | public: 43 | //! Instantiate the object with frequency domain information. 44 | PeakProfile(const FrequencyDomain& frequencyDomain); 45 | 46 | //! This will return the closest peak for the given frequency bin. 47 | std::size_t GetLocalPeakForBin(std::size_t bin); 48 | 49 | //! Returns a list of all peak bins. 50 | const std::vector& GetAllPeakBins(); 51 | 52 | //! Returns a list of all valley bins. 53 | std::pair GetValleyBins(std::size_t peakBin); 54 | 55 | private: 56 | void CalculatePeaksAndValleys(); 57 | 58 | // The following hold the bin numbers of peaks (high point of magnitude) and 59 | // the low points (or "valleys") on both sides of the peaks. 60 | std::vector peakBins_; 61 | std::vector valleyBins_; 62 | 63 | // The frequency domain given by the user at construction 64 | FrequencyDomain frequencyDomain_; 65 | }; 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Source/Signal/PhaseVocoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file PhaseVocoder.h 28 | //! @brief Implementation of a phase vocoder. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | #include 35 | #define _USE_MATH_DEFINES // Seems some compilers need this so M_PI will be defined 36 | #include 37 | #include 38 | 39 | namespace Signal { 40 | 41 | class FrequencyDomain; 42 | class PeakProfile; 43 | 44 | //! Implementation of a phase vocoder. 45 | 46 | //! A phase vocoder allows for stretching/compressing audio with respect to time. Wikipedia has a pretty good explanation 47 | //! of how a phase vocoder works. 48 | 49 | class PhaseVocoder 50 | { 51 | public: 52 | //! Instatiate the phase vocoder. 53 | // 54 | //! Params: 55 | //! 1) The sample rate of the audio it will process (e.g. 44100) 56 | //! 2) The total length in samples of the input we'll be stretching 57 | //! 3) The stretch factor which is a ratio of the input (e.g. 1.0 = no change, 0.8 = 20% speedup, 1.2 = 20% slowdown) 58 | PhaseVocoder(std::size_t sampleRate, std::size_t inputLength, double stretchFactor); 59 | virtual ~PhaseVocoder(); 60 | 61 | //! Clears internal buffers and etc to allow for restarting processing fresh. 62 | void Reset(); 63 | 64 | //! Submit audio data to be processed by the phase vocoder. 65 | void SubmitAudioData(const AudioData& audioData); 66 | 67 | //! Retrieve output audio the Phase Vocoder has processed, requesting a certain number of samples. 68 | AudioData GetAudioData(uint64_t samples); 69 | 70 | //! Returns the number of output samples currently available. 71 | std::size_t OutputSamplesAvailable(); 72 | 73 | //! At the end of processing, this can be called to get any and all remaining output samples. 74 | AudioData FlushAudioData(); 75 | 76 | //! Returns the stretch factor given at construction. 77 | double GetStretchFactor(); 78 | 79 | private: 80 | bool CheckForEdgeCases(); 81 | bool CheckForNoStretchEdgeCase(); 82 | bool CheckForShortInputEdgeCases(); 83 | 84 | void DoPrecalculations(); 85 | 86 | void HandleNoStretchInput(const AudioData& audioData); 87 | AudioData HandleShortInputCompress(); 88 | 89 | void ProcessBuffer(); 90 | 91 | void HandleFirstWindow(Signal::FrequencyDomain& frequencyDomain); 92 | void CreateSynthesizedOutputWindow(Signal::FrequencyDomain& frequencyDomain, std::size_t advancement); 93 | 94 | std::map GetPeakFrequencies(const AudioData& timeDomainSignal, Signal::PeakProfile& peakProfile); 95 | 96 | double CalculateNewPhaseWrapped(std::size_t currentBin, double currentWrappedPhase, double peakFrequency, std::size_t advancement); 97 | 98 | double ConvertUnwrappedPhaseToWrappedPhase(double unwrappedPhase); 99 | 100 | void OverlapAndAddForOutput(AudioData& newSythesizedWindow); 101 | AudioData MixAtBestCorrelation(const AudioData& transientBuffer, const AudioData& stretchBuffer); 102 | 103 | bool noStretch_{false}; 104 | bool shortInputCompress_{false}; 105 | 106 | std::size_t sampleRate_; 107 | std::size_t inputLength_; 108 | double stretchFactor_; 109 | std::size_t minimumOutputSamplesNecessary_{0}; 110 | 111 | double transientCutoff_{0.0}; 112 | 113 | double sampleAdvancement_{0.0}; 114 | double sampleAdvancementRemainder_{0.0}; 115 | 116 | std::size_t inputSamplesProcessed_{0}; 117 | 118 | // Holds a count of the total number of windows processed 119 | std::size_t windowsProcessed_{0}; 120 | std::size_t totalOutputSamplesCreated_{0}; 121 | 122 | // This buffer holds input data waiting to be processed 123 | AudioData inputData_; 124 | 125 | // Remember that we create the output through a 4x overlap-and-add process. This requires 126 | // us to have the three previous synthesized buffers in addition to the latest one. 127 | std::list windowsInUse_; 128 | 129 | // This buffer holds output data ready for the user to request 130 | AudioData outputData_; 131 | 132 | // Holds any transient audio that needs to be mixed into the output 133 | AudioData transientSamples_; 134 | 135 | std::vector previousWrappedPhases_; 136 | std::vector previousExtrapolatedUnwrappedPhases_; 137 | 138 | std::mutex mutex_; 139 | 140 | static const uint32_t FFT_SIZE{4096}; 141 | static const uint32_t QUARTER_FFT_SIZE{FFT_SIZE/4}; 142 | static constexpr double TWO_PI_RADIANS{2.0 * M_PI}; 143 | static const uint32_t OVERLAP_FACTOR{4}; 144 | 145 | static constexpr double optimalTransientCutoff_{QUARTER_FFT_SIZE * 3.0}; 146 | 147 | // I came up with this amp factors just through trial and error on a signal with contant amplitude 148 | static constexpr double SYNTHEIZED_OVERLAP_AMP_FACTOR{0.8024}; 149 | }; 150 | 151 | } -------------------------------------------------------------------------------- /Source/Signal/Resampler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file Resampler.h 28 | //! @brief Implementation of an audio resampler. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Signal { 37 | 38 | class LowPassFilter; 39 | 40 | //! Implementation of a digital audio resampler using a windowed sinc filter. 41 | 42 | //! A resampler allows for adjusting the sample rate of digital audio without unreasonably 43 | //! degrading the audio quality. 44 | 45 | class Resampler 46 | { 47 | public: 48 | //! Instatiate the resampler. 49 | // 50 | //! Example: An input sample rate of 44100Hz and a resample ratio of 0.5 51 | //! will result in an output sample rate of 22050Hz. 52 | Resampler(std::size_t inputSampleRate, double resampleRatio); 53 | 54 | virtual ~Resampler(); 55 | 56 | //! Clears internal buffers and etc to allow for restarting processing fresh. 57 | void Reset(); 58 | 59 | //! Submit audio data to be processed by the resampler. 60 | void SubmitAudioData(const AudioData& audioData); 61 | 62 | //! Retrieve output audio the resampler has processed, requesting a certain number of samples. 63 | AudioData GetAudioData(uint64_t samples); 64 | 65 | //! Returns the number of output samples currently available. 66 | std::size_t OutputSamplesAvailable(); 67 | 68 | //! At the end of processing, this can be called to get any and all remaining output samples. 69 | AudioData FlushAudioData(); 70 | 71 | private: 72 | void ValidateSampleRates(); 73 | void InstantiateLowPassFilter(); 74 | void CalculateXSincCenterAdjustmentPerInputSample(); 75 | void HandleNoSampleRateChange(const AudioData& audioData); 76 | void Process(const AudioData& audioData); 77 | AudioData LowPassFilterInput(const AudioData& audioData); 78 | void CheckForSincPositionWrapping(); 79 | void DiscardInputNoLongerNeeded(); 80 | 81 | std::size_t inputSampleRate_; 82 | double resampleRatio_; 83 | 84 | // This buffer holds input data waiting to be processed 85 | AudioData inputData_; 86 | 87 | // This buffer holds output data ready for the user to request 88 | AudioData outputData_; 89 | 90 | std::mutex mutex_; 91 | 92 | // We limit sample rate conversion to 1,000Hz-to-192,000Hz 93 | const std::size_t minimumSampleRate_{1000}; 94 | const std::size_t maximumSampleRate_{192000}; 95 | 96 | // See the document ResamplingUsingWindowedSincFilter.odg in SabbaticalNotes for more info on these constants. 97 | const std::size_t samplesPerSide_{19}; 98 | const std::size_t minimumSamplesNeededForProcessing_{(2 * samplesPerSide_) + 1}; // "+1" for the center index 99 | // of the windowed sinc filter 100 | double xSincCenterAdjustmentPerInputSample_{0.0}; 101 | double currentXSincPosition_{0.0}; 102 | std::size_t inputSampleIndex_{samplesPerSide_}; 103 | 104 | std::unique_ptr lowPassFilter_; 105 | }; 106 | 107 | } 108 | -------------------------------------------------------------------------------- /Source/Signal/SignalConversion.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file SignalConversion.h 28 | //! @brief Functions allowing for conversion of one sample type to another. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | //! \file SignalConversion.h 37 | 38 | namespace Signal { 39 | 40 | 41 | //! Convert audio signal from 64 bit float samples to signed 16 bit integer. 42 | std::vector ConvertFloat64ToSigned16(const std::vector& signal); 43 | 44 | //! Convert specific number of samples of the audio signal from 64 bit float samples to signed 16 bit integer. 45 | std::vector ConvertFloat64ToSigned16(const std::vector& signal, std::size_t sampleCount); 46 | 47 | //! Convert signal from signed 16 bit integer ssamples to 64 bit float. 48 | std::vector ConvertSigned16ToFloat64(const std::vector& signal); 49 | 50 | //! Convert specific number of samples of the audio signal from 16 bit signed to 64 bit float. 51 | std::vector ConvertSigned16ToFloat64(const std::vector& signal, std::size_t sampleCount); 52 | 53 | //! Converts audio data to 16 bit signed integer samples. 54 | std::vector ConvertAudioDataToSigned16(const AudioData& audioData); 55 | 56 | //! Converts separate audio data streams to a single, 16 bit signed integer samples interleaved (stereo). 57 | std::vector ConvertAudioDataToInterleavedSigned16(const AudioData& leftChannel, const AudioData& rightChannel); 58 | 59 | //! Converts 16 bit signed integer samples to an AudioData object. 60 | AudioData ConvertSigned16ToAudioData(const std::vector& signal); 61 | 62 | //! Converts a stereo 16 bit signal to a vector of AudioData objects. 63 | // 64 | //! The left channel is index 0. The right channel is index 1. 65 | std::vector ConvertInterleavedSigned16ToAudioData(const std::vector& interleavedSigned16); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /Source/Signal/Source/FrequencyDomain.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #define _USE_MATH_DEFINES // Seems some compilers need this so M_PI will be defined 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | Signal::FrequencyDomain::FrequencyDomain() { } 36 | 37 | Signal::FrequencyDomain::FrequencyDomain(std::vector FrequencyBin) : data_{FrequencyBin} { } 38 | 39 | void Signal::FrequencyDomain::PushFrequencyBin(FrequencyBin FrequencyBin) 40 | { 41 | data_.push_back(FrequencyBin); 42 | } 43 | 44 | std::size_t Signal::FrequencyDomain::GetSize() const 45 | { 46 | return data_.size(); 47 | } 48 | 49 | std::vector Signal::FrequencyDomain::GetRectangularFrequencyData() const 50 | { 51 | return data_; 52 | } 53 | 54 | const Signal::FrequencyBin& Signal::FrequencyDomain::GetBin(std::size_t binNumber) const 55 | { 56 | if(binNumber > GetSize()) 57 | { 58 | Utilities::ThrowException("Attempting to access a frequency bin that does not exist move more samples than exist", GetSize(), binNumber); 59 | } 60 | 61 | return data_[binNumber]; 62 | } 63 | 64 | const std::vector& Signal::FrequencyDomain::GetMagnitudes() 65 | { 66 | if(magnitudes_.size() == 0) 67 | { 68 | for(auto frequencyBinValues : data_) 69 | { 70 | magnitudes_.push_back(sqrt(frequencyBinValues.reX_ * frequencyBinValues.reX_ + frequencyBinValues.imX_ * frequencyBinValues.imX_)); 71 | } 72 | } 73 | 74 | return magnitudes_; 75 | } 76 | 77 | const std::vector& Signal::FrequencyDomain::GetWrappedPhases() 78 | { 79 | if(wrappedPhases_.size() == 0) 80 | { 81 | for(auto frequencyBinValues : data_) 82 | { 83 | wrappedPhases_.push_back(GetWrappedPhase(frequencyBinValues.reX_, frequencyBinValues.imX_)); 84 | } 85 | } 86 | 87 | return wrappedPhases_; 88 | } 89 | 90 | const std::vector& Signal::FrequencyDomain::GetRealComponent() 91 | { 92 | if(realComponent_.size() == 0) 93 | { 94 | for(auto frequencyBinValues : data_) 95 | { 96 | realComponent_.push_back(frequencyBinValues.reX_); 97 | } 98 | } 99 | 100 | return realComponent_; 101 | } 102 | 103 | const std::vector& Signal::FrequencyDomain::GetImaginaryComponent() 104 | { 105 | if(imaginaryComponent_.size() == 0) 106 | { 107 | for(auto frequencyBinValues : data_) 108 | { 109 | imaginaryComponent_.push_back(frequencyBinValues.imX_); 110 | } 111 | } 112 | 113 | return imaginaryComponent_; 114 | } 115 | 116 | Signal::FrequencyDomain::Quadrant Signal::FrequencyDomain::GetQuadrant(double reX, double imX) 117 | { 118 | if (reX > 0.00) 119 | { 120 | if (imX == 0.00) 121 | { 122 | return Signal::FrequencyDomain::Quadrant::BETWEEN_QUADRANT4_AND_QUADRANT1; 123 | } 124 | else if (imX > 0.00) 125 | { 126 | return Signal::FrequencyDomain::Quadrant::QUADRANT1; 127 | } 128 | else // imX < 0.00 129 | { 130 | return Signal::FrequencyDomain::Quadrant::QUADRANT4; 131 | } 132 | } 133 | else if (reX < 0.00) 134 | { 135 | if (imX == 0.00) 136 | { 137 | return Signal::FrequencyDomain::Quadrant::BETWEEN_QUADRANT2_AND_QUADRANT3; 138 | } 139 | else if (imX > 0.00) 140 | { 141 | return Signal::FrequencyDomain::Quadrant::QUADRANT2; 142 | } 143 | else // imX < 0.00 144 | { 145 | return Signal::FrequencyDomain::Quadrant::QUADRANT3; 146 | } 147 | } 148 | else // reX == 0.00 149 | { 150 | if (imX > 0.00) 151 | { 152 | return Signal::FrequencyDomain::Quadrant::BETWEEN_QUADRANT1_AND_QUADRANT2; 153 | } 154 | else 155 | { 156 | return Signal::FrequencyDomain::Quadrant::BETWEEN_QUADRANT3_AND_QUADRANT4; 157 | } 158 | } 159 | 160 | static_assert(true, "Failed to find the quadrant"); 161 | } 162 | 163 | double Signal::FrequencyDomain::GetWrappedPhase(double reX, double imX) 164 | { 165 | Quadrant quadrant{GetQuadrant(reX, imX)}; 166 | double atanValue{CalculateArcTangent(imX, reX)}; 167 | 168 | if (quadrant == QUADRANT1 || quadrant == BETWEEN_QUADRANT1_AND_QUADRANT2 || quadrant == BETWEEN_QUADRANT4_AND_QUADRANT1) 169 | { 170 | return atanValue; 171 | } 172 | else if (quadrant == QUADRANT2 || quadrant == BETWEEN_QUADRANT2_AND_QUADRANT3) 173 | { 174 | return (atanValue + M_PI); 175 | } 176 | else if (quadrant == QUADRANT3 || quadrant == BETWEEN_QUADRANT3_AND_QUADRANT4) 177 | { 178 | return (atanValue + M_PI); 179 | } 180 | else if (quadrant == QUADRANT4) 181 | { 182 | return (atanValue + (2.0 * M_PI)); 183 | } 184 | 185 | static_assert(true, "Failed to calculate the phase"); 186 | 187 | return 0.0; // To squelch the warning 188 | } 189 | 190 | double Signal::FrequencyDomain::CalculateArcTangent(double imaginary, double real) 191 | { 192 | if(real == 0.0) 193 | { 194 | return 0.0; 195 | } 196 | 197 | return atan(imaginary / real); 198 | } 199 | -------------------------------------------------------------------------------- /Source/Signal/Source/LowPassFilter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #define _USE_MATH_DEFINES // Seems some compilers need this so M_PI will be defined 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | Signal::LowPassFilter::LowPassFilter(double cutoffRatio, std::size_t filterLength) : 36 | cutoffRatio_{cutoffRatio}, 37 | filterLength_{filterLength} 38 | { 39 | std::lock_guard guard(mutex_); 40 | 41 | if(cutoffRatio_ < minCutoffRatioRange_ || cutoffRatio_ > maxCutoffRatioRange_) 42 | { 43 | Utilities::ThrowException("LowPassFilter cutoffRatio is out of range"); 44 | } 45 | 46 | CalculateFilterKernel(); 47 | } 48 | 49 | void Signal::LowPassFilter::Reset() 50 | { 51 | std::lock_guard guard(mutex_); 52 | 53 | audioInput_.Clear(); 54 | audioOutput_.Clear(); 55 | } 56 | 57 | void Signal::LowPassFilter::SubmitAudioData(const AudioData& audioData) 58 | { 59 | std::lock_guard guard(mutex_); 60 | 61 | audioInput_.Append(audioData); 62 | Process(); 63 | } 64 | 65 | AudioData Signal::LowPassFilter::GetAudioData(uint64_t samples) 66 | { 67 | std::lock_guard guard(mutex_); 68 | 69 | uint64_t samplesToRetrieve{samples}; 70 | if(audioOutput_.GetSize() < samplesToRetrieve) 71 | { 72 | samplesToRetrieve = audioOutput_.GetSize(); 73 | } 74 | 75 | return audioOutput_.RetrieveRemove(samplesToRetrieve); 76 | } 77 | 78 | std::size_t Signal::LowPassFilter::OutputSamplesAvailable() 79 | { 80 | std::lock_guard guard(mutex_); 81 | return audioOutput_.GetSize(); 82 | } 83 | 84 | std::size_t Signal::LowPassFilter::MinimumSamplesNeededForProcessing() 85 | { 86 | return filterLength_; 87 | } 88 | 89 | AudioData Signal::LowPassFilter::FlushAudioData() 90 | { 91 | std::lock_guard guard(mutex_); 92 | 93 | audioInput_.AddSilence(filterLength_); 94 | Process(); 95 | 96 | AudioData audioData{audioOutput_}; 97 | audioOutput_.Clear(); 98 | 99 | return audioData; 100 | } 101 | 102 | void Signal::LowPassFilter::Process() 103 | { 104 | if(audioInput_.GetSize() < filterLength_) 105 | { 106 | return; 107 | } 108 | 109 | std::size_t samplesToProcess{audioInput_.GetSize() - filterLength_}; 110 | auto inputBuffer{audioInput_.GetData()}; 111 | 112 | // Convolve the input signal and filter kernel 113 | for(uint64_t i = 0; i < samplesToProcess; ++i) 114 | { 115 | double accumulator{0}; 116 | for(uint64_t j = 0; j < filterLength_; j++) 117 | { 118 | accumulator = accumulator + (inputBuffer[i + j] * filterKernel_[j]); 119 | } 120 | 121 | if(accumulator > 1.0) 122 | { 123 | audioOutput_.PushSample(1.0); 124 | } 125 | else if(accumulator < -1.0) 126 | { 127 | audioOutput_.PushSample(-1.0); 128 | } 129 | else 130 | { 131 | audioOutput_.PushSample(accumulator); 132 | } 133 | } 134 | 135 | // Remove the samples we just processed 136 | audioInput_.RemoveFrontSamples(samplesToProcess); 137 | } 138 | 139 | void Signal::LowPassFilter::CalculateFilterKernel() 140 | { 141 | // This is straight out of "The Scientist and Engineer's Guide to Digital Signal Processing" chapter 16 table 16-1 142 | 143 | std::size_t halfFilterLength{filterLength_ / 2}; 144 | double twoPI{2.0 * M_PI}; 145 | for(std::size_t i = 0; i < filterLength_; ++i) { 146 | 147 | if((i - halfFilterLength) / 2 == 0) 148 | { 149 | filterKernel_.push_back(twoPI * cutoffRatio_); 150 | } 151 | else 152 | { 153 | double position{static_cast(i) - halfFilterLength}; 154 | filterKernel_.push_back(sin(twoPI * cutoffRatio_ * position) / position); 155 | } 156 | 157 | // Apply windowing 158 | filterKernel_[i] = filterKernel_[i] * (0.54 - 0.46 * cos(2 * M_PI * (float)i / filterLength_)); 159 | } 160 | 161 | // Normalize the low-pass filter kernal for unity gain at DC 162 | double filterKernelSum{std::accumulate(filterKernel_.begin(), filterKernel_.end(), 0.0)}; 163 | std::for_each(filterKernel_.begin(), filterKernel_.end(), [&](double& currentIndexValue) { currentIndexValue /= filterKernelSum; }); 164 | } 165 | -------------------------------------------------------------------------------- /Source/Signal/Source/PeakFrequencyDetection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #define _USE_MATH_DEFINES // Seems some compilers need this so M_PI will be defined 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace Signal { 37 | 38 | double Tau(double x) 39 | { 40 | return (0.25 * log(3.0 * (x*x) + 6.0*x + 1.0) - sqrt(6.0)/24.0 * log((x + 1.0 - sqrt(2.0/3.0)) / (x + 1.0 + sqrt(2.0/3.0)))); 41 | } 42 | 43 | } 44 | 45 | // ******************************************************************************************************* 46 | // ****************** USE THIS "QUINN" ALGORITHM AND AVOID THE CORRELATION ONE BELOW ********************* 47 | // ******************************************************************************************************* 48 | // I got this algorithm from: http://dspguru.com/dsp/howtos/how-to-interpolate-fft-peak it's called "Quinn's Second Estimator". 49 | // If there's any question about how well it performs compared to correlation, see the UT titled "TestPeakBinFrequencyAccuracy" 50 | // Note: real = the real portion of the frequency domain for the time domain signal 51 | // imaginary = the imaginary portion of the frequency domain for the time domain signal 52 | double Signal::GetPeakFrequencyByQuinn(std::size_t peakBin, 53 | std::size_t fourierSize, 54 | const std::vector& real, 55 | const std::vector& imaginary, 56 | double inputSignalSampleRate) 57 | { 58 | double hzPerFrequencyBin{inputSignalSampleRate / static_cast(fourierSize)}; 59 | 60 | double ap = (real[peakBin + 1] * real[peakBin] + imaginary[peakBin+1] * imaginary[peakBin]) / (real[peakBin] * real[peakBin] + imaginary[peakBin] * imaginary[peakBin]); 61 | double dp = (-1.0 * ap) / (1 - ap); 62 | double am = (real[peakBin - 1] * real[peakBin] + imaginary[peakBin - 1] * imaginary[peakBin]) / (real[peakBin] * real[peakBin] + imaginary[peakBin] * imaginary[peakBin]); 63 | double dm = am / (1 - am); 64 | double d = (dp + dm) / 2 + Signal::Tau(dp * dp) - Signal::Tau(dm * dm); 65 | double peakFrequency{(peakBin + d) * hzPerFrequencyBin}; 66 | 67 | return peakFrequency; 68 | } 69 | 70 | // Let's the user just pass in a peakBin and time domain signal and internally does the FFT 71 | double Signal::GetPeakFrequencyByQuinn(std::size_t peakBin, const std::vector& timeDomainSignal, double inputSignalSampleRate) 72 | { 73 | auto frequencyDomain{Signal::Fourier::ApplyFFT(AudioData(timeDomainSignal))}; 74 | return GetPeakFrequencyByQuinn(peakBin, timeDomainSignal.size(), frequencyDomain.GetRealComponent(), frequencyDomain.GetImaginaryComponent(), inputSignalSampleRate); 75 | } 76 | 77 | // The phase parameter is the starting phase of the signal and can be anywhere from 0-to-360 degrees 78 | std::vector Signal::GenerateSineWave(double sampleRate, std::size_t lengthInSamples, double signalFrequency, double phase) 79 | { 80 | std::vector sineWave; 81 | 82 | double phaseFactor{2.0 * M_PI * phase / 360.0}; 83 | 84 | double frequency{sampleRate / signalFrequency}; 85 | 86 | double preCalculation{2.0 * M_PI / frequency}; // Doing it outside of the loop to save time 87 | 88 | for(double i{0.0}; i < lengthInSamples; i += 1.0) 89 | { 90 | sineWave.push_back(sin((preCalculation * i) + phaseFactor)); 91 | } 92 | 93 | return sineWave; 94 | } 95 | 96 | // Don't use this algorithm. Use the Quinn algorithm below. If you have any question which performs better see the TestPeakBinFrequencyAccuracy UT 97 | double Signal::GetPeakFrequencyByCorrelation(std::size_t peakBin, const std::vector& timeDomainSignal, double inputSignalSampleRate) 98 | { 99 | double fourierSize{static_cast(timeDomainSignal.size())}; 100 | double hzPerFrequencyBin{inputSignalSampleRate / fourierSize}; 101 | 102 | double startFrequency{static_cast(peakBin - 1) * hzPerFrequencyBin}; 103 | double endFrequency{static_cast(peakBin + 1) * hzPerFrequencyBin}; 104 | 105 | double highestCorrelation{0.0}; 106 | double highestCorrelationFrequency{0.0}; 107 | 108 | for(double correlateFrequency{startFrequency}; correlateFrequency <= endFrequency; correlateFrequency += 0.1) 109 | { 110 | std::vector correlationSignal{Signal::GenerateSineWave(inputSignalSampleRate, timeDomainSignal.size(), correlateFrequency)}; 111 | 112 | double aTimesBSum{0}; 113 | double aSquaredSum{0}; 114 | double bSquaredSum{0}; 115 | 116 | for(std::size_t i = 0; i < correlationSignal.size(); ++i) 117 | { 118 | auto a = correlationSignal[i]; 119 | auto b = timeDomainSignal[i]; 120 | 121 | aTimesBSum += a * b; 122 | aSquaredSum += a * a; 123 | bSquaredSum += b * b; 124 | } 125 | 126 | double correlationValue = (aTimesBSum) / sqrt(aSquaredSum * bSquaredSum); 127 | 128 | if(correlationValue < 0.0) 129 | { 130 | correlationValue *= -1.0; 131 | } 132 | 133 | if(correlationValue > highestCorrelation) 134 | { 135 | highestCorrelation = correlationValue; 136 | highestCorrelationFrequency = correlateFrequency; 137 | } 138 | } 139 | 140 | return highestCorrelationFrequency; 141 | } 142 | 143 | -------------------------------------------------------------------------------- /Source/Signal/Source/PeakProfile.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #define _USE_MATH_DEFINES // Seems some compilers need this so M_PI will be defined 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | Signal::PeakProfile::PeakProfile(const Signal::FrequencyDomain& frequencyDomain) : frequencyDomain_(frequencyDomain) 36 | { 37 | CalculatePeaksAndValleys(); 38 | } 39 | 40 | // This will return the closest peak for the given frequency bin 41 | std::size_t Signal::PeakProfile::GetLocalPeakForBin(std::size_t bin) 42 | { 43 | if(peakBins_.size() == 0) 44 | { 45 | return 0; 46 | } 47 | 48 | // First find the peak bin 49 | for(std::size_t i = 0; i < (valleyBins_.size() - 1); ++i) 50 | { 51 | if(bin >= valleyBins_[i] && bin < valleyBins_[i + 1]) 52 | { 53 | return peakBins_[i]; 54 | } 55 | } 56 | 57 | return peakBins_[peakBins_.size() - 1]; // If we're here, it must be the final peak 58 | } 59 | 60 | const std::vector& Signal::PeakProfile::GetAllPeakBins() 61 | { 62 | return peakBins_; 63 | } 64 | 65 | std::pair Signal::PeakProfile::GetValleyBins(std::size_t peakBin) 66 | { 67 | auto peakPosition = std::find(peakBins_.begin(), peakBins_.end(), peakBin) - peakBins_.begin(); 68 | if(static_cast(peakPosition) >= peakBins_.size()) 69 | { 70 | throw Utilities::Exception("Given peak position not found in peak list"); 71 | } 72 | 73 | if(static_cast(peakPosition + 1) >= valleyBins_.size()) 74 | { 75 | throw Utilities::Exception("Peak position does not have corresponding valleys"); 76 | } 77 | 78 | return std::make_pair(valleyBins_[peakPosition], valleyBins_[peakPosition + 1]); 79 | } 80 | 81 | void Signal::PeakProfile::CalculatePeaksAndValleys() 82 | { 83 | auto magnitudes{frequencyDomain_.GetMagnitudes()}; 84 | 85 | std::vector magnitudesAveraged; 86 | 87 | std::size_t averageSpanLength{10}; 88 | for(std::size_t i{averageSpanLength/2}; i < (magnitudes.size() - averageSpanLength/2); ++i) 89 | { 90 | double average{0.0}; 91 | for(std::size_t j{0}; j < averageSpanLength; ++j) 92 | { 93 | average += magnitudes[(i + j) - averageSpanLength/2]; 94 | } 95 | average = average / static_cast(averageSpanLength); 96 | 97 | if(magnitudesAveraged.size() == 0) 98 | { 99 | for(std::size_t j{0}; j < averageSpanLength/2; ++j) 100 | { 101 | magnitudesAveraged.push_back(average); 102 | } 103 | } 104 | 105 | magnitudesAveraged.push_back(average); 106 | } 107 | 108 | double finalAverage{magnitudesAveraged[magnitudesAveraged.size() - 1]}; 109 | for(std::size_t i{magnitudesAveraged.size()}; i < magnitudes.size(); ++i) 110 | { 111 | magnitudesAveraged.push_back(finalAverage); 112 | } 113 | 114 | std::size_t runningLow{0}; 115 | 116 | for(std::size_t i{2}; i < (magnitudes.size() - 2); ++i) 117 | { 118 | double localAverage{(magnitudes[i - 2] + magnitudes[i - 1] + magnitudes[i] + magnitudes[i + 1] + magnitudes[i + 2]) / 5}; 119 | 120 | if(localAverage > magnitudesAveraged[i] && magnitudes[i] > 1.0) // It has to be above a certain threshold to be a peak (fix this arbitrary "1.0" as the threshold) 121 | { 122 | if((magnitudes[i] > magnitudes[i + 1] && magnitudes[i] > magnitudes[i + 2]) && 123 | (magnitudes[i] > magnitudes[i - 1] && magnitudes[i] > magnitudes[i - 2])) 124 | { 125 | peakBins_.push_back(i); 126 | if(peakBins_.size() == 1) 127 | { 128 | valleyBins_.push_back(0); 129 | } 130 | else 131 | { 132 | valleyBins_.push_back(runningLow); 133 | } 134 | 135 | runningLow = i + 1; 136 | } 137 | } 138 | 139 | if(magnitudes[i] < magnitudes[runningLow]) 140 | { 141 | runningLow = i; 142 | } 143 | } 144 | 145 | valleyBins_.push_back(magnitudes.size() - 1); 146 | } 147 | -------------------------------------------------------------------------------- /Source/Signal/Source/SignalConversion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace Signal { namespace SignalConversion { 33 | 34 | const int16_t MAX16{32767}; 35 | const int16_t MIN16{-32768}; 36 | 37 | constexpr double MAX16_FLOAT{32767.0}; 38 | constexpr double MIN16_FLOAT_REVERSE_SIGN{MIN16 * -1.0}; 39 | 40 | inline int16_t ConvertFloat64SampleToSigned16Sample(double sample) 41 | { 42 | if(sample > 0.0) 43 | { 44 | if(sample < 1.0) 45 | { 46 | return static_cast(sample * Signal::SignalConversion::MAX16_FLOAT + 0.5); 47 | } 48 | else 49 | { 50 | return Signal::SignalConversion::MAX16; 51 | } 52 | } 53 | else 54 | { 55 | if(sample < -1.0) 56 | { 57 | return MIN16; 58 | } 59 | else 60 | { 61 | return static_cast(sample * MIN16_FLOAT_REVERSE_SIGN - 0.5); 62 | } 63 | } 64 | } 65 | 66 | inline double ConvertSigned16SampleToFloat64(int16_t sample) 67 | { 68 | if(sample == 0) 69 | { 70 | return 0.0; 71 | } 72 | 73 | if(sample > 0) 74 | { 75 | if(sample < MAX16) 76 | { 77 | return static_cast(sample) / Signal::SignalConversion::MAX16_FLOAT; 78 | } 79 | else 80 | { 81 | return 1.0; 82 | } 83 | } 84 | else 85 | { 86 | if(sample > MIN16) 87 | { 88 | return static_cast(sample) / Signal::SignalConversion::MIN16_FLOAT_REVERSE_SIGN; 89 | } 90 | else 91 | { 92 | return -1.0; 93 | } 94 | } 95 | } 96 | 97 | 98 | }}; 99 | 100 | std::vector Signal::ConvertFloat64ToSigned16(const std::vector& inputSignal) 101 | { 102 | return Signal::ConvertFloat64ToSigned16(inputSignal, inputSignal.size()); 103 | } 104 | 105 | std::vector Signal::ConvertFloat64ToSigned16(const std::vector& inputSignal, std::size_t sampleCount) 106 | { 107 | std::vector returnSignal; 108 | auto conversion{[&](double sample) 109 | { 110 | returnSignal.push_back(Signal::SignalConversion::ConvertFloat64SampleToSigned16Sample(sample)); 111 | }}; 112 | 113 | std::for_each(inputSignal.begin(), inputSignal.begin() + sampleCount, conversion); 114 | 115 | return returnSignal; 116 | } 117 | 118 | std::vector Signal::ConvertSigned16ToFloat64(const std::vector& inputSignal) 119 | { 120 | return Signal::ConvertSigned16ToFloat64(inputSignal, inputSignal.size()); 121 | } 122 | 123 | std::vector Signal::ConvertSigned16ToFloat64(const std::vector& inputSignal, std::size_t sampleCount) 124 | { 125 | if(sampleCount > inputSignal.size()) 126 | { 127 | Utilities::ThrowException("Requesting more sample than exist"); 128 | } 129 | 130 | std::vector returnSignal; 131 | double min16ReverseSign{-1.0 * Signal::SignalConversion::MIN16}; 132 | 133 | auto conversion{[&](double sample) { 134 | if(sample > 0) 135 | { 136 | returnSignal.push_back(static_cast(sample) / Signal::SignalConversion::MAX16); 137 | } 138 | else 139 | { 140 | returnSignal.push_back(static_cast(sample) / min16ReverseSign); 141 | } 142 | }}; 143 | 144 | std::for_each(inputSignal.begin(), inputSignal.begin() + sampleCount, conversion); 145 | 146 | return returnSignal; 147 | } 148 | 149 | std::vector Signal::ConvertAudioDataToSigned16(const AudioData& audioData) 150 | { 151 | std::vector returnSignal; 152 | for(std::size_t i{0}; i < audioData.GetSize(); ++i) 153 | { 154 | returnSignal.push_back(Signal::SignalConversion::ConvertFloat64SampleToSigned16Sample(audioData.GetData()[i])); 155 | } 156 | 157 | return returnSignal; 158 | } 159 | 160 | std::vector Signal::ConvertAudioDataToInterleavedSigned16(const AudioData& leftChannel, const AudioData& rightChannel) 161 | { 162 | if(leftChannel.GetSize() != rightChannel.GetSize()) 163 | { 164 | Utilities::ThrowException(Utilities::CreateString(" ", "Problem interleaving samples: Left channel has", leftChannel.GetSize(), "samples" \ 165 | "and right channel has", rightChannel.GetSize(), "samples")); 166 | } 167 | 168 | std::vector returnSignal; 169 | for(std::size_t i{0}; i < leftChannel.GetSize(); ++i) 170 | { 171 | returnSignal.push_back(Signal::SignalConversion::ConvertFloat64SampleToSigned16Sample(leftChannel.GetData()[i])); 172 | returnSignal.push_back(Signal::SignalConversion::ConvertFloat64SampleToSigned16Sample(rightChannel.GetData()[i])); 173 | } 174 | 175 | return returnSignal; 176 | } 177 | 178 | AudioData Signal::ConvertSigned16ToAudioData(const std::vector& signal) 179 | { 180 | AudioData audioData; 181 | 182 | std::vector returnSignal; 183 | for(std::size_t i{0}; i < signal.size(); ++i) 184 | { 185 | audioData.PushSample(Signal::SignalConversion::ConvertSigned16SampleToFloat64(signal[i])); 186 | } 187 | 188 | return audioData; 189 | } 190 | 191 | std::vector Signal::ConvertInterleavedSigned16ToAudioData(const std::vector& interleavedSigned16) 192 | { 193 | AudioData audioDataLeft; 194 | AudioData audioDataRight; 195 | 196 | if(interleavedSigned16.size() % 2 != 0) 197 | { 198 | Utilities::ThrowException("Input given is not divisible by two and therefore cannot be interleaved"); 199 | } 200 | 201 | std::vector returnSignal; 202 | for(std::size_t i{0}; i < interleavedSigned16.size();) 203 | { 204 | audioDataLeft.PushSample(Signal::SignalConversion::ConvertSigned16SampleToFloat64(interleavedSigned16[i++])); 205 | audioDataRight.PushSample(Signal::SignalConversion::ConvertSigned16SampleToFloat64(interleavedSigned16[i++])); 206 | } 207 | 208 | return std::vector{audioDataLeft, audioDataRight}; 209 | } 210 | -------------------------------------------------------------------------------- /Source/Signal/Source/TransientPeakAndValley.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | Signal::TransientPeakAndValley::TransientPeakAndValley(std::size_t startSamplePosition, std::size_t stepSize) : 31 | startSamplePoisiton_{startSamplePosition}, 32 | stepSize_{stepSize}, 33 | peakSample_{0}, 34 | valleySample_{0} 35 | { 36 | 37 | } 38 | 39 | void Signal::TransientPeakAndValley::Reset(std::size_t startSamplePosition, std::size_t stepSize) 40 | { 41 | startSamplePoisiton_ = startSamplePosition; 42 | stepSize_ = stepSize; 43 | peakSample_ = valleySample_ = 0; 44 | plottedPoints_.clear(); 45 | } 46 | 47 | std::size_t Signal::TransientPeakAndValley::GetStartSamplePosition() const 48 | { 49 | return startSamplePoisiton_; 50 | } 51 | 52 | std::size_t Signal::TransientPeakAndValley::GetStepSize() const 53 | { 54 | return stepSize_; 55 | } 56 | 57 | std::size_t Signal::TransientPeakAndValley::GetPeakSamplePosition() const 58 | { 59 | return peakSample_; 60 | } 61 | 62 | void Signal::TransientPeakAndValley::SetPeakSamplePosition(std::size_t peak) 63 | { 64 | peakSample_ = peak; 65 | } 66 | 67 | std::size_t Signal::TransientPeakAndValley::GetValleySamplePosition() const 68 | { 69 | return valleySample_; 70 | } 71 | 72 | void Signal::TransientPeakAndValley::SetValleySamplePosition(std::size_t valley) 73 | { 74 | valleySample_ = valley; 75 | } 76 | 77 | std::size_t Signal::TransientPeakAndValley::GetPeakPoint() const 78 | { 79 | if(stepSize_ == 0) 80 | { 81 | return 0; 82 | } 83 | 84 | return (static_cast(peakSample_) - static_cast(startSamplePoisiton_)) / static_cast(stepSize_); 85 | } 86 | 87 | std::size_t Signal::TransientPeakAndValley::GetValleyPoint() const 88 | { 89 | if(stepSize_ == 0) 90 | { 91 | return 0; 92 | } 93 | 94 | return (static_cast(valleySample_) - static_cast(startSamplePoisiton_)) / static_cast(stepSize_); 95 | } 96 | 97 | void Signal::TransientPeakAndValley::PushPlottedPoint(double point) 98 | { 99 | plottedPoints_.push_back(point); 100 | } 101 | 102 | const std::vector& Signal::TransientPeakAndValley::GetPlottedPoints() const 103 | { 104 | return plottedPoints_; 105 | } 106 | -------------------------------------------------------------------------------- /Source/Signal/Source/WindowedSincValues.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file WindowedSincValues.h 28 | //! @brief Lookup table for windowed sinc values. 29 | 30 | #pragma once 31 | 32 | #include 33 | 34 | namespace Signal { 35 | 36 | const double SINC_SAMPLES_PER_X_INTEGER{224.0}; 37 | const std::size_t SINC_VALUE_SIZE{9857}; //4928 on both sides plus 1.0 in the middle 38 | const std::size_t SINC_CENTER_POINT{4928}; 39 | const double MAX_X_POSITION_POSITIVE{4928.0}; 40 | const double MIN_X_POSITION_POSITIVE{-4928.0}; 41 | 42 | //! Get the sinc value for the given x-axis position. 43 | // 44 | //! See chapter 16 of "The Scientists' and Engineers Guide to DSP" for more on the "Windowed-Sinc Filters" 45 | double GetSincValue(double xPosition); 46 | 47 | } 48 | -------------------------------------------------------------------------------- /Source/Signal/Source/Windowing.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #define _USE_MATH_DEFINES // Seems some compilers need this so M_PI will be defined 30 | #include 31 | 32 | namespace Signal { 33 | 34 | void BlackmanWindow(std::vector& inputSignal, bool inverse, bool reverse, double startPercent, double endPercent) 35 | { 36 | std::size_t size{inputSignal.size()}; 37 | 38 | double bufferSizeAsDouble{static_cast(size)}; 39 | 40 | std::size_t newSize = static_cast((bufferSizeAsDouble / ((endPercent - startPercent) / 100.0)) + 0.5); 41 | std::size_t startIndex = static_cast(static_cast(newSize) * (startPercent / 100.0) + 0.5); 42 | std::size_t endIndex = static_cast(static_cast(newSize) * (endPercent / 100.0) + 0.5); 43 | 44 | if(inputSignal.size() != (endIndex - startIndex)) 45 | { 46 | Utilities::Exception("BlackmanWindow: endIndex - startIndex does not match inputSignal size"); 47 | } 48 | 49 | std::size_t signalIndex{0}; 50 | std::size_t currentIndex{startIndex}; 51 | double twoPiDivNewSizeMinusOne{(2.0 * M_PI) / (static_cast(newSize) - 1.0)}; 52 | double fourPiDivNewSizeMinusOne{(4.0 * M_PI) / (static_cast(newSize) - 1.0)}; 53 | while(currentIndex < endIndex) 54 | { 55 | double indexAsDouble{static_cast(currentIndex)}; 56 | 57 | // See Appendix A of the Darwen Audio Phase Vocoder document for details on the Blackman window, coefficients, etc 58 | double amp{0.42659 - (0.49656 * cos(indexAsDouble * twoPiDivNewSizeMinusOne)) + (0.076849 * cos(indexAsDouble * fourPiDivNewSizeMinusOne))}; 59 | 60 | if(inverse) 61 | { 62 | amp = 1.0 - amp; 63 | } 64 | 65 | if(reverse) 66 | { 67 | inputSignal[signalIndex] = inputSignal[signalIndex] / amp; 68 | } 69 | else 70 | { 71 | inputSignal[signalIndex] = inputSignal[signalIndex] * amp; 72 | } 73 | 74 | ++signalIndex; 75 | ++currentIndex; 76 | } 77 | } 78 | } 79 | 80 | void Signal::BlackmanWindow(std::vector& inputSignal, double startPercent, double endPercent) 81 | { 82 | Signal::BlackmanWindow(inputSignal, false, false, startPercent, endPercent); 83 | } 84 | 85 | void Signal::InverseBlackmanWindow(std::vector& inputSignal, double startPercent, double endPercent) 86 | { 87 | Signal::BlackmanWindow(inputSignal, true, false, startPercent, endPercent); 88 | } 89 | 90 | void Signal::ReverseBlackmanWindow(std::vector& inputSignal, double startPercent, double endPercent) 91 | { 92 | Signal::BlackmanWindow(inputSignal, false, true, startPercent, endPercent); 93 | } 94 | 95 | void Signal::LinearFadeInOut(std::vector& inputSignal) 96 | { 97 | double halfBufferSizeAsDouble{static_cast(inputSignal.size() - 1) / 2.0}; 98 | std::size_t inputSignalSize{inputSignal.size()}; 99 | 100 | for (std::size_t n{0}; n < inputSignalSize / 2; ++n) 101 | { 102 | double ampValue{static_cast(n) / halfBufferSizeAsDouble}; 103 | inputSignal[n] *= ampValue; 104 | inputSignal[inputSignalSize - n - 1] *= ampValue; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Source/Signal/TransientDetector.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017-2018 Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file TransientDetector.h 28 | //! @brief Implementation of a transient detector. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | 35 | namespace Signal { 36 | 37 | //! Implementation of a transient detector. 38 | 39 | //! The transient detector, detects transients in audio. An audio transient is usually a high amplitude, 40 | //! short-duration sound at the beginning of a waveform. 41 | //! See https://github.com/tmdarwen/AudioLib/blob/master/Documentation/TransientDetection.md 42 | 43 | class TransientDetector 44 | { 45 | public: 46 | //! Instantiates the transient detector taking the sample rate of the audio it will process (e.g. 44100). 47 | TransientDetector(std::size_t sampleRate); 48 | virtual ~TransientDetector(); 49 | 50 | //! Finds the sample positions of transients in the given audio. 51 | // 52 | //! Return true if transients are found, false otherwise. 53 | //! If transients are found, they will be in the transients variable 54 | bool FindTransients(const AudioData& audioInput, std::vector& transients); 55 | 56 | //! The ratio of valley-to-peak growth for audio to be considered a peak. The default is 1.5. 57 | void SetValleyToPeakRatio(double ratio); 58 | 59 | //! Returns the current valley-to-peak ratio. 60 | double GetValleyToPeakRatio(); 61 | 62 | //! Set the minimum audio level of a peak sample to actually qualify as a peak. The default is 0.1. 63 | void SetMinimumPeakLevel(double minPeakLevel); 64 | 65 | //! Get the minimum audio level of a peak sample to actually qualify as a peak. 66 | double GetMinimumPeakLevel(); 67 | 68 | enum class Step 69 | { 70 | First, 71 | Second, 72 | Third 73 | }; 74 | 75 | //! Set step size in units of milliseconds. 76 | void SetStep(double stepInMilliseconds, Signal::TransientDetector::Step step); 77 | 78 | //! Set step size in units of samples. 79 | void SetStepInSamples(std::size_t samples, Signal::TransientDetector::Step step); 80 | 81 | //! Get step size in units of milliseconds. 82 | double GetStep(Signal::TransientDetector::Step step); 83 | 84 | //! Get the current step size in units of samples. 85 | std::size_t GetStepInSamples(Signal::TransientDetector::Step step); 86 | 87 | //! Clears internal data to prepare to perform transient detection on new audio. 88 | void Reset(); 89 | 90 | //! Gets the actual first step values for the given audio 91 | std::vector GetFirstStepValues(const AudioData& audioInput); 92 | 93 | //! Gets the actual first step values for the given audio found during analysis. 94 | // 95 | //! Remember that the first transient is a special case where the peak method is not used. Therefore 96 | //! this transient is not included in the list returned. 97 | std::vector GetFirstLevelPeakSamplePositions(); 98 | 99 | //! Get the number of "look ahead" samples required. 100 | // 101 | //! The Transient Detector needs to "look ahead" just beyond the given amount of data in order to get a transient 102 | //! near the end of the given audio. This returns to the user this "look ahead" amount. 103 | std::size_t GetLookAheadSampleCount(); 104 | 105 | private: 106 | std::size_t sampleRate_; 107 | std::vector firstLevel_; 108 | std::vector secondLevel_; 109 | std::vector thirdLevel_; 110 | 111 | std::vector firstLevelPeakSamplePositions_; 112 | 113 | double firstLevelStepMilliseconds_{11.60998}; // 512 samples for 44.1KHz sample rate 114 | double secondLevelStepMilliseconds_{5.80499}; // 256 samples for 44.1KHz sample rate 115 | double thirdLevelStepMilliseconds_{0.725623}; // 32 samples for 44.1KHz sample rate 116 | 117 | std::size_t firstLevelStepSize_; 118 | std::size_t secondLevelStepSize_; 119 | std::size_t thirdLevelStepSize_; 120 | 121 | double minValleyToPeakGrowthRatio_{1.5}; // The amount a peak's growth must be over the preceding valley 122 | double minPeakLevel_{0.1}; // The minimum amplitude value to be considered a peak 123 | 124 | AudioData audioDataInput_; 125 | 126 | bool transientsFound_{false}; // False until a transient is found 127 | std::size_t lastTransientValue_{0}; // Stores sample position of the last transient output 128 | 129 | std::size_t inputSamplesProcessed_{0}; // Keeps track of how many input samples the transient detector has analyzed 130 | 131 | // The amount of audio to preserve from the past when detecting peak/valleys 132 | std::size_t secondsOfPastAudioToRetain_{1}; 133 | std::size_t samplesOfPastAudioToRetain_; 134 | 135 | void CheckForOldAudio(); 136 | 137 | bool CheckForEnoughAudioToProcess(); 138 | 139 | bool CheckForAllSilence(); 140 | 141 | // Return true if transients are found, false otherwise. 142 | // If transients are found, they will be in the transients variable 143 | bool FindTransients(std::vector& transients); 144 | 145 | std::size_t FindFirstTransient(); 146 | 147 | bool GetPeakAndValley(const AudioData& audioData, std::size_t stepSize, TransientPeakAndValley& peakAndValley); 148 | 149 | std::size_t FindTransientSamplePosition(const TransientPeakAndValley& firstLevelPeakAndValley); 150 | 151 | double GetMaxSample(const AudioData& audioData, std::size_t sampleCount); 152 | 153 | bool SampleIsPeak(double centerSample, double leftSample, double rightSample); 154 | 155 | bool SampleMeetsPeekRequirements(double peakSampleValue, double valleySampleValue); 156 | }; 157 | 158 | } 159 | -------------------------------------------------------------------------------- /Source/Signal/TransientPeakAndValley.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | //! @file TransientPeakAndValley.h 28 | //! @brief Stores details on transient information. 29 | 30 | #pragma once 31 | 32 | #include 33 | #include 34 | 35 | namespace Signal { 36 | 37 | //! Stores details on transient information. 38 | 39 | class TransientPeakAndValley 40 | { 41 | public: 42 | //! Sample position of where analysis started and the first level step size used. 43 | TransientPeakAndValley(std::size_t startSamplePosition, std::size_t stepSize); 44 | 45 | //! Clears the stored data in this structure. 46 | void Reset(std::size_t startSamplePosition, std::size_t stepSize); 47 | 48 | //! Retrieve the starting position of the transient. 49 | std::size_t GetStartSamplePosition() const; 50 | 51 | //! Retrieve the step size used to find the transient. 52 | std::size_t GetStepSize() const; 53 | 54 | //! Retrieve the sample position of the peak obtained during analysis. 55 | std::size_t GetPeakSamplePosition() const; 56 | 57 | //! Set the sample position of the peak. 58 | void SetPeakSamplePosition(std::size_t peakPoint); 59 | 60 | //! Retrieve the sample position of the valley obtained during analysis. 61 | std::size_t GetValleySamplePosition() const; 62 | 63 | //! Set the valley sample position. 64 | void SetValleySamplePosition(std::size_t valleyPoint); 65 | 66 | //! Get the x-axis point of where the valley is plotted. 67 | std::size_t GetValleyPoint() const; 68 | 69 | //! Get the x-axis point of where the peak is plotted. 70 | std::size_t GetPeakPoint() const; 71 | 72 | //! Add plotted point from the analysis graph. 73 | void PushPlottedPoint(double point); 74 | 75 | //! Get all plotted points used in the analysis graph. 76 | const std::vector& GetPlottedPoints() const; 77 | 78 | private: 79 | std::size_t startSamplePoisiton_; 80 | std::size_t stepSize_; 81 | std::size_t peakSample_; 82 | std::size_t valleySample_; 83 | std::vector plottedPoints_; 84 | }; 85 | 86 | } -------------------------------------------------------------------------------- /Source/Signal/UT/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}/Externals/googletest/googletest/include") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp) 3 | add_executable(Signal-UT ${source_files}) 4 | target_link_libraries(Signal-UT gtest AudioData Signal Utilities WaveFile) 5 | 6 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 7 | 8 | file(GLOB WAV_TEST_FILES ${CMAKE_CURRENT_SOURCE_DIR}/TestAudio/*.wav) 9 | file(COPY ${WAV_TEST_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 10 | 11 | file(GLOB WAV_TEST_RESULTS ${CMAKE_CURRENT_SOURCE_DIR}/TestAudioResults/*.wav) 12 | file(COPY ${WAV_TEST_RESULTS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 13 | 14 | add_custom_command(TARGET Signal-UT POST_BUILD COMMAND Signal-UT --output-on-failure) 15 | 16 | set_target_properties(Signal-UT PROPERTIES FOLDER Libs) 17 | 18 | -------------------------------------------------------------------------------- /Source/Signal/UT/LowPassFilter-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | void DoLowPassFiltering(const std::string& inputFilename, const std::string& outputFilename, std::size_t cutoffFrequency) 36 | { 37 | WaveFile::WaveFileReader inputWaveFile{inputFilename}; 38 | 39 | double cutoffRatio{static_cast(cutoffFrequency) / static_cast(inputWaveFile.GetSampleRate())}; 40 | Signal::LowPassFilter lowPassFilter{cutoffRatio}; 41 | 42 | lowPassFilter.SubmitAudioData(inputWaveFile.GetAudioData()[WaveFile::MONO_CHANNEL]); 43 | 44 | WaveFile::WaveFileWriter waveWriter(outputFilename, inputWaveFile.GetChannels(), inputWaveFile.GetSampleRate(), inputWaveFile.GetBitsPerSample()); 45 | waveWriter.AppendAudioData(std::vector{lowPassFilter.FlushAudioData()}); 46 | } 47 | 48 | TEST(LowPassFilterTests, TestInvalidCutoff) 49 | { 50 | EXPECT_THROW(Signal::LowPassFilter(0.000001), Utilities::Exception); 51 | EXPECT_THROW(Signal::LowPassFilter(0.6), Utilities::Exception); 52 | } 53 | 54 | TEST(LowPassFilterTests, TestMinimumSamplesNeededForProcessing) 55 | { 56 | { 57 | Signal::LowPassFilter lowPassFilter{0.25}; 58 | EXPECT_EQ(100, lowPassFilter.MinimumSamplesNeededForProcessing()); 59 | } 60 | 61 | { 62 | Signal::LowPassFilter lowPassFilter{0.25, 125}; 63 | EXPECT_EQ(125, lowPassFilter.MinimumSamplesNeededForProcessing()); 64 | } 65 | 66 | { 67 | Signal::LowPassFilter lowPassFilter{0.25, 75}; 68 | EXPECT_EQ(75, lowPassFilter.MinimumSamplesNeededForProcessing()); 69 | } 70 | } 71 | 72 | TEST(LowPassFilterTests, LowPassAt1000Hz) 73 | { 74 | DoLowPassFiltering("400HzSineAnd2121HzSine.wav", "400HzSineAnd2121HzSineLowPassFilteredAt1000HzCurrentResults.wav", 1000); 75 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("400HzSineAnd2121HzSineLowPassFilteredAt1000Hz.wav", 76 | "400HzSineAnd2121HzSineLowPassFilteredAt1000HzCurrentResults.wav")); 77 | } 78 | 79 | TEST(LowPassFilterTests, LowPassAt6000Hz) 80 | { 81 | DoLowPassFiltering("5000HzSineAnd9797HzSine.wav", "5000HzSineAnd9797HzSineLowPassFilteredAt6000HzCurrentResults.wav", 6000); 82 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("5000HzSineAnd9797HzSineLowPassFilteredAt6000Hz.wav", 83 | "5000HzSineAnd9797HzSineLowPassFilteredAt6000HzCurrentResults.wav")); 84 | } 85 | 86 | TEST(LowPassFilterTests, LowPassAt8000Hz) 87 | { 88 | DoLowPassFiltering("222HzSineAnd19000HzSine.wav", "222HzSineAnd19000HzSineLowPassFilteredAt8000HzCurrentResults.wav", 8000); 89 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("222HzSineAnd19000HzSineLowPassFilteredAt8000Hz.wav", 90 | "222HzSineAnd19000HzSineLowPassFilteredAt8000HzCurrentResults.wav")); 91 | } -------------------------------------------------------------------------------- /Source/Signal/UT/PeakProfile-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | // The fixture 35 | class PeakProfileData : public testing::Test 36 | { 37 | public: 38 | Signal::FrequencyDomain frequencyDomain_; 39 | std::unique_ptr peakProfile_; 40 | 41 | PeakProfileData() 42 | { 43 | // Create a ficticious frequency domain representation with a couple of peaks 44 | frequencyDomain_.PushFrequencyBin({0.0, 0.0}); 45 | frequencyDomain_.PushFrequencyBin({0.1, 0.1}); 46 | frequencyDomain_.PushFrequencyBin({0.2, 0.2}); 47 | frequencyDomain_.PushFrequencyBin({1.5, 1.5}); 48 | frequencyDomain_.PushFrequencyBin({0.6, 0.6}); 49 | frequencyDomain_.PushFrequencyBin({0.4, 0.4}); 50 | frequencyDomain_.PushFrequencyBin({0.35, 0.35}); 51 | frequencyDomain_.PushFrequencyBin({0.3, 0.3}); 52 | frequencyDomain_.PushFrequencyBin({0.2, 0.2}); 53 | frequencyDomain_.PushFrequencyBin({0.1, 0.1}); 54 | frequencyDomain_.PushFrequencyBin({0.06, 0.06}); 55 | frequencyDomain_.PushFrequencyBin({0.7, 0.7}); 56 | frequencyDomain_.PushFrequencyBin({2.4, 2.4}); 57 | frequencyDomain_.PushFrequencyBin({0.7, 0.7}); 58 | frequencyDomain_.PushFrequencyBin({0.3, 0.3}); 59 | frequencyDomain_.PushFrequencyBin({0.1, 0.1}); 60 | 61 | peakProfile_.reset(new Signal::PeakProfile(frequencyDomain_)); 62 | } 63 | }; 64 | 65 | TEST_F(PeakProfileData, TestPeakValues) 66 | { 67 | // Test that we get the proper peak values back for each bin 68 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(0)); 69 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(1)); 70 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(2)); 71 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(3)); 72 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(4)); 73 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(5)); 74 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(6)); 75 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(7)); 76 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(8)); 77 | EXPECT_EQ(3, peakProfile_->GetLocalPeakForBin(9)); 78 | EXPECT_EQ(12, peakProfile_->GetLocalPeakForBin(10)); 79 | EXPECT_EQ(12, peakProfile_->GetLocalPeakForBin(11)); 80 | EXPECT_EQ(12, peakProfile_->GetLocalPeakForBin(12)); 81 | EXPECT_EQ(12, peakProfile_->GetLocalPeakForBin(13)); 82 | EXPECT_EQ(12, peakProfile_->GetLocalPeakForBin(14)); 83 | EXPECT_EQ(12, peakProfile_->GetLocalPeakForBin(15)); 84 | 85 | auto peakBins{peakProfile_->GetAllPeakBins()}; 86 | EXPECT_EQ(2, peakBins.size()); 87 | EXPECT_EQ(3, peakBins[0]); 88 | EXPECT_EQ(12, peakBins[1]); 89 | 90 | } 91 | 92 | TEST_F(PeakProfileData, TestGettingValleyBins) 93 | { 94 | // Test getting the valleys for peak bin 3 95 | { 96 | auto valleyBins{peakProfile_->GetValleyBins(3)}; 97 | EXPECT_EQ(0, valleyBins.first); 98 | EXPECT_EQ(10, valleyBins.second); 99 | } 100 | 101 | // Test getting the valleys for peak bin 12 102 | { 103 | auto valleyBins{peakProfile_->GetValleyBins(12)}; 104 | EXPECT_EQ(10, valleyBins.first); 105 | EXPECT_EQ(15, valleyBins.second); 106 | } 107 | } 108 | 109 | TEST_F(PeakProfileData, TestGettingValleyBinsFailure) 110 | { 111 | // Test getting the valleys for peak bin 3 112 | EXPECT_THROW(peakProfile_->GetValleyBins(4), Utilities::Exception); 113 | } 114 | 115 | TEST(PeakProfileTests, PeakProfileTest) 116 | { 117 | // Create a ficticious frequency domain representation with a couple of peaks 118 | Signal::FrequencyDomain frequencyDomain; 119 | frequencyDomain.PushFrequencyBin({0.0, 0.0}); 120 | frequencyDomain.PushFrequencyBin({0.1, 0.1}); 121 | frequencyDomain.PushFrequencyBin({0.2, 0.2}); 122 | frequencyDomain.PushFrequencyBin({1.5, 1.5}); 123 | frequencyDomain.PushFrequencyBin({0.6, 0.6}); 124 | frequencyDomain.PushFrequencyBin({0.4, 0.4}); 125 | frequencyDomain.PushFrequencyBin({0.35, 0.35}); 126 | frequencyDomain.PushFrequencyBin({0.3, 0.3}); 127 | frequencyDomain.PushFrequencyBin({0.2, 0.2}); 128 | frequencyDomain.PushFrequencyBin({0.1, 0.1}); 129 | frequencyDomain.PushFrequencyBin({0.06, 0.06}); 130 | frequencyDomain.PushFrequencyBin({0.7, 0.7}); 131 | frequencyDomain.PushFrequencyBin({2.4, 2.4}); 132 | frequencyDomain.PushFrequencyBin({0.7, 0.7}); 133 | frequencyDomain.PushFrequencyBin({0.3, 0.3}); 134 | frequencyDomain.PushFrequencyBin({0.1, 0.1}); 135 | 136 | Signal::PeakProfile peakProfile{frequencyDomain}; 137 | 138 | // Test that we get the proper peak values back for each bin 139 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(0)); 140 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(1)); 141 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(2)); 142 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(3)); 143 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(4)); 144 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(5)); 145 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(6)); 146 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(7)); 147 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(8)); 148 | EXPECT_EQ(3, peakProfile.GetLocalPeakForBin(9)); 149 | EXPECT_EQ(12, peakProfile.GetLocalPeakForBin(10)); 150 | EXPECT_EQ(12, peakProfile.GetLocalPeakForBin(11)); 151 | EXPECT_EQ(12, peakProfile.GetLocalPeakForBin(12)); 152 | EXPECT_EQ(12, peakProfile.GetLocalPeakForBin(13)); 153 | EXPECT_EQ(12, peakProfile.GetLocalPeakForBin(14)); 154 | EXPECT_EQ(12, peakProfile.GetLocalPeakForBin(15)); 155 | 156 | auto peakBins{peakProfile.GetAllPeakBins()}; 157 | EXPECT_EQ(2, peakBins.size()); 158 | EXPECT_EQ(3, peakBins[0]); 159 | EXPECT_EQ(12, peakBins[1]); 160 | } 161 | -------------------------------------------------------------------------------- /Source/Signal/UT/Resampler-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | void DoResampling(const std::string& inputFilename, const std::string& outputFilename, std::size_t newSampleRate) 35 | { 36 | WaveFile::WaveFileReader inputWaveFile{inputFilename}; 37 | 38 | double resampleRatio{static_cast(newSampleRate) / static_cast(inputWaveFile.GetSampleRate())}; 39 | 40 | Signal::Resampler resampler{inputWaveFile.GetSampleRate(), resampleRatio}; 41 | 42 | resampler.SubmitAudioData(inputWaveFile.GetAudioData()[0]); 43 | 44 | WaveFile::WaveFileWriter waveWriter(outputFilename, inputWaveFile.GetChannels(), newSampleRate, inputWaveFile.GetBitsPerSample()); 45 | waveWriter.AppendAudioData(std::vector{resampler.FlushAudioData()}); 46 | } 47 | 48 | TEST(ResamplerTests, SineWaveResampled) 49 | { 50 | DoResampling("100HzSineWaveAt32768Hz.wav", "100HzSineWaveAt32768HzResampledCurrentResult.wav", 38000); 51 | EXPECT_EQ(true, Utilities::File::CheckIfFilesMatch("100HzSineWaveAt32768HzResampled.wav", "100HzSineWaveAt32768HzResampledCurrentResult.wav")); 52 | } 53 | 54 | TEST(ResamplerTests, PianoKeyResampled) 55 | { 56 | DoResampling("SinglePianoKey.wav", "SinglePianoKeyResampledCurrentResult.wav", 24123); 57 | EXPECT_EQ(true, Utilities::File::CheckIfFilesMatch("SinglePianoKeyResampled.wav", "SinglePianoKeyResampledCurrentResult.wav")); 58 | } 59 | 60 | TEST(ResamplerTests, TestLowPassFilterAt2000Hz) 61 | { 62 | DoResampling("400HzSineAnd2121HzSine.wav", "400HzSineAnd2121HzSineResampledCurrentResult.wav", 2000); 63 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("400HzSineAnd2121HzSineResampled.wav", "400HzSineAnd2121HzSineResampledCurrentResult.wav")); 64 | } 65 | 66 | TEST(ResamplerTests, TestLowPassFilterAt5000Hz) 67 | { 68 | DoResampling("222HzSineAnd19000HzSine.wav", "222HzSineAnd19000HzSineResampledCurrentResult.wav", 5000); 69 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("222HzSineAnd19000HzSineResampled.wav", "222HzSineAnd19000HzSineResampledCurrentResult.wav")); 70 | } 71 | 72 | TEST(ResamplerTests, TestLowPassFilterAt15000Hz) 73 | { 74 | DoResampling("5000HzSineAnd9797HzSine.wav", "5000HzSineAnd9797HzSineResampledCurrentResult.wav", 15000); 75 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("5000HzSineAnd9797HzSineResampled.wav", "5000HzSineAnd9797HzSineResampledCurrentResult.wav")); 76 | } 77 | -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/100HzSineWaveAt32768Hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/100HzSineWaveAt32768Hz.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/222HzSineAnd19000HzSine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/222HzSineAnd19000HzSine.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/400HzSineAnd2121HzSine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/400HzSineAnd2121HzSine.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/5000HzSineAnd9797HzSine.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/5000HzSineAnd9797HzSine.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808BassDrum.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808BassDrum.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808RimShot616SamplesLong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808RimShot616SamplesLong.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808Snare1024SamplesLong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808Snare1024SamplesLong.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808Snare2615SamplesLong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808Snare2615SamplesLong.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808Snare4096SamplesLong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808Snare4096SamplesLong.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808Snare4097SamplesLong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808Snare4097SamplesLong.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/808Snare4100SamplesLong.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/808Snare4100SamplesLong.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/AcousticGuitarDualStringPluck.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/AcousticGuitarDualStringPluck.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/AcousticGuitarStringPluck.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/AcousticGuitarStringPluck.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/BuiltToSpillBeat32123HzSampleRate.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/BuiltToSpillBeat32123HzSampleRate.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/BuiltToSpillBeatBeginningWithSilence.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/BuiltToSpillBeatBeginningWithSilence.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/PeakAt6000And10000.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/PeakAt6000And10000.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/SinglePianoKey.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/SinglePianoKey.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/SweetEmotion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/SweetEmotion.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudio/TenSamples.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudio/TenSamples.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/100HzSineWaveAt32768HzCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/100HzSineWaveAt32768HzCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/100HzSineWaveAt32768HzResampled.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/100HzSineWaveAt32768HzResampled.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/100HzSineWaveAt32768HzStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/100HzSineWaveAt32768HzStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/222HzSineAnd19000HzSineLowPassFilteredAt8000Hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/222HzSineAnd19000HzSineLowPassFilteredAt8000Hz.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/222HzSineAnd19000HzSineResampled.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/222HzSineAnd19000HzSineResampled.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/400HzSineAnd2121HzSineLowPassFilteredAt1000Hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/400HzSineAnd2121HzSineLowPassFilteredAt1000Hz.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/400HzSineAnd2121HzSineResampled.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/400HzSineAnd2121HzSineResampled.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/5000HzSineAnd9797HzSineLowPassFilteredAt6000Hz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/5000HzSineAnd9797HzSineLowPassFilteredAt6000Hz.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/5000HzSineAnd9797HzSineResampled.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/5000HzSineAnd9797HzSineResampled.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808BassDrumCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808BassDrumCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808BassDrumStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808BassDrumStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808RimShot616SamplesLongCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808RimShot616SamplesLongCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808RimShot616SamplesLongStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808RimShot616SamplesLongStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare1024SamplesLongCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare1024SamplesLongCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare1024SamplesLongOutputStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare1024SamplesLongOutputStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare2615SamplesLongCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare2615SamplesLongCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare2615SamplesLongStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare2615SamplesLongStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare4096SamplesLongCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare4096SamplesLongCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare4096SamplesLongStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare4096SamplesLongStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare4097SamplesLongCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare4097SamplesLongCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare4097SamplesLongStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare4097SamplesLongStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare4100SamplesLongCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare4100SamplesLongCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/808Snare4100SamplesLongStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/808Snare4100SamplesLongStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/AcousticGuitarStringPluckCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/AcousticGuitarStringPluckCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/AcousticGuitarStringPluckStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/AcousticGuitarStringPluckStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/SinglePianoKeyCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/SinglePianoKeyCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/SinglePianoKeyResampled.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/SinglePianoKeyResampled.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/SinglePianoKeyStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/SinglePianoKeyStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/TenSamplesOutputCompressed.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/TenSamplesOutputCompressed.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TestAudioResults/TenSamplesOutputStretched.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/Signal/UT/TestAudioResults/TenSamplesOutputStretched.wav -------------------------------------------------------------------------------- /Source/Signal/UT/TransientPeakAndValley-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | TEST(TransientPeakAndValley, InstantiateTest) 32 | { 33 | Signal::TransientPeakAndValley transientPeakAndValley(100, 256); 34 | EXPECT_EQ(100, transientPeakAndValley.GetStartSamplePosition()); 35 | EXPECT_EQ(256, transientPeakAndValley.GetStepSize()); 36 | EXPECT_EQ(0, transientPeakAndValley.GetPeakSamplePosition()); 37 | EXPECT_EQ(0, transientPeakAndValley.GetValleySamplePosition()); 38 | EXPECT_EQ(0, transientPeakAndValley.GetPeakPoint()); 39 | EXPECT_EQ(0, transientPeakAndValley.GetValleyPoint()); 40 | EXPECT_EQ(0, transientPeakAndValley.GetPlottedPoints().size()); 41 | } 42 | 43 | TEST(TransientPeakAndValley, TestBasicValues) 44 | { 45 | Signal::TransientPeakAndValley transientPeakAndValley(100, 256); 46 | 47 | std::vector hypotheticalPoints{0.1, 0.5, 0.2, 0.3, 0.7, 0.4, 0.6, 0.8, 0.7}; 48 | std::for_each(hypotheticalPoints.begin(), hypotheticalPoints.end(), [&](double point) { transientPeakAndValley.PushPlottedPoint(point); }); 49 | 50 | transientPeakAndValley.SetPeakSamplePosition(1124); 51 | transientPeakAndValley.SetValleySamplePosition(612); 52 | 53 | auto plottedPoints{transientPeakAndValley.GetPlottedPoints()}; 54 | 55 | EXPECT_EQ(100, transientPeakAndValley.GetStartSamplePosition()); 56 | EXPECT_EQ(256, transientPeakAndValley.GetStepSize()); 57 | EXPECT_EQ(4, transientPeakAndValley.GetPeakPoint()); 58 | EXPECT_EQ(2, transientPeakAndValley.GetValleyPoint()); 59 | EXPECT_EQ(hypotheticalPoints.size(), plottedPoints.size()); 60 | EXPECT_TRUE(std::equal(hypotheticalPoints.begin(), hypotheticalPoints.end(), plottedPoints.begin(), plottedPoints.end())); 61 | } 62 | 63 | TEST(TransientPeakAndValley, TestReset) 64 | { 65 | Signal::TransientPeakAndValley transientPeakAndValley(100, 256); 66 | 67 | std::vector hypotheticalPoints{0.1, 0.5, 0.2, 0.3, 0.7, 0.4, 0.6, 0.8, 0.7}; 68 | std::for_each(hypotheticalPoints.begin(), hypotheticalPoints.end(), [&](double point) { transientPeakAndValley.PushPlottedPoint(point); }); 69 | 70 | transientPeakAndValley.SetPeakSamplePosition(1124); 71 | transientPeakAndValley.SetValleySamplePosition(612); 72 | 73 | transientPeakAndValley.Reset(200, 400); 74 | 75 | EXPECT_EQ(200, transientPeakAndValley.GetStartSamplePosition()); 76 | EXPECT_EQ(400, transientPeakAndValley.GetStepSize()); 77 | EXPECT_EQ(0, transientPeakAndValley.GetPeakPoint()); 78 | EXPECT_EQ(0, transientPeakAndValley.GetValleyPoint()); 79 | EXPECT_EQ(0, transientPeakAndValley.GetPlottedPoints().size()); 80 | } -------------------------------------------------------------------------------- /Source/Signal/UT/Windowing-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | TEST(WindowingTests, BlackmanWindowTestFullWindow) 33 | { 34 | std::vector signal(1024, 1.0); 35 | Signal::BlackmanWindow(signal); 36 | Signal::ReverseBlackmanWindow(signal); 37 | 38 | for(auto sample : signal) 39 | { 40 | EXPECT_NEAR(1.0, sample, 0.0001); 41 | } 42 | } 43 | 44 | TEST(WindowingTests, BlackmanWindowTest0To25) 45 | { 46 | std::vector signal(2048, 1.0); 47 | Signal::BlackmanWindow(signal, 0.0, 25.0); 48 | Signal::ReverseBlackmanWindow(signal, 0.0, 25.0); 49 | 50 | auto signal16{Signal::ConvertFloat64ToSigned16(signal)}; 51 | 52 | for(auto sample : signal) 53 | { 54 | EXPECT_NEAR(1.0, sample, 0.0001); 55 | } 56 | } 57 | 58 | TEST(WindowingTests, BlackmanWindowTest25To75) 59 | { 60 | std::vector signal(256, 1.0); 61 | Signal::BlackmanWindow(signal, 25.0, 75.0); 62 | Signal::ReverseBlackmanWindow(signal, 25.0, 75.0); 63 | 64 | for(auto sample : signal) 65 | { 66 | EXPECT_NEAR(1.0, sample, 0.0001); 67 | } 68 | } 69 | 70 | TEST(WindowingTests, BlackmanWindowTest75To100) 71 | { 72 | std::vector signal(256, 1.0); 73 | Signal::BlackmanWindow(signal, 75.0, 100.0); 74 | Signal::ReverseBlackmanWindow(signal, 75.0, 100.0); 75 | 76 | for(auto sample : signal) 77 | { 78 | EXPECT_NEAR(1.0, sample, 0.0001); 79 | } 80 | } 81 | 82 | TEST(WindowingTests, LinearFadeInOutEvenTest) 83 | { 84 | std::vector signal(1024, 1.0); 85 | Signal::LinearFadeInOut(signal); 86 | 87 | EXPECT_EQ(1024, signal.size()); 88 | 89 | EXPECT_NEAR(0.0, signal[0], 0.01); 90 | EXPECT_NEAR(0.25, signal[127], 0.01); 91 | EXPECT_NEAR(0.5, signal[255], 0.01); 92 | EXPECT_NEAR(0.75, signal[383], 0.01); 93 | EXPECT_NEAR(1.0, signal[511], 0.01); 94 | EXPECT_NEAR(1.0, signal[512], 0.01); 95 | EXPECT_NEAR(0.75, signal[639], 0.01); 96 | EXPECT_NEAR(0.5, signal[767], 0.01); 97 | EXPECT_NEAR(0.25, signal[895], 0.01); 98 | EXPECT_NEAR(0.0, signal[1023], 0.01); 99 | } 100 | 101 | TEST(WindowingTests, LinearFadeInOutOddTest) 102 | { 103 | std::vector signal(11, 1.0); 104 | Signal::LinearFadeInOut(signal); 105 | 106 | EXPECT_EQ(11, signal.size()); 107 | 108 | EXPECT_NEAR(0.00, signal[0], 0.00001); 109 | EXPECT_NEAR(0.20, signal[1], 0.00001); 110 | EXPECT_NEAR(0.40, signal[2], 0.00001); 111 | EXPECT_NEAR(0.60, signal[3], 0.00001); 112 | EXPECT_NEAR(0.80, signal[4], 0.00001); 113 | EXPECT_NEAR(1.00, signal[5], 0.00001); 114 | EXPECT_NEAR(0.80, signal[6], 0.00001); 115 | EXPECT_NEAR(0.60, signal[7], 0.00001); 116 | EXPECT_NEAR(0.40, signal[8], 0.00001); 117 | EXPECT_NEAR(0.20, signal[9], 0.00001); 118 | EXPECT_NEAR(0.00, signal[10], 0.00001); 119 | } 120 | 121 | TEST(WindowingTests, LinearFadeInOutAttenuatedSignal) 122 | { 123 | std::vector signal(11, 0.75); 124 | Signal::LinearFadeInOut(signal); 125 | 126 | EXPECT_EQ(11, signal.size()); 127 | 128 | EXPECT_NEAR(0.00, signal[0], 0.00001); 129 | EXPECT_NEAR(0.15, signal[1], 0.00001); 130 | EXPECT_NEAR(0.30, signal[2], 0.00001); 131 | EXPECT_NEAR(0.45, signal[3], 0.00001); 132 | EXPECT_NEAR(0.60, signal[4], 0.00001); 133 | EXPECT_NEAR(0.75, signal[5], 0.00001); 134 | EXPECT_NEAR(0.60, signal[6], 0.00001); 135 | EXPECT_NEAR(0.45, signal[7], 0.00001); 136 | EXPECT_NEAR(0.30, signal[8], 0.00001); 137 | EXPECT_NEAR(0.15, signal[9], 0.00001); 138 | EXPECT_NEAR(0.00, signal[10], 0.00001); 139 | } -------------------------------------------------------------------------------- /Source/Signal/UT/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | 29 | int main(int argc, char* argv[]) 30 | { 31 | testing::InitGoogleTest(&argc, argv); 32 | return RUN_ALL_TESTS(); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Source/Signal/Windowing.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | //! @file Windowing.h 32 | //! @brief Functions for applying various windowing to audio signals. 33 | 34 | namespace Signal { 35 | 36 | //! Apply a Blackman window. 37 | void BlackmanWindow(std::vector& inputSignal, double startPercent=0.0, double endPercent=100.0); 38 | 39 | //! Apply an inverse Blackman window. 40 | void InverseBlackmanWindow(std::vector& inputSignal, double startPercent=0.0, double endPercent=100.0); 41 | 42 | //! Apply an reverse Blackman window. 43 | void ReverseBlackmanWindow(std::vector& inputSignal, double startPercent=0.0, double endPercent=100.0); 44 | 45 | //! Apply linear window. 46 | void LinearFadeInOut(std::vector& inputSignal); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp "Source/[^.]*.h" "Source/[^.]*.cpp") 3 | add_library(ThreadSafeAudioFile ${source_files}) 4 | 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | 7 | add_subdirectory(UT) 8 | 9 | set_target_properties(ThreadSafeAudioFile PROPERTIES FOLDER Libs) 10 | 11 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/Reader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | //! @file Reader.h 35 | //! @brief A threadsafe audio file reader. 36 | 37 | namespace ThreadSafeAudioFile { 38 | 39 | //! A class allowing for reading data from an audio file in a threadsafe manner. 40 | 41 | class Reader 42 | { 43 | public: 44 | //! Construct the threadsafe audio file reader, taking the audio file name to read. 45 | Reader(const std::string& filename); 46 | virtual ~Reader(); 47 | 48 | //! Get the number of channels the audio file contains. 49 | std::size_t GetChannels(); 50 | 51 | //! Get the sample rate of the audio file. 52 | std::size_t GetSampleRate(); 53 | 54 | //! Get the bit resolution of the audio file. 55 | std::size_t GetBitsPerSample(); 56 | 57 | //! Get the number of sample in the audio file. 58 | std::size_t GetSampleCount(); 59 | 60 | //! @brief Get the number of sample in the audio file. 61 | //! @param streamID Identifies the channel. 62 | //! @param sampleStartPosition The sample number of where the read should start. 63 | //! @param samplesToRead The number of samples to read. 64 | AudioData ReadAudioStream(std::size_t streamID, std::size_t sampleStartPosition, std::size_t samplesToRead); 65 | 66 | private: 67 | std::mutex mutex_; 68 | WaveFile::WaveFileReader waveFileReader_; 69 | }; 70 | 71 | } -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/Source/Reader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | ThreadSafeAudioFile::Reader::Reader(const std::string& filename) : waveFileReader_{filename} { } 33 | 34 | ThreadSafeAudioFile::Reader::~Reader() { } 35 | 36 | std::size_t ThreadSafeAudioFile::Reader::GetChannels() 37 | { 38 | return waveFileReader_.GetChannels(); 39 | } 40 | 41 | std::size_t ThreadSafeAudioFile::Reader::GetSampleRate() 42 | { 43 | return waveFileReader_.GetSampleRate(); 44 | } 45 | 46 | std::size_t ThreadSafeAudioFile::Reader::GetBitsPerSample() 47 | { 48 | return waveFileReader_.GetBitsPerSample(); 49 | } 50 | 51 | std::size_t ThreadSafeAudioFile::Reader::GetSampleCount() 52 | { 53 | return waveFileReader_.GetSampleCount(); 54 | } 55 | 56 | AudioData ThreadSafeAudioFile::Reader::ReadAudioStream(std::size_t streamID, std::size_t sampleStartPosition, std::size_t samplesToRead) 57 | { 58 | std::lock_guard lock{mutex_}; 59 | 60 | if(streamID > waveFileReader_.GetChannels()) 61 | { 62 | Utilities::ThrowException( 63 | Utilities::Stringify("ReadAudioStream given stream ID greater than what exists. Channels: ") + 64 | Utilities::Stringify(waveFileReader_.GetChannels()) + 65 | Utilities::Stringify(" streamID: ") + 66 | Utilities::Stringify(streamID)); 67 | } 68 | 69 | // There is a lot of opportunity for making this more efficient, especially for stereo wave files. Stereo wave files have their 70 | // channels interleaved - left sample, right sample, repeat. So when the waveFileReader does a read on a stereo file, right now 71 | // we're just throwing away the entire other channel that it reads, when likely we're reading that other channeling in a different 72 | // thread. A buffering scheme could really improve this. 73 | 74 | return waveFileReader_.GetAudioData(sampleStartPosition, samplesToRead)[streamID]; 75 | } 76 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/Source/Writer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | ThreadSafeAudioFile::Writer::Writer(const std::string& filename, std::size_t channels, std::size_t sampleRate, std::size_t bitsPerSample) : 33 | waveFileWriter_{filename, channels, sampleRate, bitsPerSample} 34 | { 35 | if(waveFileWriter_.GetChannels() == 2) 36 | { 37 | audioDataBuffers_.push_back(AudioData{}); 38 | audioDataBuffers_.push_back(AudioData{}); 39 | } 40 | } 41 | 42 | ThreadSafeAudioFile::Writer::~Writer() { } 43 | 44 | void ThreadSafeAudioFile::Writer::WriteAudioStream(std::size_t streamID, const AudioData& audioData) 45 | { 46 | std::lock_guard lock{mutex_}; 47 | 48 | if(waveFileWriter_.GetChannels() == 1) 49 | { 50 | waveFileWriter_.AppendAudioData(std::vector{audioData}); 51 | } 52 | else // If not a mono file, it must be stereo (two channels) 53 | { 54 | auto oppositeStreamID{streamID ? 0 : 1}; 55 | auto oppositeStreamBufferedAmount{audioDataBuffers_[oppositeStreamID].GetSize()}; 56 | 57 | // So, we have the following possible cases: 58 | // 1) The opposite channel has zero samples buffered. If this is the case, we should just 59 | // buffer all the given audioData. 60 | // 2) The opposite channel's buffer has greater than zero, but less than audioData.GetSize() 61 | // samples. If this is the case, we should write oppositeAmount of samples and buffer the 62 | // remaining samples in audioData. 63 | // 3) The opposite channel's buffer has the same amount of samples that audioData has. If so, 64 | // write the given samples and the opposite channel's buffer to the wave file. 65 | // 4) The opposite channel's buffer has more samples than the audioData has. If so, write the 66 | // audioData.GetSize() amount of samples and leave the remaining samples in the opposite 67 | // stream's buffer. 68 | 69 | if(oppositeStreamBufferedAmount == 0) // Case 1 70 | { 71 | audioDataBuffers_[streamID].Append(audioData); 72 | } 73 | else // Remaining cases 74 | { 75 | auto samplesToWrite{std::min(audioData.GetSize(), audioDataBuffers_[oppositeStreamID].GetSize())}; 76 | 77 | std::vector dataToWrite(waveFileWriter_.GetChannels()); 78 | dataToWrite[streamID].Append(audioData.Retrieve(samplesToWrite)); 79 | dataToWrite[oppositeStreamID].Append(audioDataBuffers_[oppositeStreamID].RetrieveRemove(samplesToWrite)); 80 | 81 | waveFileWriter_.AppendAudioData(dataToWrite); 82 | 83 | if(samplesToWrite < audioData.GetSize()) // Buffer whatever might remain of audioData 84 | { 85 | audioDataBuffers_[streamID].Append(audioData.Retrieve(samplesToWrite, audioData.GetSize() - samplesToWrite)); 86 | } 87 | } 88 | 89 | maxBufferedSamples_ = std::max(std::max(audioDataBuffers_[0].GetSize(), audioDataBuffers_[1].GetSize()), maxBufferedSamples_); 90 | } 91 | } 92 | 93 | std::size_t ThreadSafeAudioFile::Writer::GetMaxBufferedSamples() 94 | { 95 | return maxBufferedSamples_; 96 | } 97 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/UT/AudioFileDataHelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | 29 | // Creates a simple 100 sample sawtooth audio waveform 30 | AudioData CreateAudioData(std::size_t samples, bool invert) 31 | { 32 | AudioData audioData; 33 | double inversionFactor{1.0}; 34 | if(invert) 35 | { 36 | inversionFactor = -1.0; 37 | } 38 | 39 | for(std::size_t i{0}; i < samples; ++i) 40 | { 41 | audioData.PushSample(static_cast(i % 100) / (100.0 * inversionFactor)); 42 | } 43 | 44 | return audioData; 45 | } 46 | 47 | AudioData CreateInvertedAudioData(std::size_t samples) 48 | { 49 | return CreateAudioData(samples, true); 50 | } 51 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/UT/AudioFileDataHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | // Creates a simple 100 sample sawtooth audio waveform 32 | AudioData CreateAudioData(std::size_t samples, bool invert=false); 33 | 34 | // Results in the same audio data as above except is inverted 35 | AudioData CreateInvertedAudioData(std::size_t samples); 36 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/UT/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}/Externals/googletest/googletest/include") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp) 3 | add_executable(ThreadSafeAudioFile-UT ${source_files}) 4 | target_link_libraries(ThreadSafeAudioFile-UT gtest gtest_main AudioData ThreadSafeAudioFile Utilities WaveFile) 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | add_custom_command(TARGET ThreadSafeAudioFile-UT POST_BUILD COMMAND ThreadSafeAudioFile-UT --output-on-failure) 7 | set_target_properties(ThreadSafeAudioFile-UT PROPERTIES FOLDER Libs) 8 | 9 | -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/UT/Reader-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | const std::size_t MonoAudio = 1; 36 | const std::size_t StereoAudio = 2; 37 | const std::size_t SampleRate = 44100; 38 | const std::size_t BitResolution = 16; 39 | 40 | TEST(ThreadSafeAudioFileReader, TestNonExistanceFile) 41 | { 42 | ASSERT_THROW(ThreadSafeAudioFile::Reader("WaveFileThatDoesntExist.wav"), Utilities::Exception); 43 | } 44 | 45 | TEST(ThreadSafeAudioFileReader, TestReadingMonoFile) 46 | { 47 | AudioData audioData{CreateAudioData(44100)}; 48 | 49 | // Scoping the following so TestFile is closed when our writer is destroyed and we don't 50 | // attempt to read the file where it might have zero size due to buffering. 51 | { 52 | WaveFile::WaveFileWriter writer{"TestFile.wav", MonoAudio, SampleRate, BitResolution}; 53 | writer.AppendAudioData(std::vector{audioData}); 54 | } 55 | 56 | // Now read the file we just wrote and confirm it contains the expected audio data 57 | ThreadSafeAudioFile::Reader reader{"TestFile.wav"}; 58 | EXPECT_EQ(MonoAudio, reader.GetChannels()); 59 | EXPECT_EQ(SampleRate, reader.GetSampleRate()); 60 | EXPECT_EQ(BitResolution, reader.GetBitsPerSample()); 61 | EXPECT_EQ(44100, reader.GetSampleCount()); 62 | auto readAudioData{reader.ReadAudioStream(0, 0, 44100)}; 63 | 64 | for(std::size_t i{0}; i < 44100; ++i) 65 | { 66 | EXPECT_NEAR(audioData.GetData()[i], readAudioData.GetData()[i], 0.001); 67 | } 68 | 69 | // Also try reading a specific section and confirm it's correct 70 | auto readAudioData2{reader.ReadAudioStream(0, 200, 10)}; 71 | for(std::size_t i{0}; i < 10; ++i) 72 | { 73 | EXPECT_NEAR(audioData.GetData()[200 + i], readAudioData2.GetData()[i], 0.001); 74 | } 75 | } 76 | 77 | TEST(ThreadSafeAudioFileReader, TestReadingStereoFile) 78 | { 79 | AudioData audioData{CreateAudioData(44100)}; 80 | AudioData audioDataInverted{CreateInvertedAudioData(44100)}; 81 | 82 | // Scoping the following so TestFile is closed when our writer is destroyed and we don't 83 | // attempt to read the file where it might have zero size due to buffering. 84 | { 85 | WaveFile::WaveFileWriter writer{"TestFile.wav", StereoAudio, SampleRate, BitResolution}; 86 | writer.AppendAudioData(std::vector{audioData, audioDataInverted}); 87 | } 88 | 89 | // Now read the file we just wrote and confirm it contains the expected audio data 90 | ThreadSafeAudioFile::Reader reader{"TestFile.wav"}; 91 | EXPECT_EQ(StereoAudio, reader.GetChannels()); 92 | EXPECT_EQ(SampleRate, reader.GetSampleRate()); 93 | EXPECT_EQ(BitResolution, reader.GetBitsPerSample()); 94 | EXPECT_EQ(44100, reader.GetSampleCount()); 95 | auto readAudioDataLeft{reader.ReadAudioStream(0, 0, 44100)}; 96 | auto readAudioDataRight{reader.ReadAudioStream(1, 0, 44100)}; 97 | 98 | for(std::size_t i{0}; i < 44100; ++i) 99 | { 100 | EXPECT_NEAR(audioData.GetData()[i], readAudioDataLeft.GetData()[i], 0.001); 101 | EXPECT_NEAR(audioDataInverted.GetData()[i], readAudioDataRight.GetData()[i], 0.001); 102 | } 103 | 104 | // Also try reading a specific section and confirm it's correct 105 | auto readAudioDataLeft2{reader.ReadAudioStream(0, 400, 10)}; 106 | auto readAudioDataRight2{reader.ReadAudioStream(1, 400, 10)}; 107 | for(std::size_t i{0}; i < 10; ++i) 108 | { 109 | EXPECT_NEAR(audioData.GetData()[400 + i], readAudioDataLeft2.GetData()[i], 0.001); 110 | EXPECT_NEAR(audioDataInverted.GetData()[400 + i], readAudioDataRight2.GetData()[i], 0.001); 111 | } 112 | } 113 | 114 | TEST(ThreadSafeAudioFileReader, TestReadingStereoFileThreaded) 115 | { 116 | AudioData audioData{CreateAudioData(44100)}; 117 | AudioData audioDataInverted{CreateInvertedAudioData(44100)}; 118 | 119 | // Scoping the following so TestFile is closed when our writer is destroyed and we don't 120 | // attempt to read the file where it might have zero size due to buffering. 121 | { 122 | WaveFile::WaveFileWriter writer{"TestFile.wav", StereoAudio, SampleRate, BitResolution}; 123 | writer.AppendAudioData(std::vector{audioData, audioDataInverted}); 124 | } 125 | 126 | ThreadSafeAudioFile::Reader reader{"TestFile.wav"}; 127 | EXPECT_EQ(StereoAudio, reader.GetChannels()); 128 | EXPECT_EQ(SampleRate, reader.GetSampleRate()); 129 | EXPECT_EQ(BitResolution, reader.GetBitsPerSample()); 130 | EXPECT_EQ(44100, reader.GetSampleCount()); 131 | AudioData leftChannelRead; 132 | AudioData rightChannelRead; 133 | 134 | std::thread leftThread{[&] { leftChannelRead = reader.ReadAudioStream(0, 0, 44100); } }; 135 | std::thread rightThread{[&] { rightChannelRead = reader.ReadAudioStream(1, 0, 44100); } }; 136 | 137 | leftThread.join(); 138 | rightThread.join(); 139 | 140 | // Confirm the read contains the expected audio data 141 | for(std::size_t i{0}; i < 44100; ++i) 142 | { 143 | EXPECT_NEAR(audioData.GetData()[i], leftChannelRead.GetData()[i], 0.001); 144 | EXPECT_NEAR(audioDataInverted.GetData()[i], rightChannelRead.GetData()[i], 0.001); 145 | } 146 | } -------------------------------------------------------------------------------- /Source/ThreadSafeAudioFile/Writer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | //! @file Writer.h 34 | //! @brief A threadsafe audio file writer. 35 | 36 | class AudioData; 37 | 38 | namespace ThreadSafeAudioFile { 39 | 40 | //! A class allowing for writing data to an audio file in a threadsafe manner. 41 | 42 | //! Good for writing multiple channel audio file data in a multithreaded manner. 43 | 44 | class Writer 45 | { 46 | public: 47 | //! @brief Construct the AudioFile Writer. 48 | //! @param filename The name of the audio filename to create. 49 | //! @param channels The number of channels in the audio file. 50 | //! @param sampleRate The sample rate of the audio file. 51 | //! @param bitsPerSample The bit resolution of the audio file. 52 | Writer(const std::string& filename, std::size_t channels, std::size_t sampleRate, std::size_t bitsPerSample); 53 | virtual ~Writer(); 54 | 55 | //! Allows for writing audio data to the audio file. 56 | //! @param streamID Identifies the channel to write audio data to. 57 | //! @param audioData The audio data to write. 58 | void WriteAudioStream(std::size_t streamID, const AudioData& audioData); 59 | 60 | //! Get the max number of buffered samples occurring while writing the audio file. 61 | std::size_t GetMaxBufferedSamples(); 62 | 63 | private: 64 | WaveFile::WaveFileWriter waveFileWriter_; 65 | std::mutex mutex_; 66 | std::vector audioDataBuffers_; 67 | 68 | std::size_t maxBufferedSamples_{0}; 69 | }; 70 | 71 | } -------------------------------------------------------------------------------- /Source/Utilities/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp "Source/[^.]*.h" "Source/[^.]*.cpp") 3 | add_library(Utilities ${source_files}) 4 | 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | 7 | add_subdirectory(UT) 8 | 9 | set_target_properties(Utilities PROPERTIES FOLDER Libs) 10 | 11 | -------------------------------------------------------------------------------- /Source/Utilities/Exception.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | //! @file Exception.h 34 | //! @brief Simple wrapper for std::exception. 35 | 36 | namespace Utilities { 37 | 38 | //! Simple wrapper class around std::exception. 39 | 40 | class Exception : public std::exception 41 | { 42 | public: 43 | 44 | //! Instantiate an exception without any information. 45 | Exception(); 46 | 47 | //! @brief Instantiate an exception with specific information. 48 | //! @param what String information concerning this exception. 49 | //! @param file The source code file the exception originated in. 50 | //! @param lineNumber The line in the source code file the exception originated in. 51 | Exception(const std::string& what, const std::string& file="", std::size_t lineNumber=0); 52 | 53 | //! Get any string information describing this exception. 54 | const char* what() const noexcept override; 55 | 56 | private: 57 | std::string what_; 58 | std::string file_; 59 | std::size_t lineNumber_; 60 | mutable std::string extendedWhat_; 61 | }; 62 | 63 | //! @brief Throw an exception without having to construct the exception object yourself. 64 | //! @param params A variable list of string information to describe the exception. 65 | template 66 | void ThrowException(Params... params) 67 | { 68 | std::string message = Utilities::CreateString("|", params...); 69 | throw Utilities::Exception(message); 70 | } 71 | 72 | } // End of namespace 73 | -------------------------------------------------------------------------------- /Source/Utilities/File.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | //! @file File.h 32 | //! @brief Class and functions for allowing easy file reading access. 33 | 34 | namespace Utilities { namespace File { 35 | 36 | //! Returns the dirname of the given path and filename 37 | std::string GetDirname(const std::string& filenameWithPath); 38 | 39 | //! Returns the basename of the given path and filename 40 | std::string GetBasename(const std::string& filenameWithPath); 41 | 42 | //! Removes the extension from the given filename 43 | void RemoveExtension(std::string& filename); 44 | 45 | //! A fairly simple class to read data from a file. 46 | 47 | class FileReader 48 | { 49 | public: 50 | //! Construct the FileReader. 51 | FileReader(const std::string filename); 52 | virtual ~FileReader(); 53 | 54 | //! Get the number of bytes contained in the file. 55 | std::size_t GetFileSize(); 56 | 57 | //! @brief Read data from the file. 58 | //! @param position Byte position in the file to start the read. 59 | //! @param bytes Number of bytes to read. 60 | std::vector ReadData(std::size_t position, std::size_t bytes); 61 | 62 | private: 63 | std::string filename_; 64 | std::ifstream fileStream_; 65 | std::size_t fileSize_; 66 | 67 | void InitializeFileReading(); 68 | }; 69 | 70 | //! Diffs two files returning true if they match and false otherwise. 71 | bool CheckIfFilesMatch(const std::string& filenameA, const std::string& filenameB); 72 | 73 | //! Diffs two files. The diffInfo will contain information about the differences if the files do not match. 74 | bool CheckIfFilesMatch(const std::string& filenameA, const std::string& filenameB, std::string& diffInfo); 75 | 76 | }} // End of namespace 77 | -------------------------------------------------------------------------------- /Source/Utilities/Source/Exception.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | Utilities::Exception::Exception() 31 | { 32 | 33 | } 34 | 35 | Utilities::Exception::Exception(const std::string& what, const std::string& file, std::size_t lineNumber) : 36 | what_(what), 37 | file_(file), 38 | lineNumber_(lineNumber) 39 | { 40 | 41 | } 42 | 43 | const char* Utilities::Exception::what() const noexcept 44 | { 45 | if(file_.size()) 46 | { 47 | // The extendedWhat_ member is necessary so that when we return the char* the memory exists 48 | // outside the scope of this if and function as opposed to just calling a c_str() on a 49 | // Utilities::Stringify object that will be destroyed once we leave the if. 50 | extendedWhat_ = Utilities::Stringify(what_.c_str()) + 51 | Utilities::Stringify(" ") + 52 | file_ + Utilities::Stringify(":") + 53 | Utilities::Stringify(lineNumber_); 54 | return extendedWhat_.c_str(); 55 | } 56 | 57 | return what_.c_str(); 58 | } 59 | -------------------------------------------------------------------------------- /Source/Utilities/Source/File.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | std::size_t GetFilenameStartPosition(const std::string& filenameWithPath) 31 | { 32 | auto slashPosition = filenameWithPath.find_last_of("/"); 33 | auto backslashPosition = filenameWithPath.find_last_of("\\"); 34 | if(slashPosition == std::string::npos && backslashPosition == std::string::npos) 35 | { 36 | return 0; 37 | } 38 | else if(slashPosition == std::string::npos) 39 | { 40 | return backslashPosition; 41 | } 42 | else if(backslashPosition == std::string::npos) 43 | { 44 | return slashPosition; 45 | } 46 | else if(slashPosition > backslashPosition) 47 | { 48 | return slashPosition; 49 | } 50 | else 51 | { 52 | return backslashPosition; 53 | } 54 | } 55 | 56 | std::string Utilities::File::GetDirname(const std::string& filenameWithPath) 57 | { 58 | return filenameWithPath.substr(0, GetFilenameStartPosition(filenameWithPath)); 59 | } 60 | 61 | std::string Utilities::File::GetBasename(const std::string& filenameWithPath) 62 | { 63 | auto finalSlashStartPosition = GetFilenameStartPosition(filenameWithPath); 64 | if(finalSlashStartPosition == 0) 65 | { 66 | // If we're here, there was no path, just a filename 67 | return filenameWithPath; 68 | } 69 | 70 | return filenameWithPath.substr(GetFilenameStartPosition(filenameWithPath) + 1); 71 | } 72 | 73 | void Utilities::File::RemoveExtension(std::string& filename) 74 | { 75 | auto position = filename.find_last_of("."); 76 | if(position != std::string::npos) 77 | { 78 | filename = filename.substr(0, position); 79 | } 80 | } 81 | 82 | Utilities::File::FileReader::FileReader(const std::string filename) : 83 | filename_{filename}, 84 | fileStream_(filename_, std::ios::in | std::ios::binary) 85 | { 86 | InitializeFileReading(); 87 | } 88 | 89 | Utilities::File::FileReader::~FileReader() 90 | { 91 | fileStream_.close(); 92 | } 93 | 94 | std::size_t Utilities::File::FileReader::GetFileSize() 95 | { 96 | return fileSize_; 97 | } 98 | 99 | std::vector Utilities::File::FileReader::ReadData(std::size_t position, std::size_t bytes) 100 | { 101 | std::vector data(bytes); 102 | 103 | fileStream_.seekg(position, std::ios_base::beg); 104 | 105 | fileStream_.read(data.data(), bytes); 106 | if(!fileStream_.good()) 107 | { 108 | Utilities::Exception(Utilities::Stringify("Failed to read audio data from file " + filename_)); 109 | } 110 | 111 | return data; 112 | } 113 | 114 | void Utilities::File::FileReader::InitializeFileReading() 115 | { 116 | if(!fileStream_.is_open()) 117 | { 118 | Utilities::ThrowException("Failed to open file", filename_, __FILE__, __LINE__); 119 | } 120 | 121 | fileStream_.seekg(0, std::ios_base::end); 122 | fileSize_ = static_cast(fileStream_.tellg()); 123 | fileStream_.seekg(0, std::ios_base::beg); 124 | } 125 | 126 | bool Utilities::File::CheckIfFilesMatch(const std::string& fileA, const std::string& fileB) 127 | { 128 | std::string diffInfo; 129 | return CheckIfFilesMatch(fileA, fileB, diffInfo); 130 | } 131 | 132 | bool Utilities::File::CheckIfFilesMatch(const std::string& fileA, const std::string& fileB, std::string& diffInfo) 133 | { 134 | diffInfo.clear(); 135 | 136 | Utilities::File::FileReader fileReaderA{fileA}; 137 | Utilities::File::FileReader fileReaderB{fileB}; 138 | 139 | if(fileReaderA.GetFileSize() != fileReaderB.GetFileSize()) 140 | { 141 | diffInfo = Utilities::CreateString("|", "File sizes differ", fileA, fileB, fileReaderA.GetFileSize(), fileReaderB.GetFileSize()); 142 | return false; 143 | } 144 | 145 | std::size_t currentPosition{0}; 146 | const std::size_t readSize{1024}; 147 | while(currentPosition < fileReaderA.GetFileSize()) 148 | { 149 | std::size_t readAmount{readSize}; 150 | if((currentPosition + readAmount) > fileReaderA.GetFileSize()) 151 | { 152 | readAmount = fileReaderA.GetFileSize() - currentPosition; 153 | } 154 | 155 | auto bufferA{fileReaderA.ReadData(currentPosition, readAmount)}; 156 | auto bufferB{fileReaderB.ReadData(currentPosition, readAmount)}; 157 | 158 | for(std::size_t i{0}; i < readAmount; ++i) 159 | { 160 | if(bufferA[i] != bufferB[i]) 161 | { 162 | diffInfo = Utilities::CreateString(" ", "Byte values differ", fileA, fileB, "byte number", currentPosition + i); 163 | return false; 164 | } 165 | } 166 | 167 | currentPosition += readAmount; 168 | } 169 | 170 | return true; 171 | } 172 | 173 | -------------------------------------------------------------------------------- /Source/Utilities/Source/Stringify.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | std::vector Utilities::DelimitedStringToVectorOfStrings(const std::string &delimitedString, char delimiter) 34 | { 35 | std::vector elements; 36 | 37 | std::stringstream ss; 38 | ss.str(delimitedString); 39 | std::string currentElement; 40 | while(std::getline(ss, currentElement, delimiter)) 41 | { 42 | elements.push_back(currentElement); 43 | } 44 | 45 | return elements; 46 | } 47 | 48 | std::string Utilities::ConvertStringToUppercase(const std::string &inputString) 49 | { 50 | std::string uc = inputString; 51 | std::transform(uc.begin(), uc.end(), uc.begin(), ::toupper); 52 | return uc; 53 | } 54 | -------------------------------------------------------------------------------- /Source/Utilities/Source/Timer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | Utilities::Timer::Timer() { } 31 | 32 | Utilities::Timer::Timer(Action) 33 | { 34 | started_ = true; 35 | startTime_ = std::chrono::system_clock::now(); 36 | } 37 | 38 | void Utilities::Timer::Start() 39 | { 40 | if(started_) 41 | { 42 | Utilities::ThrowException("Attempting to start timer when already started"); 43 | } 44 | startTime_ = std::chrono::system_clock::now(); 45 | started_ = true; 46 | } 47 | 48 | double Utilities::Timer::Stop() 49 | { 50 | if(!started_) 51 | { 52 | Utilities::ThrowException("Attempting to stop timer when not started"); 53 | } 54 | std::chrono::duration elapsed = std::chrono::system_clock::now() - startTime_; 55 | started_ = false; 56 | return elapsed.count(); 57 | } 58 | -------------------------------------------------------------------------------- /Source/Utilities/Stringify.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | 32 | //! @file Stringify.h 33 | //! @brief Functions designed to make string concatenation and conversion easy. 34 | 35 | namespace Utilities { 36 | 37 | //! @brief Stringify a fundamental type object. 38 | //! @param t The object to stringify. 39 | template 40 | typename std::enable_if::value, std::string>::type Stringify(const T& t) 41 | { 42 | return std::to_string(t); 43 | } 44 | 45 | //! @brief Stringify a non-fundamental type object. 46 | //! @param t The object to stringify. 47 | template 48 | typename std::enable_if::value, std::string>::type Stringify(const T& t) 49 | { 50 | return std::string(t); 51 | } 52 | 53 | //! @brief The terminating variadic case for CreateString. 54 | //! @param delimiter Delimiter used when concatenating given strings. 55 | //! @param param The data to concatenate. 56 | template 57 | std::string CreateString(const std::string& delimiter, T param) 58 | { 59 | return Utilities::Stringify(param); 60 | } 61 | 62 | //! @brief Allows for concatenating a number of items together into a delimted string. 63 | //! @param delimiter Delimiter used when concatenating given strings. 64 | //! @param head The first parameter to concatenate. 65 | //! @param tail The variable remaining parameters to concatenate. 66 | template 67 | std::string CreateString(const std::string& delimiter, T head, Tail... tail) 68 | { 69 | return Utilities::Stringify(head) + Utilities::Stringify(delimiter) + Utilities::CreateString(delimiter, tail...); 70 | } 71 | 72 | //! @brief Convert a string of delimted values into separate values in a std::vector. 73 | //! @param delimitedString The string to parse. 74 | //! @param delimiter Delimiter identifier for parsing the string. 75 | std::vector DelimitedStringToVectorOfStrings(const std::string &delimitedString, char delimiter); 76 | 77 | //! @brief Convert a string to all uppercase. 78 | //! @param inputString The string to convert to uppercase. 79 | std::string ConvertStringToUppercase(const std::string &inputString); 80 | 81 | } // End of namespace 82 | -------------------------------------------------------------------------------- /Source/Utilities/Timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | //! @file Timer.h 32 | //! @brief Class to allow for doing timing between lines of code. 33 | 34 | namespace Utilities { 35 | 36 | //! Class to allow for doing timing between lines of code. 37 | 38 | class Timer { 39 | 40 | public: 41 | //! Action to pass to the consutrctor to start timing now immediately. 42 | enum class Action { START_NOW }; 43 | 44 | //! Instantiate the Timer. 45 | Timer(); 46 | 47 | //! Instantiate the Timer, starting the timer running at the time of instantiation. 48 | Timer(Action); 49 | 50 | //! Start the timer. 51 | void Start(); 52 | 53 | //! Stop the timer, returning the number of seconds accumulated. 54 | double Stop(); 55 | 56 | private: 57 | std::chrono::time_point startTime_; 58 | std::atomic_bool started_{false}; 59 | }; 60 | 61 | } // End of namespace 62 | -------------------------------------------------------------------------------- /Source/Utilities/UT/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}/Externals/googletest/googletest/include") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp) 3 | add_executable(Utilities-UT ${source_files}) 4 | target_link_libraries(Utilities-UT gtest gtest_main Utilities) 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | file(GLOB TEST_FILES ${CMAKE_CURRENT_SOURCE_DIR}/TestFiles/*) 7 | file(COPY ${TEST_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 8 | add_custom_command(TARGET Utilities-UT POST_BUILD COMMAND Utilities-UT --output-on-failure) 9 | 10 | set_target_properties(Utilities-UT PROPERTIES FOLDER Libs) 11 | 12 | -------------------------------------------------------------------------------- /Source/Utilities/UT/Exception-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | void TestThrow() 31 | { 32 | Utilities::Exception theException("Throwing"); 33 | throw theException; 34 | } 35 | 36 | void TestThrowWithFilename() 37 | { 38 | Utilities::Exception theException("Throwing", "SomeFilename"); 39 | throw theException; 40 | } 41 | 42 | void TestThrowWithFilenameAndLineNumber() 43 | { 44 | Utilities::Exception theException("Throwing", "SomeFilename", 99); 45 | throw theException; 46 | } 47 | 48 | TEST(UtilitiesException, TestThrow) 49 | { 50 | // Since Utilities::Exception inherits from std::exception, it's considered both types 51 | ASSERT_THROW(TestThrow(), std::exception); 52 | ASSERT_THROW(TestThrow(), Utilities::Exception); 53 | } 54 | 55 | TEST(UtilitiesException, CatchWhat) 56 | { 57 | try 58 | { 59 | TestThrow(); 60 | } 61 | catch(std::exception& theException) 62 | { 63 | EXPECT_STREQ("Throwing", theException.what()); 64 | } 65 | 66 | try 67 | { 68 | TestThrow(); 69 | } 70 | catch(Utilities::Exception& theException) 71 | { 72 | EXPECT_STREQ("Throwing", theException.what()); 73 | } 74 | } 75 | 76 | TEST(UtilitiesException, TestThrowWithFilename) 77 | { 78 | try 79 | { 80 | TestThrowWithFilename(); 81 | } 82 | catch(std::exception& theException) 83 | { 84 | EXPECT_STREQ("Throwing SomeFilename:0", theException.what()); 85 | } 86 | 87 | try 88 | { 89 | TestThrowWithFilename(); 90 | } 91 | catch(Utilities::Exception& theException) 92 | { 93 | EXPECT_STREQ("Throwing SomeFilename:0", theException.what()); 94 | } 95 | } 96 | 97 | TEST(UtilitiesException, TestThrowWithFilenameAndLineNumber) 98 | { 99 | try 100 | { 101 | TestThrowWithFilenameAndLineNumber(); 102 | } 103 | catch(std::exception& theException) 104 | { 105 | EXPECT_STREQ("Throwing SomeFilename:99", theException.what()); 106 | } 107 | 108 | try 109 | { 110 | TestThrowWithFilenameAndLineNumber(); 111 | } 112 | catch(Utilities::Exception& theException) 113 | { 114 | EXPECT_STREQ("Throwing SomeFilename:99", theException.what()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Source/Utilities/UT/File-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | ////////////////////////////////////////////////////////////////////////////////// 31 | // GetDirname Tests 32 | 33 | TEST(FileUtilities, GetDirnameTestNoPath) 34 | { 35 | EXPECT_EQ("", Utilities::File::GetDirname("nada")); 36 | } 37 | 38 | TEST(FileUtilities, GetDirnameTestTypicalNixPath) 39 | { 40 | EXPECT_EQ("/usr/share/applications", Utilities::File::GetDirname("/usr/share/applications/nada")); 41 | } 42 | 43 | TEST(FileUtilities, GetDirnameTestTypicalWindowsPaths) 44 | { 45 | EXPECT_EQ("C:\\This\\Is\\A\\Path", Utilities::File::GetDirname("C:\\This\\Is\\A\\Path\\nada.exe")); 46 | } 47 | 48 | TEST(FileUtilities, GetDirnameTestPathMixture) 49 | { 50 | EXPECT_EQ("C:\\This\\Is/A/Path", Utilities::File::GetDirname("C:\\This\\Is/A/Path/nada.exe")); 51 | } 52 | 53 | ////////////////////////////////////////////////////////////////////////////////// 54 | // GetBasename Tests 55 | 56 | TEST(FileUtilities, GetBasenameTestNoPath) 57 | { 58 | EXPECT_EQ("nada", Utilities::File::GetBasename("nada")); 59 | } 60 | 61 | TEST(FileUtilities, GetBasenameTestTypicalNixPath) 62 | { 63 | auto x = Utilities::File::GetBasename("/usr/share/applications/nada"); 64 | EXPECT_EQ("nada", x); 65 | //EXPECT_EQ("nada", Utilities::File::GetBasename("/usr/share/applications/nada")); 66 | } 67 | 68 | TEST(FileUtilities, GetBasenameTestTypicalWindowsPaths) 69 | { 70 | EXPECT_EQ("nada.exe", Utilities::File::GetBasename("C:\\This\\Is\\A\\Path\\nada.exe")); 71 | } 72 | 73 | TEST(FileUtilities, GetBasenameTestPathMixture) 74 | { 75 | EXPECT_EQ("nada.exe", Utilities::File::GetBasename("C:\\This\\Is/A/Path/nada.exe")); 76 | } 77 | 78 | ////////////////////////////////////////////////////////////////////////////////// 79 | // RemoveExtension Tests 80 | 81 | TEST(FileUtilities, TestTypicalRemoveExtension) 82 | { 83 | std::string myFilename("MyFilename.txt"); 84 | Utilities::File::RemoveExtension(myFilename); 85 | EXPECT_EQ("MyFilename", myFilename); 86 | } 87 | 88 | TEST(FileUtilities, TestTypicalRemoveExtensionWithWindowsPath) 89 | { 90 | std::string myFilename("C:\\Some\\Path\\To\\The\\File\\MyFilename.txt"); 91 | Utilities::File::RemoveExtension(myFilename); 92 | EXPECT_EQ("C:\\Some\\Path\\To\\The\\File\\MyFilename", myFilename); 93 | } 94 | 95 | TEST(FileUtilities, TestTypicalRemoveExtensionWithUnixPath) 96 | { 97 | std::string myFilename("/usr/local/hello/world/MyFilename.txt"); 98 | Utilities::File::RemoveExtension(myFilename); 99 | EXPECT_EQ("/usr/local/hello/world/MyFilename", myFilename); 100 | } 101 | 102 | TEST(FileUtilities, TestRemoveExtensionWithNoExtension) 103 | { 104 | std::string myFilename("MyFilename"); 105 | Utilities::File::RemoveExtension(myFilename); 106 | EXPECT_EQ("MyFilename", myFilename); 107 | } 108 | 109 | TEST(FileUtilities, TestRemoveExtensionWithNoExtensionAndWindowsPath) 110 | { 111 | std::string myFilename("C:\\Some\\Path\\To\\The\\File\\MyFilename"); 112 | Utilities::File::RemoveExtension(myFilename); 113 | EXPECT_EQ("C:\\Some\\Path\\To\\The\\File\\MyFilename", myFilename); 114 | } 115 | 116 | TEST(FileUtilities, TestRemoveExtensionWithNoExtensionAndUnixPath) 117 | { 118 | std::string myFilename("/usr/local/hello/world/MyFilename"); 119 | Utilities::File::RemoveExtension(myFilename); 120 | EXPECT_EQ("/usr/local/hello/world/MyFilename", myFilename); 121 | } 122 | 123 | ////////////////////////////////////////////////////////////////////////////////// 124 | // FileReader Tests 125 | 126 | TEST(FileUtilities, TestNonExistantFile) 127 | { 128 | EXPECT_THROW(Utilities::File::FileReader fileReader("NonExistantFile"), std::exception); 129 | } 130 | 131 | TEST(FileUtilities, TestFileSize) 132 | { 133 | Utilities::File::FileReader fileReaderA("TestFileA.txt"); 134 | EXPECT_EQ(19, fileReaderA.GetFileSize()); 135 | 136 | Utilities::File::FileReader fileReaderB("TestFileB.txt"); 137 | EXPECT_EQ(60, fileReaderB.GetFileSize()); 138 | } 139 | 140 | TEST(FileUtilities, TestFileReading) 141 | { 142 | Utilities::File::FileReader fileReader("TestFileA.txt"); 143 | 144 | auto dataRead{fileReader.ReadData(0, 6)}; 145 | 146 | EXPECT_EQ(6, dataRead.size()); 147 | EXPECT_EQ('T', dataRead[0]); 148 | EXPECT_EQ('h', dataRead[1]); 149 | EXPECT_EQ('i', dataRead[2]); 150 | EXPECT_EQ('s', dataRead[3]); 151 | EXPECT_EQ(' ', dataRead[4]); 152 | EXPECT_EQ('i', dataRead[5]); 153 | } 154 | 155 | TEST(FileUtilities, FileMatchingTest1) 156 | { 157 | std::string diffInfo; 158 | EXPECT_FALSE(Utilities::File::CheckIfFilesMatch("TestFileA.txt", "TestFileB.txt", diffInfo)); 159 | EXPECT_STREQ("File sizes differ|TestFileA.txt|TestFileB.txt|19|60", diffInfo.c_str()); 160 | } 161 | 162 | TEST(FileUtilities, FileMatchingTest2) 163 | { 164 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("TestFileA.txt", "TestFileA.txt")); 165 | EXPECT_TRUE(Utilities::File::CheckIfFilesMatch("TestFileB.txt", "TestFileB.txt")); 166 | } 167 | -------------------------------------------------------------------------------- /Source/Utilities/UT/Stringify-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | TEST(Stringify, TestStringify) 31 | { 32 | int intValue(3); 33 | std::string theString("The magic number is " + Utilities::Stringify(intValue)); 34 | EXPECT_STREQ("The magic number is 3", theString.c_str()); 35 | } 36 | 37 | TEST(Stringify, StringifyCharacters) 38 | { 39 | // Unfortunately, I have not yet figured out how to Stringify characters. The following line does not work: 40 | //std::string theString(Utilities::Stringify('H') + Utilities::Stringify('e')); 41 | std::string theString(Utilities::Stringify("H") + Utilities::Stringify("e")); 42 | EXPECT_STREQ("He", theString.c_str()); 43 | } 44 | 45 | TEST(Stringify, TestCreateStringWithSpaceDelimiter) 46 | { 47 | std::string theString = Utilities::CreateString(" ", "Hello", "World", 1, 2, "Three"); 48 | EXPECT_STREQ("Hello World 1 2 Three", theString.c_str()); 49 | } 50 | 51 | TEST(Stringify, TestCreateStringWithPipeDelimiter) 52 | { 53 | std::string theString = Utilities::CreateString("|", 1, "Two", 3, "Four", 5); 54 | EXPECT_STREQ("1|Two|3|Four|5", theString.c_str()); 55 | } 56 | 57 | TEST(Stringify, TestDelimitedStringToVectorOfStrings) 58 | { 59 | std::string theString("Hello-World-One-Two"); 60 | auto parsedResult = Utilities::DelimitedStringToVectorOfStrings(theString, '-'); 61 | 62 | EXPECT_EQ(4, parsedResult.size()); 63 | EXPECT_STREQ("Hello", parsedResult[0].c_str()); 64 | EXPECT_STREQ("World", parsedResult[1].c_str()); 65 | EXPECT_STREQ("One", parsedResult[2].c_str()); 66 | EXPECT_STREQ("Two", parsedResult[3].c_str()); 67 | } 68 | 69 | TEST(Stringify, TestToUpper) 70 | { 71 | std::string theString("Hello World"); 72 | auto uc = Utilities::ConvertStringToUppercase(theString); 73 | 74 | EXPECT_STREQ("HELLO WORLD", uc.c_str()); 75 | } 76 | -------------------------------------------------------------------------------- /Source/Utilities/UT/TestFiles/TestFileA.txt: -------------------------------------------------------------------------------- 1 | This is test file A -------------------------------------------------------------------------------- /Source/Utilities/UT/TestFiles/TestFileB.txt: -------------------------------------------------------------------------------- 1 | This is test file B.It's an empty file other than this text. -------------------------------------------------------------------------------- /Source/Utilities/UT/Timer-UT.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | TEST(UtilitiesTimer, TestDoubleStart1) 33 | { 34 | Utilities::Timer timer(Utilities::Timer::Action::START_NOW); 35 | ASSERT_THROW(timer.Start(), std::exception); 36 | try 37 | { 38 | timer.Start(); 39 | } 40 | catch(std::exception& theException) 41 | { 42 | EXPECT_STREQ("Attempting to start timer when already started", theException.what()); 43 | } 44 | } 45 | 46 | TEST(UtilitiesTimer, TestDoubleStart2) 47 | { 48 | Utilities::Timer timer; 49 | timer.Start(); 50 | ASSERT_THROW(timer.Start(), std::exception); 51 | try 52 | { 53 | timer.Start(); 54 | } 55 | catch(std::exception& theException) 56 | { 57 | EXPECT_STREQ("Attempting to start timer when already started", theException.what()); 58 | } 59 | } 60 | 61 | TEST(UtilitiesTimer, TestStopBeforeStart) 62 | { 63 | Utilities::Timer timer; 64 | ASSERT_THROW(timer.Stop(), std::exception); 65 | try 66 | { 67 | timer.Stop(); 68 | } 69 | catch(std::exception& theException) 70 | { 71 | EXPECT_STREQ("Attempting to stop timer when not started", theException.what()); 72 | } 73 | } 74 | 75 | TEST(UtilitiesTimer, TestDoubleStop) 76 | { 77 | Utilities::Timer timer; 78 | timer.Start(); 79 | timer.Stop(); 80 | ASSERT_THROW(timer.Stop(), std::exception); 81 | try 82 | { 83 | timer.Stop(); 84 | } 85 | catch(std::exception& theException) 86 | { 87 | EXPECT_STREQ("Attempting to stop timer when not started", theException.what()); 88 | } 89 | } 90 | 91 | TEST(UtilitiesTimer, TestTimer) 92 | { 93 | Utilities::Timer timer(Utilities::Timer::Action::START_NOW); 94 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 95 | auto time = timer.Stop(); 96 | std::cout << "UtilitiesTimer::TestTimer unit test time value: " << time << std::endl; 97 | EXPECT_TRUE(time > 0.8 && time < 1.20); // Give us some room for overhead 98 | } 99 | -------------------------------------------------------------------------------- /Source/WaveFile/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp "Source/[^.]*.h" "Source/[^.]*.cpp") 3 | add_library(WaveFile ${source_files}) 4 | 5 | target_link_libraries(WaveFile AudioData Signal Utilities) 6 | 7 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 8 | 9 | set_target_properties(WaveFile PROPERTIES FOLDER Libs) 10 | 11 | add_subdirectory(UT) 12 | -------------------------------------------------------------------------------- /Source/WaveFile/UT/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include_directories("${PROJECT_SOURCE_DIR}/External/googletest/googletest/include") 2 | file(GLOB source_files [^.]*.h [^.]*.cpp) 3 | add_executable(WaveFile-UT ${source_files}) 4 | target_link_libraries(WaveFile-UT gtest gtest_main WaveFile Signal) 5 | include(${PROJECT_SOURCE_DIR}/CMakeSupport/CMakeLists.CompilerSettings.txt) 6 | file(GLOB WAV_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.wav) 7 | file(COPY ${WAV_FILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 8 | add_custom_command(TARGET WaveFile-UT POST_BUILD COMMAND WaveFile-UT --output-on-failure) 9 | 10 | set_target_properties(WaveFile-UT PROPERTIES FOLDER Libs) 11 | 12 | -------------------------------------------------------------------------------- /Source/WaveFile/UT/TestWaveFile.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/WaveFile/UT/TestWaveFile.wav -------------------------------------------------------------------------------- /Source/WaveFile/UT/TestWaveFileMono.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/WaveFile/UT/TestWaveFileMono.wav -------------------------------------------------------------------------------- /Source/WaveFile/UT/TestWaveFileStereo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tmdarwen/AudioLib/a13efa0ce077e0d1f76e32126588a07aa815a1c8/Source/WaveFile/UT/TestWaveFileStereo.wav -------------------------------------------------------------------------------- /Source/WaveFile/WaveFileDefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | //! @file WaveFileDefines.h 30 | //! @brief Enumerations for identifying audio data streams. 31 | 32 | namespace WaveFile { 33 | 34 | static const std::size_t MONO_CHANNEL = 0; 35 | static const std::size_t LEFT_CHANNEL = 0; 36 | static const std::size_t RIGHT_CHANNEL = 1; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Source/WaveFile/WaveFileHeader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | 31 | //! @file WaveFileHeader.h 32 | //! @brief Structure representing the a typical wave file header. 33 | 34 | namespace WaveFile { 35 | 36 | //! Structure representing the a typical wave file header. 37 | 38 | //! See http://soundfile.sapp.org/doc/WaveFormat/ for more info. 39 | 40 | struct WaveFileHeader 41 | { 42 | //! Contains the letters "RIFF". 43 | unsigned char chunkID_[4]; 44 | 45 | //! Contains a numeric value of 36 + SubChunk2Size. 46 | unsigned char chunkSize_[4]; 47 | 48 | //! Contains the letters "WAVE". 49 | unsigned char format_[4]; 50 | 51 | //! Contains the letters "fmt " 52 | unsigned char subChunk1ID_[4]; 53 | 54 | //! Contains a value of 16. 55 | unsigned char subChunk1Size_[4]; 56 | 57 | //! Contains a value of 1 for PCM data. 58 | unsigned char audioFormat_[2]; 59 | 60 | //! Contains the number of channels. 61 | unsigned char channels_[2]; 62 | 63 | //! Contains the sample rate. 64 | unsigned char sampleRate_[4]; 65 | 66 | //! Contains the byte rate: sampleRate_ * channels_ * bitsPerSample_ / 8. 67 | unsigned char byteRate_[4]; 68 | 69 | //! Contains the block alignment: channels_ * bitsPerSample_ / 8. 70 | unsigned char blockAlign_[2]; 71 | 72 | //! Contains bits per sample. 73 | unsigned char bitsPerSample_[2]; 74 | 75 | //! Contains the letters "data". 76 | unsigned char subChunk2ID_[4]; 77 | 78 | //! Number of bytes of data: samples * channels_ * bitsPerSample_ / 8. 79 | unsigned char subChunk2Size_[4]; 80 | }; 81 | 82 | //! Size of the wave file header in bytes. 83 | static const std::size_t WAVE_FILE_HEADER_SIZE{44}; 84 | 85 | } 86 | -------------------------------------------------------------------------------- /Source/WaveFile/WaveFileReader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | //! @file WaveFileReader.h 37 | //! @brief Class facilitating reading a typical wave file. 38 | 39 | namespace WaveFile { 40 | 41 | //! Class facilitating reading a typical wave file. 42 | 43 | //! See http://soundfile.sapp.org/doc/WaveFormat/ for details. 44 | 45 | class WaveFileReader 46 | { 47 | public: 48 | //! Instantiate the object with the given wave filename. 49 | WaveFileReader(const std::string& filename); 50 | ~WaveFileReader(); 51 | 52 | //! Gets the wave file header. 53 | const WaveFile::WaveFileHeader& GetHeader(); 54 | 55 | //! Get the size of the wave file in bytes. 56 | std::size_t GetFileSize(); 57 | 58 | //! Get the chunk size defined in the header. 59 | std::size_t GetChunkSize(); 60 | 61 | //! Get the byte rate defined in the header. 62 | std::size_t GetByteRate(); 63 | 64 | //! Get the channels contained in the wave file. 65 | std::size_t GetChannels(); 66 | 67 | //! Get the sample rate of the audio file. 68 | std::size_t GetSampleRate(); 69 | 70 | //! Get the bit resolution for a sample. 71 | std::size_t GetBitsPerSample(); 72 | 73 | //! Get the block alignment as defined in the header. 74 | std::size_t GetBlockAlign(); 75 | 76 | //! Get the sub chunk 1 size as defined in the header. 77 | std::size_t GetSubChunk1Size(); 78 | 79 | //! Get the sub chunk 2 size as defined in the header. 80 | std::size_t GetSubChunk2Size() const; 81 | 82 | //! Get the wave file's header size. 83 | std::size_t GetWaveHeaderSize(); 84 | 85 | //! Get the number of samples in the wave file. 86 | std::size_t GetSampleCount() const; 87 | 88 | //! Get the entire audio data content of the wave file. 89 | std::vector GetAudioData(); 90 | 91 | //! Read the next "samplesToRead" in the wave file. 92 | std::vector GetAudioData(std::size_t samplesToRead); 93 | 94 | //! Read the "samplesToRead" in the wave file for a specific position. 95 | std::vector GetAudioData(std::size_t samplesStartPosition, std::size_t samplesToRead); 96 | 97 | private: 98 | void ReadHeader(); 99 | void ValidateHeader(); 100 | 101 | // Returns the byte position in the file where the audio sample data starts 102 | std::size_t GetBytePositionInFileWhereAudioSamplesStart(); 103 | 104 | // Returns the number of bytes of audio (sample) data that exist in the file 105 | std::size_t GetByteCountOfSampleData(); 106 | 107 | // Returns the position the file pointer is currently at 108 | std::size_t GetCurrentBytePositionOfFilePointer(); 109 | 110 | // Returns the position the file pointer is currently at with respect to audio samples 111 | std::size_t GetCurrentSamplePositionOfFilePointer(); 112 | 113 | // Sets inputFileStream_ to the given audio sample position 114 | void FilePointerSeekToSamplePosition(std::size_t samplesStartPosition); 115 | 116 | std::vector Read(std::size_t bytes, std::size_t filePosition=0); 117 | 118 | std::string filename_; 119 | WaveFile::WaveFileHeader header_; 120 | static const unsigned int waveHeaderSize_{sizeof(WaveFile::WaveFileHeader)}; 121 | std::size_t sampleCount_; 122 | 123 | std::ifstream inputFileStream_; 124 | void OpenFile(); 125 | }; 126 | 127 | } // End of namespace -------------------------------------------------------------------------------- /Source/WaveFile/WaveFileWriter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * AudioLib 3 | * 4 | * Copyright (c) 2017 - Terence M. Darwen - tmdarwen.com 5 | * 6 | * The MIT License 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | //! @file WaveFileWriter.h 35 | //! @brief Class facilitating writing a typical wave file. 36 | 37 | class AudioData; 38 | 39 | namespace WaveFile { 40 | 41 | //! Class facilitating reading a typical wave file. 42 | 43 | //! See http://soundfile.sapp.org/doc/WaveFormat/ for details. 44 | 45 | class WaveFileWriter 46 | { 47 | public: 48 | //! @brief Instantiate the WaveFileWriter. 49 | //! @param filename Name of the wave file to create. 50 | //! @param channels The number of channels in the wave file. 51 | //! @param sampleRate The sample rate of the wave file. 52 | //! @param bitsPerSample The bit resolution of samples in the wave file. 53 | WaveFileWriter(const std::string& filename, std::size_t channels, std::size_t sampleRate, std::size_t bitsPerSample); 54 | ~WaveFileWriter(); 55 | 56 | //! @brief Write audio to the end of the wave file. 57 | //! An AudioData object holds a "stream" of audio. Think of a "stream" as a single channel. In order to write multiple 58 | //! channels (i.e. a stereo file) place multiple AudioData objects in the vector. For stereo, we follow the normal 59 | //! convention of index zero being the left channel and index one being the right channel. 60 | void AppendAudioData(const std::vector& audioData); 61 | 62 | //! Get the current size (in samples) of the wave file. 63 | std::size_t GetSampleCount(); 64 | 65 | //! Get the name of the wave file. 66 | const std::string& GetFilename(); 67 | 68 | //! Get the number of channels in the wave file. 69 | std::size_t GetChannels(); 70 | 71 | //! Get the sample rate of the wave file. 72 | std::size_t GetSampleRate(); 73 | 74 | //! Get the bit resolution of the wave file. 75 | std::size_t GetBitsPerSample(); 76 | 77 | private: 78 | void WriteWaveFileHeader(); 79 | 80 | std::string filename_; 81 | std::size_t channels_; 82 | std::size_t sampleRate_; 83 | std::size_t bitsPerSample_; 84 | std::size_t sampleCount_; 85 | std::ofstream fileStream_; 86 | 87 | static const uint32_t SIZE_OF_SUBCHUNK1{16}; 88 | static const uint16_t PCM_AUDIO_FORMAT{1}; 89 | }; 90 | 91 | } --------------------------------------------------------------------------------