├── .gitmodules ├── dsp ├── version.h ├── ImpulseResponse.h ├── wav.h ├── Resample.h ├── ImpulseResponse.cpp ├── dsp.h ├── dsp.cpp ├── NoiseGate.h ├── RecursiveLinearFilter.h ├── RecursiveLinearFilter.cpp ├── NoiseGate.cpp ├── ResamplingContainer │ ├── Dependencies │ │ ├── WDL │ │ │ ├── ptrlist.h │ │ │ ├── wdltypes.h │ │ │ └── heapbuf.h │ │ └── LanczosResampler.h │ └── ResamplingContainer.h └── wav.cpp ├── format.sh ├── .gitignore ├── .github └── workflows │ └── build.yml ├── README.md ├── tools ├── benchmodel.cpp ├── CMakeLists.txt └── loadmodel.cpp ├── CMakeLists.txt ├── LICENSE └── .clang-format /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dependencies/eigen"] 2 | path = Dependencies/eigen 3 | url = https://gitlab.com/libeigen/eigen 4 | -------------------------------------------------------------------------------- /dsp/version.h: -------------------------------------------------------------------------------- 1 | #ifndef version_h 2 | #define version_h 3 | 4 | #define AUDIO_DSP_TOOLS_VERSION_MAJOR 0 5 | #define AUDIO_DSP_TOOLS_VERSION_MINOR 1 6 | #define AUDIO_DSP_TOOLS_VERSION_PATCH 1 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Apply project formatting (i.e. clang-format with LLVM style) 3 | # 4 | # Usage: 5 | # $ ./format.sh 6 | 7 | echo "Formatting..." 8 | 9 | git ls-files "*.h" "*.cpp" | xargs clang-format -i . 10 | 11 | echo "Formatting complete!" 12 | echo "You can stage all of the files using:" 13 | echo "" 14 | echo ' git ls-files "*.h" "*.cpp" | xargs git add' 15 | echo "" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | [workflow_dispatch, pull_request] 5 | 6 | env: 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | build-ubuntu: 11 | name: Build Ubuntu 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3.3.0 15 | with: 16 | submodules: recursive 17 | 18 | - name: Build Tools 19 | working-directory: ${{github.workspace}}/build 20 | env: 21 | CXX: clang++ 22 | run: | 23 | cmake .. -DCMAKE_BUILD_TYPE=$BUILD_TYPE 24 | cmake --build . --config $BUILD_TYPE -j4 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AudioDSPTools 2 | A library of basic DSP things. 3 | 4 | ## Sharp edges 5 | This library uses [Eigen](http://eigen.tuxfamily.org) to do some linear algebra. If they are stored as members of objects, there is a risk with certain compilers and compiler optimizations that the memory associated with eigen objects is not aligned properly. This can be worked around by providing two preprocessor macros: `EIGEN_MAX_ALIGN_BYTES 0` and `EIGEN_DONT_VECTORIZE`, though this will probably harm performance. See [Structs Having Eigen Members](http://eigen.tuxfamily.org/dox-3.2/group__TopicStructHavingEigenMembers.html) and [NeuralAmpModelerCore Issue 67](https://github.com/sdatkinson/NeuralAmpModelerCore/issues/67) for more information. 6 | -------------------------------------------------------------------------------- /tools/benchmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: benchmodel.cpp 3 | * Created Date: Wednesday August 16th 2023 4 | * Author: Steven Atkinson (steven@atkinson.mn) 5 | */ 6 | 7 | // Test to benchmark model loading and running 8 | 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | // #include "NAM/dsp.h" 15 | 16 | // using std::chrono::duration; 17 | // using std::chrono::duration_cast; 18 | // using std::chrono::high_resolution_clock; 19 | // using std::chrono::milliseconds; 20 | 21 | // #define AUDIO_BUFFER_SIZE 64 22 | 23 | // double buffer[AUDIO_BUFFER_SIZE]; 24 | // double* buffers[1]; 25 | 26 | int main(int argc, char* argv[]) 27 | { 28 | std::cout << "FIXME!" << std::endl; 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(NAM VERSION 0.0.1) 4 | 5 | set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") 6 | 7 | set(CMAKE_CXX_STANDARD 20) 8 | set(CMAKE_CXX_STANDARD_REQUIRED OFF) 9 | set(CMAKE_CXX_EXTENSIONS OFF) 10 | 11 | if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") 12 | # Use libc++ on macOS, system default on Linux 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 14 | include_directories(SYSTEM /usr/local/include) 15 | elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") 16 | link_libraries(stdc++fs) 17 | elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") 18 | add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN) 19 | else() 20 | message(FATAL_ERROR "Unrecognized Platform!") 21 | endif() 22 | 23 | set(NAM_DEPS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Dependencies") 24 | 25 | add_subdirectory(tools) 26 | 27 | #file(MAKE_DIRECTORY build/tools) 28 | 29 | #add_custom_target(copy_tools ALL 30 | # ${CMAKE_COMMAND} -E copy "$" tools/ 31 | # DEPENDS tools 32 | #) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Steven Atkinson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE AUDIO_DSP_TOOLS_SOURCES ../dsp/*.cpp ../dsp*.h) 2 | 3 | set(TOOLS benchmodel) 4 | 5 | add_custom_target(tools ALL 6 | DEPENDS ${TOOLS}) 7 | 8 | include_directories(tools ..) 9 | include_directories(tools ${NAM_DEPS_PATH}/eigen) 10 | include_directories(tools ${NAM_DEPS_PATH}/nlohmann) 11 | 12 | add_executable(loadmodel loadmodel.cpp ${AUDIO_DSP_TOOLS_SOURCES}) 13 | add_executable(benchmodel benchmodel.cpp ${AUDIO_DSP_TOOLS_SOURCES}) 14 | 15 | source_group(NAM ${CMAKE_CURRENT_SOURCE_DIR} FILES ${AUDIO_DSP_TOOLS_SOURCES}) 16 | 17 | target_compile_features(${TOOLS} PUBLIC cxx_std_17) 18 | 19 | set_target_properties(${TOOLS} 20 | PROPERTIES 21 | CXX_VISIBILITY_PRESET hidden 22 | INTERPROCEDURAL_OPTIMIZATION TRUE 23 | PREFIX "" 24 | ) 25 | 26 | if (CMAKE_SYSTEM_NAME STREQUAL "Windows") 27 | target_compile_definitions(${TOOLS} PRIVATE NOMINMAX WIN32_LEAN_AND_MEAN) 28 | endif() 29 | 30 | if (MSVC) 31 | target_compile_options(${TOOLS} PRIVATE 32 | "$<$:/W4>" 33 | "$<$:/O2>" 34 | ) 35 | else() 36 | target_compile_options(${TOOLS} PRIVATE 37 | -Wall -Wextra -Wpedantic -Wstrict-aliasing -Wunreachable-code -Weffc++ -Wno-unused-parameter 38 | "$<$:-Og;-ggdb;-Werror>" 39 | "$<$:-Ofast>" 40 | ) 41 | endif() -------------------------------------------------------------------------------- /dsp/ImpulseResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImpulseResponse.h 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 12/30/22. 6 | // 7 | // Impulse response processing 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include "dsp.h" 16 | #include "wav.h" 17 | 18 | namespace dsp 19 | { 20 | class ImpulseResponse : public History 21 | { 22 | public: 23 | struct IRData; 24 | ImpulseResponse(const char* fileName, const double sampleRate); 25 | ImpulseResponse(const IRData& irData, const double sampleRate); 26 | double** Process(double** inputs, const size_t numChannels, const size_t numFrames) override; 27 | IRData GetData(); 28 | double GetSampleRate() const { return mSampleRate; }; 29 | // TODO states for the IR class 30 | dsp::wav::LoadReturnCode GetWavState() const { return this->mWavState; }; 31 | 32 | private: 33 | // Set the weights, given that the plugin is running at the provided sample 34 | // rate. 35 | void _SetWeights(); 36 | 37 | // State of audio 38 | dsp::wav::LoadReturnCode mWavState; 39 | // Keep a copy of the raw audio that was loaded so that it can be resampled 40 | std::vector mRawAudio; 41 | double mRawAudioSampleRate; 42 | // Resampled to the required sample rate. 43 | std::vector mResampled; 44 | double mSampleRate; 45 | 46 | const size_t mMaxLength = 8192; 47 | // The weights 48 | Eigen::VectorXf mWeight; 49 | }; 50 | 51 | struct dsp::ImpulseResponse::IRData 52 | { 53 | std::vector mRawAudio; 54 | double mRawAudioSampleRate; 55 | }; 56 | 57 | }; // namespace dsp 58 | -------------------------------------------------------------------------------- /dsp/wav.h: -------------------------------------------------------------------------------- 1 | // 2 | // wav.h 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 12/31/22. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace dsp 15 | { 16 | namespace wav 17 | { 18 | enum class LoadReturnCode 19 | { 20 | SUCCESS = 0, 21 | ERROR_OPENING, 22 | ERROR_NOT_RIFF, 23 | ERROR_NOT_WAVE, 24 | ERROR_MISSING_FMT, 25 | ERROR_INVALID_FILE, 26 | ERROR_UNSUPPORTED_FORMAT_ALAW, 27 | ERROR_UNSUPPORTED_FORMAT_MULAW, 28 | ERROR_UNSUPPORTED_FORMAT_OTHER, 29 | ERROR_UNSUPPORTED_BITS_PER_SAMPLE, 30 | ERROR_NOT_MONO, 31 | ERROR_OTHER 32 | }; 33 | 34 | // Get a string describing the error 35 | std::string GetMsgForLoadReturnCode(LoadReturnCode rc); 36 | 37 | // Load a WAV file into a provided array of doubles, 38 | // And note the sample rate. 39 | // 40 | // Returns: as per return cases above 41 | LoadReturnCode Load(const char* fileName, std::vector& audio, double& sampleRate); 42 | 43 | // Load samples, 16-bit 44 | void _LoadSamples16(std::ifstream& wavFile, const int chunkSize, std::vector& samples); 45 | // Load samples, 24-bit 46 | void _LoadSamples24(std::ifstream& wavFile, const int chunkSize, std::vector& samples); 47 | // Load samples, 32-bit 48 | void _LoadSamples32FloatingPoint(std::ifstream& wavFile, const int chunkSize, std::vector& samples); 49 | // Load samples, 32-bit fixed point 50 | void _LoadSamples32FixedPoint(std::ifstream& wavFile, const int chunkSize, std::vector& samples); 51 | 52 | // Read in a 24-bit sample and convert it to an int 53 | int _ReadSigned24BitInt(std::ifstream& stream); 54 | }; // namespace wav 55 | }; // namespace dsp 56 | -------------------------------------------------------------------------------- /tools/loadmodel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: loadmodel.cpp 3 | * Created Date: Wednesday August 16th 2023 4 | * Author: Steven Atkinson (steven@atkinson.mn) 5 | */ 6 | 7 | // Test loading a model 8 | // FIXME let's get some proper testing going! 9 | 10 | #include 11 | #include "dsp/dsp.h" 12 | 13 | class Dummy : public dsp::DSP 14 | { 15 | public: 16 | ~Dummy() { _DeallocateOutputPointers(); } 17 | DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) override 18 | { 19 | if (numChannels > _GetNumChannels()) 20 | { 21 | throw std::runtime_error("Asked to process too many channels!\n"); 22 | } 23 | if (numFrames > _GetNumFrames()) 24 | { 25 | throw std::runtime_error("Asked to process too many samples!\n"); 26 | } 27 | for (int c = 0; c < numChannels; c++) 28 | { 29 | for (int f = 0; f < numFrames; f++) 30 | { 31 | mOutputs[c][f] = inputs[c][f]; 32 | } 33 | } 34 | return _GetPointers(); 35 | }; 36 | void PrepareForAudio(const int maxChannels, const int maxFrames) { _PrepareBuffers(maxChannels, maxFrames); }; 37 | }; 38 | 39 | int main(int argc, char* argv[]) 40 | { 41 | Dummy dummy; 42 | const int maxChannels = 2; 43 | const int maxSamples = 128; 44 | dummy.PrepareForAudio(maxChannels, maxSamples); 45 | DSP_SAMPLE** inputs = new DSP_SAMPLE*[maxChannels]; 46 | for (int c = 0; c < maxChannels; c++) 47 | { 48 | inputs[c] = new DSP_SAMPLE[maxSamples]; 49 | } 50 | 51 | const int numBuffers = 2; 52 | for (int b = 0; b < numBuffers; b++) 53 | { 54 | // Fill the input buffer 55 | for (int c = 0; c < maxChannels; c++) 56 | { 57 | for (int s = 0; s < maxSamples; s++) 58 | { 59 | inputs[c][s] = 0.01 * c + 0.02 * s / (DSP_SAMPLE)maxSamples; 60 | } 61 | } 62 | dummy.Process(inputs, maxChannels, maxSamples); 63 | // Yay 64 | } 65 | 66 | // Deallocate 67 | for (int c = 0; c < maxChannels; c++) 68 | delete[] inputs[c]; 69 | delete[] inputs; 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Right 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: true 11 | AllowShortFunctionsOnASingleLine: Inline 12 | AllowShortIfStatementsOnASingleLine: false 13 | AllowShortLoopsOnASingleLine: false 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: true 18 | BinPackArguments: true 19 | BinPackParameters: true 20 | BraceWrapping: 21 | AfterCaseLabel: true 22 | AfterClass: true 23 | AfterControlStatement: true 24 | AfterEnum: true 25 | AfterFunction: true 26 | AfterNamespace: true 27 | AfterStruct: true 28 | AfterUnion: true 29 | BeforeCatch: true 30 | BeforeElse: true 31 | IndentBraces: false 32 | SplitEmptyFunction: true 33 | SplitEmptyRecord: true 34 | SplitEmptyNamespace: true 35 | BreakBeforeBinaryOperators: NonAssignment 36 | BreakBeforeBraces: Custom 37 | BreakBeforeInheritanceComma: false 38 | BreakBeforeTernaryOperators: true 39 | BreakConstructorInitializers: BeforeComma 40 | BreakStringLiterals: true 41 | ColumnLimit: 120 42 | CompactNamespaces: false 43 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 44 | ConstructorInitializerIndentWidth: 0 45 | ContinuationIndentWidth: 2 46 | Cpp11BracedListStyle: true 47 | DerivePointerAlignment: false 48 | FixNamespaceComments: true 49 | IndentCaseLabels: true 50 | IndentPPDirectives: BeforeHash 51 | IndentWidth: 2 52 | IndentWrappedFunctionNames: false 53 | KeepEmptyLinesAtTheStartOfBlocks: true 54 | MaxEmptyLinesToKeep: 2 55 | NamespaceIndentation: None 56 | ObjCBinPackProtocolList: Auto 57 | ObjCBlockIndentWidth: 2 58 | ObjCBreakBeforeNestedBlockParam: true 59 | ObjCSpaceAfterProperty: false 60 | ObjCSpaceBeforeProtocolList: true 61 | PenaltyBreakBeforeFirstCallParameter: 0 62 | PenaltyReturnTypeOnItsOwnLine: 1000 63 | PointerAlignment: Left 64 | ReflowComments: true 65 | SortIncludes: false 66 | SortUsingDeclarations: true 67 | SpaceAfterCStyleCast: false 68 | SpaceAfterTemplateKeyword: true 69 | SpaceBeforeAssignmentOperators: true 70 | SpaceBeforeParens: ControlStatements 71 | SpaceInEmptyParentheses: false 72 | SpacesBeforeTrailingComments: 1 73 | SpacesInAngles: false 74 | SpacesInContainerLiterals: true 75 | SpacesInCStyleCastParentheses: false 76 | SpacesInParentheses: false 77 | SpacesInSquareBrackets: false 78 | Standard: Cpp11 79 | UseTab: Never 80 | -------------------------------------------------------------------------------- /dsp/Resample.h: -------------------------------------------------------------------------------- 1 | // 2 | // Resample.h 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 1/2/23. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | namespace dsp 15 | { 16 | // Resample a provided vector in inputs to outputs. 17 | // Creates an array of the required length to fill all points from the SECOND 18 | // input to the SECOND-TO-LAST input point, exclusive. 19 | // (Seconds bc cubic. and ew want to only interpoalte between points 2 and 20 | // 3.) 21 | // tOutputStart: location of first output point relative to the second input 22 | // point (should be >=0.0) 23 | template 24 | void ResampleCubic(const std::vector& inputs, const double originalSampleRate, const double desiredSampleRate, 25 | const double tOutputStart, std::vector& outputs); 26 | // Interpolate the 4 provided equispaced points to x in [-1,2] 27 | template 28 | T _CubicInterpolation(T p[4], T x) 29 | { 30 | return p[1] 31 | + 0.5 * x 32 | * (p[2] - p[0] 33 | + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))); 34 | }; 35 | }; // namespace dsp 36 | 37 | template 38 | void dsp::ResampleCubic(const std::vector& inputs, const double originalSampleRate, const double desiredSampleRate, 39 | const double tOutputStart, std::vector& outputs) 40 | { 41 | if (tOutputStart < 0.0) 42 | throw std::runtime_error("Starting time must be non-negative"); 43 | 44 | // Time increment for each sample in the original audio file 45 | const double timeIncrement = 1.0 / originalSampleRate; 46 | 47 | // Time increment for each sample in the resampled audio file 48 | const double resampledTimeIncrement = 1.0 / desiredSampleRate; 49 | 50 | // Current time 51 | double time = timeIncrement + tOutputStart; 52 | 53 | const double endTimeOriginal = (inputs.size() - 1) * timeIncrement; 54 | while (time < endTimeOriginal) 55 | { 56 | // Find the index of the sample in the original audio file that is just 57 | // before the current time in the resampled audio file 58 | long index = (long)std::floor(time / timeIncrement); 59 | 60 | // Calculate the time difference between the current time in the resampled 61 | // audio file and the sample in the original audio file 62 | double timeDifference = time - index * timeIncrement; 63 | 64 | // Get the four surrounding samples in the original audio file for cubic 65 | // interpolation 66 | double p[4]; 67 | p[0] = (index == 0) ? inputs[0] : inputs[index - 1]; 68 | p[1] = inputs[index]; 69 | p[2] = (index == inputs.size() - 1) ? inputs[inputs.size() - 1] : inputs[index + 1]; 70 | p[3] = (index == inputs.size() - 2) ? inputs[inputs.size() - 1] : inputs[index + 2]; 71 | 72 | // Use cubic interpolation to estimate the value of the audio signal at the 73 | // current time in the resampled audio file 74 | T resampledValue = dsp::_CubicInterpolation(p, timeDifference / timeIncrement); 75 | 76 | // Add the estimated value to the resampled audio file 77 | outputs.push_back(resampledValue); 78 | 79 | // Update the current time in the resampled audio file 80 | time += resampledTimeIncrement; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /dsp/ImpulseResponse.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ImpulseResponse.cpp 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 12/30/22. 6 | // 7 | 8 | #include "Resample.h" 9 | #include "wav.h" 10 | 11 | #include "ImpulseResponse.h" 12 | 13 | dsp::ImpulseResponse::ImpulseResponse(const char* fileName, const double sampleRate) 14 | : mWavState(dsp::wav::LoadReturnCode::ERROR_OTHER) 15 | , mSampleRate(sampleRate) 16 | { 17 | // Try to load the WAV 18 | this->mWavState = dsp::wav::Load(fileName, this->mRawAudio, this->mRawAudioSampleRate); 19 | if (this->mWavState != dsp::wav::LoadReturnCode::SUCCESS) 20 | { 21 | std::stringstream ss; 22 | ss << "Failed to load IR at " << fileName << std::endl; 23 | } 24 | else 25 | // Set the weights based on the raw audio. 26 | this->_SetWeights(); 27 | } 28 | 29 | dsp::ImpulseResponse::ImpulseResponse(const IRData& irData, const double sampleRate) 30 | : mWavState(dsp::wav::LoadReturnCode::SUCCESS) 31 | , mSampleRate(sampleRate) 32 | { 33 | this->mRawAudio = irData.mRawAudio; 34 | this->mRawAudioSampleRate = irData.mRawAudioSampleRate; 35 | this->_SetWeights(); 36 | } 37 | 38 | double** dsp::ImpulseResponse::Process(double** inputs, const size_t numChannels, const size_t numFrames) 39 | { 40 | this->_PrepareBuffers(numChannels, numFrames); 41 | this->_UpdateHistory(inputs, numChannels, numFrames); 42 | 43 | for (size_t i = 0, j = this->mHistoryIndex - this->mHistoryRequired; i < numFrames; i++, j++) 44 | { 45 | auto input = Eigen::Map(&this->mHistory[j], this->mHistoryRequired + 1); 46 | this->mOutputs[0][i] = (double)this->mWeight.dot(input); 47 | } 48 | // Copy out for more-than-mono. 49 | for (size_t c = 1; c < numChannels; c++) 50 | for (size_t i = 0; i < numFrames; i++) 51 | this->mOutputs[c][i] = this->mOutputs[0][i]; 52 | 53 | this->_AdvanceHistoryIndex(numFrames); 54 | return this->_GetPointers(); 55 | } 56 | 57 | void dsp::ImpulseResponse::_SetWeights() 58 | { 59 | if (this->mRawAudioSampleRate == mSampleRate) 60 | { 61 | this->mResampled.resize(this->mRawAudio.size()); 62 | memcpy(this->mResampled.data(), this->mRawAudio.data(), sizeof(float) * this->mResampled.size()); 63 | } 64 | else 65 | { 66 | // Cubic resampling 67 | std::vector padded; 68 | padded.resize(this->mRawAudio.size() + 2); 69 | padded[0] = 0.0f; 70 | padded[padded.size() - 1] = 0.0f; 71 | memcpy(padded.data() + 1, this->mRawAudio.data(), sizeof(float) * this->mRawAudio.size()); 72 | dsp::ResampleCubic(padded, this->mRawAudioSampleRate, mSampleRate, 0.0, this->mResampled); 73 | } 74 | // Simple implementation w/ no resample... 75 | const size_t irLength = std::min(this->mResampled.size(), this->mMaxLength); 76 | this->mWeight.resize(irLength); 77 | // Gain reduction. 78 | // https://github.com/sdatkinson/NeuralAmpModelerPlugin/issues/100#issuecomment-1455273839 79 | // Add sample rate-dependence 80 | const float gain = pow(10, -18 * 0.05) * 48000 / mSampleRate; 81 | for (size_t i = 0, j = irLength - 1; i < irLength; i++, j--) 82 | this->mWeight[j] = gain * this->mResampled[i]; 83 | this->mHistoryRequired = irLength - 1; 84 | } 85 | 86 | dsp::ImpulseResponse::IRData dsp::ImpulseResponse::GetData() 87 | { 88 | IRData irData; 89 | irData.mRawAudio = this->mRawAudio; 90 | irData.mRawAudioSampleRate = this->mRawAudioSampleRate; 91 | return irData; 92 | } 93 | -------------------------------------------------------------------------------- /dsp/dsp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #ifdef DSP_SAMPLE_FLOAT 11 | #define DSP_SAMPLE float 12 | #else 13 | #define DSP_SAMPLE double 14 | #endif 15 | 16 | // Version 2 DSP abstraction ================================================== 17 | 18 | namespace dsp 19 | { 20 | class Params 21 | { 22 | }; 23 | 24 | class DSP 25 | { 26 | public: 27 | DSP(); 28 | virtual ~DSP(); 29 | // The main interface for processing audio. 30 | // The incoming audio is given as a raw pointer-to-pointers. 31 | // The indexing is [channel][frame]. 32 | // The output shall be a pointer-to-pointers of matching size. 33 | // This object instance will own the data referenced by the pointers and be 34 | // responsible for its allocation and deallocation. 35 | virtual DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) = 0; 36 | // Update the parameters of the DSP object according to the provided params. 37 | // Not declaring a pure virtual bc there's no concrete definition that can 38 | // use Params. 39 | // But, use this name :) 40 | // virtual void SetParams(Params* params) = 0; 41 | 42 | protected: 43 | // Methods 44 | 45 | // Allocate mOutputPointers. 46 | // Assumes it's already null (Use _DeallocateOutputPointers()). 47 | void _AllocateOutputPointers(const size_t numChannels); 48 | // Ensure mOutputPointers is freed. 49 | void _DeallocateOutputPointers(); 50 | 51 | size_t _GetNumChannels() const { return this->mOutputs.size(); }; 52 | size_t _GetNumFrames() const { return this->_GetNumChannels() > 0 ? this->mOutputs[0].size() : 0; } 53 | // Return a pointer-to-pointers for the DSP's output buffers (all channels) 54 | // Assumes that ._PrepareBuffers() was called recently enough. 55 | DSP_SAMPLE** _GetPointers(); 56 | // Resize mOutputs to (numChannels, numFrames) and ensure that the raw 57 | // pointers are also keeping up. 58 | virtual void _PrepareBuffers(const size_t numChannels, const size_t numFrames); 59 | // Resize the pointer-to-pointers for the vector-of-vectors. 60 | void _ResizePointers(const size_t numChannels); 61 | 62 | // Attributes 63 | 64 | // The output array into which the DSP module's calculations will be written. 65 | // Pointers to this member's data will be returned by .Process(), and std 66 | // Will ensure proper allocation. 67 | std::vector> mOutputs; 68 | // A pointer to pointers of which copies will be given out as the output of 69 | // .Process(). This object will ensure proper allocation and deallocation of 70 | // the first level; The second level points to .data() from mOutputs. 71 | DSP_SAMPLE** mOutputPointers; 72 | size_t mOutputPointersSize; 73 | }; 74 | 75 | // A class where a longer buffer of history is needed to correctly calculate 76 | // the DSP algorithm (e.g. algorithms involving convolution). 77 | // 78 | // Hacky stuff: 79 | // * Mono 80 | // * Single-precision floats. 81 | class History : public DSP 82 | { 83 | public: 84 | History(); 85 | 86 | protected: 87 | // Called at the end of the DSP, advance the hsitory index to the next open 88 | // spot. Does not ensure that it's at a valid address. 89 | void _AdvanceHistoryIndex(const size_t bufferSize); 90 | // Drop the new samples into the history array. 91 | // Manages history array size 92 | void _UpdateHistory(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames); 93 | 94 | // The history array that's used for DSP calculations. 95 | std::vector mHistory; 96 | // How many samples previous are required. 97 | // Zero means that no history is required--only the current sample. 98 | size_t mHistoryRequired; 99 | // Location of the first sample in the current buffer. 100 | // Shall always be in the range [mHistoryRequired, mHistory.size()). 101 | size_t mHistoryIndex; 102 | 103 | private: 104 | // Make sure that the history array is long enough. 105 | void _EnsureHistorySize(const size_t bufferSize); 106 | // Copy the end of the history back to the fron and reset mHistoryIndex 107 | void _RewindHistory(); 108 | }; 109 | }; // namespace dsp 110 | -------------------------------------------------------------------------------- /dsp/dsp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: dsp.cpp 3 | * Created Date: March 17, 2023 4 | * Author: Steven Atkinson (steven@atkinson.mn) 5 | */ 6 | 7 | #include // std::max_element 8 | #include 9 | #include // pow, tanh, expf 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "dsp.h" 17 | 18 | dsp::DSP::DSP() 19 | : mOutputPointers(nullptr) 20 | , mOutputPointersSize(0) 21 | { 22 | } 23 | 24 | dsp::DSP::~DSP() 25 | { 26 | this->_DeallocateOutputPointers(); 27 | }; 28 | 29 | void dsp::DSP::_AllocateOutputPointers(const size_t numChannels) 30 | { 31 | if (this->mOutputPointers != nullptr) 32 | throw std::runtime_error("Tried to re-allocate over non-null mOutputPointers"); 33 | this->mOutputPointers = new DSP_SAMPLE*[numChannels]; 34 | if (this->mOutputPointers == nullptr) 35 | throw std::runtime_error("Failed to allocate pointer to output buffer!\n"); 36 | this->mOutputPointersSize = numChannels; 37 | } 38 | 39 | void dsp::DSP::_DeallocateOutputPointers() 40 | { 41 | if (this->mOutputPointers != nullptr) 42 | { 43 | delete[] this->mOutputPointers; 44 | this->mOutputPointers = nullptr; 45 | } 46 | if (this->mOutputPointers != nullptr) 47 | throw std::runtime_error("Failed to deallocate output pointer!"); 48 | this->mOutputPointersSize = 0; 49 | } 50 | 51 | DSP_SAMPLE** dsp::DSP::_GetPointers() 52 | { 53 | for (auto c = 0; c < this->_GetNumChannels(); c++) 54 | this->mOutputPointers[c] = this->mOutputs[c].data(); 55 | return this->mOutputPointers; 56 | } 57 | 58 | void dsp::DSP::_PrepareBuffers(const size_t numChannels, const size_t numFrames) 59 | { 60 | const size_t oldFrames = this->_GetNumFrames(); 61 | const size_t oldChannels = this->_GetNumChannels(); 62 | 63 | const bool resizeChannels = oldChannels != numChannels; 64 | const bool resizeFrames = resizeChannels || (oldFrames != numFrames); 65 | if (resizeChannels) 66 | { 67 | this->mOutputs.resize(numChannels); 68 | this->_ResizePointers(numChannels); 69 | } 70 | if (resizeFrames) 71 | for (auto c = 0; c < numChannels; c++) 72 | this->mOutputs[c].resize(numFrames); 73 | } 74 | 75 | void dsp::DSP::_ResizePointers(const size_t numChannels) 76 | { 77 | if (this->mOutputPointersSize == numChannels) 78 | return; 79 | this->_DeallocateOutputPointers(); 80 | this->_AllocateOutputPointers(numChannels); 81 | } 82 | 83 | dsp::History::History() 84 | : DSP() 85 | , mHistoryRequired(0) 86 | , mHistoryIndex(0) 87 | { 88 | } 89 | 90 | void dsp::History::_AdvanceHistoryIndex(const size_t bufferSize) 91 | { 92 | this->mHistoryIndex += bufferSize; 93 | } 94 | 95 | void dsp::History::_EnsureHistorySize(const size_t bufferSize) 96 | { 97 | const size_t repeatSize = std::max(bufferSize, this->mHistoryRequired); 98 | const size_t requiredHistoryArraySize = 10 * repeatSize; // Just so we don't spend too much time copying back. 99 | if (this->mHistory.size() < requiredHistoryArraySize) 100 | { 101 | this->mHistory.resize(requiredHistoryArraySize); 102 | std::fill(this->mHistory.begin(), this->mHistory.end(), 0.0f); 103 | this->mHistoryIndex = this->mHistoryRequired; // Guaranteed to be less than 104 | // requiredHistoryArraySize 105 | } 106 | } 107 | 108 | void dsp::History::_RewindHistory() 109 | { 110 | // TODO memcpy? Should be fine w/ history array being >2x the history length. 111 | for (size_t i = 0, j = this->mHistoryIndex - this->mHistoryRequired; i < this->mHistoryRequired; i++, j++) 112 | this->mHistory[i] = this->mHistory[j]; 113 | this->mHistoryIndex = this->mHistoryRequired; 114 | } 115 | 116 | void dsp::History::_UpdateHistory(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) 117 | { 118 | this->_EnsureHistorySize(numFrames); 119 | if (numChannels < 1) 120 | throw std::runtime_error("Zero channels?"); 121 | if (this->mHistoryIndex + numFrames >= this->mHistory.size()) 122 | this->_RewindHistory(); 123 | // Grabs channel 1, drops hannel 2. 124 | for (size_t i = 0, j = this->mHistoryIndex; i < numFrames; i++, j++) 125 | // Convert down to float here. 126 | this->mHistory[j] = (float)inputs[0][i]; 127 | } 128 | -------------------------------------------------------------------------------- /dsp/NoiseGate.h: -------------------------------------------------------------------------------- 1 | // 2 | // NoiseGate.h 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 2/5/23. 6 | // 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include "dsp.h" 15 | 16 | namespace dsp 17 | { 18 | namespace noise_gate 19 | { 20 | // Disclaimer: No one told me how noise gates work. I'm just going to try 21 | // and have fun with it and see if I like what I get! :D 22 | 23 | // "The noise floor." The loudness of anything quieter than this is bumped 24 | // up to as if it were this loud for gating purposes (i.e. computing gain 25 | // reduction). 26 | const double MINIMUM_LOUDNESS_DB = -120.0; 27 | const double MINIMUM_LOUDNESS_POWER = pow(10.0, MINIMUM_LOUDNESS_DB / 10.0); 28 | 29 | // Parts 2: The gain module. 30 | // This applies the gain reduction taht was determined by the trigger. 31 | // It's declared first so that the trigger can define listeners without a 32 | // forward declaration. 33 | 34 | // The class that applies the gain reductions calculated by a trigger instance. 35 | class Gain : public DSP 36 | { 37 | public: 38 | DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) override; 39 | 40 | void SetGainReductionDB(std::vector>& gainReductionDB) 41 | { 42 | this->mGainReductionDB = gainReductionDB; 43 | } 44 | 45 | private: 46 | std::vector> mGainReductionDB; 47 | }; 48 | 49 | // Part 1 of the noise gate: the trigger. 50 | // This listens to a stream of incoming audio and determines how much gain 51 | // to apply based on the loudness of the signal. 52 | 53 | class TriggerParams 54 | { 55 | public: 56 | TriggerParams(const double time, const double threshold, const double ratio, const double openTime, 57 | const double holdTime, const double closeTime) 58 | : mTime(time) 59 | , mThreshold(threshold) 60 | , mRatio(ratio) 61 | , mOpenTime(openTime) 62 | , mHoldTime(holdTime) 63 | , mCloseTime(closeTime) {}; 64 | 65 | double GetTime() const { return this->mTime; }; 66 | double GetThreshold() const { return this->mThreshold; }; 67 | double GetRatio() const { return this->mRatio; }; 68 | double GetOpenTime() const { return this->mOpenTime; }; 69 | double GetHoldTime() const { return this->mHoldTime; }; 70 | double GetCloseTime() const { return this->mCloseTime; }; 71 | 72 | private: 73 | // The time constant for quantifying the loudness of the signal. 74 | double mTime; 75 | // The threshold at which expanssion starts 76 | double mThreshold; 77 | // The compression ratio. 78 | double mRatio; 79 | // How long it takes to go from maximum gain reduction to zero. 80 | double mOpenTime; 81 | // How long to stay open before starting to close. 82 | double mHoldTime; 83 | // How long it takes to go from open to maximum gain reduction. 84 | double mCloseTime; 85 | }; 86 | 87 | class Trigger : public DSP 88 | { 89 | public: 90 | Trigger(); 91 | 92 | DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) override; 93 | std::vector> GetGainReduction() const { return this->mGainReductionDB; }; 94 | void SetParams(const TriggerParams& params) { this->mParams = params; }; 95 | void SetSampleRate(const double sampleRate) { this->mSampleRate = sampleRate; } 96 | std::vector> GetGainReductionDB() const { return this->mGainReductionDB; }; 97 | 98 | void AddListener(Gain* gain) 99 | { 100 | // This might be risky dropping a raw pointer, but I don't think that the 101 | // gain would be destructed, so probably ok. 102 | this->mGainListeners.insert(gain); 103 | } 104 | 105 | private: 106 | enum class State 107 | { 108 | MOVING = 0, 109 | HOLDING 110 | }; 111 | 112 | double _GetGainReduction(const double levelDB) const 113 | { 114 | const double threshold = this->mParams.GetThreshold(); 115 | // Quadratic gain reduction? :) 116 | return levelDB < threshold ? -(this->mParams.GetRatio()) * (levelDB - threshold) * (levelDB - threshold) : 0.0; 117 | } 118 | double _GetMaxGainReduction() const { return this->_GetGainReduction(MINIMUM_LOUDNESS_DB); } 119 | virtual void _PrepareBuffers(const size_t numChannels, const size_t numFrames) override; 120 | 121 | TriggerParams mParams; 122 | std::vector mState; // One per channel 123 | std::vector mLevel; 124 | 125 | // Hold the vectors of gain reduction for the block, in dB. 126 | // These can be given to the Gain object. 127 | std::vector> mGainReductionDB; 128 | std::vector mLastGainReductionDB; 129 | 130 | double mSampleRate; 131 | // How long we've been holding 132 | std::vector mTimeHeld; 133 | 134 | std::unordered_set mGainListeners; 135 | }; 136 | 137 | }; // namespace noise_gate 138 | }; // namespace dsp -------------------------------------------------------------------------------- /dsp/RecursiveLinearFilter.h: -------------------------------------------------------------------------------- 1 | // 2 | // RecursiveLinearFilter.h 3 | // 4 | // 5 | // Created by Steven Atkinson on 12/28/22. 6 | // 7 | // Recursive linear filters (LPF, HPF, Peaking, Shelving) 8 | 9 | #pragma once 10 | 11 | #include "dsp.h" 12 | #include // pow, sin 13 | #include 14 | 15 | #define MATH_PI 3.14159265358979323846 16 | 17 | // TODO refactor base DSP into a common abstraction. 18 | 19 | namespace recursive_linear_filter 20 | { 21 | class Base : public dsp::DSP 22 | { 23 | public: 24 | Base(const size_t inputDegree, const size_t outputDegree); 25 | DSP_SAMPLE** Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) override; 26 | 27 | protected: 28 | // Methods 29 | size_t _GetInputDegree() const { return this->mInputCoefficients.size(); }; 30 | size_t _GetOutputDegree() const { return this->mOutputCoefficients.size(); }; 31 | // Additionally prepares mInputHistory and mOutputHistory. 32 | void _PrepareBuffers(const size_t numChannels, const size_t numFrames) override; 33 | 34 | // Coefficients for the DSP filter 35 | // [0] is for the current sample 36 | // [1] is for the previous 37 | // [2] before that 38 | // (mOutputCoefficients[0] should always be zero. It'll never be used.) 39 | std::vector mInputCoefficients; 40 | std::vector mOutputCoefficients; 41 | 42 | // Arrays holding the history on which the filter depends recursively. 43 | // First index is channel 44 | // Second index, [0] is the current input/output, [1] is the previous, [2] is 45 | // before that, etc. 46 | std::vector> mInputHistory; 47 | std::vector> mOutputHistory; 48 | // Indices for history. 49 | // Designates which index is currently "0". Use modulus to wrap around. 50 | long mInputStart; 51 | long mOutputStart; 52 | }; 53 | 54 | class LevelParams : public dsp::Params 55 | { 56 | public: 57 | LevelParams(const double gain) 58 | : Params() 59 | , mGain(gain) {}; 60 | double GetGain() const { return this->mGain; }; 61 | 62 | private: 63 | // The gain (multiplicative, i.e. not dB) 64 | double mGain; 65 | }; 66 | 67 | class Level : public Base 68 | { 69 | public: 70 | Level() 71 | : Base(1, 0) {}; 72 | // Invalid usage: require a pointer to recursive_linear_filter::Params so 73 | // that SetCoefficients() is defined. 74 | void SetParams(const LevelParams& params) { this->mInputCoefficients[0] = params.GetGain(); }; 75 | ; 76 | }; 77 | 78 | // The same 3 params (frequency, quality, gain) describe a bunch of filters. 79 | // (Low shelf, high shelf, peaking) 80 | class BiquadParams : public dsp::Params 81 | { 82 | public: 83 | BiquadParams(const double sampleRate, const double frequency, const double quality, const double gainDB) 84 | : dsp::Params() 85 | , mFrequency(frequency) 86 | , mGainDB(gainDB) 87 | , mQuality(quality) 88 | , mSampleRate(sampleRate) {}; 89 | 90 | // Parameters defined in 91 | // https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html 92 | double GetA() const { return pow(10.0, this->mGainDB / 40.0); }; 93 | double GetOmega0() const { return 2.0 * MATH_PI * this->mFrequency / this->mSampleRate; }; 94 | double GetAlpha(const double omega_0) const { return sin(omega_0) / (2.0 * this->mQuality); }; 95 | double GetCosW(const double omega_0) const { return cos(omega_0); }; 96 | 97 | private: 98 | double mFrequency; 99 | double mGainDB; 100 | double mQuality; 101 | double mSampleRate; 102 | }; 103 | 104 | class Biquad : public Base 105 | { 106 | public: 107 | Biquad() 108 | : Base(3, 3) {}; 109 | virtual void SetParams(const BiquadParams& params) = 0; 110 | 111 | protected: 112 | void _AssignCoefficients(const double a0, const double a1, const double a2, const double b0, const double b1, 113 | const double b2); 114 | }; 115 | 116 | class LowShelf : public Biquad 117 | { 118 | public: 119 | void SetParams(const BiquadParams& params) override; 120 | }; 121 | 122 | class Peaking : public Biquad 123 | { 124 | public: 125 | void SetParams(const BiquadParams& params) override; 126 | }; 127 | 128 | class HighShelf : public Biquad 129 | { 130 | public: 131 | void SetParams(const BiquadParams& params) override; 132 | }; 133 | 134 | // HPF only has one param: frequency 135 | // TODO LPF (alpha calculation is different though) 136 | class HighPassParams : public dsp::Params 137 | { 138 | public: 139 | HighPassParams(const double sampleRate, const double frequency) 140 | : dsp::Params() 141 | , mFrequency(frequency) 142 | , mSampleRate(sampleRate) {}; 143 | 144 | double GetAlpha() const 145 | { 146 | const double c = 2.0 * MATH_PI * mFrequency / mSampleRate; 147 | return 1.0 / (c + 1.0); 148 | }; 149 | 150 | private: 151 | double mFrequency; 152 | double mSampleRate; 153 | }; 154 | 155 | class HighPass : public Base 156 | { 157 | public: 158 | HighPass() 159 | : Base(2, 2) {}; 160 | void SetParams(const HighPassParams& params) 161 | { 162 | const double alpha = params.GetAlpha(); 163 | // y[i] = alpha * y[i-1] + alpha * (x[i]-x[i-1]) 164 | mInputCoefficients[0] = alpha; 165 | mInputCoefficients[1] = -alpha; 166 | mOutputCoefficients[0] = 0.0; 167 | mOutputCoefficients[1] = alpha; 168 | } 169 | }; 170 | 171 | class LowPassParams : public dsp::Params 172 | { 173 | public: 174 | LowPassParams(const double sampleRate, const double frequency) 175 | : dsp::Params() 176 | , mFrequency(frequency) 177 | , mSampleRate(sampleRate) {}; 178 | 179 | double GetAlpha() const 180 | { 181 | const double c = 2.0 * MATH_PI * mFrequency / mSampleRate; 182 | return c / (c + 1.0); 183 | }; 184 | 185 | private: 186 | double mFrequency; 187 | double mSampleRate; 188 | }; 189 | 190 | class LowPass : public Base 191 | { 192 | public: 193 | LowPass() 194 | : Base(1, 2) {}; 195 | void SetParams(const LowPassParams& params) 196 | { 197 | const double alpha = params.GetAlpha(); 198 | // y[i] = alpha * x[i] + (1-alpha) * y[i-1] 199 | mInputCoefficients[0] = alpha; 200 | mOutputCoefficients[0] = 0.0; 201 | mOutputCoefficients[1] = 1.0 - alpha; 202 | } 203 | }; 204 | 205 | }; // namespace recursive_linear_filter 206 | -------------------------------------------------------------------------------- /dsp/RecursiveLinearFilter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // RecursiveLinearFilter.cpp 3 | // 4 | // 5 | // Created by Steven Atkinson on 12/28/22. 6 | // 7 | // See: https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html 8 | 9 | #include // std::fill 10 | #include // isnan 11 | #include 12 | 13 | #include "RecursiveLinearFilter.h" 14 | 15 | recursive_linear_filter::Base::Base(const size_t inputDegree, const size_t outputDegree) 16 | : dsp::DSP() 17 | , mInputStart(inputDegree) 18 | , // 1 is subtracted before first use 19 | mOutputStart(outputDegree) 20 | { 21 | this->mInputCoefficients.resize(inputDegree); 22 | this->mOutputCoefficients.resize(outputDegree); 23 | } 24 | 25 | DSP_SAMPLE** recursive_linear_filter::Base::Process(DSP_SAMPLE** inputs, const size_t numChannels, 26 | const size_t numFrames) 27 | { 28 | this->_PrepareBuffers(numChannels, numFrames); 29 | long inputStart = 0; 30 | long outputStart = 0; 31 | // Degree = longest history 32 | // E.g. if y[n] explicitly depends on x[n-2], then input degree is 3 33 | // (n,n-1,n-2). NOTE: output degree is never 1 because y[n] is never used to 34 | // explicitly calculate...itself! 35 | // 0,2,3,... are fine. 36 | const size_t inputDegree = this->_GetInputDegree(); 37 | const size_t outputDegree = this->_GetOutputDegree(); 38 | for (auto c = 0; c < numChannels; c++) 39 | { 40 | inputStart = this->mInputStart; // Should be plenty fine 41 | outputStart = this->mOutputStart; 42 | for (auto s = 0; s < numFrames; s++) 43 | { 44 | DSP_SAMPLE out = 0.0; 45 | // Compute input terms 46 | inputStart -= 1; 47 | if (inputStart < 0) 48 | inputStart = inputDegree - 1; 49 | this->mInputHistory[c][inputStart] = inputs[c][s]; // Store current input 50 | for (auto i = 0; i < inputDegree; i++) 51 | out += this->mInputCoefficients[i] * this->mInputHistory[c][(inputStart + i) % inputDegree]; 52 | 53 | // Output terms 54 | outputStart -= 1; 55 | if (outputStart < 0) 56 | outputStart = outputDegree - 1; 57 | for (auto i = 1; i < outputDegree; i++) 58 | out += this->mOutputCoefficients[i] * this->mOutputHistory[c][(outputStart + i) % outputDegree]; 59 | // Prevent a NaN from jamming the filter! 60 | if (std::isnan(out)) 61 | out = 0.0; 62 | // Store the output! 63 | if (outputDegree >= 1) 64 | this->mOutputHistory[c][outputStart] = out; 65 | this->mOutputs[c][s] = out; 66 | } 67 | } 68 | this->mInputStart = inputStart; 69 | this->mOutputStart = outputStart; 70 | return this->_GetPointers(); 71 | } 72 | 73 | void recursive_linear_filter::Base::_PrepareBuffers(const size_t numChannels, const size_t numFrames) 74 | { 75 | // Check for new channel count *before* parent class ensures they match! 76 | const bool newChannels = this->_GetNumChannels() != numChannels; 77 | // Parent implementation takes care of mOutputs and mOutputPointers 78 | this->dsp::DSP::_PrepareBuffers(numChannels, numFrames); 79 | if (newChannels) 80 | { 81 | this->mInputHistory.resize(numChannels); 82 | this->mOutputHistory.resize(numChannels); 83 | const size_t inputDegree = this->_GetInputDegree(); 84 | const size_t outputDegree = this->_GetOutputDegree(); 85 | for (auto c = 0; c < numChannels; c++) 86 | { 87 | this->mInputHistory[c].resize(inputDegree); 88 | this->mOutputHistory[c].resize(outputDegree); 89 | std::fill(this->mInputHistory[c].begin(), this->mInputHistory[c].end(), 0.0); 90 | std::fill(this->mOutputHistory[c].begin(), this->mOutputHistory[c].end(), 0.0); 91 | } 92 | } 93 | } 94 | 95 | void recursive_linear_filter::Biquad::_AssignCoefficients(const double a0, const double a1, const double a2, 96 | const double b0, const double b1, const double b2) 97 | { 98 | this->mInputCoefficients[0] = b0 / a0; 99 | this->mInputCoefficients[1] = b1 / a0; 100 | this->mInputCoefficients[2] = b2 / a0; 101 | // this->mOutputCoefficients[0] = 0.0; // Always 102 | // Sign flip due so we add during main loop (cf Eq. (4)) 103 | this->mOutputCoefficients[1] = -a1 / a0; 104 | this->mOutputCoefficients[2] = -a2 / a0; 105 | } 106 | 107 | void recursive_linear_filter::LowShelf::SetParams(const recursive_linear_filter::BiquadParams& params) 108 | { 109 | const double a = params.GetA(); 110 | const double omega_0 = params.GetOmega0(); 111 | const double alpha = params.GetAlpha(omega_0); 112 | const double cosw = params.GetCosW(omega_0); 113 | 114 | const double ap = a + 1.0; 115 | const double am = a - 1.0; 116 | const double roota2alpha = 2.0 * sqrt(a) * alpha; 117 | 118 | const double b0 = a * (ap - am * cosw + roota2alpha); 119 | const double b1 = 2.0 * a * (am - ap * cosw); 120 | const double b2 = a * (ap - am * cosw - roota2alpha); 121 | const double a0 = ap + am * cosw + roota2alpha; 122 | const double a1 = -2.0 * (am + ap * cosw); 123 | const double a2 = ap + am * cosw - roota2alpha; 124 | 125 | this->_AssignCoefficients(a0, a1, a2, b0, b1, b2); 126 | } 127 | 128 | void recursive_linear_filter::Peaking::SetParams(const recursive_linear_filter::BiquadParams& params) 129 | { 130 | const double a = params.GetA(); 131 | const double omega_0 = params.GetOmega0(); 132 | const double alpha = params.GetAlpha(omega_0); 133 | const double cosw = params.GetCosW(omega_0); 134 | 135 | const double b0 = 1.0 + alpha * a; 136 | const double b1 = -2.0 * cosw; 137 | const double b2 = 1.0 - alpha * a; 138 | const double a0 = 1.0 + alpha / a; 139 | const double a1 = -2.0 * cosw; 140 | const double a2 = 1.0 - alpha / a; 141 | 142 | this->_AssignCoefficients(a0, a1, a2, b0, b1, b2); 143 | } 144 | 145 | void recursive_linear_filter::HighShelf::SetParams(const recursive_linear_filter::BiquadParams& params) 146 | { 147 | const double a = params.GetA(); 148 | const double omega_0 = params.GetOmega0(); 149 | const double alpha = params.GetAlpha(omega_0); 150 | const double cosw = params.GetCosW(omega_0); 151 | 152 | const double roota2alpha = 2.0 * sqrt(a) * alpha; 153 | const double ap = a + 1.0; 154 | const double am = a - 1.0; 155 | 156 | const double b0 = a * (ap + am * cosw + roota2alpha); 157 | const double b1 = -2.0 * a * (am + ap * cosw); 158 | const double b2 = a * (ap + am * cosw - roota2alpha); 159 | const double a0 = ap - am * cosw + roota2alpha; 160 | const double a1 = 2.0 * (am - ap * cosw); 161 | const double a2 = ap - am * cosw - roota2alpha; 162 | 163 | this->_AssignCoefficients(a0, a1, a2, b0, b1, b2); 164 | } 165 | -------------------------------------------------------------------------------- /dsp/NoiseGate.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // NoiseGate.cpp 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 2/5/23. 6 | // 7 | 8 | #include // std::clamp 9 | #include // memcpy 10 | #include // pow 11 | #include 12 | 13 | #include "NoiseGate.h" 14 | 15 | double _LevelToDB(const double db) 16 | { 17 | return 10.0 * log10(db); 18 | } 19 | 20 | double _DBToLevel(const double level) 21 | { 22 | return pow(10.0, level / 10.0); 23 | } 24 | 25 | dsp::noise_gate::Trigger::Trigger() 26 | : mParams(0.05, -60.0, 1.5, 0.002, 0.050, 0.050) 27 | , mSampleRate(0) 28 | { 29 | } 30 | 31 | double signum(const double val) 32 | { 33 | return (0.0 < val) - (val < 0.0); 34 | } 35 | 36 | DSP_SAMPLE** dsp::noise_gate::Trigger::Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) 37 | { 38 | this->_PrepareBuffers(numChannels, numFrames); 39 | 40 | // A bunch of numbers we'll use a few times. 41 | const double alpha = pow(0.5, 1.0 / (this->mParams.GetTime() * this->mSampleRate)); 42 | const double beta = 1.0 - alpha; 43 | const double threshold = this->mParams.GetThreshold(); 44 | const double dt = 1.0 / this->mSampleRate; 45 | const double maxHold = this->mParams.GetHoldTime(); 46 | const double maxGainReduction = this->_GetMaxGainReduction(); 47 | // Amount of open or close in a sample: rate times time 48 | const double dOpen = -this->_GetMaxGainReduction() / this->mParams.GetOpenTime() * dt; // >0 49 | const double dClose = this->_GetMaxGainReduction() / this->mParams.GetCloseTime() * dt; // <0 50 | 51 | // The main algorithm: compute the gain reduction 52 | for (auto c = 0; c < numChannels; c++) 53 | { 54 | for (auto s = 0; s < numFrames; s++) 55 | { 56 | this->mLevel[c] = 57 | std::clamp(alpha * this->mLevel[c] + beta * (inputs[c][s] * inputs[c][s]), MINIMUM_LOUDNESS_POWER, 1000.0); 58 | const double levelDB = _LevelToDB(this->mLevel[c]); 59 | if (this->mState[c] == dsp::noise_gate::Trigger::State::HOLDING) 60 | { 61 | this->mGainReductionDB[c][s] = 0.0; 62 | this->mLastGainReductionDB[c] = 0.0; 63 | if (levelDB < threshold) 64 | { 65 | this->mTimeHeld[c] += dt; 66 | if (this->mTimeHeld[c] >= maxHold) 67 | this->mState[c] = dsp::noise_gate::Trigger::State::MOVING; 68 | } 69 | else 70 | { 71 | this->mTimeHeld[c] = 0.0; 72 | } 73 | } 74 | else 75 | { // Moving 76 | const double targetGainReduction = this->_GetGainReduction(levelDB); 77 | if (targetGainReduction > this->mLastGainReductionDB[c]) 78 | { 79 | const double dGain = std::clamp(0.5 * (targetGainReduction - this->mLastGainReductionDB[c]), 0.0, dOpen); 80 | this->mLastGainReductionDB[c] += dGain; 81 | if (this->mLastGainReductionDB[c] >= 0.0) 82 | { 83 | this->mLastGainReductionDB[c] = 0.0; 84 | this->mState[c] = dsp::noise_gate::Trigger::State::HOLDING; 85 | this->mTimeHeld[c] = 0.0; 86 | } 87 | } 88 | else if (targetGainReduction < this->mLastGainReductionDB[c]) 89 | { 90 | const double dGain = std::clamp(0.5 * (targetGainReduction - this->mLastGainReductionDB[c]), dClose, 0.0); 91 | this->mLastGainReductionDB[c] += dGain; 92 | if (this->mLastGainReductionDB[c] < maxGainReduction) 93 | { 94 | this->mLastGainReductionDB[c] = maxGainReduction; 95 | } 96 | } 97 | this->mGainReductionDB[c][s] = this->mLastGainReductionDB[c]; 98 | } 99 | } 100 | } 101 | 102 | // Share the results with gain objects that are listening to this trigger: 103 | for (auto gain = this->mGainListeners.begin(); gain != this->mGainListeners.end(); ++gain) 104 | (*gain)->SetGainReductionDB(this->mGainReductionDB); 105 | 106 | // Copy input to output 107 | for (auto c = 0; c < numChannels; c++) 108 | memcpy(this->mOutputs[c].data(), inputs[c], numFrames * sizeof(DSP_SAMPLE)); 109 | return this->_GetPointers(); 110 | } 111 | 112 | void dsp::noise_gate::Trigger::_PrepareBuffers(const size_t numChannels, const size_t numFrames) 113 | { 114 | const size_t oldChannels = this->_GetNumChannels(); 115 | const size_t oldFrames = this->_GetNumFrames(); 116 | this->DSP::_PrepareBuffers(numChannels, numFrames); 117 | 118 | const bool updateChannels = numChannels != oldChannels; 119 | const bool updateFrames = updateChannels || numFrames != oldFrames; 120 | 121 | if (updateChannels || updateFrames) 122 | { 123 | const double maxGainReduction = this->_GetMaxGainReduction(); 124 | if (updateChannels) 125 | { 126 | this->mGainReductionDB.resize(numChannels); 127 | this->mLastGainReductionDB.resize(numChannels); 128 | std::fill(this->mLastGainReductionDB.begin(), this->mLastGainReductionDB.end(), maxGainReduction); 129 | this->mState.resize(numChannels); 130 | std::fill(this->mState.begin(), this->mState.end(), dsp::noise_gate::Trigger::State::MOVING); 131 | this->mLevel.resize(numChannels); 132 | std::fill(this->mLevel.begin(), this->mLevel.end(), MINIMUM_LOUDNESS_POWER); 133 | this->mTimeHeld.resize(numChannels); 134 | std::fill(this->mTimeHeld.begin(), this->mTimeHeld.end(), 0.0); 135 | } 136 | if (updateFrames) 137 | { 138 | for (auto i = 0; i < this->mGainReductionDB.size(); i++) 139 | { 140 | this->mGainReductionDB[i].resize(numFrames); 141 | std::fill(this->mGainReductionDB[i].begin(), this->mGainReductionDB[i].end(), maxGainReduction); 142 | } 143 | } 144 | } 145 | } 146 | 147 | // Gain======================================================================== 148 | 149 | DSP_SAMPLE** dsp::noise_gate::Gain::Process(DSP_SAMPLE** inputs, const size_t numChannels, const size_t numFrames) 150 | { 151 | // Assume that SetGainReductionDB() was just called to get data from a 152 | // trigger. Could use listeners... 153 | this->_PrepareBuffers(numChannels, numFrames); 154 | 155 | if (this->mGainReductionDB.size() != numChannels) 156 | { 157 | std::stringstream ss; 158 | ss << "Gain module expected to operate on " << this->mGainReductionDB.size() << "channels, but " << numChannels 159 | << " were provided."; 160 | throw std::runtime_error(ss.str()); 161 | } 162 | if ((this->mGainReductionDB.size() == 0) && (numFrames > 0)) 163 | { 164 | std::stringstream ss; 165 | ss << "No channels expected by gain module, yet " << numFrames << " were provided?"; 166 | throw std::runtime_error(ss.str()); 167 | } 168 | else if (this->mGainReductionDB[0].size() != numFrames) 169 | { 170 | std::stringstream ss; 171 | ss << "Gain module expected to operate on " << this->mGainReductionDB[0].size() << "frames, but " << numFrames 172 | << " were provided."; 173 | throw std::runtime_error(ss.str()); 174 | } 175 | 176 | // Apply gain! 177 | for (auto c = 0; c < numChannels; c++) 178 | for (auto s = 0; s < numFrames; s++) 179 | this->mOutputs[c][s] = _DBToLevel(this->mGainReductionDB[c][s]) * inputs[c][s]; 180 | 181 | return this->_GetPointers(); 182 | } 183 | -------------------------------------------------------------------------------- /dsp/ResamplingContainer/Dependencies/WDL/ptrlist.h: -------------------------------------------------------------------------------- 1 | /* 2 | WDL - ptrlist.h 3 | Copyright (C) 2005 and later, Cockos Incorporated 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | /* 24 | 25 | This file provides a simple templated class for a list of pointers. By default this list 26 | doesn't free any of the pointers, but you can call Empty(true) or Delete(x,true) to delete the pointer, 27 | or you can use Empty(true,free) etc to call free (or any other function). 28 | 29 | Note: on certain compilers, instantiating with WDL_PtrList bla; will give a warning, since 30 | the template will create code for "delete (void *)x;" which isn't technically valid. Oh well. 31 | 32 | */ 33 | 34 | #ifndef _WDL_PTRLIST_H_ 35 | #define _WDL_PTRLIST_H_ 36 | 37 | #include "heapbuf.h" 38 | 39 | template 40 | class WDL_PtrList 41 | { 42 | public: 43 | explicit WDL_PtrList(int defgran = 4096) 44 | : m_hb(defgran WDL_HEAPBUF_TRACEPARM("WDL_PtrList")) 45 | { 46 | } 47 | 48 | ~WDL_PtrList() {} 49 | 50 | PTRTYPE** GetList() const { return (PTRTYPE**)m_hb.Get(); } 51 | PTRTYPE* Get(INT_PTR index) const 52 | { 53 | PTRTYPE** list = (PTRTYPE**)m_hb.Get(); 54 | if (list && (UINT_PTR)index < (UINT_PTR)(m_hb.GetSize() / sizeof(PTRTYPE*))) 55 | return list[index]; 56 | return NULL; 57 | } 58 | 59 | int GetSize(void) const { return m_hb.GetSize() / (unsigned int)sizeof(PTRTYPE*); } 60 | 61 | int Find(const PTRTYPE* p) const 62 | { 63 | if (p) 64 | { 65 | PTRTYPE** list = (PTRTYPE**)m_hb.Get(); 66 | int x; 67 | const int n = GetSize(); 68 | for (x = 0; x < n; x++) 69 | if (list[x] == p) 70 | return x; 71 | } 72 | return -1; 73 | } 74 | int FindR(const PTRTYPE* p) const 75 | { 76 | if (p) 77 | { 78 | PTRTYPE** list = (PTRTYPE**)m_hb.Get(); 79 | int x = GetSize(); 80 | while (--x >= 0) 81 | if (list[x] == p) 82 | return x; 83 | } 84 | return -1; 85 | } 86 | 87 | PTRTYPE* Add(PTRTYPE* item) 88 | { 89 | const int s = GetSize(); 90 | PTRTYPE** list = (PTRTYPE**)m_hb.ResizeOK((s + 1) * (unsigned int)sizeof(PTRTYPE*), false); 91 | if (list) 92 | { 93 | list[s] = item; 94 | return item; 95 | } 96 | return NULL; 97 | } 98 | 99 | PTRTYPE* Set(int index, PTRTYPE* item) 100 | { 101 | PTRTYPE** list = (PTRTYPE**)m_hb.Get(); 102 | if (list && index >= 0 && index < GetSize()) 103 | return list[index] = item; 104 | return NULL; 105 | } 106 | 107 | PTRTYPE* Insert(int index, PTRTYPE* item) 108 | { 109 | int s = GetSize(); 110 | PTRTYPE** list = (PTRTYPE**)m_hb.ResizeOK((s + 1) * (unsigned int)sizeof(PTRTYPE*), false); 111 | 112 | if (!list) 113 | return item; 114 | 115 | if (index < 0) 116 | index = 0; 117 | 118 | int x; 119 | for (x = s; x > index; x--) 120 | list[x] = list[x - 1]; 121 | return (list[x] = item); 122 | } 123 | int FindSorted(const PTRTYPE* p, int (*compar)(const PTRTYPE** a, const PTRTYPE** b)) const 124 | { 125 | bool m; 126 | int i = LowerBound(p, &m, compar); 127 | return m ? i : -1; 128 | } 129 | PTRTYPE* InsertSorted(PTRTYPE* item, int (*compar)(const PTRTYPE** a, const PTRTYPE** b)) 130 | { 131 | bool m; 132 | return Insert(LowerBound(item, &m, compar), item); 133 | } 134 | 135 | void Delete(int index) 136 | { 137 | PTRTYPE** list = GetList(); 138 | int size = GetSize(); 139 | if (list && index >= 0 && index < size) 140 | { 141 | if (index < --size) 142 | memmove(list + index, list + index + 1, (unsigned int)sizeof(PTRTYPE*) * (size - index)); 143 | m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*), false); 144 | } 145 | } 146 | void Delete(int index, bool wantDelete, void (*delfunc)(void*) = NULL) 147 | { 148 | PTRTYPE** list = GetList(); 149 | int size = GetSize(); 150 | if (list && index >= 0 && index < size) 151 | { 152 | if (wantDelete) 153 | { 154 | if (delfunc) 155 | delfunc(Get(index)); 156 | else 157 | delete Get(index); 158 | } 159 | if (index < --size) 160 | memmove(list + index, list + index + 1, (unsigned int)sizeof(PTRTYPE*) * (size - index)); 161 | m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*), false); 162 | } 163 | } 164 | void Delete(int index, void (*delfunc)(PTRTYPE*)) 165 | { 166 | PTRTYPE** list = GetList(); 167 | int size = GetSize(); 168 | if (list && index >= 0 && index < size) 169 | { 170 | if (delfunc) 171 | delfunc(Get(index)); 172 | if (index < --size) 173 | memmove(list + index, list + index + 1, (unsigned int)sizeof(PTRTYPE*) * (size - index)); 174 | m_hb.Resize(size * (unsigned int)sizeof(PTRTYPE*), false); 175 | } 176 | } 177 | void DeletePtr(const PTRTYPE* p) { Delete(Find(p)); } 178 | void DeletePtr(const PTRTYPE* p, bool wantDelete, void (*delfunc)(void*) = NULL) 179 | { 180 | Delete(Find(p), wantDelete, delfunc); 181 | } 182 | void DeletePtr(const PTRTYPE* p, void (*delfunc)(PTRTYPE*)) { Delete(Find(p), delfunc); } 183 | 184 | void Empty() { m_hb.Resize(0, false); } 185 | void Empty(bool wantDelete, void (*delfunc)(void*) = NULL) 186 | { 187 | if (wantDelete) 188 | { 189 | int x; 190 | for (x = GetSize() - 1; x >= 0; x--) 191 | { 192 | PTRTYPE* p = Get(x); 193 | if (p) 194 | { 195 | if (delfunc) 196 | delfunc(p); 197 | else 198 | delete p; 199 | } 200 | m_hb.Resize(x * (unsigned int)sizeof(PTRTYPE*), false); 201 | } 202 | } 203 | m_hb.Resize(0, false); 204 | } 205 | void Empty(void (*delfunc)(PTRTYPE*)) 206 | { 207 | int x; 208 | for (x = GetSize() - 1; x >= 0; x--) 209 | { 210 | PTRTYPE* p = Get(x); 211 | if (delfunc && p) 212 | delfunc(p); 213 | m_hb.Resize(x * (unsigned int)sizeof(PTRTYPE*), false); 214 | } 215 | } 216 | void EmptySafe(bool wantDelete = false, void (*delfunc)(void*) = NULL) 217 | { 218 | if (!wantDelete) 219 | Empty(); 220 | else 221 | { 222 | WDL_PtrList tmp; 223 | int x; 224 | for (x = 0; x < GetSize(); x++) 225 | tmp.Add(Get(x)); 226 | Empty(); 227 | tmp.Empty(true, delfunc); 228 | } 229 | } 230 | 231 | int LowerBound(const PTRTYPE* key, bool* ismatch, int (*compar)(const PTRTYPE** a, const PTRTYPE** b)) const 232 | { 233 | int a = 0; 234 | int c = GetSize(); 235 | PTRTYPE** list = GetList(); 236 | while (a != c) 237 | { 238 | int b = (a + c) / 2; 239 | int cmp = compar((const PTRTYPE**)&key, (const PTRTYPE**)(list + b)); 240 | if (cmp > 0) 241 | a = b + 1; 242 | else if (cmp < 0) 243 | c = b; 244 | else 245 | { 246 | *ismatch = true; 247 | return b; 248 | } 249 | } 250 | *ismatch = false; 251 | return a; 252 | } 253 | 254 | void Compact() { m_hb.Resize(m_hb.GetSize(), true); } 255 | 256 | 257 | int DeleteBatch(bool (*proc)(PTRTYPE* p, void* ctx), 258 | void* ctx = NULL) // proc returns true to remove item. returns number removed 259 | { 260 | const int sz = GetSize(); 261 | int cnt = 0; 262 | PTRTYPE **rd = GetList(), **wr = rd; 263 | for (int x = 0; x < sz; x++) 264 | { 265 | if (!proc(*rd, ctx)) 266 | { 267 | if (rd != wr) 268 | *wr = *rd; 269 | wr++; 270 | cnt++; 271 | } 272 | rd++; 273 | } 274 | if (cnt < sz) 275 | m_hb.Resize(cnt * sizeof(PTRTYPE*), false); 276 | return sz - cnt; 277 | } 278 | 279 | private: 280 | WDL_HeapBuf m_hb; 281 | }; 282 | 283 | 284 | template 285 | class WDL_PtrList_DeleteOnDestroy : public WDL_PtrList 286 | { 287 | public: 288 | explicit WDL_PtrList_DeleteOnDestroy(void (*delfunc)(void*) = NULL, int defgran = 4096) 289 | : WDL_PtrList(defgran) 290 | , m_delfunc(delfunc) 291 | { 292 | } 293 | ~WDL_PtrList_DeleteOnDestroy() { WDL_PtrList::EmptySafe(true, m_delfunc); } 294 | 295 | private: 296 | void (*m_delfunc)(void*); 297 | }; 298 | 299 | #endif 300 | -------------------------------------------------------------------------------- /dsp/ResamplingContainer/Dependencies/WDL/wdltypes.h: -------------------------------------------------------------------------------- 1 | #ifndef _WDLTYPES_ 2 | #define _WDLTYPES_ 3 | 4 | #ifdef _MSC_VER 5 | 6 | typedef __int64 WDL_INT64; 7 | typedef unsigned __int64 WDL_UINT64; 8 | 9 | #else 10 | 11 | typedef long long WDL_INT64; 12 | typedef unsigned long long WDL_UINT64; 13 | 14 | #endif 15 | 16 | #ifdef _MSC_VER 17 | #define WDL_UINT64_CONST(x) (x##ui64) 18 | #define WDL_INT64_CONST(x) (x##i64) 19 | #else 20 | #define WDL_UINT64_CONST(x) (x##ULL) 21 | #define WDL_INT64_CONST(x) (x##LL) 22 | #endif 23 | 24 | #ifdef _WIN32 25 | #define WDL_PRI_UINT64 "I64u" 26 | #define WDL_PRI_INT64 "I64d" 27 | #else 28 | #define WDL_PRI_UINT64 "llu" 29 | #define WDL_PRI_INT64 "lld" 30 | #endif 31 | 32 | #if !defined(_MSC_VER) || _MSC_VER > 1200 33 | #define WDL_DLGRET INT_PTR CALLBACK 34 | #else 35 | #define WDL_DLGRET BOOL CALLBACK 36 | #endif 37 | 38 | 39 | #ifdef _WIN32 40 | #include 41 | #include 42 | #else 43 | #include 44 | typedef intptr_t INT_PTR; 45 | typedef uintptr_t UINT_PTR; 46 | #endif 47 | #include 48 | 49 | #if defined(__ppc__) || !defined(__cplusplus) 50 | typedef char WDL_bool; 51 | #else 52 | typedef bool WDL_bool; 53 | #endif 54 | 55 | #ifndef GWLP_USERDATA 56 | #define GWLP_USERDATA GWL_USERDATA 57 | #define GWLP_WNDPROC GWL_WNDPROC 58 | #define GWLP_HINSTANCE GWL_HINSTANCE 59 | #define GWLP_HWNDPARENT GWL_HWNDPARENT 60 | #define DWLP_USER DWL_USER 61 | #define DWLP_DLGPROC DWL_DLGPROC 62 | #define DWLP_MSGRESULT DWL_MSGRESULT 63 | #define SetWindowLongPtr(a, b, c) SetWindowLong(a, b, c) 64 | #define GetWindowLongPtr(a, b) GetWindowLong(a, b) 65 | #define SetWindowLongPtrW(a, b, c) SetWindowLongW(a, b, c) 66 | #define GetWindowLongPtrW(a, b) GetWindowLongW(a, b) 67 | #define SetWindowLongPtrA(a, b, c) SetWindowLongA(a, b, c) 68 | #define GetWindowLongPtrA(a, b) GetWindowLongA(a, b) 69 | 70 | #define GCLP_WNDPROC GCL_WNDPROC 71 | #define GCLP_HICON GCL_HICON 72 | #define GCLP_HICONSM GCL_HICONSM 73 | #define SetClassLongPtr(a, b, c) SetClassLong(a, b, c) 74 | #define GetClassLongPtr(a, b) GetClassLong(a, b) 75 | #endif 76 | 77 | #if !defined(WDL_BIG_ENDIAN) && !defined(WDL_LITTLE_ENDIAN) 78 | #ifdef __ppc__ 79 | #define WDL_BIG_ENDIAN 80 | #else 81 | #define WDL_LITTLE_ENDIAN 82 | #endif 83 | #endif 84 | 85 | #if defined(WDL_BIG_ENDIAN) && defined(WDL_LITTLE_ENDIAN) 86 | #error WDL_BIG_ENDIAN and WDL_LITTLE_ENDIAN both defined 87 | #endif 88 | 89 | 90 | #ifdef __GNUC__ 91 | // for structures that contain doubles, or doubles in structures that are after stuff of questionable alignment (for 92 | // OSX/linux) 93 | #define WDL_FIXALIGN __attribute__((aligned(8))) 94 | // usage: void func(int a, const char *fmt, ...) WDL_VARARG_WARN(printf,2,3); // note: if member function, this 95 | // pointer is counted as well, so as member function that would be 3,4 96 | #define WDL_VARARG_WARN(x, n, s) __attribute__((format(x, n, s))) 97 | #define WDL_STATICFUNC_UNUSED __attribute__((unused)) 98 | 99 | #else 100 | #define WDL_FIXALIGN 101 | #define WDL_VARARG_WARN(x, n, s) 102 | #define WDL_STATICFUNC_UNUSED 103 | #endif 104 | 105 | #ifndef WDL_WANT_NEW_EXCEPTIONS 106 | #if defined(__cplusplus) 107 | #include 108 | #define WDL_NEW (std::nothrow) 109 | #endif 110 | #else 111 | #define WDL_NEW 112 | #endif 113 | 114 | 115 | #if !defined(max) && defined(WDL_DEFINE_MINMAX) 116 | #define max(x, y) ((x) < (y) ? (y) : (x)) 117 | #define min(x, y) ((x) < (y) ? (x) : (y)) 118 | #endif 119 | 120 | #ifndef wdl_max 121 | #define wdl_max(x, y) ((x) < (y) ? (y) : (x)) 122 | #define wdl_min(x, y) ((x) < (y) ? (x) : (y)) 123 | #define wdl_abs(x) ((x) < 0 ? -(x) : (x)) 124 | #define wdl_clamp(x, minv, maxv) \ 125 | (WDL_NOT_NORMALLY((maxv) < (minv)) || (x) < (minv) ? (minv) : ((x) > (maxv) ? (maxv) : (x))) 126 | #endif 127 | 128 | #ifndef _WIN32 129 | #ifndef strnicmp 130 | #define strnicmp(x, y, z) strncasecmp(x, y, z) 131 | #endif 132 | #ifndef stricmp 133 | #define stricmp(x, y) strcasecmp(x, y) 134 | #endif 135 | #endif 136 | 137 | #ifdef WDL_BACKSLASHES_ARE_ORDINARY 138 | #define WDL_IS_DIRCHAR(x) ((x) == '/') 139 | #else 140 | // for multi-platform applications it seems better to treat backslashes as directory separators even if it 141 | // isn't supported by the underying system (for resolving filenames, etc) 142 | #ifdef _WIN32 143 | #define WDL_IS_DIRCHAR(x) ((x) == '\\' || (x) == '/') 144 | #else 145 | #define WDL_IS_DIRCHAR(x) ((x) == '/' || (x) == '\\') 146 | #endif 147 | #endif 148 | 149 | #if defined(_WIN32) && !defined(WDL_BACKSLASHES_ARE_ORDINARY) 150 | #define WDL_DIRCHAR '\\' 151 | #define WDL_DIRCHAR_STR "\\" 152 | #else 153 | #define WDL_DIRCHAR '/' 154 | #define WDL_DIRCHAR_STR "/" 155 | #endif 156 | 157 | #if defined(_WIN32) || defined(__APPLE__) 158 | // on __APPLE__ we should ideally check the filesystem for case-sensitivity, assuming a case-insensitive-only match 159 | #define wdl_filename_cmp(x, y) stricmp(x, y) 160 | #define wdl_filename_cmpn(x, y, n) strnicmp(x, y, n) 161 | #else 162 | #define wdl_filename_cmp(x, y) strcmp(x, y) 163 | #define wdl_filename_cmpn(x, y, n) strncmp(x, y, n) 164 | #endif 165 | 166 | #if defined(__GNUC__) || defined(__INTEL_COMPILER) 167 | #define WDL_likely(x) (__builtin_expect(!!(x), 1)) 168 | #define WDL_unlikely(x) (__builtin_expect(!!(x), 0)) 169 | #else 170 | #define WDL_likely(x) (!!(x)) 171 | #define WDL_unlikely(x) (!!(x)) 172 | #endif 173 | 174 | #if defined(_DEBUG) || defined(DEBUG) 175 | #include 176 | 177 | #ifdef _MSC_VER 178 | // msvc assert failure allows message loop to run, potentially resulting in recursive asserts 179 | static LONG WDL_ASSERT_INTERNALCNT; 180 | static int WDL_ASSERT_END() 181 | { 182 | WDL_ASSERT_INTERNALCNT = 0; 183 | return 0; 184 | } 185 | static int WDL_ASSERT_BEGIN() 186 | { 187 | return InterlockedCompareExchange(&WDL_ASSERT_INTERNALCNT, 1, 0) == 0; 188 | } 189 | #define WDL_ASSERT(x) \ 190 | do \ 191 | { \ 192 | if (WDL_ASSERT_BEGIN()) \ 193 | { \ 194 | assert(x); \ 195 | WDL_ASSERT_END(); \ 196 | } \ 197 | } while (0) 198 | #else 199 | #define WDL_ASSERT_BEGIN() (1) 200 | #define WDL_ASSERT_END() (0) 201 | #define WDL_ASSERT(x) assert(x) 202 | #endif 203 | #define WDL_NORMALLY(x) ((x) ? 1 : (WDL_ASSERT_BEGIN() && (assert(0 /*ignorethis*/ && (x)), WDL_ASSERT_END()))) 204 | #define WDL_NOT_NORMALLY(x) ((x) ? !WDL_ASSERT_BEGIN() || (assert(0 /*ignorethis*/ && !(x)), !WDL_ASSERT_END()) : 0) 205 | #else 206 | #define WDL_ASSERT(x) 207 | #define WDL_NORMALLY(x) WDL_likely(x) 208 | #define WDL_NOT_NORMALLY(x) WDL_unlikely(x) 209 | #endif 210 | 211 | 212 | typedef unsigned int WDL_TICKTYPE; 213 | 214 | static WDL_bool WDL_STATICFUNC_UNUSED WDL_TICKS_IN_RANGE(WDL_TICKTYPE current, WDL_TICKTYPE refstart, 215 | int len) // current >= refstart && current < refstart+len 216 | { 217 | WDL_ASSERT(len > 0); 218 | return (current - refstart) < (WDL_TICKTYPE)len; 219 | } 220 | 221 | static WDL_bool WDL_STATICFUNC_UNUSED WDL_TICKS_IN_RANGE_ENDING_AT(WDL_TICKTYPE current, WDL_TICKTYPE refend, 222 | int len) // current >= refend-len && current < refend 223 | { 224 | const WDL_TICKTYPE refstart = refend - len; 225 | WDL_ASSERT(len > 0); 226 | return (current - refstart) < (WDL_TICKTYPE)len; 227 | // return ((refend-1) - current) < (WDL_TICKTYPE)len; 228 | } 229 | 230 | // use this if you want validate that nothing that includes wdltypes.h calls fopen() directly on win32 231 | // #define WDL_CHECK_FOR_NON_UTF8_FOPEN 232 | 233 | #if defined(WDL_CHECK_FOR_NON_UTF8_FOPEN) && !defined(_WDL_WIN32_UTF8_H_) 234 | #ifdef fopen 235 | #undef fopen 236 | #endif 237 | #include 238 | static WDL_STATICFUNC_UNUSED FILE* WDL_fopenA(const char* fn, const char* mode) 239 | { 240 | return fopen(fn, mode); 241 | } 242 | #define fopen this_should_be_fopenUTF8_include_win32_utf8.h 243 | #else 244 | // callers of WDL_fopenA don't mind being non-UTF8-compatible on win32 245 | // (this could map to either fopen() or fopenUTF8() 246 | #define WDL_fopenA(fn, mode) fopen(fn, mode) 247 | #endif 248 | 249 | #ifndef WDL_ALLOW_UNSIGNED_DEFAULT_CHAR 250 | typedef char wdl_assert_failed_unsigned_char[((char)-1) > 0 ? -1 : 1]; 251 | #endif 252 | 253 | // wdl_log() / printf() wrapper. no-op on release builds 254 | #if !defined(_DEBUG) && !defined(WDL_LOG_ON_RELEASE) 255 | static void WDL_STATICFUNC_UNUSED WDL_VARARG_WARN(printf, 1, 2) wdl_log(const char* format, ...) {} 256 | #elif defined(_WIN32) 257 | static void WDL_STATICFUNC_UNUSED WDL_VARARG_WARN(printf, 1, 2) wdl_log(const char* format, ...) 258 | { 259 | int rv; 260 | va_list va; 261 | 262 | char tmp[3800]; 263 | va_start(va, format); 264 | tmp[0] = 0; 265 | rv = _vsnprintf(tmp, sizeof(tmp), format, va); // returns -1 if over, and does not null terminate, ugh 266 | va_end(va); 267 | 268 | if (rv < 0 || rv >= (int)sizeof(tmp) - 1) 269 | tmp[sizeof(tmp) - 1] = 0; 270 | OutputDebugStringA(tmp); 271 | } 272 | #else 273 | #define wdl_log printf 274 | #endif 275 | 276 | static void WDL_STATICFUNC_UNUSED wdl_bswap_copy(void* bout, const void* bin, size_t nelem, size_t elemsz) 277 | { 278 | char p[8], po[8]; 279 | WDL_ASSERT(elemsz > 0); 280 | if (elemsz > 1 && WDL_NORMALLY(elemsz <= sizeof(p))) 281 | { 282 | size_t i, x; 283 | for (i = 0; i < nelem; i++) 284 | { 285 | memcpy(p, bin, elemsz); 286 | for (x = 0; x < elemsz; x++) 287 | po[x] = p[elemsz - 1 - x]; 288 | memcpy(bout, po, elemsz); 289 | bin = (const char*)bin + elemsz; 290 | bout = (char*)bout + elemsz; 291 | } 292 | } 293 | else if (bout != bin) 294 | memmove(bout, bin, elemsz * nelem); 295 | } 296 | 297 | static void WDL_STATICFUNC_UNUSED wdl_memcpy_le(void* bout, const void* bin, size_t nelem, size_t elemsz) 298 | { 299 | WDL_ASSERT(elemsz > 0 && elemsz <= 8); 300 | #ifdef WDL_BIG_ENDIAN 301 | if (elemsz > 1) 302 | wdl_bswap_copy(bout, bin, nelem, elemsz); 303 | else 304 | #endif 305 | if (bout != bin) 306 | memmove(bout, bin, elemsz * nelem); 307 | } 308 | 309 | static void WDL_STATICFUNC_UNUSED wdl_memcpy_be(void* bout, const void* bin, size_t nelem, size_t elemsz) 310 | { 311 | WDL_ASSERT(elemsz > 0 && elemsz <= 8); 312 | #ifdef WDL_LITTLE_ENDIAN 313 | if (elemsz > 1) 314 | wdl_bswap_copy(bout, bin, nelem, elemsz); 315 | else 316 | #endif 317 | if (bout != bin) 318 | memmove(bout, bin, elemsz * nelem); 319 | } 320 | 321 | static void WDL_STATICFUNC_UNUSED wdl_mem_store_int(void* bout, int v) 322 | { 323 | memcpy(bout, &v, sizeof(v)); 324 | } 325 | 326 | static void WDL_STATICFUNC_UNUSED wdl_mem_store_int_le(void* bout, int v) 327 | { 328 | wdl_memcpy_le(bout, &v, 1, sizeof(v)); 329 | } 330 | 331 | static void WDL_STATICFUNC_UNUSED wdl_mem_store_int_be(void* bout, int v) 332 | { 333 | wdl_memcpy_be(bout, &v, 1, sizeof(v)); 334 | } 335 | 336 | static int WDL_STATICFUNC_UNUSED wdl_mem_load_int(const void* rd) 337 | { 338 | int v; 339 | memcpy(&v, rd, sizeof(v)); 340 | return v; 341 | } 342 | 343 | static int WDL_STATICFUNC_UNUSED wdl_mem_load_int_le(const void* rd) 344 | { 345 | int v; 346 | wdl_memcpy_le(&v, rd, 1, sizeof(v)); 347 | return v; 348 | } 349 | 350 | static int WDL_STATICFUNC_UNUSED wdl_mem_load_int_be(const void* rd) 351 | { 352 | int v; 353 | wdl_memcpy_be(&v, rd, 1, sizeof(v)); 354 | return v; 355 | } 356 | 357 | 358 | #endif 359 | -------------------------------------------------------------------------------- /dsp/ResamplingContainer/Dependencies/WDL/heapbuf.h: -------------------------------------------------------------------------------- 1 | /* 2 | WDL - heapbuf.h 3 | Copyright (C) 2005 and later Cockos Incorporated 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | 21 | */ 22 | 23 | /* 24 | 25 | This file provides the interface and implementation for WDL_HeapBuf, a simple 26 | malloc() wrapper for resizeable blocks. 27 | 28 | Also in this file is WDL_TypedBuf which is a templated version WDL_HeapBuf 29 | that manages type and type-size. 30 | 31 | */ 32 | 33 | #ifndef _WDL_HEAPBUF_H_ 34 | #define _WDL_HEAPBUF_H_ 35 | 36 | #ifndef WDL_HEAPBUF_IMPL_ONLY 37 | 38 | #ifdef WDL_HEAPBUF_TRACE 39 | #include 40 | #define WDL_HEAPBUF_TRACEPARM(x) , (x) 41 | #else 42 | #define WDL_HEAPBUF_TRACEPARM(x) 43 | #endif 44 | 45 | #include "wdltypes.h" 46 | 47 | class WDL_HeapBuf 48 | { 49 | public: 50 | // interface 51 | #ifdef WDL_HEAPBUF_INTF_ONLY 52 | void* Resize(int newsize, bool resizedown = true); 53 | void CopyFrom(const WDL_HeapBuf* hb, bool exactCopyOfConfig = false); 54 | #endif 55 | void* Get() const { return m_size ? m_buf : NULL; } // returns NULL if size is 0 56 | void* GetFast() const { return m_buf; } // returns last buffer if size is 0 57 | int GetSize() const { return m_size; } 58 | void* GetAligned(int align) const { return (void*)(((UINT_PTR)Get() + (align - 1)) & ~(UINT_PTR)(align - 1)); } 59 | 60 | void SetGranul(int granul) { m_granul = granul; } 61 | int GetGranul() const { return m_granul; } 62 | 63 | void* ResizeOK(int newsize, bool resizedown = true) 64 | { 65 | void* p = Resize(newsize, resizedown); 66 | return GetSize() == newsize ? p : NULL; 67 | } 68 | 69 | WDL_HeapBuf(const WDL_HeapBuf& cp) 70 | { 71 | m_buf = 0; 72 | CopyFrom(&cp, true); 73 | } 74 | WDL_HeapBuf& operator=(const WDL_HeapBuf& cp) 75 | { 76 | CopyFrom(&cp, false); 77 | return *this; 78 | } 79 | 80 | 81 | #ifndef WDL_HEAPBUF_TRACE 82 | explicit WDL_HeapBuf(int granul = 4096) 83 | : m_buf(NULL) 84 | , m_alloc(0) 85 | , m_size(0) 86 | , m_granul(granul) 87 | { 88 | } 89 | ~WDL_HeapBuf() { free(m_buf); } 90 | #else 91 | explicit WDL_HeapBuf(int granul = 4096, const char* tracetype = "WDL_HeapBuf") 92 | : m_buf(NULL) 93 | , m_alloc(0) 94 | , m_size(0) 95 | , m_granul(granul) 96 | { 97 | m_tracetype = tracetype; 98 | wdl_log("WDL_HeapBuf: created type: %s granul=%d\n", tracetype, granul); 99 | } 100 | ~WDL_HeapBuf() 101 | { 102 | wdl_log("WDL_HeapBuf: destroying type: %s (alloc=%d, size=%d)\n", m_tracetype, m_alloc, m_size); 103 | free(m_buf); 104 | } 105 | #endif 106 | 107 | #endif // !WDL_HEAPBUF_IMPL_ONLY 108 | 109 | // implementation bits 110 | #ifndef WDL_HEAPBUF_INTF_ONLY 111 | #ifdef WDL_HEAPBUF_IMPL_ONLY 112 | void* WDL_HeapBuf::Resize(int newsize, bool resizedown) 113 | #else 114 | void* Resize(int newsize, bool resizedown = true) 115 | #endif 116 | { 117 | if (newsize < 0) 118 | newsize = 0; 119 | #ifdef DEBUG_TIGHT_ALLOC // horribly slow, do not use for release builds 120 | if (newsize == m_size) 121 | return m_buf; 122 | 123 | int a = newsize; 124 | if (a > m_size) 125 | a = m_size; 126 | void* newbuf = newsize ? malloc(newsize) : 0; 127 | if (!newbuf && newsize) 128 | { 129 | #ifdef WDL_HEAPBUF_ONMALLOCFAIL 130 | WDL_HEAPBUF_ONMALLOCFAIL(newsize) 131 | #endif 132 | return m_buf; 133 | } 134 | if (newbuf && m_buf) 135 | memcpy(newbuf, m_buf, a); 136 | m_size = m_alloc = newsize; 137 | free(m_buf); 138 | return m_buf = newbuf; 139 | #endif 140 | 141 | if (newsize != m_size || (resizedown && newsize < m_alloc / 2)) 142 | { 143 | int resizedown_under = 0; 144 | if (resizedown && newsize < m_size) 145 | { 146 | // shrinking buffer: only shrink if allocation decreases to min(alloc/2, alloc-granul*4) or 0 147 | resizedown_under = m_alloc - (m_granul << 2); 148 | if (resizedown_under > m_alloc / 2) 149 | resizedown_under = m_alloc / 2; 150 | if (resizedown_under < 1) 151 | resizedown_under = 1; 152 | } 153 | 154 | if (newsize > m_alloc || newsize < resizedown_under) 155 | { 156 | int granul = newsize / 2; 157 | int newalloc; 158 | if (granul < m_granul) 159 | granul = m_granul; 160 | 161 | if (newsize < 1) 162 | newalloc = 0; 163 | else if (m_granul < 4096) 164 | newalloc = newsize + granul; 165 | else 166 | { 167 | granul &= ~4095; 168 | if (granul < 4096) 169 | granul = 4096; 170 | else if (granul > 4 * 1024 * 1024) 171 | granul = 4 * 1024 * 1024; 172 | newalloc = ((newsize + granul + 96) & ~4095) - 96; 173 | } 174 | 175 | if (newalloc != m_alloc) 176 | { 177 | 178 | #ifdef WDL_HEAPBUF_TRACE 179 | wdl_log("WDL_HeapBuf: type %s realloc(%d) from %d\n", m_tracetype, newalloc, m_alloc); 180 | #endif 181 | if (newalloc <= 0) 182 | { 183 | free(m_buf); 184 | m_buf = 0; 185 | m_alloc = 0; 186 | m_size = 0; 187 | return 0; 188 | } 189 | void* nbuf = realloc(m_buf, newalloc); 190 | if (!nbuf) 191 | { 192 | if (!(nbuf = malloc(newalloc))) 193 | { 194 | #ifdef WDL_HEAPBUF_ONMALLOCFAIL 195 | WDL_HEAPBUF_ONMALLOCFAIL(newalloc); 196 | #endif 197 | return m_size ? m_buf : 0; // failed, do not resize 198 | } 199 | 200 | if (m_buf) 201 | { 202 | int sz = newsize < m_size ? newsize : m_size; 203 | if (sz > 0) 204 | memcpy(nbuf, m_buf, sz); 205 | free(m_buf); 206 | } 207 | } 208 | 209 | m_buf = nbuf; 210 | m_alloc = newalloc; 211 | } // alloc size change 212 | } // need size up or down 213 | m_size = newsize; 214 | } // size change 215 | return m_size ? m_buf : 0; 216 | } 217 | 218 | #ifdef WDL_HEAPBUF_IMPL_ONLY 219 | void WDL_HeapBuf::CopyFrom(const WDL_HeapBuf* hb, bool exactCopyOfConfig) 220 | #else 221 | void CopyFrom(const WDL_HeapBuf* hb, bool exactCopyOfConfig = false) 222 | #endif 223 | { 224 | if (exactCopyOfConfig) // copy all settings 225 | { 226 | free(m_buf); 227 | 228 | #ifdef WDL_HEAPBUF_TRACE 229 | m_tracetype = hb->m_tracetype; 230 | #endif 231 | m_granul = hb->m_granul; 232 | 233 | m_size = m_alloc = 0; 234 | m_buf = hb->m_buf && hb->m_alloc > 0 ? malloc(m_alloc = hb->m_alloc) : NULL; 235 | #ifdef WDL_HEAPBUF_ONMALLOCFAIL 236 | if (!m_buf && m_alloc) 237 | { 238 | WDL_HEAPBUF_ONMALLOCFAIL(m_alloc) 239 | }; 240 | #endif 241 | if (m_buf) 242 | memcpy(m_buf, hb->m_buf, m_size = hb->m_size); 243 | else 244 | m_alloc = 0; 245 | } 246 | else // copy just the data + size 247 | { 248 | const int newsz = hb->GetSize(); 249 | Resize(newsz, true); 250 | if (GetSize() != newsz) 251 | Resize(0); 252 | else 253 | memcpy(Get(), hb->Get(), newsz); 254 | } 255 | } 256 | 257 | #endif // ! WDL_HEAPBUF_INTF_ONLY 258 | 259 | #ifndef WDL_HEAPBUF_IMPL_ONLY 260 | 261 | private: 262 | void* m_buf; 263 | int m_alloc; 264 | int m_size; 265 | int m_granul; 266 | 267 | #if defined(_WIN64) || defined(__LP64__) 268 | public: 269 | int ___pad; // keep size 8 byte aligned 270 | #endif 271 | 272 | #ifdef WDL_HEAPBUF_TRACE 273 | const char* m_tracetype; 274 | #endif 275 | }; 276 | 277 | template 278 | class WDL_TypedBuf 279 | { 280 | public: 281 | PTRTYPE* Get() const { return (PTRTYPE*)m_hb.Get(); } 282 | PTRTYPE* GetFast() const { return (PTRTYPE*)m_hb.GetFast(); } 283 | int GetSize() const { return m_hb.GetSize() / (unsigned int)sizeof(PTRTYPE); } 284 | int GetSizeBytes() const { return m_hb.GetSize(); } 285 | 286 | PTRTYPE* Resize(int newsize, bool resizedown = true) 287 | { 288 | return (PTRTYPE*)m_hb.Resize(newsize * sizeof(PTRTYPE), resizedown); 289 | } 290 | PTRTYPE* ResizeOK(int newsize, bool resizedown = true) 291 | { 292 | return (PTRTYPE*)m_hb.ResizeOK(newsize * sizeof(PTRTYPE), resizedown); 293 | } 294 | 295 | void SetToZero() { memset(m_hb.Get(), 0, m_hb.GetSize()); } 296 | 297 | PTRTYPE* GetAligned(int align) const { return (PTRTYPE*)m_hb.GetAligned(align); } 298 | 299 | PTRTYPE* Add(PTRTYPE val) 300 | { 301 | const int sz = GetSize(); 302 | PTRTYPE* p = ResizeOK(sz + 1, false); 303 | if (p) 304 | { 305 | p[sz] = val; 306 | return p + sz; 307 | } 308 | return NULL; 309 | } 310 | PTRTYPE* Add(const PTRTYPE* buf, int bufsz) 311 | { 312 | if (bufsz > 0) 313 | { 314 | const int sz = GetSize(); 315 | PTRTYPE* p = ResizeOK(sz + bufsz, false); 316 | if (p) 317 | { 318 | p += sz; 319 | if (buf) 320 | memcpy(p, buf, bufsz * sizeof(PTRTYPE)); 321 | else 322 | memset((char*)p, 0, bufsz * sizeof(PTRTYPE)); 323 | return p; 324 | } 325 | } 326 | return NULL; 327 | } 328 | PTRTYPE* Set(const PTRTYPE* buf, int bufsz) 329 | { 330 | if (bufsz >= 0) 331 | { 332 | PTRTYPE* p = ResizeOK(bufsz, false); 333 | if (p) 334 | { 335 | if (buf) 336 | memcpy(p, buf, bufsz * sizeof(PTRTYPE)); 337 | else 338 | memset((char*)p, 0, bufsz * sizeof(PTRTYPE)); 339 | return p; 340 | } 341 | } 342 | return NULL; 343 | } 344 | PTRTYPE* Insert(PTRTYPE val, int idx) 345 | { 346 | const int sz = GetSize(); 347 | if (idx >= 0 && idx <= sz) 348 | { 349 | PTRTYPE* p = ResizeOK(sz + 1, false); 350 | if (p) 351 | { 352 | memmove(p + idx + 1, p + idx, (sz - idx) * sizeof(PTRTYPE)); 353 | p[idx] = val; 354 | return p + idx; 355 | } 356 | } 357 | return NULL; 358 | } 359 | 360 | void Delete(int idx) 361 | { 362 | PTRTYPE* p = Get(); 363 | const int sz = GetSize(); 364 | if (idx >= 0 && idx < sz) 365 | { 366 | memmove(p + idx, p + idx + 1, (sz - idx - 1) * sizeof(PTRTYPE)); 367 | Resize(sz - 1, false); 368 | } 369 | } 370 | 371 | void SetGranul(int gran) { m_hb.SetGranul(gran); } 372 | 373 | int Find(PTRTYPE val) const 374 | { 375 | const PTRTYPE* p = Get(); 376 | const int sz = GetSize(); 377 | int i; 378 | for (i = 0; i < sz; ++i) 379 | if (p[i] == val) 380 | return i; 381 | return -1; 382 | } 383 | 384 | #ifndef WDL_HEAPBUF_TRACE 385 | explicit WDL_TypedBuf(int granul = 4096) 386 | : m_hb(granul) 387 | { 388 | } 389 | #else 390 | explicit WDL_TypedBuf(int granul = 4096, const char* tracetype = "WDL_TypedBuf") 391 | : m_hb(granul WDL_HEAPBUF_TRACEPARM(tracetype)) 392 | { 393 | } 394 | #endif 395 | ~WDL_TypedBuf() {} 396 | 397 | WDL_HeapBuf* GetHeapBuf() { return &m_hb; } 398 | const WDL_HeapBuf* GetHeapBuf() const { return &m_hb; } 399 | 400 | int DeleteBatch(bool (*proc)(PTRTYPE* p, void* ctx), 401 | void* ctx = NULL) // proc returns true to delete item. returns number deleted 402 | { 403 | const int sz = GetSize(); 404 | int cnt = 0; 405 | PTRTYPE *rd = Get(), *wr = rd; 406 | for (int x = 0; x < sz; x++) 407 | { 408 | if (!proc(rd, ctx)) 409 | { 410 | if (rd != wr) 411 | *wr = *rd; 412 | wr++; 413 | cnt++; 414 | } 415 | rd++; 416 | } 417 | if (cnt < sz) 418 | Resize(cnt, false); 419 | return sz - cnt; 420 | } 421 | 422 | private: 423 | WDL_HeapBuf m_hb; 424 | }; 425 | 426 | #endif // ! WDL_HEAPBUF_IMPL_ONLY 427 | 428 | #endif // _WDL_HEAPBUF_H_ 429 | -------------------------------------------------------------------------------- /dsp/ResamplingContainer/ResamplingContainer.h: -------------------------------------------------------------------------------- 1 | // File: ResamplingContainer.h 2 | // Created Date: Saturday December 16th 2023 3 | // Author: Steven Atkinson (steven@atkinson.mn) 4 | 5 | // A container for real-time resampling using a Lanczos anti-aliasing filter 6 | 7 | // This file originally came from the iPlug2 library and has been subsequently modified; 8 | // the following license is copied as required from 9 | // https://github.com/iPlug2/iPlug2/blob/40ebb560eba68f096221e99ef0ae826611fc2bda/LICENSE.txt 10 | // ------------------------------------------------------------------------------------- 11 | 12 | /* 13 | iPlug 2 C++ Plug-in Framework. 14 | 15 | Copyright (C) the iPlug 2 Developers. Portions copyright other contributors, see each source file for more information. 16 | 17 | Based on WDL-OL/iPlug by Oli Larkin (2011-2018), and the original iPlug v1 (2008) by John Schwartz / Cockos 18 | 19 | LICENSE: 20 | 21 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable 22 | for any damages arising from the use of this software. 23 | 24 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it 25 | and redistribute it freely, subject to the following restrictions: 26 | 27 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If 28 | you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not 29 | required. 30 | 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original 31 | software. 32 | 1. This notice may not be removed or altered from any source distribution. 33 | 34 | iPlug 2 includes the following 3rd party libraries (see each license info): 35 | 36 | * Cockos WDL https://www.cockos.com/wdl 37 | * NanoVG https://github.com/memononen/nanovg 38 | * NanoSVG https://github.com/memononen/nanosvg 39 | * MetalNanoVG https://github.com/ollix/MetalNanoVG 40 | * RTAudio https://www.music.mcgill.ca/~gary/rtaudio 41 | * RTMidi https://www.music.mcgill.ca/~gary/rtmidi 42 | */ 43 | // ------------------------------------------------------------------------------------- 44 | 45 | #pragma once 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | // #include "IPlugPlatform.h" 52 | 53 | // #include "heapbuf.h" 54 | #include "Dependencies/WDL/ptrlist.h" 55 | 56 | #include "Dependencies/LanczosResampler.h" 57 | 58 | namespace dsp 59 | { 60 | 61 | /** A multi-channel real-time resampling container that can be used to resample 62 | * audio processing to a specified sample rate for the situation where you have 63 | * some arbitary DSP code that requires a specific sample rate, then back to 64 | * the original external sample rate, encapsulating the arbitrary DSP code. 65 | 66 | * Three modes are supported: 67 | * - Linear interpolation: simple linear interpolation between samples 68 | * - Cubic interpolation: cubic interpolation between samples 69 | * - Lanczos: Lanczos resampling uses an approximation of the sinc function to 70 | * interpolate between samples. This is the highest quality resampling mode. 71 | * 72 | * The Lanczos resampler has a configurable filter size (A) that affects the 73 | * latency of the resampler. It can also optionally use SIMD instructions to 74 | * when T==float. 75 | * 76 | * 77 | * @tparam T the sampletype 78 | * @tparam NCHANS the number of channels 79 | * @tparam A The Lanczos filter size for the LanczosResampler resampler mode 80 | A higher value makes the filter closer to an 81 | ideal stop-band that rejects high-frequency content (anti-aliasing), 82 | but at the expense of higher latency 83 | */ 84 | template 85 | class ResamplingContainer 86 | { 87 | public: 88 | using BlockProcessFunc = std::function; 89 | using LanczosResampler = LanczosResampler; 90 | 91 | // :param renderingSampleRate: The sample rate required by the code to be encapsulated. 92 | ResamplingContainer(double renderingSampleRate) 93 | : mRenderingSampleRate(renderingSampleRate) 94 | { 95 | } 96 | 97 | ResamplingContainer(const ResamplingContainer&) = delete; 98 | ResamplingContainer& operator=(const ResamplingContainer&) = delete; 99 | 100 | // :param inputSampleRate: The external sample rate interacting with this object. 101 | // :param blockSize: The largest block size that will be given to this class to process until Reset() is called 102 | // again. 103 | void Reset(double inputSampleRate, int blockSize = DEFAULT_BLOCK_SIZE) 104 | { 105 | if (mInputSampleRate == inputSampleRate && mMaxBlockSize == blockSize) 106 | { 107 | ClearBuffers(); 108 | return; 109 | } 110 | 111 | mInputSampleRate = inputSampleRate; 112 | mRatio1 = mInputSampleRate / mRenderingSampleRate; 113 | mRatio2 = mRenderingSampleRate / mInputSampleRate; 114 | // The buffers for the encapsulated code need to be long enough to hold the correesponding number of samples 115 | mMaxBlockSize = blockSize; 116 | mMaxEncapsulatedBlockSize = MaxEncapsulatedBlockSize(blockSize); 117 | 118 | mScratchExternalInputData.Resize(mMaxBlockSize * NCHANS); // This may contain junk right now. 119 | mEncapsulatedInputData.Resize(mMaxEncapsulatedBlockSize * NCHANS); // This may contain junk right now. 120 | mEncapsulatedOutputData.Resize(mMaxEncapsulatedBlockSize * NCHANS); // This may contain junk right now. 121 | mScratchExternalInputPointers.Empty(); 122 | mEncapsulatedInputPointers.Empty(); 123 | mEncapsulatedOutputPointers.Empty(); 124 | 125 | for (auto chan = 0; chan < NCHANS; chan++) 126 | { 127 | mScratchExternalInputPointers.Add(mScratchExternalInputData.Get() + (chan * mMaxBlockSize)); 128 | mEncapsulatedInputPointers.Add(mEncapsulatedInputData.Get() + (chan * mMaxEncapsulatedBlockSize)); 129 | mEncapsulatedOutputPointers.Add(mEncapsulatedOutputData.Get() + (chan * mMaxEncapsulatedBlockSize)); 130 | } 131 | 132 | { 133 | mResampler1 = std::make_unique(mInputSampleRate, mRenderingSampleRate); 134 | mResampler2 = std::make_unique(mRenderingSampleRate, mInputSampleRate); 135 | 136 | // Zeroes the scratch pointers so that we warm up with silence. 137 | ClearBuffers(); 138 | 139 | // Warm up the resampling container with enough silence that the first real buffer can yield the required number 140 | // of output samples. 141 | const auto midSamples = mResampler2->GetNumSamplesRequiredFor(1); 142 | mLatency = int(mResampler1->GetNumSamplesRequiredFor(midSamples)); 143 | // 1. Push some silence through the first resampler. 144 | // 145 | mResampler1->PushBlock(mScratchExternalInputPointers.GetList(), mLatency); 146 | const size_t populated = mResampler1->PopBlock(mEncapsulatedInputPointers.GetList(), midSamples); 147 | if (populated < midSamples) 148 | { 149 | throw std::runtime_error("Didn't get enough samples required for pre-population!"); 150 | } 151 | // 2. "process" the warm-up in the encapsulated DSP. 152 | // Since this is an audio effect, we can assume that (1) it's causal and (2) that it's silent until 153 | // a non-silent input is given to it. 154 | // Therefore, we don't *acutally* need to use `func()`--we can assume that it would output silence! 155 | // func(mEncapsulatedInputPointers.GetList(), mEncapsulatedOutputPointers.GetList(), (int)populated); 156 | FallbackFunc(mEncapsulatedInputPointers.GetList(), mEncapsulatedOutputPointers.GetList(), (int)populated); 157 | mResampler2->PushBlock(mEncapsulatedOutputPointers.GetList(), populated); 158 | // Now we're ready for the first "real" buffer. 159 | } 160 | } 161 | 162 | /** Resample an input block with a per-block function (up sample input -> process with function -> down sample) 163 | * @param inputs Two-dimensional array containing the non-interleaved input buffers of audio samples for all channels 164 | * @param outputs Two-dimensional array for audio output (non-interleaved). 165 | * @param nFrames The block size for this block: number of samples per channel. 166 | * @param func The function that processes the audio sample at the higher sampling rate. NOTE: std::function can call 167 | * malloc if you pass in captures */ 168 | void ProcessBlock(T** inputs, T** outputs, int nFrames, BlockProcessFunc func) 169 | { 170 | mResampler1->PushBlock(inputs, nFrames); 171 | // This is the most samples the encapsualted context might get. Sometimes it'll get fewer. 172 | const auto maxEncapsulatedLen = MaxEncapsulatedBlockSize(nFrames); 173 | 174 | // Process as much audio as you can with the encapsulated DSP, and push it into the second resampler. 175 | // This will give the second reasmpler enough for it to pop the required buffer size to complete this function 176 | // correctly. 177 | while (mResampler1->GetNumSamplesRequiredFor(1) == 0) // i.e. there's more to process 178 | { 179 | // Get a block no larger than the encapsulated DSP is expecting. 180 | const size_t populated1 = mResampler1->PopBlock(mEncapsulatedInputPointers.GetList(), maxEncapsulatedLen); 181 | if (populated1 > maxEncapsulatedLen) 182 | { 183 | throw std::runtime_error("Got more encapsulated samples than the encapsulated DSP is prepared to handle!"); 184 | } 185 | func(mEncapsulatedInputPointers.GetList(), mEncapsulatedOutputPointers.GetList(), (int)populated1); 186 | // And push the results into the second resampler so that it has what the external context requires. 187 | mResampler2->PushBlock(mEncapsulatedOutputPointers.GetList(), populated1); 188 | } 189 | 190 | // Pop the required output from the second resampler for the external context. 191 | const auto populated2 = mResampler2->PopBlock(outputs, nFrames); 192 | if (populated2 < nFrames) 193 | { 194 | std::cerr << "Did not yield enough samples (" << populated2 << ") to provide the required output buffer (expected" 195 | << nFrames << ")! Filling with last sample..." << std::endl; 196 | for (int c = 0; c < NCHANS; c++) 197 | { 198 | const T lastSample = populated2 > 0 ? outputs[c][populated2 - 1] : 0.0; 199 | for (int i = populated2; i < nFrames; i++) 200 | { 201 | outputs[c][i] = lastSample; 202 | } 203 | } 204 | } 205 | // Get ready for the next block: 206 | mResampler1->RenormalizePhases(); 207 | mResampler2->RenormalizePhases(); 208 | } 209 | 210 | int GetLatency() const { return mLatency; } 211 | 212 | private: 213 | static inline int LinearInterpolate(T** inputs, T** outputs, int inputLen, double ratio, int maxOutputLen) 214 | { 215 | // FIXME check through this! 216 | const auto outputLen = std::min(static_cast(std::ceil(static_cast(inputLen) / ratio)), maxOutputLen); 217 | 218 | for (auto writePos = 0; writePos < outputLen; writePos++) 219 | { 220 | const auto readPos = ratio * static_cast(writePos); 221 | const auto readPostionTrunc = std::floor(readPos); 222 | const auto readPosInt = static_cast(readPostionTrunc); 223 | 224 | if (readPosInt < inputLen) 225 | { 226 | const auto y = readPos - readPostionTrunc; 227 | 228 | for (auto chan = 0; chan < NCHANS; chan++) 229 | { 230 | const auto x0 = inputs[chan][readPosInt]; 231 | const auto x1 = ((readPosInt + 1) < inputLen) ? inputs[chan][readPosInt + 1] : inputs[chan][readPosInt - 1]; 232 | outputs[chan][writePos] = (1.0 - y) * x0 + y * x1; 233 | } 234 | } 235 | } 236 | 237 | return outputLen; 238 | } 239 | 240 | static inline int CubicInterpolate(T** inputs, T** outputs, int inputLen, double ratio, int maxOutputLen) 241 | { 242 | // FIXME check through this! 243 | const auto outputLen = std::min(static_cast(std::ceil(static_cast(inputLen) / ratio)), maxOutputLen); 244 | 245 | for (auto writePos = 0; writePos < outputLen; writePos++) 246 | { 247 | const auto readPos = ratio * static_cast(writePos); 248 | const auto readPostionTrunc = std::floor(readPos); 249 | const auto readPosInt = static_cast(readPostionTrunc); 250 | 251 | if (readPosInt < inputLen) 252 | { 253 | const auto y = readPos - readPostionTrunc; 254 | 255 | for (auto chan = 0; chan < NCHANS; chan++) 256 | { 257 | const auto xm1 = ((readPosInt - 1) > 0) ? inputs[chan][readPosInt - 1] : 0.0f; 258 | const auto x0 = ((readPosInt) < inputLen) ? inputs[chan][readPosInt] : inputs[chan][readPosInt - 1]; 259 | const auto x1 = ((readPosInt + 1) < inputLen) ? inputs[chan][readPosInt + 1] : inputs[chan][readPosInt - 1]; 260 | const auto x2 = ((readPosInt + 2) < inputLen) ? inputs[chan][readPosInt + 2] : inputs[chan][readPosInt - 1]; 261 | 262 | const auto c = (x1 - xm1) * 0.5; 263 | const auto v = x0 - x1; 264 | const auto w = c + v; 265 | const auto a = w + v + (x2 - x0) * 0.5; 266 | const auto b = w + a; 267 | 268 | outputs[chan][writePos] = ((((a * y) - b) * y + c) * y + x0); 269 | } 270 | } 271 | } 272 | 273 | return outputLen; 274 | } 275 | 276 | void ClearBuffers() 277 | { 278 | memset(mScratchExternalInputData.Get(), 0.0f, DataSize(mMaxBlockSize)); 279 | const auto encapsulatedDataSize = DataSize(mMaxEncapsulatedBlockSize); 280 | memset(mEncapsulatedInputData.Get(), 0.0f, encapsulatedDataSize); 281 | memset(mEncapsulatedOutputData.Get(), 0.0f, encapsulatedDataSize); 282 | 283 | if (mResampler1 != nullptr) 284 | { 285 | mResampler1->ClearBuffer(); 286 | } 287 | if (mResampler2 != nullptr) 288 | { 289 | mResampler2->ClearBuffer(); 290 | } 291 | } 292 | 293 | // How big could the corresponding encapsulated buffer be for a buffer at the external sample rate of a given size? 294 | int MaxEncapsulatedBlockSize(const int externalBlockSize) const 295 | { 296 | return static_cast(std::ceil(static_cast(externalBlockSize) / mRatio1)); 297 | } 298 | 299 | // Size of the multi-channel data for a given block size 300 | size_t DataSize(const int blockSize) const { return blockSize * NCHANS * sizeof(T); }; 301 | 302 | void FallbackFunc(T** inputs, T** outputs, int n) 303 | { 304 | for (int i = 0; i < NCHANS; i++) 305 | { 306 | memcpy(inputs[i], outputs[i], n * sizeof(T)); 307 | } 308 | } 309 | 310 | // Buffers for scratch input data for Reset() to use 311 | WDL_TypedBuf mScratchExternalInputData; 312 | WDL_PtrList mScratchExternalInputPointers; 313 | // Buffers for the input & output to the encapsulated DSP 314 | WDL_TypedBuf mEncapsulatedInputData; 315 | WDL_PtrList mEncapsulatedInputPointers; 316 | WDL_TypedBuf mEncapsulatedOutputData; 317 | WDL_PtrList mEncapsulatedOutputPointers; 318 | // Sample rate ratio from external to encapsulated, from encapsulated to external. 319 | double mRatio1 = 0.0, mRatio2 = 0.0; 320 | // Sample rate of the external context. 321 | double mInputSampleRate = 0.0; 322 | // The size of the largest block the external context may provide. (It might provide something smaller.) 323 | int mMaxBlockSize = 0; 324 | // The size of the largest possible encapsulated block 325 | int mMaxEncapsulatedBlockSize = 0; 326 | // How much latency this object adds due to both of its resamplers. This does _not_ include the latency due to the 327 | // encapsulated `func()`. 328 | int mLatency = 0; 329 | // The sample rate required by the DSP that this object encapsulates 330 | const double mRenderingSampleRate; 331 | // Pair of resamplers for (1) external -> encapsulated, (2) encapsulated -> external 332 | std::unique_ptr mResampler1, mResampler2; 333 | }; 334 | 335 | }; // namespace dsp 336 | -------------------------------------------------------------------------------- /dsp/ResamplingContainer/Dependencies/LanczosResampler.h: -------------------------------------------------------------------------------- 1 | // File: LanczosResampler.h 2 | // Created Date: Saturday December 16th 2023 3 | // Author: Steven Atkinson (steven@atkinson.mn) 4 | 5 | 6 | // This file originally came from the iPlug2 library and has been subsequently modified; 7 | // the following license is copied as required from 8 | // https://github.com/iPlug2/iPlug2/blob/40ebb560eba68f096221e99ef0ae826611fc2bda/LICENSE.txt 9 | // ------------------------------------------------------------------------------------- 10 | 11 | /* 12 | iPlug 2 C++ Plug-in Framework. 13 | 14 | Copyright (C) the iPlug 2 Developers. Portions copyright other contributors, see each source file for more information. 15 | 16 | Based on WDL-OL/iPlug by Oli Larkin (2011-2018), and the original iPlug v1 (2008) by John Schwartz / Cockos 17 | 18 | LICENSE: 19 | 20 | This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable 21 | for any damages arising from the use of this software. 22 | 23 | Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it 24 | and redistribute it freely, subject to the following restrictions: 25 | 26 | 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If 27 | you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not 28 | required. 29 | 1. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original 30 | software. 31 | 1. This notice may not be removed or altered from any source distribution. 32 | 33 | iPlug 2 includes the following 3rd party libraries (see each license info): 34 | 35 | * Cockos WDL https://www.cockos.com/wdl 36 | * NanoVG https://github.com/memononen/nanovg 37 | * NanoSVG https://github.com/memononen/nanosvg 38 | * MetalNanoVG https://github.com/ollix/MetalNanoVG 39 | * RTAudio https://www.music.mcgill.ca/~gary/rtaudio 40 | * RTMidi https://www.music.mcgill.ca/~gary/rtmidi 41 | */ 42 | // ------------------------------------------------------------------------------------- 43 | 44 | /* 45 | This code is derived from 46 | https://github.com/surge-synthesizer/sst-basic-blocks/blob/main/include/sst/basic-blocks/dsp/LanczosResampler.h 47 | 48 | The following license info is copied from the above file: 49 | 50 | * sst-basic-blocks - an open source library of core audio utilities 51 | * built by Surge Synth Team. 52 | * 53 | * Provides a collection of tools useful on the audio thread for blocks, 54 | * modulation, etc... or useful for adapting code to multiple environments. 55 | * 56 | * Copyright 2023, various authors, as described in the GitHub 57 | * transaction log. Parts of this code are derived from similar 58 | * functions original in Surge or ShortCircuit. 59 | * 60 | * sst-basic-blocks is released under the GNU General Public Licence v3 61 | * or later (GPL-3.0-or-later). The license is found in the "LICENSE" 62 | * file in the root of this repository, or at 63 | * https://www.gnu.org/licenses/gpl-3.0.en.html 64 | * 65 | * All source in sst-basic-blocks available at 66 | * https://github.com/surge-synthesizer/sst-basic-blocks 67 | 68 | * A special note on licensing: This file (and only this file) 69 | * has Paul Walker (baconpaul) as the sole author to date. 70 | * 71 | * In order to make this handy small function based on public 72 | * information available to a set of open source projects 73 | * adapting hardware to software, but which are licensed under 74 | * MIT or BSD or similar licenses, this file and only this file 75 | * can be used in an MIT/BSD context as well as a GPL3 context, by 76 | * copying it and modifying it as you see fit. 77 | * 78 | * If you do that, you will need to replace the `sum_ps_to_float` 79 | * call below with either an hadd if you are SSE3 or higher or 80 | * an appropriate reduction operator from your toolkit. 81 | * 82 | * But basically: Need to resample 48k to variable rate with 83 | * a small window and want to use this? Go for it! 84 | * 85 | * For avoidance of doubt, this license exception only 86 | * applies to this file. 87 | */ 88 | 89 | #pragma once 90 | 91 | #include 92 | #include 93 | #include 94 | #include 95 | 96 | #if defined AUDIODSPTOOLS_SIMDE 97 | #if defined(__arm64__) 98 | #define SIMDE_ENABLE_NATIVE_ALIASES 99 | #include "simde/x86/sse2.h" 100 | #else 101 | #include 102 | #endif 103 | #endif 104 | 105 | // #include "IPlugConstants.h" 106 | 107 | namespace dsp 108 | { 109 | /* LanczosResampler 110 | * 111 | * A class that implement Lanczos resampling, optionally using SIMD instructions. 112 | * Define AUDIODSPTOOLS_SIMDE at project level in order to use SIMD and if on non-x86_64 113 | * include the SIMDE library in your search paths in order to translate intel 114 | * intrinsics to e.g. arm64 115 | * 116 | * See https://en.wikipedia.org/wiki/Lanczos_resampling 117 | * 118 | * @tparam T the sampletype 119 | * @tparam NCHANS the number of channels 120 | * @tparam A The Lanczos filter size. A higher value makes the filter closer to an 121 | ideal stop-band that rejects high-frequency content (anti-aliasing), 122 | but at the expense of higher latency 123 | */ 124 | template 125 | class LanczosResampler 126 | { 127 | private: 128 | #if AUDIODSPTOOLS_SIMDE 129 | static_assert(std::is_same::value, "LanczosResampler requires T to be float when using SIMD instructions"); 130 | static_assert(false, "SIMD version has not been checked! You need to remove this to use it at your own risk!"); 131 | #endif 132 | 133 | // The buffer size. This needs to be at least as large as the largest block of samples 134 | // that the input side will see. 135 | // WARNING: hard-coded to accommodate 8192 samples, from 44.1 to 192k! 136 | // If someone is past this, then maybe they know what they're doing ;) 137 | // (size_t)(8192 * 3 * 192000 / 44100)+1 = 106998 138 | static constexpr size_t kBufferSize = 131072; // Round up to pow2. I don't know why, but it doesn't work otherwise. 139 | // The filter width. 2x because the filter goes from -A to A 140 | static constexpr size_t kFilterWidth = A * 2; 141 | // The discretization resolution for the filter table. 142 | static constexpr size_t kTablePoints = 8192; 143 | static constexpr double kDeltaX = 1.0 / (kTablePoints); 144 | 145 | public: 146 | /** Constructor 147 | * @param inputRate The input sample rate 148 | * @param outputRate The output sample rate 149 | */ 150 | LanczosResampler(float inputRate, float outputRate) 151 | : mInputSampleRate(inputRate) 152 | , mOutputSampleRate(outputRate) 153 | { 154 | SetPhases(); 155 | ClearBuffer(); 156 | 157 | 158 | auto kernel = [](double x) { 159 | if (std::fabs(x) < 1e-7) 160 | return T(1.0); 161 | 162 | const auto pi = iplug::PI; 163 | return T(A * std::sin(pi * x) * std::sin(pi * x / A) / (pi * pi * x * x)); 164 | }; 165 | 166 | if (!sTablesInitialized) 167 | { 168 | for (auto t = 0; t < kTablePoints + 1; ++t) 169 | { 170 | const double x0 = kDeltaX * t; 171 | 172 | for (auto i = 0; i < kFilterWidth; ++i) 173 | { 174 | const double x = x0 + i - A; 175 | sTable[t][i] = kernel(x); 176 | } 177 | } 178 | 179 | for (auto t = 0; t < kTablePoints; ++t) 180 | { 181 | for (auto i = 0; i < kFilterWidth; ++i) 182 | { 183 | sDeltaTable[t][i] = sTable[t + 1][i] - sTable[t][i]; 184 | } 185 | } 186 | 187 | for (auto i = 0; i < kFilterWidth; ++i) 188 | { 189 | // Wrap at the end - delta is the same 190 | sDeltaTable[kTablePoints][i] = sDeltaTable[0][i]; 191 | } 192 | sTablesInitialized = true; 193 | } 194 | } 195 | 196 | inline size_t GetNumSamplesRequiredFor(size_t nOutputSamples) const 197 | { 198 | /* 199 | * So (mPhaseIn + mPhaseInIncr * res - mPhaseOut - mPhaseOutIncr * nOutputSamples) * sri > A + 1 200 | * 201 | * Use the fact that mPhaseInIncr = mInputSampleRate and find 202 | * res > (A+1) - (mPhaseIn - mPhaseOut + mPhaseOutIncr * desiredOutputs) * sri 203 | */ 204 | double res = A + 1.0 205 | - ((double)(mPhaseInNumerator - mPhaseOutNumerator - mPhaseOutIncrNumerator * (long)nOutputSamples)) 206 | / ((double)mPhaseDenominator); 207 | 208 | return static_cast(std::max(res + 1.0, 0.0)); 209 | } 210 | 211 | inline void PushBlock(T** inputs, size_t nFrames) 212 | { 213 | for (auto s = 0; s < nFrames; s++) 214 | { 215 | for (auto c = 0; c < NCHANS; c++) 216 | { 217 | mInputBuffer[c][mWritePos] = inputs[c][s]; 218 | mInputBuffer[c][mWritePos + kBufferSize] = inputs[c][s]; // this way we can always wrap 219 | } 220 | 221 | mWritePos = (mWritePos + 1) & (kBufferSize - 1); 222 | mPhaseInNumerator += mPhaseInIncrNumerator; 223 | } 224 | } 225 | 226 | size_t PopBlock(T** outputs, size_t max) 227 | { 228 | int populated = 0; 229 | while (populated < max && (mPhaseInNumerator - mPhaseOutNumerator) > mPhaseDenominator * (A + 1)) 230 | { 231 | ReadSamples(((double)(mPhaseInNumerator - mPhaseOutNumerator)) / ((double)mPhaseDenominator), outputs, populated); 232 | mPhaseOutNumerator += mPhaseOutIncrNumerator; 233 | populated++; 234 | } 235 | return populated; 236 | } 237 | 238 | inline void RenormalizePhases() 239 | { 240 | mPhaseInNumerator -= mPhaseOutNumerator; 241 | mPhaseOutNumerator = 0; 242 | } 243 | 244 | void Reset() { ClearBuffer(); } 245 | 246 | void ClearBuffer() { memset(mInputBuffer, 0, NCHANS * kBufferSize * 2 * sizeof(T)); } 247 | 248 | private: 249 | #ifdef IPLUG_SIMDE 250 | inline void ReadSamples(double xBack, T** outputs, int s) const 251 | { 252 | float bufferReadPosition = static_cast(mWritePos - xBack); 253 | int bufferReadIndex = static_cast(std::floor(bufferReadPosition)); 254 | float bufferFracPosition = 1.0f - (bufferReadPosition - static_cast(bufferReadIndex)); 255 | 256 | bufferReadIndex = (bufferReadIndex + kBufferSize) & (kBufferSize - 1); 257 | bufferReadIndex += (bufferReadIndex <= static_cast(A)) * kBufferSize; 258 | 259 | float tablePosition = bufferFracPosition * kTablePoints; 260 | int tableIndex = static_cast(tablePosition); 261 | float tableFracPosition = (tablePosition - tableIndex); 262 | 263 | __m128 sum[NCHANS]; 264 | for (auto& v : sum) 265 | { 266 | v = _mm_setzero_ps(); // Initialize sum vectors to zero 267 | } 268 | 269 | for (int i = 0; i < A; i += 4) // Process four samples at a time 270 | { 271 | // Load filter coefficients and input samples into SSE registers 272 | __m128 f0 = _mm_load_ps(&sTable[tableIndex][i]); 273 | __m128 df0 = _mm_load_ps(&sDeltaTable[tableIndex][i]); 274 | __m128 f1 = _mm_load_ps(&sTable[tableIndex][A + i]); 275 | __m128 df1 = _mm_load_ps(&sDeltaTable[tableIndex][A + i]); 276 | 277 | // Interpolate filter coefficients 278 | __m128 tfp = _mm_set1_ps(tableFracPosition); 279 | f0 = _mm_add_ps(f0, _mm_mul_ps(df0, tfp)); 280 | f1 = _mm_add_ps(f1, _mm_mul_ps(df1, tfp)); 281 | 282 | for (int c = 0; c < NCHANS; c++) 283 | { 284 | // Load input data 285 | __m128 d0 = 286 | _mm_set_ps(mInputBuffer[c][bufferReadIndex - A + i + 3], mInputBuffer[c][bufferReadIndex - A + i + 2], 287 | mInputBuffer[c][bufferReadIndex - A + i + 1], mInputBuffer[c][bufferReadIndex - A + i]); 288 | __m128 d1 = _mm_set_ps(mInputBuffer[c][bufferReadIndex + i + 3], mInputBuffer[c][bufferReadIndex + i + 2], 289 | mInputBuffer[c][bufferReadIndex + i + 1], mInputBuffer[c][bufferReadIndex + i]); 290 | 291 | // Perform multiplication and accumulate 292 | __m128 result0 = _mm_mul_ps(f0, d0); 293 | __m128 result1 = _mm_mul_ps(f1, d1); 294 | sum[c] = _mm_add_ps(sum[c], _mm_add_ps(result0, result1)); 295 | } 296 | } 297 | 298 | // Extract the final sums and store them in the output 299 | for (int c = 0; c < NCHANS; c++) 300 | { 301 | float sumArray[4]; 302 | _mm_store_ps(sumArray, sum[c]); 303 | outputs[c][s] = sumArray[0] + sumArray[1] + sumArray[2] + sumArray[3]; 304 | } 305 | } 306 | #else // scalar 307 | inline void ReadSamples(double xBack, T** outputs, int s) const 308 | { 309 | double bufferReadPosition = mWritePos - xBack; 310 | int bufferReadIndex = std::floor(bufferReadPosition); 311 | double bufferFracPosition = 1.0 - (bufferReadPosition - bufferReadIndex); 312 | 313 | bufferReadIndex = (bufferReadIndex + kBufferSize) & (kBufferSize - 1); 314 | bufferReadIndex += (bufferReadIndex <= static_cast(A)) * kBufferSize; 315 | 316 | double tablePosition = bufferFracPosition * kTablePoints; 317 | int tableIndex = static_cast(tablePosition); 318 | double tableFracPosition = (tablePosition - tableIndex); 319 | 320 | T sum[NCHANS] = {0.0}; 321 | 322 | for (auto i = 0; i < A; i++) 323 | { 324 | auto f0 = sTable[tableIndex][i]; 325 | const auto df0 = sDeltaTable[tableIndex][i]; 326 | f0 += df0 * tableFracPosition; 327 | 328 | auto f1 = sTable[tableIndex][A + i]; 329 | const auto df1 = sDeltaTable[tableIndex][A + i]; 330 | f1 += df1 * tableFracPosition; 331 | 332 | for (auto c = 0; c < NCHANS; c++) 333 | { 334 | const auto d0 = mInputBuffer[c][bufferReadIndex - A + i]; 335 | const auto d1 = mInputBuffer[c][bufferReadIndex + i]; 336 | const auto rv = (f0 * d0) + (f1 * d1); 337 | sum[c] += rv; 338 | } 339 | } 340 | 341 | for (auto c = 0; c < NCHANS; c++) 342 | { 343 | outputs[c][s] = sum[c]; 344 | } 345 | } 346 | #endif 347 | void SetPhases() 348 | { 349 | // This is going to assume I can treat the sample rates as longs... 350 | // But if they're not, then things will sound just a little wrong and honestly I'm fine with that. 351 | // It's your fault for not using something normal like 44.1k, 48k, or their multiples. 352 | // (Looking at you, VST3PluginTestHost!) 353 | auto AssertLongLikeSampleRate = [](double x) { 354 | if ((double)((long)x) != x) 355 | { 356 | std::cerr << "Expected long-like sample rate; got " << x << " instead! Truncating..." << std::endl; 357 | } 358 | return (long)x; 359 | }; 360 | 361 | // Greatest common denominator 362 | auto gcd = [](long a, long b) -> long { 363 | while (b != 0) 364 | { 365 | long temp = b; 366 | b = a % b; 367 | a = temp; 368 | } 369 | return a; 370 | }; 371 | 372 | const long inputSampleRate = AssertLongLikeSampleRate(mInputSampleRate); 373 | const long outputSampleRate = AssertLongLikeSampleRate(mOutputSampleRate); 374 | const long g = gcd(inputSampleRate, outputSampleRate); 375 | 376 | // mPhaseInIncr = 1.0 377 | // mPhaseOutIncr = mInputSampleRate / mOutputSampleRate 378 | mPhaseInIncrNumerator = outputSampleRate / g; 379 | mPhaseDenominator = mPhaseInIncrNumerator; // val / val = 1 380 | mPhaseOutIncrNumerator = inputSampleRate / g; 381 | }; 382 | 383 | static T sTable alignas(16)[kTablePoints + 1][kFilterWidth]; 384 | static T sDeltaTable alignas(16)[kTablePoints + 1][kFilterWidth]; 385 | static bool sTablesInitialized; 386 | 387 | T mInputBuffer[NCHANS][kBufferSize * 2]; 388 | int mWritePos = 0; 389 | const float mInputSampleRate; 390 | const float mOutputSampleRate; 391 | // Phase is treated as rational numbers to ensure floating point errors don't accumulate and we stay exactly on. 392 | // (Issue 15) 393 | long mPhaseInNumerator = 0; 394 | long mPhaseOutNumerator = 0; 395 | long mPhaseInIncrNumerator = 1; 396 | long mPhaseOutIncrNumerator = 1; 397 | long mPhaseDenominator = 1; 398 | }; 399 | 400 | template 401 | T LanczosResampler::sTable alignas( 402 | 16)[LanczosResampler::kTablePoints + 1][LanczosResampler::kFilterWidth]; 403 | 404 | template 405 | T LanczosResampler::sDeltaTable alignas( 406 | 16)[LanczosResampler::kTablePoints + 1][LanczosResampler::kFilterWidth]; 407 | 408 | template 409 | bool LanczosResampler::sTablesInitialized{false}; 410 | 411 | } // namespace dsp 412 | -------------------------------------------------------------------------------- /dsp/wav.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // wav.cpp 3 | // NeuralAmpModeler-macOS 4 | // 5 | // Created by Steven Atkinson on 12/31/22. 6 | // 7 | 8 | #include 9 | #include // strncmp 10 | #include // pow 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "wav.h" 19 | 20 | struct WaveFileData 21 | { 22 | // TODO use types like uint32_t, etc 23 | struct RiffChunk 24 | { 25 | bool valid = false; // Have we gotten this info yet? 26 | int size; // NB: Of the rest of the file 27 | char format[4]; 28 | } riffChunk; 29 | 30 | struct FmtChunk 31 | { 32 | bool valid = false; 33 | int size; 34 | // PCM: 1 35 | // IEEE: 3 36 | // A-law: 6 37 | // mu-law: 7 38 | // Extensible: 65534 39 | unsigned short audioFormat; 40 | short numChannels; 41 | int sampleRate; 42 | int byteRate; 43 | short blockAlign; 44 | short bitsPerSample; 45 | struct Extensible 46 | { 47 | std::uint16_t validBitsPerSample; 48 | std::uint16_t channelMask; 49 | std::uint32_t subFormat; // PCM, IEEE 50 | } extensible; 51 | } fmtChunk; 52 | 53 | struct FactChunk 54 | { 55 | bool valid = false; 56 | int size; 57 | int numSamples; 58 | } factChunk; 59 | 60 | struct DataChunk 61 | { 62 | bool valid = false; 63 | char id[4]; 64 | int size; 65 | } dataChunk; 66 | }; 67 | 68 | const unsigned short AUDIO_FORMAT_PCM = 1; 69 | const unsigned short AUDIO_FORMAT_IEEE = 3; 70 | const unsigned short AUDIO_FORMAT_ALAW = 6; 71 | const unsigned short AUDIO_FORMAT_MULAW = 7; 72 | const unsigned short AUDIO_FORMAT_EXTENSIBLE = 65534; 73 | 74 | bool idIsNotJunk(char* id) 75 | { 76 | return strncmp(id, "RIFF", 4) == 0 || strncmp(id, "WAVE", 4) == 0 || strncmp(id, "fmt ", 4) == 0 77 | || strncmp(id, "data", 4) == 0; 78 | } 79 | 80 | int ReadInt(std::ifstream& file) 81 | { 82 | int value; 83 | file.read(reinterpret_cast(&value), 4); 84 | return value; 85 | } 86 | 87 | short ReadShort(std::ifstream& file) 88 | { 89 | short value; 90 | file.read(reinterpret_cast(&value), 2); 91 | return value; 92 | } 93 | 94 | unsigned short ReadUnsignedShort(std::ifstream& file) 95 | { 96 | unsigned short value; 97 | file.read(reinterpret_cast(&value), 2); 98 | return value; 99 | } 100 | 101 | dsp::wav::LoadReturnCode ReadJunk(std::ifstream& file) 102 | { 103 | int chunkSize = ReadInt(file); 104 | file.ignore(chunkSize + (chunkSize % 2)); // Pad to 2 bytes at a time 105 | return file.good() ? dsp::wav::LoadReturnCode::SUCCESS : dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 106 | } 107 | 108 | std::string dsp::wav::GetMsgForLoadReturnCode(LoadReturnCode retCode) 109 | { 110 | std::stringstream message; 111 | 112 | switch (retCode) 113 | { 114 | case (LoadReturnCode::ERROR_OPENING): 115 | message << "Failed to open file (is it being used by another " 116 | "program?)"; 117 | break; 118 | case (LoadReturnCode::ERROR_NOT_RIFF): message << "File is not a WAV file."; break; 119 | case (LoadReturnCode::ERROR_NOT_WAVE): message << "File is not a WAV file."; break; 120 | case (LoadReturnCode::ERROR_MISSING_FMT): message << "File is missing expected format chunk."; break; 121 | case (LoadReturnCode::ERROR_INVALID_FILE): message << "WAV file contents are invalid."; break; 122 | case (LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_ALAW): message << "Unsupported file format \"A-law\""; break; 123 | case (LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_MULAW): message << "Unsupported file format \"mu-law\""; break; 124 | case (LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_OTHER): message << "Unsupported file format."; break; 125 | case (LoadReturnCode::ERROR_NOT_MONO): message << "File is not mono."; break; 126 | case (LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE): message << "Unsupported bits per sample"; break; 127 | case (dsp::wav::LoadReturnCode::ERROR_OTHER): message << "???"; break; 128 | default: message << "???"; break; 129 | } 130 | 131 | return message.str(); 132 | } 133 | 134 | dsp::wav::LoadReturnCode ReadRiffChunk(std::ifstream& wavFile, WaveFileData::RiffChunk& chunk) 135 | { 136 | if (chunk.valid) 137 | { 138 | std::cerr << "Error: RIFF chunk already read." << std::endl; 139 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 140 | } 141 | chunk.size = ReadInt(wavFile); 142 | wavFile.read(chunk.format, 4); 143 | if (strncmp(chunk.format, "WAVE", 4) != 0) 144 | { 145 | std::cerr << "Error: File format is not expected 'WAVE'. Got '" << chunk.format << "' instead." << std::endl; 146 | return dsp::wav::LoadReturnCode::ERROR_NOT_WAVE; 147 | } 148 | chunk.valid = true; 149 | return dsp::wav::LoadReturnCode::SUCCESS; 150 | } 151 | 152 | dsp::wav::LoadReturnCode ReadFmtChunk(std::ifstream& wavFile, WaveFileData& wfd, double& sampleRate) 153 | { 154 | if (wfd.fmtChunk.valid) 155 | { 156 | std::cerr << "Error: Format chunk already read." << std::endl; 157 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 158 | } 159 | if (!wfd.riffChunk.valid) 160 | { 161 | std::cerr << "Error: Missing RIFF chunk." << std::endl; 162 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 163 | } 164 | 165 | wfd.fmtChunk.size = ReadInt(wavFile); 166 | if (wfd.fmtChunk.size < 16) 167 | { 168 | std::cerr << "WAV chunk 1 size is " << wfd.fmtChunk.size 169 | << ", which is smaller than the requried 16 to fit the expected " 170 | "information." 171 | << std::endl; 172 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 173 | } 174 | 175 | wfd.fmtChunk.audioFormat = ReadUnsignedShort(wavFile); 176 | std::unordered_set supportedFormats{AUDIO_FORMAT_PCM, AUDIO_FORMAT_IEEE, AUDIO_FORMAT_EXTENSIBLE}; 177 | if (supportedFormats.find(wfd.fmtChunk.audioFormat) == supportedFormats.end()) 178 | { 179 | std::cerr << "Error: Unsupported WAV format detected. "; 180 | switch (wfd.fmtChunk.audioFormat) 181 | { 182 | case AUDIO_FORMAT_ALAW: 183 | std::cerr << "(Got: A-law)" << std::endl; 184 | return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_ALAW; 185 | case AUDIO_FORMAT_MULAW: 186 | std::cerr << "(Got: mu-law)" << std::endl; 187 | return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_MULAW; 188 | default: 189 | std::cerr << "(Got unknown format " << wfd.fmtChunk.audioFormat << ")" << std::endl; 190 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 191 | } 192 | } 193 | 194 | wfd.fmtChunk.numChannels = ReadShort(wavFile); 195 | // HACK 196 | // Note for future: for multi-channel files, samples are laid out with channel in the inner loop. 197 | if (wfd.fmtChunk.numChannels != 1) 198 | { 199 | std::cerr << "Require mono (using for IR loading)" << std::endl; 200 | return dsp::wav::LoadReturnCode::ERROR_NOT_MONO; 201 | } 202 | 203 | wfd.fmtChunk.sampleRate = ReadInt(wavFile); 204 | wfd.fmtChunk.byteRate = ReadInt(wavFile); 205 | wfd.fmtChunk.blockAlign = ReadShort(wavFile); 206 | wfd.fmtChunk.bitsPerSample = ReadShort(wavFile); 207 | int bytesRead = 16; 208 | 209 | if (wfd.fmtChunk.audioFormat == AUDIO_FORMAT_EXTENSIBLE) 210 | { 211 | unsigned short cbSize = ReadUnsignedShort(wavFile); 212 | // Do we need to assert or modify the data loading below if this doesn't match bitsPerSample? 213 | wfd.fmtChunk.extensible.validBitsPerSample = ReadUnsignedShort(wavFile); 214 | auto read_u32 = [&]() -> std::uint32_t { 215 | std::uint8_t b[4]; 216 | wavFile.read((char*)b, 4); 217 | return b[0] | (b[1] << 8) | (b[2] << 16) | (b[3] << 24); 218 | }; 219 | wfd.fmtChunk.extensible.channelMask = read_u32(); 220 | std::uint8_t guid[16]; 221 | wavFile.read((char*)guid, 16); 222 | wfd.fmtChunk.extensible.subFormat = guid[1] << 8 | guid[0]; 223 | bytesRead += cbSize + 2; // Don't forget the 2 for the cbSize itself! 224 | } 225 | 226 | // Skip any extra bytes in the fmt chunk 227 | // This should probably be a remainder of a dword so that we're mod-4 228 | if (wfd.fmtChunk.size > bytesRead) 229 | { 230 | const int extraBytes = wfd.fmtChunk.size - bytesRead; 231 | if (extraBytes >= 4) 232 | { 233 | std::cerr << "More than 4 extra bytes in fmt chunk." << std::endl; 234 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 235 | } 236 | wavFile.ignore(extraBytes); 237 | } 238 | 239 | // Store SR for final return 240 | sampleRate = (double)wfd.fmtChunk.sampleRate; 241 | 242 | wfd.fmtChunk.valid = true; 243 | return dsp::wav::LoadReturnCode::SUCCESS; 244 | } 245 | 246 | dsp::wav::LoadReturnCode ReadFactChunk(std::ifstream& wavFile, WaveFileData& wfd) 247 | { 248 | if (wfd.factChunk.valid) 249 | { 250 | std::cerr << "Error: Duplicate fact chunk." << std::endl; 251 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 252 | } 253 | if (!wfd.riffChunk.valid) 254 | { 255 | std::cerr << "Error: Missing RIFF chunk." << std::endl; 256 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 257 | } 258 | // We could assert that the fmt chunk was also read first but I'm not sure that's necessary for the file to be valid. 259 | 260 | wfd.factChunk.size = ReadInt(wavFile); 261 | if (wfd.factChunk.size != 4) 262 | { 263 | std::cerr << "Error: Invalid fact chunk size. Only 4 is supported; got " << wfd.factChunk.size << " instead." 264 | << std::endl; 265 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 266 | } 267 | wfd.factChunk.numSamples = ReadInt(wavFile); 268 | 269 | wfd.factChunk.valid = true; 270 | return dsp::wav::LoadReturnCode::SUCCESS; 271 | } 272 | 273 | int GetAudioFormat(WaveFileData& wfd) 274 | { 275 | return wfd.fmtChunk.audioFormat == AUDIO_FORMAT_EXTENSIBLE ? wfd.fmtChunk.extensible.subFormat 276 | : wfd.fmtChunk.audioFormat; 277 | } 278 | 279 | dsp::wav::LoadReturnCode ReadDataChunk(std::ifstream& wavFile, WaveFileData& wfd, std::vector& audio) 280 | { 281 | if (wfd.dataChunk.valid) 282 | { 283 | std::cerr << "Error: Already read data chunk." << std::endl; 284 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 285 | } 286 | if (!wfd.riffChunk.valid) 287 | { 288 | std::cerr << "Error: Missing RIFF chunk." << std::endl; 289 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 290 | } 291 | if (!wfd.fmtChunk.valid) // fmt chunk must come before data chunk 292 | { 293 | std::cerr << "Error: Tried to read data chunk before fmt chunk." << std::endl; 294 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 295 | } 296 | if (wfd.fmtChunk.audioFormat == AUDIO_FORMAT_EXTENSIBLE 297 | && !wfd.factChunk.valid) // fact chunk must come before data chunk 298 | { 299 | std::cerr << "Error: Tried to read data chunk before fact chunk for extensible format WAVE file." << std::endl; 300 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 301 | } 302 | 303 | // Size of the data chunk, in bits. 304 | wfd.dataChunk.size = ReadInt(wavFile); 305 | 306 | const int audioFormat = GetAudioFormat(wfd); 307 | if (audioFormat == AUDIO_FORMAT_IEEE) 308 | { 309 | if (wfd.fmtChunk.bitsPerSample == 32) 310 | dsp::wav::_LoadSamples32FloatingPoint(wavFile, wfd.dataChunk.size, audio); 311 | else 312 | { 313 | std::cerr << "Error: Unsupported bits per sample for IEEE files: " << wfd.fmtChunk.bitsPerSample << std::endl; 314 | return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE; 315 | } 316 | } 317 | else if (audioFormat == AUDIO_FORMAT_PCM) 318 | { 319 | if (wfd.fmtChunk.bitsPerSample == 16) 320 | dsp::wav::_LoadSamples16(wavFile, wfd.dataChunk.size, audio); 321 | else if (wfd.fmtChunk.bitsPerSample == 24) 322 | dsp::wav::_LoadSamples24(wavFile, wfd.dataChunk.size, audio); 323 | else if (wfd.fmtChunk.bitsPerSample == 32) 324 | dsp::wav::_LoadSamples32FixedPoint(wavFile, wfd.dataChunk.size, audio); 325 | else 326 | { 327 | std::cerr << "Error: Unsupported bits per sample for PCM files: " << wfd.fmtChunk.bitsPerSample << std::endl; 328 | return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE; 329 | } 330 | } 331 | else 332 | { 333 | std::cerr << "Error: Unsupported audio format: " << audioFormat << std::endl; 334 | return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_OTHER; 335 | } 336 | wfd.dataChunk.valid = true; 337 | return dsp::wav::LoadReturnCode::SUCCESS; 338 | } 339 | 340 | dsp::wav::LoadReturnCode dsp::wav::Load(const char* fileName, std::vector& audio, double& sampleRate) 341 | { 342 | // FYI: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html 343 | // Open the WAV file for reading 344 | std::ifstream wavFile(fileName, std::ios::binary); 345 | 346 | // Check if the file was opened successfully 347 | if (!wavFile.is_open()) 348 | { 349 | std::cerr << "Error opening WAV file" << std::endl; 350 | return dsp::wav::LoadReturnCode::ERROR_OPENING; 351 | } 352 | 353 | char chunkId[4]; 354 | auto ReadChunkID = [&]() { wavFile.read(chunkId, 4); }; 355 | 356 | WaveFileData wfd; 357 | dsp::wav::LoadReturnCode returnCode; 358 | while (!wfd.dataChunk.valid && !wavFile.eof()) 359 | { 360 | ReadChunkID(); 361 | if (!wfd.riffChunk.valid && strncmp(chunkId, "RIFF", 4) != 0) 362 | { 363 | { 364 | std::cerr << "Error: File does not start with expected RIFF chunk. Got" << chunkId << " instead." << std::endl; 365 | wavFile.close(); 366 | return dsp::wav::LoadReturnCode::ERROR_NOT_RIFF; 367 | } 368 | } 369 | // Read the various chunks 370 | if (strncmp(chunkId, "RIFF", 4) == 0) 371 | { 372 | returnCode = ReadRiffChunk(wavFile, wfd.riffChunk); 373 | } 374 | else if (strncmp(chunkId, "fmt ", 4) == 0) 375 | { 376 | returnCode = ReadFmtChunk(wavFile, wfd, sampleRate); 377 | } 378 | else if (strncmp(chunkId, "fact", 4) == 0) 379 | { 380 | returnCode = ReadFactChunk(wavFile, wfd); 381 | } 382 | else if (strncmp(chunkId, "data", 4) == 0) 383 | { 384 | returnCode = ReadDataChunk(wavFile, wfd, audio); 385 | } 386 | else 387 | { // There might be junk chunks; just ignore them. 388 | returnCode = ReadJunk(wavFile); 389 | } 390 | if (returnCode != dsp::wav::LoadReturnCode::SUCCESS) 391 | { 392 | wavFile.close(); 393 | return returnCode; 394 | } 395 | } 396 | wavFile.close(); 397 | if (!wfd.dataChunk.valid) 398 | { // This implicitly asserts that the fmt chunk was read and gave us the sample rate 399 | std::cerr << "Error: File does not contain expected data chunk." << std::endl; 400 | return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; 401 | } 402 | return dsp::wav::LoadReturnCode::SUCCESS; 403 | } 404 | 405 | void dsp::wav::_LoadSamples16(std::ifstream& wavFile, const int chunkSize, std::vector& samples) 406 | { 407 | // Allocate an array to hold the samples 408 | std::vector tmp(chunkSize / 2); // 16 bits (2 bytes) per sample 409 | 410 | // Read the samples from the file into the array 411 | wavFile.read(reinterpret_cast(tmp.data()), chunkSize); 412 | 413 | // Copy into the return array 414 | const float scale = 1.0 / ((double)(1 << 15)); 415 | samples.resize(tmp.size()); 416 | for (auto i = 0; i < samples.size(); i++) 417 | samples[i] = scale * ((float)tmp[i]); // 2^16 418 | } 419 | 420 | void dsp::wav::_LoadSamples24(std::ifstream& wavFile, const int chunkSize, std::vector& samples) 421 | { 422 | // Allocate an array to hold the samples 423 | std::vector tmp(chunkSize / 3); // 24 bits (3 bytes) per sample 424 | // Read in and convert the samples 425 | for (int& x : tmp) 426 | { 427 | x = dsp::wav::_ReadSigned24BitInt(wavFile); 428 | } 429 | 430 | // Copy into the return array 431 | const float scale = 1.0 / ((double)(1 << 23)); 432 | samples.resize(tmp.size()); 433 | for (auto i = 0; i < samples.size(); i++) 434 | samples[i] = scale * ((float)tmp[i]); 435 | } 436 | 437 | int dsp::wav::_ReadSigned24BitInt(std::ifstream& stream) 438 | { 439 | // Read the three bytes of the 24-bit integer. 440 | std::uint8_t bytes[3]; 441 | stream.read(reinterpret_cast(bytes), 3); 442 | 443 | // Combine the three bytes into a single integer using bit shifting and 444 | // masking. This works by isolating each byte using a bit mask (0xff) and then 445 | // shifting the byte to the correct position in the final integer. 446 | int value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16); 447 | 448 | // The value is stored in two's complement format, so if the most significant 449 | // bit (the 24th bit) is set, then the value is negative. In this case, we 450 | // need to extend the sign bit to get the correct negative value. 451 | if (value & (1 << 23)) 452 | { 453 | value |= ~((1 << 24) - 1); 454 | } 455 | 456 | return value; 457 | } 458 | 459 | void dsp::wav::_LoadSamples32FloatingPoint(std::ifstream& wavFile, const int chunkSize, std::vector& samples) 460 | { 461 | // NOTE: 32-bit is float. 462 | samples.resize(chunkSize / 4); // 32 bits (4 bytes) per sample 463 | // Read the samples from the file into the array 464 | wavFile.read(reinterpret_cast(samples.data()), chunkSize); 465 | } 466 | 467 | void dsp::wav::_LoadSamples32FixedPoint(std::ifstream& wavFile, const int chunkSize, std::vector& samples) 468 | { 469 | // Allocate an array to hold the samples 470 | std::vector tmp(chunkSize / 4); // 32 bits (4 bytes) per sample 471 | 472 | // Read the samples from the file into the array 473 | wavFile.read(reinterpret_cast(tmp.data()), chunkSize); 474 | 475 | // Copy into the return array 476 | const float scale = 1.0 / ((double)(1 << 31)); // 2^31 for 32-bit fixed point 477 | samples.resize(tmp.size()); 478 | for (auto i = 0; i < samples.size(); i++) 479 | samples[i] = scale * ((float)tmp[i]); 480 | } 481 | --------------------------------------------------------------------------------