├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── auto-format.yml │ ├── bench.yml │ ├── clang-tidy.yml │ ├── cov.yml │ ├── docs.yml │ └── test.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bench ├── CMakeLists.txt └── WrightOmegaBench.cpp ├── cmake ├── CPM.cmake └── CXXStandard.cmake ├── doxygen ├── Doxyfile ├── Makefile ├── header.html └── logo.png ├── include └── chowdsp_wdf │ ├── README.md │ ├── chowdsp_wdf.h │ ├── math │ ├── omega.h │ ├── sample_type.h │ └── signum.h │ ├── rtype │ ├── root_rtype_adaptor.h │ ├── rtype.h │ ├── rtype_adaptor.h │ ├── rtype_detail.h │ └── wdf_rtype.h │ ├── util │ └── defer_impedance.h │ ├── wdf │ ├── wdf.h │ ├── wdf_adaptors.h │ ├── wdf_base.h │ ├── wdf_nonlinearities.h │ ├── wdf_one_ports.h │ └── wdf_sources.h │ └── wdft │ ├── wdft.h │ ├── wdft_adaptors.h │ ├── wdft_base.h │ ├── wdft_nonlinearities.h │ ├── wdft_one_ports.h │ └── wdft_sources.h ├── notes ├── combining_wdfs.pdf └── combining_wdfs.typ ├── single_include └── chowdsp_wdf │ └── chowdsp_wdf.h ├── tests ├── BasicCircuitTest.cpp ├── BassmanToneStack.h ├── BassmanToneStackPoly.h ├── BaxandallEQ.h ├── BaxandallEQPoly.h ├── CMakeLists.txt ├── CombinedComponentTest.cpp ├── OmegaTest.cpp ├── RTypeTest.cpp ├── SIMDTest.cpp ├── StaticBasicCircuitTest.cpp ├── TestRunner.cpp └── catch2 │ └── catch2.hpp └── tools └── amalgamate ├── CHANGES.md ├── README.md ├── amalgamate.py ├── config_chowdsp_wdf.json └── run_amalgamate.sh /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: Left 7 | AlignOperands: Align 8 | AlignTrailingComments: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: Never 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: All 13 | AllowShortIfStatementsOnASingleLine: Never 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: false 18 | AlwaysBreakTemplateDeclarations: Yes 19 | BinPackArguments: false 20 | BinPackParameters: false 21 | BreakAfterJavaFieldAnnotations: false 22 | BreakBeforeBinaryOperators: NonAssignment 23 | BreakBeforeBraces: Allman 24 | BreakBeforeTernaryOperators: true 25 | BreakConstructorInitializersBeforeComma: false 26 | BreakStringLiterals: false 27 | ColumnLimit: 0 28 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 29 | ConstructorInitializerIndentWidth: 4 30 | ContinuationIndentWidth: 4 31 | Cpp11BracedListStyle: false 32 | DerivePointerAlignment: false 33 | DisableFormat: false 34 | ExperimentalAutoDetectBinPacking: false 35 | ForEachMacros: ['forEachXmlChildElement'] 36 | IndentCaseLabels: true 37 | IndentWidth: 4 38 | IndentWrappedFunctionNames: true 39 | KeepEmptyLinesAtTheStartOfBlocks: false 40 | Language: Cpp 41 | MaxEmptyLinesToKeep: 1 42 | NamespaceIndentation: Inner 43 | PointerAlignment: Left 44 | ReflowComments: false 45 | SortIncludes: false 46 | SpaceAfterCStyleCast: true 47 | SpaceAfterLogicalNot: true 48 | SpaceBeforeAssignmentOperators: true 49 | SpaceBeforeCpp11BracedList: true 50 | SpaceBeforeParens: NonEmptyParentheses 51 | SpaceInEmptyParentheses: false 52 | SpaceBeforeInheritanceColon: true 53 | SpacesInAngles: false 54 | SpacesInCStyleCastParentheses: false 55 | SpacesInContainerLiterals: true 56 | SpacesInParentheses: false 57 | SpacesInSquareBrackets: false 58 | Standard: "c++17" 59 | TabWidth: 4 60 | UseTab: Never 61 | --- 62 | Language: ObjC 63 | BasedOnStyle: Chromium 64 | AlignTrailingComments: true 65 | BreakBeforeBraces: Allman 66 | ColumnLimit: 0 67 | IndentWidth: 4 68 | KeepEmptyLinesAtTheStartOfBlocks: false 69 | ObjCSpaceAfterProperty: true 70 | ObjCSpaceBeforeProtocolList: true 71 | PointerAlignment: Left 72 | SpacesBeforeTrailingComments: 1 73 | TabWidth: 4 74 | UseTab: Never 75 | ... 76 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | HeaderFilterRegex: '.*include/chowdsp_wdf.*' 2 | WarningsAsErrors: '*' 3 | Checks: '-*, 4 | bugprone-argument-comment, 5 | bugprone-assert-side-effect, 6 | bugprone-bad-signal-to-kill-thread, 7 | bugprone-branch-clone, 8 | bugprone-copy-constructor-init, 9 | bugprone-dangling-handle, 10 | bugprone-dynamic-static-initializers, 11 | bugprone-fold-init-type, 12 | bugprone-forward-declaration-namespace, 13 | bugprone-forwarding-reference-overload, 14 | bugprone-inaccurate-erase, 15 | bugprone-incorrect-roundings, 16 | bugprone-integer-division, 17 | bugprone-lambda-function-name, 18 | bugprone-macro-parentheses, 19 | bugprone-macro-repeated-side-effects, 20 | bugprone-misplaced-operator-in-strlen-in-alloc, 21 | bugprone-misplaced-pointer-arithmetic-in-alloc, 22 | bugprone-misplaced-widening-cast, 23 | bugprone-move-forwarding-reference, 24 | bugprone-multiple-statement-macro, 25 | bugprone-no-escape,bugprone-not-null-terminated-result, 26 | bugprone-posix-return, 27 | bugprone-reserved-identifier, 28 | bugprone-sizeof-container, 29 | bugprone-sizeof-expression, 30 | bugprone-spuriously-wake-up-functions, 31 | bugprone-string-constructor, 32 | bugprone-string-integer-assignment, 33 | bugprone-string-literal-with-embedded-nul, 34 | bugprone-suspicious-enum-usage, 35 | bugprone-suspicious-memory-comparison, 36 | bugprone-suspicious-memset-usage, 37 | bugprone-suspicious-missing-comma, 38 | bugprone-suspicious-semicolon, 39 | bugprone-suspicious-string-compare, 40 | bugprone-swapped-arguments, 41 | bugprone-terminating-continue, 42 | bugprone-throw-keyword-missing, 43 | bugprone-too-small-loop-variable, 44 | bugprone-undefined-memory-manipulation, 45 | bugprone-undelegated-constructor, 46 | bugprone-unhandled-self-assignment, 47 | bugprone-unused-raii, 48 | bugprone-unused-return-value, 49 | bugprone-use-after-move, 50 | bugprone-virtual-near-miss, 51 | cert-dcl21-cpp, 52 | cert-dcl58-cpp, 53 | cert-err34-c, 54 | cert-err52-cpp, 55 | cert-err60-cpp, 56 | cert-msc50-cpp, 57 | cert-msc51-cpp, 58 | cert-str34-c, 59 | cppcoreguidelines-interfaces-global-init, 60 | cppcoreguidelines-narrowing-conversions, 61 | cppcoreguidelines-pro-type-member-init, 62 | cppcoreguidelines-pro-type-static-cast-downcast, 63 | google-explicit-constructor, 64 | google-runtime-operator, 65 | hicpp-exception-baseclass, 66 | hicpp-multiway-paths-covered, 67 | misc-misplaced-const, 68 | misc-new-delete-overloads, 69 | misc-non-copyable-objects, 70 | misc-throw-by-value-catch-by-reference, 71 | misc-unconventional-assign-operator, 72 | misc-uniqueptr-reset-release, 73 | modernize-avoid-bind, 74 | modernize-concat-nested-namespaces, 75 | modernize-deprecated-headers, 76 | modernize-deprecated-ios-base-aliases, 77 | modernize-loop-convert, 78 | modernize-make-shared, 79 | modernize-make-unique, 80 | modernize-raw-string-literal, 81 | modernize-redundant-void-arg, 82 | modernize-replace-auto-ptr, 83 | modernize-replace-disallow-copy-and-assign-macro, 84 | modernize-replace-random-shuffle, 85 | modernize-return-braced-init-list, 86 | modernize-shrink-to-fit, 87 | modernize-unary-static-assert, 88 | modernize-use-auto, 89 | modernize-use-bool-literals, 90 | modernize-use-emplace, 91 | modernize-use-equals-default, 92 | modernize-use-equals-delete, 93 | modernize-use-nodiscard, 94 | modernize-use-noexcept, 95 | modernize-use-nullptr, 96 | modernize-use-override, 97 | modernize-use-transparent-functors, 98 | modernize-use-uncaught-exceptions, 99 | mpi-buffer-deref, 100 | mpi-type-mismatch, 101 | openmp-use-default-none, 102 | performance-faster-string-find, 103 | performance-for-range-copy, 104 | performance-implicit-conversion-in-loop, 105 | performance-inefficient-algorithm, 106 | performance-inefficient-string-concatenation, 107 | performance-inefficient-vector-operation, 108 | performance-move-constructor-init, 109 | performance-no-automatic-move, 110 | performance-noexcept-move-constructor, 111 | performance-trivially-destructible, 112 | performance-type-promotion-in-math-fn, 113 | performance-unnecessary-copy-initialization, 114 | performance-unnecessary-value-param, 115 | portability-simd-intrinsics, 116 | readability-const-return-type, 117 | readability-container-size-empty, 118 | readability-convert-member-functions-to-static, 119 | readability-delete-null-pointer, 120 | readability-deleted-default, 121 | readability-make-member-function-const, 122 | readability-misleading-indentation, 123 | readability-misplaced-array-index, 124 | readability-non-const-parameter, 125 | readability-redundant-control-flow, 126 | readability-redundant-declaration, 127 | readability-redundant-function-ptr-dereference, 128 | readability-redundant-smartptr-get, 129 | readability-redundant-string-cstr, 130 | readability-redundant-string-init, 131 | readability-simplify-subscript-expr, 132 | readability-static-definition-in-anonymous-namespace, 133 | readability-string-compare, 134 | readability-uniqueptr-delete-release, 135 | readability-use-anyofallof' 136 | -------------------------------------------------------------------------------- /.github/workflows/auto-format.yml: -------------------------------------------------------------------------------- 1 | name: Auto-formatter 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | paths: 8 | - '**.cpp' 9 | - '**.h' 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build_and_test: 15 | name: Apply clang-format to pull request 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Install Linux Deps 20 | run: | 21 | sudo apt update 22 | sudo apt -y install clang-format-14 23 | clang-format-14 --version 24 | - name: Checkout code 25 | uses: actions/checkout@v2 26 | with: 27 | persist-credentials: false 28 | fetch-depth: 0 29 | 30 | - name: Run clang-format 31 | shell: bash 32 | run: find . -iname "*.h" -o -iname "*.cpp" | xargs clang-format-14 -style=file -verbose -i 33 | 34 | - name: Commit & Push changes 35 | uses: actions-js/push@master 36 | with: 37 | message: "Apply clang-format" 38 | branch: ${{ github.head_ref }} 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: Bench 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build_and_test: 15 | name: Run tests on ${{ matrix.os }} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false # show all errors for each platform (vs. cancel jobs on error) 19 | matrix: 20 | os: [ubuntu-latest, windows-2019, macos-latest] 21 | 22 | steps: 23 | - name: Get latest CMake 24 | uses: lukka/get-cmake@latest 25 | 26 | - name: Checkout code 27 | uses: actions/checkout@v2 28 | 29 | - name: Configure 30 | shell: bash 31 | run: cmake -DCMAKE_BUILD_TYPE=Release -DCHOWDSP_WDF_TEST_WITH_XSIMD_VERSION=8.0.5 -Bbuild 32 | 33 | - name: Build 34 | shell: bash 35 | run: cmake --build build --config Release --parallel 4 --target wright_omega_bench 36 | 37 | - name: Run 38 | shell: bash 39 | run: ./build/bench-binary/wright_omega_bench 40 | -------------------------------------------------------------------------------- /.github/workflows/clang-tidy.yml: -------------------------------------------------------------------------------- 1 | name: Clang-Tidy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build_and_test: 15 | name: Run clang-tidy 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Install Linux Deps 20 | run: | 21 | sudo apt-get update 22 | sudo apt install clang-tidy 23 | 24 | - name: Get latest CMake 25 | uses: lukka/get-cmake@latest 26 | 27 | - name: Checkout code 28 | uses: actions/checkout@v2 29 | 30 | - name: Configure 31 | shell: bash 32 | run: cmake -Bbuild -DCHOWDSP_WDF_RUN_CLANG_TIDY=ON -DCMAKE_EXPORT_COMPILE_COMMANDS=ON 33 | 34 | - name: Build 35 | shell: bash 36 | run: cmake --build build --target chowdsp_wdf_clang_tidy 37 | -------------------------------------------------------------------------------- /.github/workflows/cov.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - develop 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build_and_test: 16 | name: Analyze test coverage with XSIMD version ${{ matrix.xsimd_version }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false # show all errors for each platform (vs. cancel jobs on error) 20 | matrix: 21 | xsimd_version: ["", "8.0.5"] 22 | 23 | steps: 24 | - name: Install Linux Deps 25 | if: runner.os == 'Linux' 26 | run: | 27 | sudo apt-get update 28 | sudo apt install lcov 29 | lcov --version 30 | 31 | - name: Get latest CMake 32 | uses: lukka/get-cmake@latest 33 | 34 | - name: Checkout code 35 | uses: actions/checkout@v2 36 | 37 | - name: Configure 38 | shell: bash 39 | run: cmake -Bbuild -DCHOWDSP_WDF_CODE_COVERAGE=ON -DCHOWDSP_WDF_TEST_WITH_XSIMD_VERSION="${{ matrix.xsimd_version }}" 40 | 41 | - name: Build 42 | shell: bash 43 | run: cmake --build build --parallel 4 --target chowdsp_wdf_tests 44 | 45 | - name: Test 46 | shell: bash 47 | run: ./build/test-binary/chowdsp_wdf_tests 48 | 49 | - name: Collect Coverage Data 50 | shell: bash 51 | run: | 52 | lcov --directory . --capture --output-file coverage.info 53 | lcov --remove coverage.info '/usr/*' "${HOME}"'/.cache/*' '/Applications/Xcode*' '*tests*' --output-file coverage.info 54 | 55 | - name: Report Coverage Data 56 | shell: bash 57 | run: lcov --list coverage.info 58 | 59 | - name: Upload Coverage Data 60 | shell: bash 61 | run: bash <(curl -s https://codecov.io/bash) -f coverage.info || echo "Codecov did not collect coverage reports" 62 | env: 63 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 64 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build_and_test: 12 | name: Make and deploy docs 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Install Linux Deps 17 | run: | 18 | sudo apt-get update 19 | sudo apt install doxygen graphviz 20 | 21 | - name: Checkout code 22 | uses: actions/checkout@v2 23 | 24 | - name: Make Docs 25 | working-directory: doxygen 26 | run: make 27 | 28 | - name: Deploy docs 29 | if: ${{ github.ezvent_name != 'pull_request' }} 30 | uses: garygrossgarten/github-action-scp@release 31 | with: 32 | local: docs 33 | remote: Library/Web/chowdsp/chowdsp_wdf 34 | host: ccrma-gate.stanford.edu 35 | username: jatin 36 | password: ${{ secrets.CCRMA_PASS }} 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | build_and_test: 15 | name: Run tests on ${{ matrix.os }} with XSIMD version ${{ matrix.xsimd_version }} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false # show all errors for each platform (vs. cancel jobs on error) 19 | matrix: 20 | os: [ubuntu-latest, windows-2019, macos-latest] 21 | xsimd_version: ["", "9.0.1", "8.0.0", "8.1.0"] 22 | include: 23 | - os: macos-latest 24 | xsimd_version: "8.0.5" 25 | cmake_args: "-GXcode -D\"CMAKE_OSX_ARCHITECTURES=arm64;x86_64\" -DCHOWDSP_WDF_BUILD_BENCHMARKS=OFF" 26 | 27 | steps: 28 | - name: Get latest CMake 29 | uses: lukka/get-cmake@latest 30 | 31 | - name: Checkout code 32 | uses: actions/checkout@v2 33 | 34 | - name: Configure 35 | shell: bash 36 | run: cmake -DCMAKE_BUILD_TYPE=Release -Bbuild -DCHOWDSP_WDF_TEST_WITH_XSIMD_VERSION="${{ matrix.xsimd_version }}" ${{ matrix.cmake_args }} 37 | 38 | - name: Build 39 | shell: bash 40 | run: cmake --build build --config Release --parallel 4 --target chowdsp_wdf_tests 41 | 42 | - name: Test 43 | shell: bash 44 | run: ./build/test-binary/chowdsp_wdf_tests 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build*/ 2 | cmake-build*/ 3 | .vscode/ 4 | docs/ 5 | .idea/ 6 | 7 | .DS_Store 8 | chowdsp_wdf_clang_tidy.cpp 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(chowdsp_wdf VERSION 1.0.0 LANGUAGES C CXX) 3 | include(cmake/CXXStandard.cmake) 4 | 5 | message(STATUS "Configuring ${PROJECT_NAME} library...") 6 | add_library(${PROJECT_NAME} INTERFACE) 7 | add_library(chowdsp::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 8 | target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) 9 | 10 | if(MSVC) 11 | target_compile_definitions(${PROJECT_NAME} 12 | INTERFACE 13 | _USE_MATH_DEFINES=1 # So that we can have M_PI on MSVC 14 | ) 15 | endif() 16 | 17 | # Check if this is the top-level project 18 | get_directory_property(parent_dir PARENT_DIRECTORY) 19 | if ("${parent_dir}" STREQUAL "") 20 | set(is_toplevel 1) 21 | else () 22 | set(is_toplevel 0) 23 | endif () 24 | 25 | # if we are the top-level project, then configure the project tests and benchmarks 26 | option(CHOWDSP_WDF_BUILD_TESTS "Add targets for building and running chowdsp_wdf tests" ${is_toplevel}) 27 | option(CHOWDSP_WDF_BUILD_BENCHMARKS "Add targets for building and running chowdsp_wdf benchmarks" ${is_toplevel}) 28 | 29 | set(CHOWDSP_WDF_TEST_WITH_XSIMD_VERSION "" CACHE STRING "Tests chowdsp_wdf with XSIMD version") 30 | if(NOT ("${CHOWDSP_WDF_TEST_WITH_XSIMD_VERSION}" STREQUAL "")) 31 | include(cmake/CPM.cmake) 32 | message(STATUS "Importing XSIMD version ${CHOWDSP_WDF_TEST_WITH_XSIMD_VERSION} with CPM") 33 | CPMAddPackage( 34 | NAME xsimd 35 | GITHUB_REPOSITORY xtensor-stack/xsimd 36 | GIT_TAG ${CHOWDSP_WDF_TEST_WITH_XSIMD_VERSION} 37 | ) 38 | endif() 39 | 40 | if (CHOWDSP_WDF_BUILD_TESTS) 41 | include(cmake/CPM.cmake) 42 | add_subdirectory(tests) 43 | endif() 44 | 45 | if (CHOWDSP_WDF_BUILD_BENCHMARKS) 46 | include(cmake/CPM.cmake) 47 | add_subdirectory(bench) 48 | endif() 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Chowdhury-DSP 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![CI](https://github.com/Chowdhury-DSP/chowdsp_wdf/actions/workflows/test.yml/badge.svg)](https://github.com/Chowdhury-DSP/chowdsp_wdf/actions/workflows/test.yml) 2 | [![License](https://img.shields.io/badge/License-BSD-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 3 | [![codecov](https://codecov.io/gh/Chowdhury-DSP/chowdsp_wdf/branch/main/graph/badge.svg?token=DR1OKVN2KJ)](https://codecov.io/gh/Chowdhury-DSP/chowdsp_wdf) 4 | [![arXiv](https://img.shields.io/badge/arXiv-2210.12554-b31b1b.svg)](https://arxiv.org/abs/2210.12554) 5 | [![Try it on godbolt online](https://img.shields.io/badge/on-godbolt-blue.svg)](https://godbolt.org/z/6veooxjfn) 6 | 7 | # Wave Digital Filters 8 | 9 | `chowdsp_wdf` is a header only library for implementing real-time 10 | Wave Digital Filter (WDF) circuit models in C++. 11 | 12 | More information: 13 | - [API docs](https://ccrma.stanford.edu/~jatin/chowdsp/chowdsp_wdf) 14 | - [Examples](https://github.com/jatinchowdhury18/WaveDigitalFilters) 15 | - Other projects using `chowdsp_wdf`: 16 | - [Build Your Own Distortion](https://github.com/Chowdhury-DSP/BYOD) 17 | - [ChowKick](https://github.com/Chowdhury-DSP/ChowKick) 18 | - [ChowCentaur](https://github.com/jatinchowdhury18/KlonCentaur) 19 | 20 | ## Usage 21 | 22 | Since `chowdsp_wdf` is a header-only library, it should be possible to use the library 23 | simply by including `include/chowdsp_wdf/chowdsp_wdf.h`. However, it is recommended to 24 | use the library via CMake. 25 | 26 | ```cmake 27 | add_subdirectory(chowdsp_wdf) 28 | target_link_libraries(my_cmake_target PUBLIC chowdsp_wdf) 29 | ``` 30 | 31 | ### Dependencies 32 | - C++14 or higher 33 | - [CMake](https://cmake.org/) (Version 3.1+, optional) 34 | - [XSIMD](https://github.com/xtensor-stack/xsimd) (Version 8.0.0+, optional) 35 | 36 | ### Basic Usage 37 | 38 | A basic RC lowpass filter can be constructed as follows: 39 | ```cpp 40 | namespace wdft = chowdsp::wdft; 41 | struct RCLowpass { 42 | wdft::ResistorT r1 { 1.0e3 }; // 1 KOhm Resistor 43 | wdft::CapacitorT c1 { 1.0e-6 }; // 1 uF capacitor 44 | 45 | wdft::WDFSeriesT s1 { r1, c1 }; // series connection of r1 and c1 46 | wdft::PolarityInverterT i1 { s1 }; // invert polarity 47 | wdft::IdealVoltageSourceT vs { s1 }; // input voltage source 48 | 49 | // prepare the WDF model here... 50 | void prepare (double sampleRate) { 51 | c1.prepare (sampleRate); 52 | } 53 | 54 | // use the WDF model to process one sample of data 55 | inline double processSample (double x) { 56 | vs.setVoltage (x); 57 | 58 | vs.incident(i1.reflected()); 59 | i1.incident(vs.reflected()); 60 | 61 | return wdft::voltage (c1); 62 | } 63 | }; 64 | ``` 65 | 66 | More complicated examples can be found in the 67 | [examples](https://github.com/jatinchowdhury18/WaveDigitalFilters) repository. 68 | 69 | ### Using XSIMD 70 | 71 | There are two specific situations where you may want to use 72 | SIMD intrinsics as part of your WDF model: 73 | 1. You want to run the same circuit model on several values in parallel. For example, 74 | maybe you have a WDF model of a synthesizer voice, and want to run several voices, 75 | like for a polyphonic synthesizer. 76 | 2. You have a circuit model that requires an R-Type adaptor. The R-Type adaptor requires 77 | a matrix multiply operation which can be greatly accelerated with SIMD intrinsics. 78 | 79 | In both cases, to use SIMD intrinsics in your WDF model, you must include `XSIMD` 80 | in your project _before_ `chowdsp_wdf`. 81 | 82 | ```cpp 83 | #include // this must be included _before_ the chowdsp_wdf header! 84 | #include 85 | ``` 86 | 87 | For case 2 above, simply construct your circuit with an R-Type adaptor as desired, 88 | and the SIMD optomizations will be taken care of behind the scenes! 89 | 90 | For case 1 above, you will want to construct your WDF model so that the circuit elements 91 | may process XSIMD types. Going back to the RC lowpass example: 92 | ```cpp 93 | namespace wdft = chowdsp::wdft; 94 | 95 | // Define the WDF model to process a template-defined FloatType 96 | template 97 | struct RCLowpass { 98 | wdft::ResistorT r1 { 1.0e3 }; // 1 KOhm Resistor 99 | wdft::CapacitorT c1 { 1.0e-6 }; // 1 uF capacitor 100 | 101 | wdft::WDFSeriesT s1 { r1, c1 }; // series connection of r1 and c1 102 | wdft::PolarityInverterT i1 { s1 }; // invert polarity 103 | wdft::IdealVoltageSourceT vs { s1 }; // input voltage source 104 | 105 | // prepare the WDF model here... 106 | void prepare (double sampleRate) { 107 | c1.prepare ((FloatType) sampleRate); 108 | } 109 | 110 | // use the WDF model to process one sample of data 111 | inline FloatType processSample (FloatType x) { 112 | vs.setVoltage (x); 113 | 114 | vs.incident (i1.reflected()); 115 | i1.incident (vs.reflected()); 116 | 117 | return wdft::voltage (c1); 118 | } 119 | }; 120 | 121 | RCLowpass> myFilter; // instantiate the WDF to process an XSIMD type! 122 | ``` 123 | 124 | If you are using `chowdsp_wdf` with XSIMD, please remember to abide by the XSIMD license. 125 | 126 | ## Citation 127 | 128 | If you are using `chowdsp_wdf` as part of an academic work, please cite the library as follows: 129 | ``` 130 | @article{chowdhury2022chowdspwdf, 131 | title = {chowdsp_wdf: An Advanced C++ Library for Wave Digital Circuit Modelling}, 132 | author = {Chowdhury, Jatin}, 133 | year = {2022}, 134 | journal={arXiv preprint arXiv:2210.12554} 135 | doi = {10.48550/ARXIV.2210.12554}, 136 | url = {https://arxiv.org/abs/2210.12554}, 137 | } 138 | ``` 139 | 140 | ## Resources 141 | 142 | The design and implementation of the library were discussed on The Audio Programmer 143 | meetup in December 2021. The presentation can be watched on [YouTube](https://www.youtube.com/watch?v=Auwf9z0k_7E&t=1s). 144 | 145 | The following academic papers may also be useful: 146 | 147 | [1] Alfred Fettweis, "Wave Digital Filters: Theory and Practice", 148 | 1986, IEEE Invited Paper, 149 | [link](https://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=1457726). 150 | 151 | [2] Julius Smith, "Wave Digital Filters", (Chapter from *Physical 152 | Audio Signal Processing*) [link](https://ccrma.stanford.edu/~jos/pasp/Wave_Digital_Filters_I.html). 153 | 154 | [3] David Yeh and Julius Smith, "Simulating Guitar Distortion Circuits 155 | Using Wave Digital And Nonlinear State Space Formulations", Proc. of the 156 | 11th Int. Conference on Digital Audio Effects, 2008, 157 | [link](http://legacy.spa.aalto.fi/dafx08/papers/dafx08_04.pdf). 158 | 159 | [4] Kurt Werner, et al., "Wave Digital Filter Modeling of Circuits 160 | with Operational Amplifiers", 24th European Signal Processing Conference, 161 | 2016, [link](https://www.eurasip.org/Proceedings/Eusipco/Eusipco2016/papers/1570255463.pdf). 162 | 163 | [5] Kurt Werner, et al., "Resolving Wave Digital Filters with 164 | Multiple/Multiport Nonlinearities", Proc. of the 18th Int. Conference 165 | on Digital Audio Effects, 2015, [link](https://ccrma.stanford.edu/~jingjiez/portfolio/gtr-amp-sim/pdfs/Resolving%20Wave%20Digital%20Filters%20with%20MultipleMultiport%20Nonlinearities.pdf). 166 | 167 | [6] Kurt Werner, "Virtual Analog Modeling of Audio Circuitry Using 168 | Wave Digital Filters", PhD. Dissertation, Stanford University, 2016, 169 | [link](https://stacks.stanford.edu/file/druid:jy057cz8322/KurtJamesWernerDissertation-augmented.pdf). 170 | 171 | [7] Jingjie Zhang and Julius Smith, "Real-time Wave Digital Simulation 172 | of Cascaded Vacuum Tube Amplifiers Using Modified Blockwise Method", 173 | Proc. of the 21st International Conference on Digital Audio Effects, 174 | 2018, [link](https://www.dafx.de/paper-archive/2018/papers/DAFx2018_paper_25.pdf). 175 | 176 | ## Credits 177 | 178 | The diode models in the library utilise an approximation of the Wright Omega 179 | function based on [Stefano D'Angelo's implementation](https://www.dangelo.audio/dafx2019-omega.html), 180 | which is licensed under the MIT license. 181 | 182 | Many thanks to the following individuals who have contributed to the 183 | theory, design, and implementation of the library: 184 | - Julius Smith 185 | - Jingjie Zhang 186 | - Kurt Werner 187 | - [Paul Walker](https://github.com/baconpaul) 188 | - [Eyal Amir](https://github.com/eyalamirmusic) 189 | - [Dirk Roosenburg](https://github.com/droosenb) 190 | 191 | ## Licensing 192 | 193 | The code in this repository is open source, and licensed under the BSD 3-Clause License. 194 | Enjoy! 195 | -------------------------------------------------------------------------------- /bench/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | message(STATUS "Importing Googe Benchmark with CPM") 2 | CPMAddPackage( 3 | NAME benchmark 4 | GITHUB_REPOSITORY google/benchmark 5 | VERSION 1.7.1 6 | OPTIONS "BENCHMARK_ENABLE_TESTING Off" 7 | ) 8 | 9 | # setup_benchmark( ) 10 | # 11 | # Sets up a minimal benchmarking app 12 | function(setup_benchmark target file) 13 | message(STATUS "Configuring Benchmark ${target}") 14 | 15 | add_executable(${target}) 16 | target_sources(${target} PRIVATE ${file}) 17 | 18 | target_link_libraries(${target} PRIVATE 19 | chowdsp_wdf 20 | benchmark::benchmark 21 | ) 22 | 23 | if(NOT ("${CHOWDSP_WDF_TEST_WITH_XSIMD_VERSION}" STREQUAL "")) 24 | target_link_libraries(${target} PRIVATE ${PROJECT_NAME} xsimd) 25 | target_compile_definitions(${target} PRIVATE CHOWDSP_WDF_TEST_WITH_XSIMD=1) 26 | endif() 27 | 28 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") 29 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 30 | # Any Clang 31 | if(CMAKE_CXX_COMPILER_ID MATCHES "^AppleClang$") 32 | # Apple Clang only 33 | endif() 34 | elseif(CMAKE_CXX_COMPILER_ID MATCHES "^GNU$") 35 | # GCC only 36 | target_compile_options(${target} PUBLIC 37 | -Wno-pessimizing-move 38 | -Wno-redundant-decls 39 | ) 40 | endif() 41 | endif() 42 | 43 | add_custom_command(TARGET ${target} 44 | POST_BUILD 45 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 46 | COMMAND ${CMAKE_COMMAND} -E echo "Copying $ to bench-binary" 47 | COMMAND ${CMAKE_COMMAND} -E make_directory bench-binary 48 | COMMAND ${CMAKE_COMMAND} -E copy "$" bench-binary 49 | ) 50 | endfunction(setup_benchmark) 51 | 52 | setup_benchmark(wright_omega_bench WrightOmegaBench.cpp) 53 | -------------------------------------------------------------------------------- /bench/WrightOmegaBench.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 5 | #include 6 | #endif 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | template 13 | inline auto makeRandomVector (int num) 14 | { 15 | std::random_device rnd_device; 16 | std::mt19937 mersenne_engine { rnd_device() }; // Generates random integers 17 | std::normal_distribution dist { (T) -10, (T) 10 }; 18 | 19 | std::vector vec ((size_t) num); 20 | std::generate (vec.begin(), vec.end(), [&dist, &mersenne_engine]() { return dist (mersenne_engine); }); 21 | 22 | return std::move (vec); 23 | } 24 | 25 | #define SCALAR_BENCH(name, testVec, func) \ 26 | static void name (benchmark::State& state) \ 27 | { \ 28 | for (auto _ : state) \ 29 | { \ 30 | for (int i = 0; i < N; ++i) \ 31 | (testVec)[i] = func ((testVec) [i]); \ 32 | } \ 33 | } \ 34 | BENCHMARK(name)->MinTime (3); 35 | 36 | #define SIMD_BENCH(name, testVec, func, SIMDType) \ 37 | static void name (benchmark::State& state) \ 38 | { \ 39 | SIMDType y; \ 40 | for (auto _ : state) \ 41 | { \ 42 | for (int i = 0; i < N; ++i) \ 43 | y = func ((SIMDType) (testVec) [i]); \ 44 | } \ 45 | (testVec)[0] = y.get (0);\ 46 | } \ 47 | BENCHMARK(name)->MinTime (3); 48 | 49 | constexpr int N = 1000; 50 | static auto testFloatVec = makeRandomVector (N); 51 | static auto testDoubleVec = makeRandomVector (N); 52 | 53 | SCALAR_BENCH (floatWrightOmega3, testFloatVec, chowdsp::Omega::omega3) 54 | SCALAR_BENCH (floatWrightOmega4, testFloatVec, chowdsp::Omega::omega4) 55 | SCALAR_BENCH (doubleWrightOmega3, testDoubleVec, chowdsp::Omega::omega3) 56 | SCALAR_BENCH (doubleWrightOmega4, testDoubleVec, chowdsp::Omega::omega4) 57 | 58 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 59 | SIMD_BENCH (floatSIMDWrightOmega3, testFloatVec, chowdsp::Omega::omega3, xsimd::batch) 60 | SIMD_BENCH (floatSIMDWrightOmega4, testFloatVec, chowdsp::Omega::omega4, xsimd::batch) 61 | SIMD_BENCH (doubleSIMDWrightOmega3, testDoubleVec, chowdsp::Omega::omega3, xsimd::batch) 62 | SIMD_BENCH (doubleSIMDWrightOmega4, testDoubleVec, chowdsp::Omega::omega4, xsimd::batch) 63 | #endif 64 | 65 | BENCHMARK_MAIN(); 66 | -------------------------------------------------------------------------------- /cmake/CXXStandard.cmake: -------------------------------------------------------------------------------- 1 | if("${CMAKE_CXX_STANDARD}" STREQUAL "") 2 | message(STATUS "A C++ Standard has not been set for this project! chowdsp_wdf is selecting C++14...") 3 | set(CMAKE_CXX_STANDARD 14) 4 | else() 5 | if(${CMAKE_CXX_STANDARD} LESS 14) 6 | message(FATAL_ERROR "chowdsp_wdf requires C++ 14 or later") 7 | endif() 8 | endif() 9 | -------------------------------------------------------------------------------- /doxygen/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all deploy clean 2 | 3 | all: 4 | doxygen Doxyfile 5 | 6 | clean: 7 | rm -Rf ../docs/** 8 | 9 | deploy: all 10 | scp -r ../docs jatin@ccrma-gate.stanford.edu:~/Library/Web/chowdsp/chowdsp_utils 11 | -------------------------------------------------------------------------------- /doxygen/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | $projectname: $title 10 | $title 11 | 12 | 13 | 14 | 15 | $treeview 16 | $search 17 | $mathjax 18 | $extrastylesheet 19 | 20 | 21 | 22 | 29 | 30 | 31 | 32 |
33 | 34 | 35 |
36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
44 |
$projectname 45 |  $projectnumber 46 |
47 |
$projectbrief
48 |
53 |
$projectbrief
54 |
$searchbox
65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /doxygen/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/chowdsp_wdf/d67af36a82545d61234b7f07245628eef77045d6/doxygen/logo.png -------------------------------------------------------------------------------- /include/chowdsp_wdf/README.md: -------------------------------------------------------------------------------- 1 | # Wave Digital Filters 2 | 3 | This directory contains a standalone Wave Digital Filter (WDF) library. Examples demonstrating how to use the library 4 | can be found in a [separate repository](https://github.com/jatinchowdhury18/WaveDigitalFilters). 5 | 6 | Since the WDF library is header-only, the only requirement to using the library is to include the correct header file 7 | for your use case: 8 | - If you would like to build a WDF defined at run-time, you should `#include "wdf.h"`. 9 | - If you would like to build a WDF defined at compile-time, you should `#include "wdf_t.h"`. 10 | - If you would like to build a WDF using R-type adaptors (still experimental), you should `#include "r_type.h"`. 11 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/chowdsp_wdf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if defined(_MSC_VER) 4 | #pragma warning(push) 5 | #pragma warning(disable : 4244 4464 4514 4324) 6 | #endif 7 | 8 | // define maybe_unused if we can 9 | #if __cplusplus >= 201703L 10 | #define CHOWDSP_WDF_MAYBE_UNUSED [[maybe_unused]] 11 | #else 12 | #define CHOWDSP_WDF_MAYBE_UNUSED 13 | #endif 14 | 15 | // Define a default SIMD alignment 16 | #if defined(XSIMD_HPP) 17 | constexpr auto CHOWDSP_WDF_DEFAULT_SIMD_ALIGNMENT = (int) xsimd::default_arch::alignment(); 18 | #else 19 | constexpr int CHOWDSP_WDF_DEFAULT_SIMD_ALIGNMENT = 16; 20 | #endif 21 | 22 | #include "wdft/wdft.h" 23 | #include "wdf/wdf.h" 24 | #include "rtype/rtype.h" 25 | 26 | #include "util/defer_impedance.h" 27 | 28 | #if defined(_MSC_VER) 29 | #pragma warning(pop) 30 | #endif 31 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/math/omega.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Stefano D'Angelo 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | * 22 | * NOTE: code has been modified significantly by Jatin Chowdhury 23 | */ 24 | 25 | #ifndef OMEGA_H_INCLUDED 26 | #define OMEGA_H_INCLUDED 27 | 28 | #include 29 | #include 30 | #include 31 | #include "sample_type.h" 32 | 33 | namespace chowdsp 34 | { 35 | /** 36 | * Useful approximations for evaluating the Wright Omega function. 37 | * 38 | * This approach was devloped by Stefano D'Angelo, and adapted from his 39 | * original implementation under the MIT license. 40 | * - Paper: https://www.dafx.de/paper-archive/2019/DAFx2019_paper_5.pdf 41 | * - Original Source: https://www.dangelo.audio/dafx2019-omega.html 42 | */ 43 | namespace Omega 44 | { 45 | /** 46 | * Evaluates a polynomial of a given order, using Estrin's scheme. 47 | * Coefficients should be given in the form { a_n, a_n-1, ..., a_1, a_0 } 48 | * https://en.wikipedia.org/wiki/Estrin%27s_scheme 49 | */ 50 | template 51 | inline typename std::enable_if<(ORDER == 1), decltype (T {} * X {})>::type estrin (const T (&coeffs)[ORDER + 1], const X& x) 52 | { 53 | return coeffs[1] + coeffs[0] * x; 54 | } 55 | 56 | template 57 | inline typename std::enable_if<(ORDER > 1), decltype (T {} * X {})>::type estrin (const T (&coeffs)[ORDER + 1], const X& x) 58 | { 59 | decltype (T {} * X {}) temp[ORDER / 2 + 1]; 60 | for (int n = ORDER; n >= 0; n -= 2) 61 | temp[n / 2] = coeffs[n] + coeffs[n - 1] * x; 62 | 63 | temp[0] = (ORDER % 2 == 0) ? coeffs[0] : temp[0]; 64 | 65 | return estrin (temp, x * x); // recurse! 66 | } 67 | 68 | /** approximation for log_2(x), optimized on the range [1, 2] */ 69 | template 70 | constexpr T log2_approx (T x) 71 | { 72 | constexpr auto alpha = (T) 0.1640425613334452; 73 | constexpr auto beta = (T) -1.098865286222744; 74 | constexpr auto gamma = (T) 3.148297929334117; 75 | constexpr auto zeta = (T) -2.213475204444817; 76 | 77 | return estrin<3> ({ alpha, beta, gamma, zeta }, x); 78 | } 79 | 80 | #if defined(XSIMD_HPP) 81 | /** approximation for log_2(x), optimized on the range [1, 2] */ 82 | template 83 | inline xsimd::batch log2_approx (const xsimd::batch& x) 84 | { 85 | static constexpr auto alpha = (NumericType) 0.1640425613334452; 86 | static constexpr auto beta = (NumericType) -1.098865286222744; 87 | static constexpr auto gamma = (NumericType) 3.148297929334117; 88 | static constexpr auto zeta = (NumericType) -2.213475204444817; 89 | 90 | return estrin<3> ({ alpha, beta, gamma, zeta }, x); 91 | } 92 | #endif 93 | 94 | /** approximation for log(x) */ 95 | template 96 | constexpr T log_approx (T x); 97 | 98 | /** approximation for log(x) (32-bit) */ 99 | template <> 100 | CHOWDSP_WDF_MAYBE_UNUSED constexpr float log_approx (float x) 101 | { 102 | union 103 | { 104 | int32_t i; 105 | float f; 106 | } v {}; 107 | v.f = x; 108 | int32_t ex = v.i & 0x7f800000; 109 | int32_t e = (ex >> 23) - 127; 110 | v.i = (v.i - ex) | 0x3f800000; 111 | 112 | return 0.693147180559945f * ((float) e + log2_approx (v.f)); 113 | } 114 | 115 | /** approximation for log(x) (64-bit) */ 116 | template <> 117 | CHOWDSP_WDF_MAYBE_UNUSED constexpr double log_approx (double x) 118 | { 119 | union 120 | { 121 | int64_t i; 122 | double d; 123 | } v {}; 124 | v.d = x; 125 | int64_t ex = v.i & 0x7ff0000000000000; 126 | int64_t e = (ex >> 53) - 510; 127 | v.i = (v.i - ex) | 0x3ff0000000000000; 128 | 129 | return 0.693147180559945 * ((double) e + log2_approx (v.d)); 130 | } 131 | 132 | #if defined(XSIMD_HPP) 133 | /** approximation for log(x) (SIMD 32-bit) */ 134 | template <> 135 | CHOWDSP_WDF_MAYBE_UNUSED inline xsimd::batch log_approx (xsimd::batch x) 136 | { 137 | union 138 | { 139 | xsimd::batch i; 140 | xsimd::batch f; 141 | } v; 142 | v.f = x; 143 | xsimd::batch ex = v.i & 0x7f800000; 144 | xsimd::batch e = xsimd::to_float ((ex >> 23) - 127); 145 | v.i = (v.i - ex) | 0x3f800000; 146 | 147 | return 0.693147180559945f * (log2_approx (v.f) + e); 148 | } 149 | 150 | /** approximation for log(x) (64-bit) */ 151 | template <> 152 | CHOWDSP_WDF_MAYBE_UNUSED inline xsimd::batch log_approx (xsimd::batch x) 153 | { 154 | union 155 | { 156 | xsimd::batch i; 157 | xsimd::batch d; 158 | } v {}; 159 | v.d = x; 160 | xsimd::batch ex = v.i & 0x7ff0000000000000; 161 | xsimd::batch e = xsimd::to_float ((ex >> 53) - 510); 162 | v.i = (v.i - ex) | 0x3ff0000000000000; 163 | 164 | return 0.693147180559945 * (e + log2_approx (v.d)); 165 | } 166 | #endif 167 | 168 | /** approximation for 2^x, optimized on the range [0, 1] */ 169 | template 170 | constexpr T pow2_approx (T x) 171 | { 172 | constexpr auto alpha = (T) 0.07944154167983575; 173 | constexpr auto beta = (T) 0.2274112777602189; 174 | constexpr auto gamma = (T) 0.6931471805599453; 175 | constexpr auto zeta = (T) 1.0; 176 | 177 | return estrin<3> ({ alpha, beta, gamma, zeta }, x); 178 | } 179 | 180 | #if defined(XSIMD_HPP) 181 | template 182 | inline xsimd::batch pow2_approx (const xsimd::batch& x) 183 | { 184 | static constexpr auto alpha = (NumericType) 0.07944154167983575; 185 | static constexpr auto beta = (NumericType) 0.2274112777602189; 186 | static constexpr auto gamma = (NumericType) 0.6931471805599453; 187 | static constexpr auto zeta = (NumericType) 1.0; 188 | 189 | return estrin<3> ({ alpha, beta, gamma, zeta }, x); 190 | } 191 | #endif 192 | 193 | /** approximation for exp(x) */ 194 | template 195 | T exp_approx (T x); 196 | 197 | /** approximation for exp(x) (32-bit) */ 198 | template <> 199 | CHOWDSP_WDF_MAYBE_UNUSED constexpr float exp_approx (float x) 200 | { 201 | x = std::max (-126.0f, 1.442695040888963f * x); 202 | 203 | union 204 | { 205 | int32_t i; 206 | float f; 207 | } v {}; 208 | 209 | auto xi = (int32_t) x; 210 | int32_t l = x < 0.0f ? xi - 1 : xi; 211 | float f = x - (float) l; 212 | v.i = (l + 127) << 23; 213 | 214 | return v.f * pow2_approx (f); 215 | } 216 | 217 | /** approximation for exp(x) (64-bit) */ 218 | template <> 219 | CHOWDSP_WDF_MAYBE_UNUSED constexpr double exp_approx (double x) 220 | { 221 | x = std::max ((double) -126.0, 1.442695040888963 * x); 222 | 223 | union 224 | { 225 | int64_t i; 226 | double d; 227 | } v {}; 228 | 229 | auto xi = (int64_t) x; 230 | int64_t l = x < 0.0 ? xi - 1 : xi; 231 | double d = x - (double) l; 232 | v.i = (l + 1023) << 52; 233 | 234 | return v.d * pow2_approx (d); 235 | } 236 | 237 | #if defined(XSIMD_HPP) 238 | /** approximation for exp(x) (32-bit) */ 239 | template <> 240 | CHOWDSP_WDF_MAYBE_UNUSED inline xsimd::batch exp_approx (xsimd::batch x) 241 | { 242 | x = xsimd::max (xsimd::broadcast (-126.0f), 1.442695040888963f * x); 243 | 244 | union 245 | { 246 | xsimd::batch i; 247 | xsimd::batch f; 248 | } v {}; 249 | 250 | auto xi = xsimd::to_int (x); 251 | xsimd::batch l = xsimd::select (xi < 0, xi - 1, xi); 252 | xsimd::batch f = x - xsimd::to_float (l); 253 | v.i = (l + 127) << 23; 254 | 255 | return v.f * pow2_approx (f); 256 | } 257 | 258 | /** approximation for exp(x) (64-bit) */ 259 | template <> 260 | CHOWDSP_WDF_MAYBE_UNUSED inline xsimd::batch exp_approx (xsimd::batch x) 261 | { 262 | x = xsimd::max (xsimd::broadcast (-126.0), 1.442695040888963 * x); 263 | 264 | union 265 | { 266 | xsimd::batch i; 267 | xsimd::batch d; 268 | } v {}; 269 | 270 | auto xi = xsimd::to_int (x); 271 | xsimd::batch l = xsimd::select (xi < 0, xi - 1, xi); 272 | xsimd::batch d = x - xsimd::to_float (l); 273 | v.i = (l + 1023) << 52; 274 | 275 | return v.d * pow2_approx (d); 276 | } 277 | #endif 278 | 279 | /** First-order approximation of the Wright Omega functions */ 280 | template 281 | constexpr T omega1 (T x) 282 | { 283 | #if defined(XSIMD_HPP) 284 | using xsimd::max; 285 | #endif 286 | using std::max; 287 | 288 | return max (x, (T) 0); 289 | } 290 | 291 | /** Second-order approximation of the Wright Omega functions */ 292 | template 293 | constexpr T omega2 (T x) 294 | { 295 | constexpr auto x1 = (NumericType) -3.684303659906469; 296 | constexpr auto x2 = (NumericType) 1.972967391708859; 297 | constexpr auto a = (NumericType) 9.451797158780131e-3; 298 | constexpr auto b = (NumericType) 1.126446405111627e-1; 299 | constexpr auto c = (NumericType) 4.451353886588814e-1; 300 | constexpr auto d = (NumericType) 5.836596684310648e-1; 301 | 302 | return select (x < x1, (T) 0, select (x > x2, x, estrin<3> ({ a, b, c, d }, x))); 303 | } 304 | 305 | /** Third-order approximation of the Wright Omega functions */ 306 | template 307 | constexpr T omega3 (T x) 308 | { 309 | constexpr auto x1 = (NumericType) -3.341459552768620; 310 | constexpr auto x2 = (NumericType) 8.0; 311 | constexpr auto a = (NumericType) -1.314293149877800e-3; 312 | constexpr auto b = (NumericType) 4.775931364975583e-2; 313 | constexpr auto c = (NumericType) 3.631952663804445e-1; 314 | constexpr auto d = (NumericType) 6.313183464296682e-1; 315 | 316 | return select (x < x1, (T) 0, select (x < x2, estrin<3> ({ a, b, c, d }, x), x - log_approx (x))); 317 | } 318 | 319 | /** Fourth-order approximation of the Wright Omega functions */ 320 | template 321 | constexpr T omega4 (T x) 322 | { 323 | const auto y = omega3 (x); 324 | return y - (y - exp_approx (x - y)) / (y + (T) 1); 325 | } 326 | 327 | struct Omega 328 | { 329 | template 330 | static T omega (T x) 331 | { 332 | return omega4 (x); 333 | } 334 | }; 335 | } // namespace Omega 336 | } // namespace chowdsp 337 | 338 | #endif //OMEGA_H_INCLUDED 339 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/math/sample_type.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_SAMPLE_TYPE_H 2 | #define CHOWDSP_WDF_SAMPLE_TYPE_H 3 | 4 | #include 5 | 6 | #ifndef DOXYGEN 7 | 8 | namespace chowdsp 9 | { 10 | #if ! (JUCE_MODULE_AVAILABLE_chowdsp_dsp) 11 | /** Useful structs for determining the internal data type of SIMD types */ 12 | namespace SampleTypeHelpers 13 | { 14 | template ::value> 15 | struct ElementType 16 | { 17 | using Type = T; 18 | }; 19 | 20 | template 21 | struct ElementType 22 | { 23 | using Type = typename T::value_type; 24 | }; 25 | } // namespace SampleTypeHelpers 26 | #endif 27 | 28 | /** Type alias for a SIMD numeric type */ 29 | template 30 | using NumericType = typename SampleTypeHelpers::ElementType::Type; 31 | 32 | /** Returns true if all the elements in a SIMD vector are equal */ 33 | inline bool all (bool x) 34 | { 35 | return x; 36 | } 37 | 38 | /** Ternary select operation */ 39 | template 40 | inline T select (bool b, const T& t, const T& f) 41 | { 42 | return b ? t : f; 43 | } 44 | } // namespace chowdsp 45 | 46 | #endif // DOXYGEN 47 | 48 | #endif //CHOWDSP_WDF_SAMPLE_TYPE_H 49 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/math/signum.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace chowdsp 4 | { 5 | /** Methods for implementing the signum function */ 6 | namespace signum 7 | { 8 | /** Signum function to determine the sign of the input. */ 9 | template 10 | inline int signum (T val) 11 | { 12 | return (T (0) < val) - (val < T (0)); 13 | } 14 | 15 | #if defined(XSIMD_HPP) 16 | /** Signum function to determine the sign of the input. */ 17 | template 18 | inline xsimd::batch signum (xsimd::batch val) 19 | { 20 | using v_type = xsimd::batch; 21 | const auto positive = xsimd::select (val > v_type ((T) 0), v_type ((T) 1), v_type ((T) 0)); 22 | const auto negative = xsimd::select (val < v_type ((T) 0), v_type ((T) 1), v_type ((T) 0)); 23 | return positive - negative; 24 | } 25 | #endif 26 | 27 | } // namespace signum 28 | } // namespace chowdsp 29 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/rtype/root_rtype_adaptor.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_ROOT_RTYPE_ADAPTOR_H 2 | #define CHOWDSP_WDF_ROOT_RTYPE_ADAPTOR_H 3 | 4 | #include "../wdft/wdft_base.h" 5 | #include "rtype_detail.h" 6 | 7 | namespace chowdsp 8 | { 9 | namespace wdft 10 | { 11 | /** 12 | * A non-adaptable R-Type adaptor. 13 | * For more information see: https://searchworks.stanford.edu/view/11891203, chapter 2 14 | * 15 | * The ImpedanceCalculator template argument with a static method of the form: 16 | * @code 17 | * template 18 | * static void calcImpedance (RType& R); 19 | * @endcode 20 | */ 21 | template 22 | class RootRtypeAdaptor : public RootWDF 23 | { 24 | public: 25 | /** Number of ports connected to RootRtypeAdaptor */ 26 | static constexpr auto numPorts = int (sizeof...(PortTypes)); 27 | 28 | explicit RootRtypeAdaptor (PortTypes&... dps) : downPorts (std::tie (dps...)) 29 | { 30 | b_vec.clear(); 31 | a_vec.clear(); 32 | 33 | rtype_detail::forEachInTuple ([&] (auto& port, size_t) { port.connectToParent (this); }, 34 | downPorts); 35 | } 36 | 37 | [[deprecated ("Prefer the alternative constuctor which accepts the port references directly")]] explicit RootRtypeAdaptor (std::tuple dps) : downPorts (dps) 38 | { 39 | b_vec.clear(); 40 | a_vec.clear(); 41 | 42 | rtype_detail::forEachInTuple ([&] (auto& port, size_t) { port.connectToParent (this); }, 43 | downPorts); 44 | } 45 | 46 | /** Recomputes internal variables based on the incoming impedances */ 47 | void calcImpedance() override 48 | { 49 | ImpedanceCalculator::calcImpedance (*this); 50 | } 51 | 52 | constexpr auto getPortImpedances() 53 | { 54 | std::array portImpedances {}; 55 | rtype_detail::forEachInTuple ([&] (auto& port, size_t i) { portImpedances[i] = port.wdf.R; }, 56 | downPorts); 57 | 58 | return portImpedances; 59 | } 60 | 61 | /** Use this function to set the scattering matrix data. */ 62 | void setSMatrixData (const T (&mat)[numPorts][numPorts]) 63 | { 64 | for (int i = 0; i < numPorts; ++i) 65 | for (int j = 0; j < numPorts; ++j) 66 | S_matrix[j][i] = mat[i][j]; 67 | } 68 | 69 | /** Computes both the incident and reflected waves at this root node. */ 70 | inline void compute() noexcept 71 | { 72 | rtype_detail::RtypeScatter (S_matrix, a_vec, b_vec); 73 | rtype_detail::forEachInTuple ([&] (auto& port, size_t i) { 74 | port.incident (b_vec[i]); 75 | a_vec[i] = port.reflected(); }, 76 | downPorts); 77 | } 78 | 79 | private: 80 | std::tuple downPorts; // tuple of ports connected to RtypeAdaptor 81 | 82 | rtype_detail::Matrix S_matrix; // square matrix representing S 83 | rtype_detail::AlignedArray a_vec; // temp matrix of inputs to Rport 84 | rtype_detail::AlignedArray b_vec; // temp matrix of outputs from Rport 85 | }; 86 | } // namespace wdft 87 | } // namespace chowdsp 88 | 89 | #endif //CHOWDSP_WDF_ROOT_RTYPE_ADAPTOR_H 90 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/rtype/rtype.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_RTYPE_H_INCLUDED 2 | #define CHOWDSP_WDF_RTYPE_H_INCLUDED 3 | 4 | #include "rtype_adaptor.h" 5 | #include "root_rtype_adaptor.h" 6 | #include "wdf_rtype.h" 7 | 8 | #endif // CHOWDSP_WDF_RTYPE_H_INCLUDED 9 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/rtype/rtype_adaptor.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_RTYPE_ADAPTOR_H 2 | #define CHOWDSP_WDF_RTYPE_ADAPTOR_H 3 | 4 | #include "../wdft/wdft_base.h" 5 | #include "rtype_detail.h" 6 | 7 | namespace chowdsp 8 | { 9 | namespace wdft 10 | { 11 | /** 12 | * An adaptable R-Type adaptor. 13 | * For more information see: https://searchworks.stanford.edu/view/11891203, chapter 2 14 | * 15 | * The upPortIndex argument descibes with port of the scattering matrix is being adapted. 16 | * 17 | * The ImpedanceCalculator template argument with a static method of the form: 18 | * @code 19 | * template 20 | * static T calcImpedance (RType& R); 21 | * @endcode 22 | */ 23 | template 24 | class RtypeAdaptor : public BaseWDF 25 | { 26 | public: 27 | /** Number of ports connected to RtypeAdaptor */ 28 | static constexpr auto numPorts = int (sizeof...(PortTypes) + 1); 29 | 30 | explicit RtypeAdaptor (PortTypes&... dps) : downPorts (std::tie (dps...)) 31 | { 32 | b_vec.clear(); 33 | a_vec.clear(); 34 | 35 | rtype_detail::forEachInTuple ([&] (auto& port, size_t) { port.connectToParent (this); }, 36 | downPorts); 37 | } 38 | 39 | [[deprecated ("Prefer the alternative constuctor which accepts the port references directly")]] explicit RtypeAdaptor (std::tuple dps) : downPorts (dps) 40 | { 41 | b_vec.clear(); 42 | a_vec.clear(); 43 | 44 | rtype_detail::forEachInTuple ([&] (auto& port, size_t) { port.connectToParent (this); }, 45 | downPorts); 46 | } 47 | 48 | /** Re-computes the port impedance at the adapted upward-facing port */ 49 | void calcImpedance() override 50 | { 51 | wdf.R = ImpedanceCalculator::calcImpedance (*this); 52 | wdf.G = (T) 1 / wdf.R; 53 | } 54 | 55 | constexpr auto getPortImpedances() 56 | { 57 | std::array portImpedances {}; 58 | rtype_detail::forEachInTuple ([&] (auto& port, size_t i) { portImpedances[i] = port.wdf.R; }, 59 | downPorts); 60 | 61 | return portImpedances; 62 | } 63 | 64 | /** Use this function to set the scattering matrix data. */ 65 | void setSMatrixData (const T (&mat)[numPorts][numPorts]) 66 | { 67 | for (int i = 0; i < numPorts; ++i) 68 | for (int j = 0; j < numPorts; ++j) 69 | S_matrix[j][i] = mat[i][j]; 70 | } 71 | 72 | /** Computes the incident wave. */ 73 | inline void incident (T downWave) noexcept 74 | { 75 | wdf.a = downWave; 76 | a_vec[upPortIndex] = wdf.a; 77 | 78 | rtype_detail::RtypeScatter (S_matrix, a_vec, b_vec); 79 | rtype_detail::forEachInTuple ([&] (auto& port, size_t i) { 80 | auto portIndex = getPortIndex ((int) i); 81 | port.incident (b_vec[portIndex]); }, 82 | downPorts); 83 | } 84 | 85 | /** Computes the reflected wave */ 86 | inline T reflected() noexcept 87 | { 88 | rtype_detail::forEachInTuple ([&] (auto& port, size_t i) { 89 | auto portIndex = getPortIndex ((int) i); 90 | a_vec[portIndex] = port.reflected(); }, 91 | downPorts); 92 | 93 | wdf.b = b_vec[upPortIndex]; 94 | return wdf.b; 95 | } 96 | 97 | WDFMembers wdf; 98 | 99 | private: 100 | constexpr auto getPortIndex (int tupleIndex) 101 | { 102 | return tupleIndex < upPortIndex ? tupleIndex : tupleIndex + 1; 103 | } 104 | 105 | std::tuple downPorts; // tuple of ports connected to RtypeAdaptor 106 | 107 | rtype_detail::Matrix S_matrix; // square matrix representing S 108 | rtype_detail::AlignedArray a_vec; // temp matrix of inputs to Rport 109 | rtype_detail::AlignedArray b_vec; // temp matrix of outputs from Rport 110 | }; 111 | } // namespace wdft 112 | } // namespace chowdsp 113 | 114 | #endif //CHOWDSP_WDF_RTYPE_ADAPTOR_H 115 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/rtype/rtype_detail.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_RTYPE_DETAIL_H 2 | #define CHOWDSP_WDF_RTYPE_DETAIL_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace chowdsp 11 | { 12 | #ifndef DOXYGEN 13 | namespace wdft 14 | { 15 | /** Utility functions used internally by the R-Type adaptor */ 16 | namespace rtype_detail 17 | { 18 | /** Divides two numbers and rounds up if there is a remainder. */ 19 | template 20 | constexpr T ceil_div (T num, T den) 21 | { 22 | return (num + den - 1) / den; 23 | } 24 | 25 | template 26 | constexpr typename std::enable_if::value, size_t>::type array_pad() 27 | { 28 | #if defined(XSIMD_HPP) 29 | using v_type = xsimd::simd_type; 30 | constexpr auto simd_size = v_type::size; 31 | constexpr auto num_simd_registers = ceil_div (base_size, simd_size); 32 | return num_simd_registers * simd_size; 33 | #else 34 | return base_size; 35 | #endif 36 | } 37 | 38 | template 39 | constexpr typename std::enable_if::value, size_t>::type array_pad() 40 | { 41 | return base_size; 42 | } 43 | 44 | /** Functions to do a function for each element in the tuple */ 45 | template 46 | constexpr void forEachInTuple (Fn&& fn, Tuple&& tuple, std::index_sequence) noexcept (noexcept (std::initializer_list { (fn (std::get (tuple), Ix), 0)... })) 47 | { 48 | (void) std::initializer_list { ((void) fn (std::get (tuple), Ix), 0)... }; 49 | } 50 | 51 | template 52 | using TupleIndexSequence = std::make_index_sequence>>::value>; 53 | 54 | template 55 | constexpr void forEachInTuple (Fn&& fn, Tuple&& tuple) noexcept (noexcept (forEachInTuple (std::forward (fn), std::forward (tuple), TupleIndexSequence {}))) 56 | { 57 | forEachInTuple (std::forward (fn), std::forward (tuple), TupleIndexSequence {}); 58 | } 59 | 60 | template 61 | struct AlignedArray 62 | { 63 | template 64 | ElementType& operator[] (IntType index) noexcept 65 | { 66 | return array[index]; 67 | } 68 | template 69 | const ElementType& operator[] (IntType index) const noexcept 70 | { 71 | return array[index]; 72 | } 73 | 74 | ElementType* data() noexcept { return array; } 75 | const ElementType* data() const noexcept { return array; } 76 | 77 | void clear() { std::fill (std::begin (array), std::end (array), ElementType {}); } 78 | static constexpr int size() noexcept { return arraySize; } 79 | 80 | private: 81 | alignas (alignment) ElementType array[array_pad()] {}; 82 | }; 83 | 84 | template 85 | using Matrix = AlignedArray[(size_t) nCols]; 86 | 87 | /** Implementation for float/double. */ 88 | template 89 | constexpr typename std::enable_if::value, void>::type 90 | RtypeScatter (const Matrix& S_, const AlignedArray& a_, AlignedArray& b_) 91 | { 92 | // input matrix (S) of size dim x dim 93 | // input vector (a) of size 1 x dim 94 | // output vector (b) of size 1 x dim 95 | 96 | #if defined(XSIMD_HPP) 97 | using v_type = xsimd::simd_type; 98 | constexpr auto simd_size = (int) v_type::size; 99 | constexpr auto vec_size = ceil_div (numPorts, simd_size) * simd_size; 100 | 101 | for (int c = 0; c < vec_size; c += simd_size) 102 | { 103 | auto b_vec = a_[0] * xsimd::load_aligned (S_[0].data() + c); 104 | for (int r = 1; r < numPorts; ++r) 105 | b_vec = xsimd::fma (xsimd::broadcast (a_[r]), xsimd::load_aligned (S_[r].data() + c), b_vec); 106 | 107 | xsimd::store_aligned (b_.data() + c, b_vec); 108 | } 109 | #else // No SIMD 110 | for (int c = 0; c < numPorts; ++c) 111 | { 112 | b_[c] = S_[0][c] * a_[0]; 113 | for (int r = 1; r < numPorts; ++r) 114 | b_[c] += S_[r][c] * a_[r]; 115 | } 116 | #endif // SIMD options 117 | } 118 | 119 | #if defined(XSIMD_HPP) 120 | /** Implementation for SIMD float/double. */ 121 | template 122 | constexpr typename std::enable_if::value, void>::type 123 | RtypeScatter (const Matrix& S_, const AlignedArray& a_, AlignedArray& b_) 124 | { 125 | for (int c = 0; c < numPorts; ++c) 126 | { 127 | b_[c] = S_[0][c] * a_[0]; 128 | for (int r = 1; r < numPorts; ++r) 129 | b_[c] += S_[r][c] * a_[r]; 130 | } 131 | } 132 | #endif // XSIMD 133 | } // namespace rtype_detail 134 | } // namespace wdft 135 | 136 | namespace wdf 137 | { 138 | /** Utility functions used internally by the R-Type adaptor */ 139 | namespace rtype_detail 140 | { 141 | using wdft::rtype_detail::ceil_div; 142 | 143 | template 144 | typename std::enable_if::value, size_t>::type array_pad (size_t base_size) 145 | { 146 | #if defined(XSIMD_HPP) 147 | using v_type = xsimd::simd_type; 148 | constexpr auto simd_size = v_type::size; 149 | const auto num_simd_registers = ceil_div (base_size, simd_size); 150 | return num_simd_registers * simd_size; 151 | #else 152 | return base_size; 153 | #endif 154 | } 155 | 156 | template 157 | typename std::enable_if::value, size_t>::type array_pad (size_t base_size) 158 | { 159 | return base_size; 160 | } 161 | 162 | template 163 | struct AlignedArray 164 | { 165 | explicit AlignedArray (size_t size) : m_size ((int) size), 166 | vector (array_pad (size), ElementType {}) 167 | { 168 | } 169 | 170 | ElementType& operator[] (int index) noexcept { return vector[index]; } 171 | const ElementType& operator[] (int index) const noexcept { return vector[index]; } 172 | 173 | ElementType* data() noexcept { return vector.data(); } 174 | const ElementType* data() const noexcept { return vector.data(); } 175 | 176 | void clear() { std::fill (std::begin (vector), std::end (vector), ElementType {}); } 177 | int size() const noexcept { return (int) m_size; } 178 | 179 | private: 180 | const int m_size; 181 | #if defined(XSIMD_HPP) 182 | std::vector> vector; 183 | #else 184 | std::vector vector; 185 | #endif 186 | }; 187 | 188 | template 189 | struct Matrix 190 | { 191 | Matrix (size_t nRows, size_t nCols) : vector (nCols, AlignedArray (nRows)) 192 | { 193 | } 194 | 195 | AlignedArray& operator[] (int index) noexcept { return vector[(size_t) index]; } 196 | const AlignedArray& operator[] (int index) const noexcept { return vector[(size_t) index]; } 197 | 198 | private: 199 | std::vector> vector; 200 | }; 201 | 202 | /** Implementation for float/double. */ 203 | template 204 | constexpr typename std::enable_if::value, void>::type 205 | RtypeScatter (const Matrix& S_, const AlignedArray& a_, AlignedArray& b_) 206 | { 207 | // input matrix (S) of size dim x dim 208 | // input vector (a) of size 1 x dim 209 | // output vector (b) of size 1 x dim 210 | 211 | #if defined(XSIMD_HPP) 212 | using v_type = xsimd::simd_type; 213 | constexpr auto simd_size = (int) v_type::size; 214 | const auto numPorts = a_.size(); 215 | const auto vec_size = ceil_div (numPorts, simd_size) * simd_size; 216 | 217 | for (int c = 0; c < vec_size; c += simd_size) 218 | { 219 | auto b_vec = a_[0] * xsimd::load_aligned (S_[0].data() + c); 220 | for (int r = 1; r < numPorts; ++r) 221 | b_vec = xsimd::fma (xsimd::broadcast (a_[r]), xsimd::load_aligned (S_[r].data() + c), b_vec); 222 | 223 | xsimd::store_aligned (b_.data() + c, b_vec); 224 | } 225 | #else // No SIMD 226 | const auto numPorts = a_.size(); 227 | for (int c = 0; c < numPorts; ++c) 228 | { 229 | b_[c] = S_[0][c] * a_[0]; 230 | for (int r = 1; r < numPorts; ++r) 231 | b_[c] += S_[r][c] * a_[r]; 232 | } 233 | #endif // SIMD options 234 | } 235 | 236 | #if defined(XSIMD_HPP) 237 | /** Implementation for SIMD float/double. */ 238 | template 239 | constexpr typename std::enable_if::value, void>::type 240 | RtypeScatter (const Matrix& S_, const AlignedArray& a_, AlignedArray& b_) 241 | { 242 | const auto numPorts = a_.size(); 243 | for (int c = 0; c < numPorts; ++c) 244 | { 245 | b_[c] = S_[0][c] * a_[0]; 246 | for (int r = 1; r < numPorts; ++r) 247 | b_[c] += S_[r][c] * a_[r]; 248 | } 249 | } 250 | #endif // XSIMD 251 | } // namespace rtype_detail 252 | } // namespace wdf 253 | #endif // DOXYGEN 254 | } // namespace chowdsp 255 | 256 | #endif //CHOWDSP_WDF_RTYPE_DETAIL_H 257 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/rtype/wdf_rtype.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDF_RTYPE_H 2 | #define CHOWDSP_WDF_WDF_RTYPE_H 3 | 4 | #include 5 | #include "../wdf/wdf_base.h" 6 | #include "rtype_detail.h" 7 | 8 | namespace chowdsp 9 | { 10 | namespace wdf 11 | { 12 | /** 13 | * A non-adaptable R-Type adaptor. 14 | * For more information see: https://searchworks.stanford.edu/view/11891203, chapter 2 15 | */ 16 | template 17 | class RootRtypeAdaptor : public WDF 18 | { 19 | public: 20 | RootRtypeAdaptor (std::initializer_list*> dps) 21 | : WDF ("Root R-Type Adaptor"), 22 | downPorts (dps), 23 | S_matrix (downPorts.size(), downPorts.size()), 24 | a_vec (downPorts.size()), 25 | b_vec (downPorts.size()) 26 | { 27 | for (auto* port : downPorts) 28 | port->connectToParent (this); 29 | } 30 | 31 | /** Returns the number of ports connected to RootRtypeAdaptor */ 32 | size_t getNumPorts() const noexcept { return downPorts.size(); } 33 | 34 | /** 35 | * Returns the port impedance for the given port index. 36 | * Note: it is the caller's responsibility to ensure that the portIndex is in range! 37 | */ 38 | T getPortImpedance (size_t portIndex) const noexcept { return downPorts[portIndex]->wdf.R; } 39 | 40 | /** Recomputes internal variables based on the incoming impedances */ 41 | void calcImpedance() override 42 | { 43 | impedanceCalculator (*this); 44 | } 45 | 46 | /** Use this function to set the scattering matrix data. */ 47 | template 48 | void setSMatrixData (const T (&mat)[N][N]) 49 | { 50 | const auto numPorts = a_vec.size(); 51 | for (int i = 0; i < numPorts; ++i) 52 | for (int j = 0; j < numPorts; ++j) 53 | S_matrix[j][i] = mat[i][j]; 54 | } 55 | 56 | /** Computes both the incident and reflected waves at this root node. */ 57 | inline void compute() noexcept 58 | { 59 | rtype_detail::RtypeScatter (S_matrix, a_vec, b_vec); 60 | 61 | int i = 0; 62 | for (auto* port : downPorts) 63 | { 64 | port->incident (b_vec[i]); 65 | a_vec[i] = port->reflected(); 66 | i++; 67 | } 68 | } 69 | 70 | /** Implement this function to set the scattering matrix when an incoming impedance changes */ 71 | std::function impedanceCalculator = [] (auto&) {}; 72 | 73 | private: 74 | void incident (T) noexcept override {} 75 | T reflected() noexcept override { return T {}; } 76 | 77 | std::vector*> downPorts; 78 | 79 | rtype_detail::Matrix S_matrix; // square matrix representing S 80 | rtype_detail::AlignedArray a_vec; // temp matrix of inputs to Rport 81 | rtype_detail::AlignedArray b_vec; // temp matrix of outputs from Rport 82 | }; 83 | 84 | /** 85 | * An adaptable R-Type adaptor. 86 | * For more information see: https://searchworks.stanford.edu/view/11891203, chapter 2 87 | */ 88 | template 89 | class RtypeAdaptor : public WDF 90 | { 91 | public: 92 | /** The upPortIndex argument describes with port of the scattering matrix is being adapted. */ 93 | RtypeAdaptor (std::initializer_list*> dps, int upPortIndex) 94 | : WDF ("R-Type Adaptor"), 95 | m_upPortIndex (upPortIndex), 96 | downPorts (dps), 97 | S_matrix (downPorts.size() + 1, downPorts.size() + 1), 98 | a_vec (downPorts.size() + 1), 99 | b_vec (downPorts.size() + 1) 100 | { 101 | for (auto* port : downPorts) 102 | port->connectToParent (this); 103 | } 104 | 105 | /** Returns the number of ports connected to RtypeAdaptor */ 106 | size_t getNumPorts() const noexcept { return downPorts.size() + 1; } 107 | 108 | /** 109 | * Returns the port impedance for the given port index. 110 | * Note: it is the caller's responsibility to ensure that the portIndex is in range! 111 | */ 112 | T getPortImpedance (size_t portIndex) const noexcept { return downPorts[portIndex]->wdf.R; } 113 | 114 | /** Recomputes internal variables based on the incoming impedances */ 115 | void calcImpedance() override 116 | { 117 | this->wdf.R = impedanceCalculator (*this); 118 | this->wdf.G = (T) 1 / this->wdf.R; 119 | } 120 | 121 | /** Use this function to set the scattering matrix data. */ 122 | template 123 | void setSMatrixData (const T (&mat)[N][N]) 124 | { 125 | const auto numPorts = a_vec.size(); 126 | for (int i = 0; i < numPorts; ++i) 127 | for (int j = 0; j < numPorts; ++j) 128 | S_matrix[j][i] = mat[i][j]; 129 | } 130 | 131 | /** Computes the incident wave. */ 132 | inline void incident (T downWave) noexcept override 133 | { 134 | this->wdf.a = downWave; 135 | a_vec[m_upPortIndex] = this->wdf.a; 136 | 137 | rtype_detail::RtypeScatter (S_matrix, a_vec, b_vec); 138 | 139 | int i = 0; 140 | for (auto* port : downPorts) 141 | { 142 | auto portIndex = getPortIndex (i); 143 | port->incident (b_vec[i]); 144 | i++; 145 | } 146 | } 147 | 148 | /** Computes the reflected wave */ 149 | inline T reflected() noexcept override 150 | { 151 | int i = 0; 152 | for (auto* port : downPorts) 153 | { 154 | auto portIndex = getPortIndex (i); 155 | a_vec[portIndex] = port->reflected(); 156 | i++; 157 | } 158 | 159 | this->wdf.b = b_vec[m_upPortIndex]; 160 | return this->wdf.b; 161 | } 162 | 163 | /** Implement this function to set the scattering matrix when an incoming impedance changes */ 164 | std::function impedanceCalculator = [] (auto&) { return (T) 1; }; 165 | 166 | private: 167 | int getPortIndex (int vectorIndex) 168 | { 169 | return vectorIndex < m_upPortIndex ? vectorIndex : vectorIndex + 1; 170 | } 171 | 172 | std::vector*> downPorts; 173 | 174 | const int m_upPortIndex; 175 | 176 | rtype_detail::Matrix S_matrix; // square matrix representing S 177 | rtype_detail::AlignedArray a_vec; // temp matrix of inputs to Rport 178 | rtype_detail::AlignedArray b_vec; // temp matrix of outputs from Rport 179 | }; 180 | } // namespace wdf 181 | } // namespace chowdsp 182 | 183 | #endif //CHOWDSP_WDF_WDF_RTYPE_H 184 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/util/defer_impedance.h: -------------------------------------------------------------------------------- 1 | #ifndef WAVEDIGITALFILTERS_DEFER_IMPEDANCE_H 2 | #define WAVEDIGITALFILTERS_DEFER_IMPEDANCE_H 3 | 4 | namespace chowdsp 5 | { 6 | namespace wdft 7 | { 8 | /** 9 | * Let's say that you've got some fancy adaptor in your WDF (e.g. an R-Type adaptor), 10 | * which requires a somewhat expensive computation to re-calculate the adaptor's impedance. 11 | * In that case, you may not want to re-calculate the impedance every time a downstream element 12 | * changes its impedance, since the impedance might need to be re-calculated several times. 13 | * For that purpose, you can use this class as follows: 14 | * ```cpp 15 | * class MyWDF 16 | * { 17 | * // ... 18 | * 19 | * Resistor pot1 {}; 20 | * WDFSeries S1 { pot1, ... }; 21 | * 22 | * Resistor pot2 {}; 23 | * WDFParallel P1 { pot2, ... }; 24 | * 25 | * FancyAdaptor adaptor { std::tie (S1, P1, ...); }; // this adaptor does heavy computation when re-calculating the impedance 26 | * 27 | * public: 28 | * void setParameters (float pot1Value, float pot2Value) 29 | * { 30 | * { 31 | * // while this object is alive, any impedance changes that are propagated 32 | * // to S1 will cause S1 to recompute its impedance, but will not allow impeance 33 | * // changes to be propagated any further up the tree, and the same for P1. 34 | * 35 | * ScopedDeferImpedancePropagation deferImpedance { S1, P1 }; 36 | * pot1.setResistanceValue (pot1Value); 37 | * pot2.setResistanceValue (pot2Value); 38 | * } 39 | * 40 | * // Now we must manually tell the fancyAdaptor that downstream impedances have changed: 41 | * fancyAdaptor.propagateImpedanceChange(); 42 | * } 43 | * } 44 | * ``` 45 | */ 46 | template 47 | class ScopedDeferImpedancePropagation 48 | { 49 | public: 50 | explicit ScopedDeferImpedancePropagation (Elements&... elems) : elements (std::tie (elems...)) 51 | { 52 | #if __cplusplus >= 201703L // With C++17 and later, it's easy to assert that all the elements are derived from BaseWDF 53 | static_assert ((std::is_base_of::value && ...), "All element types must be derived from BaseWDF"); 54 | #endif 55 | 56 | rtype_detail::forEachInTuple ( 57 | [] (auto& el, size_t) { 58 | el.dontPropagateImpedance = true; 59 | }, 60 | elements); 61 | } 62 | 63 | ~ScopedDeferImpedancePropagation() 64 | { 65 | rtype_detail::forEachInTuple ( 66 | [] (auto& el, size_t) { 67 | el.dontPropagateImpedance = false; 68 | el.calcImpedance(); 69 | }, 70 | elements); 71 | } 72 | 73 | private: 74 | std::tuple elements; 75 | }; 76 | } // namespace wdft 77 | } // namespace chowdsp 78 | 79 | #endif //WAVEDIGITALFILTERS_DEFER_IMPEDANCE_H 80 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdf/wdf.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_H_INCLUDED 2 | #define CHOWDSP_WDF_H_INCLUDED 3 | 4 | namespace chowdsp 5 | { 6 | /** API for constructing Wave Digital Filters with run-time flexibility */ 7 | namespace wdf 8 | { 9 | } 10 | 11 | } // namespace chowdsp 12 | 13 | #include "wdf_one_ports.h" 14 | #include "wdf_sources.h" 15 | #include "wdf_adaptors.h" 16 | #include "wdf_nonlinearities.h" 17 | 18 | #endif // CHOWDSP_WDF_H_INCLUDED 19 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdf/wdf_adaptors.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDF_ADAPTORS_H 2 | #define CHOWDSP_WDF_WDF_ADAPTORS_H 3 | 4 | #include "wdf_base.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdf 9 | { 10 | /** WDF Voltage Polarity Inverter */ 11 | template 12 | class PolarityInverter final : public WDFWrapper>> 13 | { 14 | public: 15 | /** Creates a new WDF polarity inverter 16 | * @param port1: the port to connect to the inverter 17 | */ 18 | explicit PolarityInverter (WDF* port1) : WDFWrapper>> ("Polarity Inverter", *port1) 19 | { 20 | port1->connectToNode (this); 21 | } 22 | }; 23 | 24 | /** WDF y-parameter 2-port (short circuit admittance) */ 25 | template 26 | class YParameter final : public WDFWrapper>> 27 | { 28 | public: 29 | YParameter (WDF* port1, T y11, T y12, T y21, T y22) : WDFWrapper>> ("YParameter", *port1, y11, y12, y21, y22) 30 | { 31 | port1->connectToNode (this); 32 | } 33 | }; 34 | 35 | /** WDF 3-port parallel adaptor */ 36 | template 37 | class WDFParallel final : public WDFWrapper, WDF>> 38 | { 39 | public: 40 | /** Creates a new WDF parallel adaptor from two connected ports. */ 41 | WDFParallel (WDF* port1, WDF* port2) : WDFWrapper, WDF>> ("Parallel", *port1, *port2) 42 | { 43 | port1->connectToNode (this); 44 | port2->connectToNode (this); 45 | } 46 | }; 47 | 48 | /** WDF 3-port series adaptor */ 49 | template 50 | class WDFSeries final : public WDFWrapper, WDF>> 51 | { 52 | public: 53 | /** Creates a new WDF series adaptor from two connected ports. */ 54 | WDFSeries (WDF* port1, WDF* port2) : WDFWrapper, WDF>> ("Series", *port1, *port2) 55 | { 56 | port1->connectToNode (this); 57 | port2->connectToNode (this); 58 | } 59 | }; 60 | } // namespace wdf 61 | } // namespace chowdsp 62 | 63 | #endif //CHOWDSP_WDF_WDF_ADAPTORS_H 64 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdf/wdf_base.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDF_BASE_H 2 | #define CHOWDSP_WDF_WDF_BASE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../wdft/wdft.h" 8 | 9 | namespace chowdsp 10 | { 11 | namespace wdf 12 | { 13 | /** Wave digital filter base class */ 14 | template 15 | class WDF : public wdft::BaseWDF 16 | { 17 | public: 18 | explicit WDF (std::string type) : type (std::move (type)) {} 19 | ~WDF() override = default; 20 | 21 | void connectToNode (WDF* p) { wdfParent = p; } 22 | 23 | /** Sub-classes override this function to propagate 24 | * an impedance change to the upstream elements in 25 | * the WDF tree. 26 | */ 27 | virtual inline void propagateImpedance() 28 | { 29 | calcImpedance(); 30 | 31 | if (wdfParent != nullptr) 32 | wdfParent->propagateImpedance(); 33 | } 34 | 35 | /** Sub-classes override this function to accept an incident wave. */ 36 | virtual void incident (T x) noexcept = 0; 37 | 38 | /** Sub-classes override this function to propogate a reflected wave. */ 39 | virtual T reflected() noexcept = 0; 40 | 41 | /** Probe the voltage across this circuit element. */ 42 | inline T voltage() const noexcept 43 | { 44 | return (wdf.a + wdf.b) / (T) 2.0; 45 | } 46 | 47 | /**Probe the current through this circuit element. */ 48 | inline T current() const noexcept 49 | { 50 | return (wdf.a - wdf.b) / ((T) 2.0 * wdf.R); 51 | } 52 | 53 | // These classes need access to a,b 54 | template 55 | friend class YParameter; 56 | 57 | template 58 | friend class WDFParallel; 59 | 60 | template 61 | friend class WDFSeries; 62 | 63 | wdft::WDFMembers wdf; 64 | 65 | private: 66 | const std::string type; 67 | 68 | WDF* wdfParent = nullptr; 69 | }; 70 | 71 | template 72 | class WDFWrapper : public WDF 73 | { 74 | public: 75 | template 76 | explicit WDFWrapper (const std::string& name, Args&&... args) : WDF (name), 77 | internalWDF (std::forward (args)...) 78 | { 79 | calcImpedance(); 80 | } 81 | 82 | /** Computes the impedance of the WDF resistor, Z_R = R. */ 83 | inline void calcImpedance() override 84 | { 85 | internalWDF.calcImpedance(); 86 | this->wdf.R = internalWDF.wdf.R; 87 | this->wdf.G = internalWDF.wdf.G; 88 | } 89 | 90 | /** Accepts an incident wave into a WDF resistor. */ 91 | inline void incident (T x) noexcept override 92 | { 93 | this->wdf.a = x; 94 | internalWDF.incident (x); 95 | } 96 | 97 | /** Propogates a reflected wave from a WDF resistor. */ 98 | inline T reflected() noexcept override 99 | { 100 | this->wdf.b = internalWDF.reflected(); 101 | return this->wdf.b; 102 | } 103 | 104 | protected: 105 | WDFType internalWDF; 106 | }; 107 | 108 | template 109 | class WDFRootWrapper : public WDFWrapper 110 | { 111 | public: 112 | template 113 | WDFRootWrapper (const std::string& name, Next& next, Args&&... args) : WDFWrapper (name, std::forward (args)...) 114 | { 115 | next.connectToNode (this); 116 | calcImpedance(); 117 | } 118 | 119 | inline void propagateImpedance() override 120 | { 121 | this->calcImpedance(); 122 | } 123 | 124 | inline void calcImpedance() override 125 | { 126 | this->internalWDF.calcImpedance(); 127 | } 128 | }; 129 | } // namespace wdf 130 | } // namespace chowdsp 131 | 132 | #endif //CHOWDSP_WDF_WDF_BASE_H 133 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdf/wdf_nonlinearities.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDF_NONLINEARITIES_H 2 | #define CHOWDSP_WDF_WDF_NONLINEARITIES_H 3 | 4 | #include "wdf_base.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdf 9 | { 10 | /** WDF Switch (non-adaptable) */ 11 | template 12 | class Switch final : public WDFWrapper>> 13 | { 14 | public: 15 | explicit Switch (WDF* next) : WDFWrapper>> ("Switch", *next) 16 | { 17 | next->connectToNode (this); 18 | } 19 | 20 | /** Sets the state of the switch. */ 21 | void setClosed (bool shouldClose) 22 | { 23 | this->internalWDF.setClosed (shouldClose); 24 | } 25 | }; 26 | 27 | /** WDF Open circuit (non-adaptable) */ 28 | template 29 | class Open final : public WDF 30 | { 31 | public: 32 | Open() : WDF ("Open") 33 | { 34 | this->R = (T) 1.0e15; 35 | this->G = (T) 1.0 / this->R; 36 | } 37 | 38 | inline void calcImpedance() override {} 39 | 40 | /** Accepts an incident wave into a WDF open. */ 41 | inline void incident (T x) noexcept override 42 | { 43 | this->a = x; 44 | } 45 | 46 | /** Propogates a reflected wave from a WDF open. */ 47 | inline T reflected() noexcept override 48 | { 49 | this->b = this->a; 50 | return this->b; 51 | } 52 | }; 53 | 54 | /** WDF Short circuit (non-adaptable) */ 55 | template 56 | class Short final : public WDF 57 | { 58 | public: 59 | Short() : WDF ("Short") 60 | { 61 | this->R = (T) 1.0e-15; 62 | this->G = (T) 1.0 / this->R; 63 | } 64 | 65 | inline void calcImpedance() override {} 66 | 67 | /** Accepts an incident wave into a WDF short. */ 68 | inline void incident (T x) noexcept override 69 | { 70 | this->a = x; 71 | } 72 | 73 | /** Propogates a reflected wave from a WDF short. */ 74 | inline T reflected() noexcept override 75 | { 76 | this->b = -this->a; 77 | return this->b; 78 | } 79 | }; 80 | 81 | /** 82 | * WDF diode pair (non-adaptable) 83 | * See Werner et al., "An Improved and Generalized Diode Clipper Model for Wave Digital Filters" 84 | * https://www.researchgate.net/publication/299514713_An_Improved_and_Generalized_Diode_Clipper_Model_for_Wave_Digital_Filters 85 | */ 86 | template 87 | class DiodePair final : public WDFRootWrapper, Q>> 88 | { 89 | public: 90 | /** 91 | * Creates a new WDF diode pair, with the given diode specifications. 92 | * @param next: the next element in the WDF connection tree 93 | * @param Is: reverse saturation current 94 | * @param Vt: thermal voltage 95 | * @param nDiodes: the number of series diodes 96 | */ 97 | DiodePair (WDF* next, T Is, T Vt = (NumericType) 25.85e-3, T nDiodes = (T) 1) : WDFRootWrapper, Q>> ("DiodePair", *next, *next, Is, Vt, nDiodes) 98 | { 99 | next->connectToNode (this); 100 | } 101 | 102 | /** Sets diode specific parameters */ 103 | void setDiodeParameters (T newIs, T newVt, T nDiodes) 104 | { 105 | this->internalWDF.setDiodeParameters (newIs, newVt, nDiodes); 106 | } 107 | }; 108 | 109 | /** 110 | * WDF diode (non-adaptable) 111 | * See Werner et al., "An Improved and Generalized Diode Clipper Model for Wave Digital Filters" 112 | * https://www.researchgate.net/publication/299514713_An_Improved_and_Generalized_Diode_Clipper_Model_for_Wave_Digital_Filters 113 | */ 114 | template 115 | class Diode final : public WDFRootWrapper>> 116 | { 117 | public: 118 | /** 119 | * Creates a new WDF diode, with the given diode specifications. 120 | * @param next: the next element in the WDF connection tree 121 | * @param Is: reverse saturation current 122 | * @param Vt: thermal voltage 123 | * @param nDiodes: the number of series diodes 124 | */ 125 | Diode (WDF* next, T Is, T Vt = (NumericType) 25.85e-3, T nDiodes = 1) : WDFRootWrapper>> ("Diode", *next, *next, Is, Vt, nDiodes) 126 | { 127 | next->connectToNode (this); 128 | } 129 | 130 | /** Sets diode specific parameters */ 131 | void setDiodeParameters (T newIs, T newVt, T nDiodes) 132 | { 133 | this->internalWDF.setDiodeParameters (newIs, newVt, nDiodes); 134 | } 135 | }; 136 | } // namespace wdf 137 | } // namespace chowdsp 138 | 139 | #endif //CHOWDSP_WDF_WDF_NONLINEARITIES_H 140 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdf/wdf_one_ports.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDF_ONE_PORTS_H 2 | #define CHOWDSP_WDF_WDF_ONE_PORTS_H 3 | 4 | #include "wdf_base.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdf 9 | { 10 | /** WDF Resistor Node */ 11 | template 12 | class Resistor final : public WDFWrapper> 13 | { 14 | public: 15 | /** Creates a new WDF Resistor with a given resistance. 16 | * @param value: resistance in Ohms 17 | */ 18 | explicit Resistor (T value) : WDFWrapper> ("Resistor", value) 19 | { 20 | } 21 | 22 | /** Sets the resistance value of the WDF resistor, in Ohms. */ 23 | void setResistanceValue (T newR) 24 | { 25 | this->internalWDF.setResistanceValue (newR); 26 | this->propagateImpedance(); 27 | } 28 | }; 29 | 30 | /** WDF Capacitor Node */ 31 | template 32 | class Capacitor final : public WDFWrapper> 33 | { 34 | public: 35 | /** Creates a new WDF Capacitor. 36 | * @param value: Capacitance value in Farads 37 | * @param fs: WDF sample rate 38 | */ 39 | explicit Capacitor (T value, T fs = (T) 48000.0) : WDFWrapper> ("Capacitor", value, fs) 40 | { 41 | } 42 | 43 | /** Sets the capacitance value of the WDF capacitor, in Farads. */ 44 | void setCapacitanceValue (T newC) 45 | { 46 | this->internalWDF.setCapacitanceValue (newC); 47 | this->propagateImpedance(); 48 | } 49 | 50 | /** Prepares the capacitor to operate at a new sample rate */ 51 | void prepare (T sampleRate) 52 | { 53 | this->internalWDF.prepare (sampleRate); 54 | this->propagateImpedance(); 55 | } 56 | 57 | /** Resets the capacitor state */ 58 | void reset() 59 | { 60 | this->internalWDF.reset(); 61 | } 62 | }; 63 | 64 | /** WDF Capacitor Node with alpha transform parameter */ 65 | template 66 | class CapacitorAlpha final : public WDFWrapper> 67 | { 68 | public: 69 | /** Creates a new WDF Capacitor. 70 | * @param value: Capacitance value in Farads 71 | * @param fs: WDF sample rate 72 | * @param alpha: alpha value to be used for the alpha transform, 73 | * use 0 for Backwards Euler, use 1 for Bilinear Transform. 74 | */ 75 | explicit CapacitorAlpha (T value, T fs = (T) 48000.0, T alpha = (T) 1.0) : WDFWrapper> ("Capacitor", value, fs, alpha) 76 | { 77 | } 78 | 79 | /** Sets the capacitance value of the WDF capacitor, in Farads. */ 80 | void setCapacitanceValue (T newC) 81 | { 82 | this->internalWDF.setCapacitanceValue (newC); 83 | this->propagateImpedance(); 84 | } 85 | 86 | /** Prepares the capacitor to operate at a new sample rate */ 87 | void prepare (T sampleRate) 88 | { 89 | this->internalWDF.prepare (sampleRate); 90 | this->propagateImpedance(); 91 | } 92 | 93 | /** Resets the capacitor state */ 94 | void reset() 95 | { 96 | this->internalWDF.reset(); 97 | } 98 | 99 | /** Sets a new alpha value to use for the alpha transform */ 100 | void setAlpha (T newAlpha) 101 | { 102 | this->internalWDF.setAlpha (newAlpha); 103 | this->propagateImpedance(); 104 | } 105 | }; 106 | 107 | /** WDF Inductor Node */ 108 | template 109 | class Inductor final : public WDFWrapper> 110 | { 111 | public: 112 | /** Creates a new WDF Inductor. 113 | * @param value: Inductance value in Farads 114 | * @param fs: WDF sample rate 115 | */ 116 | explicit Inductor (T value, T fs = (T) 48000.0) : WDFWrapper> ("Inductor", value, fs) 117 | { 118 | } 119 | 120 | /** Sets the inductance value of the WDF inductor, in Henries. */ 121 | void setInductanceValue (T newL) 122 | { 123 | this->internalWDF.setInductanceValue (newL); 124 | this->propagateImpedance(); 125 | } 126 | 127 | /** Prepares the inductor to operate at a new sample rate */ 128 | void prepare (T sampleRate) 129 | { 130 | this->internalWDF.prepare (sampleRate); 131 | this->propagateImpedance(); 132 | } 133 | 134 | /** Resets the inductor state */ 135 | void reset() 136 | { 137 | this->internalWDF.reset(); 138 | } 139 | }; 140 | 141 | /** WDF Inductor Node with alpha transform parameter */ 142 | template 143 | class InductorAlpha final : public WDFWrapper> 144 | { 145 | public: 146 | /** Creates a new WDF Inductor. 147 | * @param value: Inductance value in Farads 148 | * @param fs: WDF sample rate 149 | * @param alpha: alpha value to be used for the alpha transform, 150 | * use 0 for Backwards Euler, use 1 for Bilinear Transform. 151 | */ 152 | explicit InductorAlpha (T value, T fs = 48000.0, T alpha = 1.0) : WDFWrapper> ("Inductor", value, fs, alpha) 153 | { 154 | } 155 | 156 | /** Sets the inductance value of the WDF inductor, in Henries. */ 157 | void setInductanceValue (T newL) 158 | { 159 | this->internalWDF.setInductanceValue (newL); 160 | this->propagateImpedance(); 161 | } 162 | 163 | /** Prepares the inductor to operate at a new sample rate */ 164 | void prepare (T sampleRate) 165 | { 166 | this->internalWDF.prepare (sampleRate); 167 | this->propagateImpedance(); 168 | } 169 | 170 | /** Resets the inductor state */ 171 | void reset() 172 | { 173 | this->internalWDF.reset(); 174 | } 175 | 176 | /** Sets a new alpha value to use for the alpha transform */ 177 | void setAlpha (T newAlpha) 178 | { 179 | this->internalWDF.setAlpha (newAlpha); 180 | this->propagateImpedance(); 181 | } 182 | }; 183 | 184 | /** WDF Resistor/Capacitor Series Node */ 185 | template 186 | class ResistorCapacitorSeries final : public WDFWrapper> 187 | { 188 | public: 189 | /** Creates a new WDF Resistor/Capacitor Series node. 190 | * @param res_value: resistance in Ohms 191 | * @param cap_value: capacitance in Farads 192 | */ 193 | explicit ResistorCapacitorSeries (T res_value, T cap_value) 194 | : WDFWrapper> ("Resistor/Capacitor Series", res_value, cap_value) 195 | { 196 | } 197 | 198 | /** Sets the resistance value of the WDF resistor, in Ohms. */ 199 | void setResistanceValue (T newR) 200 | { 201 | this->internalWDF.setResistanceValue (newR); 202 | this->propagateImpedance(); 203 | } 204 | 205 | /** Sets the capacitance value of the WDF capacitor, in Farads. */ 206 | void setCapacitanceValue (T newC) 207 | { 208 | this->internalWDF.setCapacitanceValue (newC); 209 | this->propagateImpedance(); 210 | } 211 | 212 | /** Prepares the capacitor to operate at a new sample rate */ 213 | void prepare (T sampleRate) 214 | { 215 | this->internalWDF.prepare (sampleRate); 216 | this->propagateImpedance(); 217 | } 218 | 219 | /** Resets the capacitor state */ 220 | void reset() 221 | { 222 | this->internalWDF.reset(); 223 | } 224 | }; 225 | 226 | /** WDF Resistor/Capacitor Parallel Node */ 227 | template 228 | class ResistorCapacitorParallel final : public WDFWrapper> 229 | { 230 | public: 231 | /** Creates a new WDF Resistor/Capacitor Parallel node. 232 | * @param res_value: resistance in Ohms 233 | * @param cap_value: capacitance in Farads 234 | */ 235 | explicit ResistorCapacitorParallel (T res_value, T cap_value) 236 | : WDFWrapper> ("Resistor/Capacitor Parallel", res_value, cap_value) 237 | { 238 | } 239 | 240 | /** Sets the resistance value of the WDF resistor, in Ohms. */ 241 | void setResistanceValue (T newR) 242 | { 243 | this->internalWDF.setResistanceValue (newR); 244 | this->propagateImpedance(); 245 | } 246 | 247 | /** Sets the capacitance value of the WDF capacitor, in Farads. */ 248 | void setCapacitanceValue (T newC) 249 | { 250 | this->internalWDF.setCapacitanceValue (newC); 251 | this->propagateImpedance(); 252 | } 253 | 254 | /** Prepares the capacitor to operate at a new sample rate */ 255 | void prepare (T sampleRate) 256 | { 257 | this->internalWDF.prepare (sampleRate); 258 | this->propagateImpedance(); 259 | } 260 | 261 | /** Resets the capacitor state */ 262 | void reset() 263 | { 264 | this->internalWDF.reset(); 265 | } 266 | }; 267 | } // namespace wdf 268 | } // namespace chowdsp 269 | 270 | #endif //CHOWDSP_WDF_WDF_ONE_PORTS_H 271 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdf/wdf_sources.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDF_SOURCES_H 2 | #define CHOWDSP_WDF_WDF_SOURCES_H 3 | 4 | #include "wdf_base.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdf 9 | { 10 | /** WDF Voltage source with series resistance */ 11 | template 12 | class ResistiveVoltageSource final : public WDFWrapper> 13 | { 14 | public: 15 | /** Creates a new resistive voltage source. 16 | * @param value: initial resistance value, in Ohms 17 | */ 18 | explicit ResistiveVoltageSource (T value = (NumericType) 1.0e-9) : WDFWrapper> ("Resistive Voltage", value) 19 | { 20 | } 21 | 22 | /** Sets the resistance value of the series resistor, in Ohms. */ 23 | void setResistanceValue (T newR) 24 | { 25 | this->internalWDF.setResistanceValue (newR); 26 | this->propagateImpedance(); 27 | } 28 | 29 | /** Sets the voltage of the voltage source, in Volts */ 30 | void setVoltage (T newV) { this->internalWDF.setVoltage (newV); } 31 | }; 32 | 33 | /** WDF Ideal Voltage source (non-adaptable) */ 34 | template 35 | class IdealVoltageSource final : public WDFWrapper>> 36 | { 37 | public: 38 | explicit IdealVoltageSource (WDF* next) : WDFWrapper>> ("IdealVoltage", *next) 39 | { 40 | next->connectToNode (this); 41 | } 42 | 43 | /** Sets the voltage of the voltage source, in Volts */ 44 | void setVoltage (T newV) { this->internalWDF.setVoltage (newV); } 45 | }; 46 | 47 | /** WDF Current source with parallel resistance */ 48 | template 49 | class ResistiveCurrentSource final : public WDFWrapper> 50 | { 51 | public: 52 | /** Creates a new resistive current source. 53 | * @param value: initial resistance value, in Ohms 54 | */ 55 | explicit ResistiveCurrentSource (T value = (NumericType) 1.0e9) : WDFWrapper> ("Resistive Current", value) 56 | { 57 | } 58 | 59 | /** Sets the resistance value of the parallel resistor, in Ohms. */ 60 | void setResistanceValue (T newR) 61 | { 62 | this->internalWDF.setResistanceValue (newR); 63 | this->propagateImpedance(); 64 | } 65 | 66 | /** Sets the current of the current source, in Amps */ 67 | void setCurrent (T newI) { this->internalWDF.setCurrent (newI); } 68 | }; 69 | 70 | /** WDF Current source (non-adpatable) */ 71 | template 72 | class IdealCurrentSource final : public WDFWrapper>> 73 | { 74 | public: 75 | explicit IdealCurrentSource (WDF* next) : WDFWrapper>> ("Ideal Current", *next) 76 | { 77 | next->connectToNode (this); 78 | } 79 | 80 | /** Sets the current of the current source, in Amps */ 81 | void setCurrent (T newI) { this->internalWDF.setCurrent (newI); } 82 | }; 83 | } // namespace wdf 84 | } // namespace chowdsp 85 | 86 | #endif //CHOWDSP_WDF_WDF_SOURCES_H 87 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdft/wdft.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_T_INCLUDED 2 | #define CHOWDSP_WDF_T_INCLUDED 3 | 4 | namespace chowdsp 5 | { 6 | /** API for constructing Wave Digital Filters with a fixed compile-time architecture */ 7 | namespace wdft 8 | { 9 | } 10 | 11 | } // namespace chowdsp 12 | 13 | #include "wdft_one_ports.h" 14 | #include "wdft_sources.h" 15 | #include "wdft_adaptors.h" 16 | #include "wdft_nonlinearities.h" 17 | 18 | #endif // CHOWDSP_WDF_T_INCLUDED 19 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdft/wdft_adaptors.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDFT_ADAPTORS_H 2 | #define CHOWDSP_WDF_WDFT_ADAPTORS_H 3 | 4 | #include "wdft_base.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdft 9 | { 10 | /** WDF 3-port parallel adaptor */ 11 | template 12 | class WDFParallelT final : public BaseWDF 13 | { 14 | public: 15 | /** Creates a new WDF parallel adaptor from two connected ports. */ 16 | WDFParallelT (Port1Type& p1, Port2Type& p2) : port1 (p1), 17 | port2 (p2) 18 | { 19 | port1.connectToParent (this); 20 | port2.connectToParent (this); 21 | calcImpedance(); 22 | } 23 | 24 | /** Computes the impedance for a WDF parallel adaptor. 25 | * 1 1 1 26 | * --- = --- + --- 27 | * Z_p Z_1 Z_2 28 | */ 29 | inline void calcImpedance() override 30 | { 31 | wdf.G = port1.wdf.G + port2.wdf.G; 32 | wdf.R = (T) 1.0 / wdf.G; 33 | port1Reflect = port1.wdf.G / wdf.G; 34 | } 35 | 36 | /** Accepts an incident wave into a WDF parallel adaptor. */ 37 | inline void incident (T x) noexcept 38 | { 39 | const auto b2 = wdf.b - port2.wdf.b + x; 40 | port1.incident (b2 + bDiff); 41 | port2.incident (b2); 42 | 43 | wdf.a = x; 44 | } 45 | 46 | /** Propogates a reflected wave from a WDF parallel adaptor. */ 47 | inline T reflected() noexcept 48 | { 49 | port1.reflected(); 50 | port2.reflected(); 51 | 52 | bDiff = port2.wdf.b - port1.wdf.b; 53 | wdf.b = port2.wdf.b - port1Reflect * bDiff; 54 | 55 | return wdf.b; 56 | } 57 | 58 | Port1Type& port1; 59 | Port2Type& port2; 60 | 61 | WDFMembers wdf; 62 | 63 | private: 64 | T port1Reflect = (T) 1.0; 65 | T bDiff = (T) 0.0; 66 | }; 67 | 68 | /** WDF 3-port series adaptor */ 69 | template 70 | class WDFSeriesT final : public BaseWDF 71 | { 72 | public: 73 | /** Creates a new WDF series adaptor from two connected ports. */ 74 | WDFSeriesT (Port1Type& p1, Port2Type& p2) : port1 (p1), 75 | port2 (p2) 76 | { 77 | port1.connectToParent (this); 78 | port2.connectToParent (this); 79 | calcImpedance(); 80 | } 81 | 82 | /** Computes the impedance for a WDF parallel adaptor. 83 | * Z_s = Z_1 + Z_2 84 | */ 85 | inline void calcImpedance() override 86 | { 87 | wdf.R = port1.wdf.R + port2.wdf.R; 88 | wdf.G = (T) 1.0 / wdf.R; 89 | port1Reflect = port1.wdf.R / wdf.R; 90 | } 91 | 92 | /** Accepts an incident wave into a WDF series adaptor. */ 93 | inline void incident (T x) noexcept 94 | { 95 | const auto b1 = port1.wdf.b - port1Reflect * (x + port1.wdf.b + port2.wdf.b); 96 | port1.incident (b1); 97 | port2.incident (-(x + b1)); 98 | 99 | wdf.a = x; 100 | } 101 | 102 | /** Propogates a reflected wave from a WDF series adaptor. */ 103 | inline T reflected() noexcept 104 | { 105 | wdf.b = -(port1.reflected() + port2.reflected()); 106 | return wdf.b; 107 | } 108 | 109 | Port1Type& port1; 110 | Port2Type& port2; 111 | 112 | WDFMembers wdf; 113 | 114 | private: 115 | T port1Reflect = (T) 1.0; 116 | }; 117 | 118 | /** WDF Voltage Polarity Inverter */ 119 | template 120 | class PolarityInverterT final : public BaseWDF 121 | { 122 | public: 123 | /** Creates a new WDF polarity inverter */ 124 | explicit PolarityInverterT (PortType& p) : port1 (p) 125 | { 126 | port1.connectToParent (this); 127 | calcImpedance(); 128 | } 129 | 130 | /** Calculates the impedance of the WDF inverter 131 | * (same impedance as the connected node). 132 | */ 133 | inline void calcImpedance() override 134 | { 135 | wdf.R = port1.wdf.R; 136 | wdf.G = (T) 1.0 / wdf.R; 137 | } 138 | 139 | /** Accepts an incident wave into a WDF inverter. */ 140 | inline void incident (T x) noexcept 141 | { 142 | wdf.a = x; 143 | port1.incident (-x); 144 | } 145 | 146 | /** Propogates a reflected wave from a WDF inverter. */ 147 | inline T reflected() noexcept 148 | { 149 | wdf.b = -port1.reflected(); 150 | return wdf.b; 151 | } 152 | 153 | WDFMembers wdf; 154 | 155 | private: 156 | PortType& port1; 157 | }; 158 | 159 | /** WDF y-parameter 2-port (short circuit admittance) */ 160 | template 161 | class YParameterT final : public BaseWDF 162 | { 163 | public: 164 | /** Creates a new WDF Y-Parameter, with the given coefficients */ 165 | YParameterT (PortType& port1, T y11, T y12, T y21, T y22) : port1 (port1) 166 | { 167 | y[0][0] = y11; 168 | y[0][1] = y12; 169 | y[1][0] = y21; 170 | y[1][1] = y22; 171 | 172 | port1.connectToParent (this); 173 | calcImpedance(); 174 | } 175 | 176 | /** Calculates the impedance of the WDF Y-Parameter */ 177 | inline void calcImpedance() override 178 | { 179 | denominator = y[1][1] + port1.wdf.R * y[0][0] * y[1][1] - port1.wdf.R * y[0][1] * y[1][0]; 180 | wdf.R = (port1.wdf.R * y[0][0] + (T) 1.0) / denominator; 181 | wdf.G = (T) 1.0 / wdf.R; 182 | 183 | T rSq = port1.wdf.R * port1.wdf.R; 184 | T num1A = -y[1][1] * rSq * y[0][0] * y[0][0]; 185 | T num2A = y[0][1] * y[1][0] * rSq * y[0][0]; 186 | 187 | A = (num1A + num2A + y[1][1]) / (denominator * (port1.wdf.R * y[0][0] + (T) 1.0)); 188 | B = -port1.wdf.R * y[0][1] / (port1.wdf.R * y[0][0] + (T) 1.0); 189 | C = -y[1][0] / denominator; 190 | } 191 | 192 | /** Accepts an incident wave into a WDF Y-Parameter. */ 193 | inline void incident (T x) noexcept 194 | { 195 | wdf.a = x; 196 | port1.incident (A * port1.wdf.b + B * x); 197 | } 198 | 199 | /** Propogates a reflected wave from a WDF Y-Parameter. */ 200 | inline T reflected() noexcept 201 | { 202 | wdf.b = C * port1.reflected(); 203 | return wdf.b; 204 | } 205 | 206 | WDFMembers wdf; 207 | 208 | private: 209 | PortType& port1; 210 | T y[2][2] = { { (T) 0.0, (T) 0.0 }, { (T) 0.0, (T) 0.0 } }; 211 | 212 | T denominator = (T) 1.0; 213 | T A = (T) 1.0; 214 | T B = (T) 1.0; 215 | T C = (T) 1.0; 216 | }; 217 | 218 | // useful "factory" functions so you don't have to declare all the template parameters 219 | 220 | /** Factory method for creating a parallel adaptor between two elements. */ 221 | template 222 | CHOWDSP_WDF_MAYBE_UNUSED WDFParallelT makeParallel (P1Type& p1, P2Type& p2) 223 | { 224 | return WDFParallelT (p1, p2); 225 | } 226 | 227 | /** Factory method for creating a series adaptor between two elements. */ 228 | template 229 | CHOWDSP_WDF_MAYBE_UNUSED WDFSeriesT makeSeries (P1Type& p1, P2Type& p2) 230 | { 231 | return WDFSeriesT (p1, p2); 232 | } 233 | 234 | /** Factory method for creating a polarity inverter. */ 235 | template 236 | CHOWDSP_WDF_MAYBE_UNUSED PolarityInverterT makeInverter (PType& p1) 237 | { 238 | return PolarityInverterT (p1); 239 | } 240 | } // namespace wdft 241 | } // namespace chowdsp 242 | 243 | #endif //CHOWDSP_WDF_WDFT_ADAPTORS_H 244 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdft/wdft_base.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDFT_BASE_H 2 | #define CHOWDSP_WDF_WDFT_BASE_H 3 | 4 | #include "../math/sample_type.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdft 9 | { 10 | /** Base WDF class for propagating impedance changes between elements */ 11 | class BaseWDF 12 | { 13 | public: 14 | virtual ~BaseWDF() = default; 15 | 16 | void connectToParent (BaseWDF* p) { parent = p; } 17 | 18 | virtual void calcImpedance() = 0; 19 | 20 | inline virtual void propagateImpedanceChange() 21 | { 22 | if (dontPropagateImpedance) 23 | return; // the impedance propagation is being deferred until later... 24 | 25 | calcImpedance(); 26 | 27 | if (parent != nullptr) 28 | parent->propagateImpedanceChange(); 29 | } 30 | 31 | protected: 32 | BaseWDF* parent = nullptr; 33 | 34 | private: 35 | bool dontPropagateImpedance = false; 36 | 37 | template 38 | friend class ScopedDeferImpedancePropagation; 39 | }; 40 | 41 | /** Base class for propagating impedance changes into root WDF elements */ 42 | class RootWDF : public BaseWDF 43 | { 44 | public: 45 | inline void propagateImpedanceChange() override { calcImpedance(); } 46 | 47 | private: 48 | // don't try to connect root nodes! 49 | void connectToParent (BaseWDF*) {} 50 | }; 51 | 52 | /** Helper struct for common WDF member variables */ 53 | template 54 | struct WDFMembers 55 | { 56 | T R = (NumericType) 1.0e-9; /* impedance */ 57 | T G = (T) 1.0 / R; /* admittance */ 58 | T a = (T) 0.0; /* incident wave */ 59 | T b = (T) 0.0; /* reflected wave */ 60 | }; 61 | 62 | /** Probe the voltage across this circuit element. */ 63 | template 64 | inline T voltage (const WDFType& wdf) noexcept 65 | { 66 | return (wdf.wdf.a + wdf.wdf.b) * (T) 0.5; 67 | } 68 | 69 | /**Probe the current through this circuit element. */ 70 | template 71 | inline T current (const WDFType& wdf) noexcept 72 | { 73 | return (wdf.wdf.a - wdf.wdf.b) * ((T) 0.5 * wdf.wdf.G); 74 | } 75 | } // namespace wdft 76 | } // namespace chowdsp 77 | 78 | #endif // CHOWDSP_WDF_WDFT_BASE_H 79 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdft/wdft_nonlinearities.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDFT_NONLINEARITIES_H 2 | #define CHOWDSP_WDF_WDFT_NONLINEARITIES_H 3 | 4 | #include 5 | 6 | #include "wdft_base.h" 7 | 8 | #include "../math/signum.h" 9 | #include "../math/omega.h" 10 | 11 | namespace chowdsp 12 | { 13 | namespace wdft 14 | { 15 | /** Enum to determine which diode approximation eqn. to use */ 16 | enum DiodeQuality 17 | { 18 | Good, // see reference eqn (18) 19 | Best, // see reference eqn (39) 20 | }; 21 | 22 | /** 23 | * WDF diode pair (non-adaptable) 24 | * See Werner et al., "An Improved and Generalized Diode Clipper Model for Wave Digital Filters" 25 | * https://www.researchgate.net/publication/299514713_An_Improved_and_Generalized_Diode_Clipper_Model_for_Wave_Digital_Filters 26 | */ 27 | template 28 | class DiodePairT final : public RootWDF 29 | { 30 | public: 31 | /** 32 | * Creates a new WDF diode pair, with the given diode specifications. 33 | * @param n: the next element in the WDF connection tree 34 | * @param Is: reverse saturation current 35 | * @param Vt: thermal voltage 36 | * @param nDiodes: the number of series diodes 37 | */ 38 | DiodePairT (Next& n, T Is, T Vt = NumericType (25.85e-3), T nDiodes = 1) : next (n) 39 | { 40 | n.connectToParent (this); 41 | setDiodeParameters (Is, Vt, nDiodes); 42 | } 43 | 44 | /** Sets diode specific parameters */ 45 | void setDiodeParameters (T newIs, T newVt, T nDiodes) 46 | { 47 | Is = newIs; 48 | Vt = nDiodes * newVt; 49 | twoVt = (T) 2 * Vt; 50 | oneOverVt = (T) 1 / Vt; 51 | calcImpedance(); 52 | } 53 | 54 | inline void calcImpedance() override 55 | { 56 | #if defined(XSIMD_HPP) 57 | using xsimd::log; 58 | #endif 59 | using std::log; 60 | 61 | R_Is = next.wdf.R * Is; 62 | R_Is_overVt = R_Is * oneOverVt; 63 | logR_Is_overVt = log (R_Is_overVt); 64 | } 65 | 66 | /** Accepts an incident wave into a WDF diode pair. */ 67 | inline void incident (T x) noexcept 68 | { 69 | wdf.a = x; 70 | } 71 | 72 | /** Propogates a reflected wave from a WDF diode pair. */ 73 | inline T reflected() noexcept 74 | { 75 | reflectedInternal(); 76 | return wdf.b; 77 | } 78 | 79 | WDFMembers wdf; 80 | 81 | private: 82 | /** Implementation for float/double (Good). */ 83 | template 84 | inline typename std::enable_if::type 85 | reflectedInternal() noexcept 86 | { 87 | // See eqn (18) from reference paper 88 | T lambda = (T) signum::signum (wdf.a); 89 | wdf.b = wdf.a + (T) 2 * lambda * (R_Is - Vt * OmegaProvider::omega (logR_Is_overVt + lambda * wdf.a * oneOverVt + R_Is_overVt)); 90 | } 91 | 92 | /** Implementation for float/double (Best). */ 93 | template 94 | inline typename std::enable_if::type 95 | reflectedInternal() noexcept 96 | { 97 | // See eqn (39) from reference paper 98 | T lambda = (T) signum::signum (wdf.a); 99 | T lambda_a_over_vt = lambda * wdf.a * oneOverVt; 100 | wdf.b = wdf.a - twoVt * lambda * (Omega::omega4 (logR_Is_overVt + lambda_a_over_vt) - Omega::omega4 (logR_Is_overVt - lambda_a_over_vt)); 101 | } 102 | 103 | T Is; // reverse saturation current 104 | T Vt; // thermal voltage 105 | 106 | // pre-computed vars 107 | T twoVt; 108 | T oneOverVt; 109 | T R_Is; 110 | T R_Is_overVt; 111 | T logR_Is_overVt; 112 | 113 | const Next& next; 114 | }; 115 | 116 | /** 117 | * WDF diode (non-adaptable) 118 | * See Werner et al., "An Improved and Generalized Diode Clipper Model for Wave Digital Filters" 119 | * https://www.researchgate.net/publication/299514713_An_Improved_and_Generalized_Diode_Clipper_Model_for_Wave_Digital_Filters 120 | */ 121 | template 122 | class DiodeT final : public RootWDF 123 | { 124 | public: 125 | /** 126 | * Creates a new WDF diode, with the given diode specifications. 127 | * @param n: the next element in the WDF connection tree 128 | * @param Is: reverse saturation current 129 | * @param Vt: thermal voltage 130 | * @param nDiodes: the number of series diodes 131 | */ 132 | DiodeT (Next& n, T Is, T Vt = NumericType (25.85e-3), T nDiodes = 1) : next (n) 133 | { 134 | n.connectToParent (this); 135 | setDiodeParameters (Is, Vt, nDiodes); 136 | } 137 | 138 | /** Sets diode specific parameters */ 139 | void setDiodeParameters (T newIs, T newVt, T nDiodes) 140 | { 141 | Is = newIs; 142 | Vt = nDiodes * newVt; 143 | twoVt = (T) 2 * Vt; 144 | oneOverVt = (T) 1 / Vt; 145 | calcImpedance(); 146 | } 147 | 148 | inline void calcImpedance() override 149 | { 150 | #if defined(XSIMD_HPP) 151 | using xsimd::log; 152 | #endif 153 | using std::log; 154 | 155 | twoR_Is = (T) 2 * next.wdf.R * Is; 156 | R_Is_overVt = next.wdf.R * Is * oneOverVt; 157 | logR_Is_overVt = log (R_Is_overVt); 158 | } 159 | 160 | /** Accepts an incident wave into a WDF diode. */ 161 | inline void incident (T x) noexcept 162 | { 163 | wdf.a = x; 164 | } 165 | 166 | /** Propogates a reflected wave from a WDF diode. */ 167 | inline T reflected() noexcept 168 | { 169 | // See eqn (10) from reference paper 170 | wdf.b = wdf.a + twoR_Is - twoVt * OmegaProvider::omega (logR_Is_overVt + wdf.a * oneOverVt + R_Is_overVt); 171 | return wdf.b; 172 | } 173 | 174 | WDFMembers wdf; 175 | 176 | private: 177 | T Is; // reverse saturation current 178 | T Vt; // thermal voltage 179 | 180 | // pre-computed vars 181 | T twoVt; 182 | T oneOverVt; 183 | T twoR_Is; 184 | T R_Is_overVt; 185 | T logR_Is_overVt; 186 | 187 | const Next& next; 188 | }; 189 | 190 | /** WDF Switch (non-adaptable) */ 191 | template 192 | class SwitchT final : public RootWDF 193 | { 194 | public: 195 | explicit SwitchT (Next& next) 196 | { 197 | next.connectToParent (this); 198 | } 199 | 200 | inline void calcImpedance() override {} 201 | 202 | /** Sets the state of the switch. */ 203 | void setClosed (bool shouldClose) { closed = shouldClose; } 204 | 205 | /** Accepts an incident wave into a WDF switch. */ 206 | inline void incident (T x) noexcept 207 | { 208 | wdf.a = x; 209 | } 210 | 211 | /** Propogates a reflected wave from a WDF switch. */ 212 | inline T reflected() noexcept 213 | { 214 | wdf.b = closed ? -wdf.a : wdf.a; 215 | return wdf.b; 216 | } 217 | 218 | WDFMembers wdf; 219 | 220 | private: 221 | bool closed = true; 222 | }; 223 | } // namespace wdft 224 | } // namespace chowdsp 225 | 226 | #endif //CHOWDSP_WDF_WDFT_NONLINEARITIES_H 227 | -------------------------------------------------------------------------------- /include/chowdsp_wdf/wdft/wdft_sources.h: -------------------------------------------------------------------------------- 1 | #ifndef CHOWDSP_WDF_WDFT_SOURCES_H 2 | #define CHOWDSP_WDF_WDFT_SOURCES_H 3 | 4 | #include "wdft_base.h" 5 | 6 | namespace chowdsp 7 | { 8 | namespace wdft 9 | { 10 | /** WDF Ideal Voltage source (non-adaptable) */ 11 | template 12 | class IdealVoltageSourceT final : public RootWDF 13 | { 14 | public: 15 | explicit IdealVoltageSourceT (Next& next) 16 | { 17 | next.connectToParent (this); 18 | calcImpedance(); 19 | } 20 | 21 | void calcImpedance() override {} 22 | 23 | /** Sets the voltage of the voltage source, in Volts */ 24 | void setVoltage (T newV) { Vs = newV; } 25 | 26 | /** Accepts an incident wave into a WDF ideal voltage source. */ 27 | inline void incident (T x) noexcept 28 | { 29 | wdf.a = x; 30 | } 31 | 32 | /** Propogates a reflected wave from a WDF ideal voltage source. */ 33 | inline T reflected() noexcept 34 | { 35 | wdf.b = -wdf.a + (T) 2.0 * Vs; 36 | return wdf.b; 37 | } 38 | 39 | WDFMembers wdf; 40 | 41 | private: 42 | T Vs = (T) 0.0; 43 | }; 44 | 45 | /** WDF Voltage source with series resistance */ 46 | template 47 | class ResistiveVoltageSourceT final : public BaseWDF 48 | { 49 | public: 50 | /** Creates a new resistive voltage source. 51 | * @param value: initial resistance value, in Ohms 52 | */ 53 | explicit ResistiveVoltageSourceT (T value = NumericType (1.0e-9)) : R_value (value) 54 | { 55 | calcImpedance(); 56 | } 57 | 58 | /** Sets the resistance value of the series resistor, in Ohms. */ 59 | void setResistanceValue (T newR) 60 | { 61 | if (all (newR == R_value)) 62 | return; 63 | 64 | R_value = newR; 65 | propagateImpedanceChange(); 66 | } 67 | 68 | /** Computes the impedance for a WDF resistive voltage souce 69 | * Z_Vr = Z_R 70 | */ 71 | inline void calcImpedance() override 72 | { 73 | wdf.R = R_value; 74 | wdf.G = (T) 1.0 / wdf.R; 75 | } 76 | 77 | /** Sets the voltage of the voltage source, in Volts */ 78 | void setVoltage (T newV) { Vs = newV; } 79 | 80 | /** Accepts an incident wave into a WDF resistive voltage source. */ 81 | inline void incident (T x) noexcept 82 | { 83 | wdf.a = x; 84 | } 85 | 86 | /** Propogates a reflected wave from a WDF resistive voltage source. */ 87 | inline T reflected() noexcept 88 | { 89 | wdf.b = Vs; 90 | return wdf.b; 91 | } 92 | 93 | WDFMembers wdf; 94 | 95 | private: 96 | T Vs = (T) 0.0; 97 | T R_value = (T) 1.0e-9; 98 | }; 99 | 100 | /** WDF Voltage source with series capacitance */ 101 | template 102 | class CapacitiveVoltageSourceT final : public BaseWDF 103 | { 104 | public: 105 | /** Creates a new resistive voltage source. 106 | * @param value: initial resistance value, in Ohms 107 | */ 108 | explicit CapacitiveVoltageSourceT (T value = NumericType (1.0e-6), T fs = (T) 48000) 109 | : C_value (value), 110 | fs (fs) 111 | 112 | { 113 | calcImpedance(); 114 | } 115 | 116 | /** Prepares the capacitor to operate at a new sample rate */ 117 | void prepare (T sampleRate) 118 | { 119 | fs = sampleRate; 120 | propagateImpedanceChange(); 121 | 122 | reset(); 123 | } 124 | 125 | void reset() 126 | { 127 | z = (T) 0; 128 | v_1 = (T) 0; 129 | } 130 | 131 | /** Sets the capacitance value of the series resistor, in Farads. */ 132 | void setCapacitanceValue (T newC) 133 | { 134 | if (newC == C_value) 135 | return; 136 | 137 | C_value = newC; 138 | propagateImpedanceChange(); 139 | } 140 | 141 | /** Computes the impedance of the WDF capacitor, 142 | * 1 143 | * Z_C = -------------- 144 | * 2 * f_s * C 145 | */ 146 | inline void calcImpedance() override 147 | { 148 | wdf.R = (T) 1.0 / ((T) 2.0 * C_value * fs); 149 | wdf.G = (T) 1.0 / wdf.R; 150 | } 151 | 152 | /** Sets the voltage of the voltage source, in Volts */ 153 | void setVoltage (T newV) 154 | { 155 | v_0 = newV; 156 | } 157 | 158 | /** Accepts an incident wave into a WDF resistive voltage source. */ 159 | inline void incident (T x) noexcept 160 | { 161 | wdf.a = x; 162 | z = wdf.a; 163 | } 164 | 165 | /** Propogates a reflected wave from a WDF resistive voltage source. */ 166 | inline T reflected() noexcept 167 | { 168 | wdf.b = z + v_0 - v_1; 169 | v_1 = v_0; 170 | return wdf.b; 171 | } 172 | 173 | WDFMembers wdf; 174 | 175 | private: 176 | T C_value = (T) 1.0e-6; 177 | 178 | T z {}; 179 | T v_0 {}; 180 | T v_1 {}; 181 | 182 | T fs; 183 | }; 184 | 185 | /** WDF Current source (non-adaptable) */ 186 | template 187 | class IdealCurrentSourceT final : public RootWDF 188 | { 189 | public: 190 | explicit IdealCurrentSourceT (Next& n) : next (n) 191 | { 192 | n.connectToParent (this); 193 | calcImpedance(); 194 | } 195 | 196 | inline void calcImpedance() override 197 | { 198 | twoR = (T) 2.0 * next.wdf.R; 199 | twoR_Is = twoR * Is; 200 | } 201 | 202 | /** Sets the current of the current source, in Amps */ 203 | void setCurrent (T newI) 204 | { 205 | Is = newI; 206 | twoR_Is = twoR * Is; 207 | } 208 | 209 | /** Accepts an incident wave into a WDF ideal current source. */ 210 | inline void incident (T x) noexcept 211 | { 212 | wdf.a = x; 213 | } 214 | 215 | /** Propogates a reflected wave from a WDF ideal current source. */ 216 | inline T reflected() noexcept 217 | { 218 | wdf.b = twoR_Is + wdf.a; 219 | return wdf.b; 220 | } 221 | 222 | WDFMembers wdf; 223 | 224 | private: 225 | const Next& next; 226 | 227 | T Is = (T) 0.0; 228 | T twoR; 229 | T twoR_Is; 230 | }; 231 | 232 | /** WDF Current source with parallel resistance */ 233 | template 234 | class ResistiveCurrentSourceT final : public BaseWDF 235 | { 236 | public: 237 | /** Creates a new resistive current source. 238 | * @param value: initial resistance value, in Ohms 239 | */ 240 | explicit ResistiveCurrentSourceT (T value = NumericType (1.0e9)) : R_value (value) 241 | { 242 | calcImpedance(); 243 | } 244 | 245 | /** Sets the resistance value of the parallel resistor, in Ohms. */ 246 | void setResistanceValue (T newR) 247 | { 248 | if (all (newR == R_value)) 249 | return; 250 | 251 | R_value = newR; 252 | propagateImpedanceChange(); 253 | } 254 | 255 | /** Computes the impedance for a WDF resistive current souce 256 | * Z_Ir = Z_R 257 | */ 258 | inline void calcImpedance() override 259 | { 260 | wdf.R = R_value; 261 | wdf.G = (T) 1.0 / wdf.R; 262 | } 263 | 264 | /** Sets the current of the current source, in Amps */ 265 | void setCurrent (T newI) { Is = newI; } 266 | 267 | /** Accepts an incident wave into a WDF resistive current source. */ 268 | inline void incident (T x) noexcept 269 | { 270 | wdf.a = x; 271 | } 272 | 273 | /** Propogates a reflected wave from a WDF resistive current source. */ 274 | inline T reflected() noexcept 275 | { 276 | wdf.b = wdf.R * Is; 277 | return wdf.b; 278 | } 279 | 280 | WDFMembers wdf; 281 | 282 | private: 283 | T Is = (T) 0.0; 284 | T R_value = (T) 1.0e9; 285 | }; 286 | 287 | /** WDF Resistor and Capacitor and Voltage source in Series */ 288 | template 289 | class ResistiveCapacitiveVoltageSourceT final : public BaseWDF 290 | { 291 | public: 292 | /** Creates a new WDF Resistor/Capacitor Series. 293 | * @param cap_value: Resistance value in Ohms 294 | * @param res_value: Capacitance value in Farads 295 | * @param fs: WDF sample rate 296 | */ 297 | explicit ResistiveCapacitiveVoltageSourceT (T res_value, T cap_value, T fs = (T) 48000.0) 298 | : R_value (res_value), 299 | C_value (cap_value), 300 | tt ((T) 1 / fs) 301 | { 302 | calcImpedance(); 303 | reset(); 304 | } 305 | 306 | /** Prepares the capacitor to operate at a new sample rate */ 307 | void prepare (T sampleRate) 308 | { 309 | tt = (T) 1 / sampleRate; 310 | propagateImpedanceChange(); 311 | 312 | reset(); 313 | } 314 | 315 | /** Resets the capacitor state */ 316 | void reset() 317 | { 318 | z = 0.0f; 319 | } 320 | 321 | /** Sets the resistance value of the WDF resistor, in Ohms. */ 322 | void setResistanceValue (T newR) 323 | { 324 | if (all (newR == R_value)) 325 | return; 326 | 327 | R_value = newR; 328 | propagateImpedanceChange(); 329 | } 330 | 331 | /** Sets the capacitance value of the WDF capacitor, in Farads. */ 332 | void setCapacitanceValue (T newC) 333 | { 334 | if (all (newC == C_value)) 335 | return; 336 | 337 | C_value = newC; 338 | propagateImpedanceChange(); 339 | } 340 | 341 | /** Sets the voltage of the voltage source, in Volts */ 342 | void setVoltage (T newV) { Vs = newV; } 343 | 344 | /** Computes the impedance of the WDF resistor/capacitor combination */ 345 | inline void calcImpedance() override 346 | { 347 | wdf.R = tt / ((T) 2.0 * C_value) + R_value; 348 | wdf.G = (T) 1.0 / wdf.R; 349 | T_over_2RC = tt / ((T) 2 * C_value * R_value); 350 | } 351 | 352 | /** Accepts an incident wave into the WDF. */ 353 | inline void incident (T x) noexcept 354 | { 355 | wdf.a = x; 356 | z -= T_over_2RC * (wdf.a - wdf.b); 357 | } 358 | 359 | /** Propogates a reflected wave from the WDF. */ 360 | inline T reflected() noexcept 361 | { 362 | wdf.b = -(z + Vs); 363 | return wdf.b; 364 | } 365 | 366 | WDFMembers wdf; 367 | 368 | private: 369 | T Vs = (T) 0.0; 370 | T R_value = (T) 1.0e3; 371 | T C_value = (T) 1.0e-6; 372 | 373 | T T_over_2RC = (T) 0.0; 374 | 375 | T z = (T) 0.0; 376 | 377 | T tt; 378 | }; 379 | } // namespace wdft 380 | } // namespace chowdsp 381 | 382 | #endif //CHOWDSP_WDF_WDFT_SOURCES_H 383 | -------------------------------------------------------------------------------- /notes/combining_wdfs.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chowdhury-DSP/chowdsp_wdf/d67af36a82545d61234b7f07245628eef77045d6/notes/combining_wdfs.pdf -------------------------------------------------------------------------------- /notes/combining_wdfs.typ: -------------------------------------------------------------------------------- 1 | #align(center, text(17pt)[*Combining WDF Elements*]) 2 | 3 | With WDFs, it's pretty common to combine a series 4 | voltage source plus resistor into a "resistive voltage 5 | source", in order to be able to use a voltage source 6 | as an "adaptable" WDF one-port. However, we can also 7 | combine other WDF elements with the goal being that 8 | the C++ compiler can better optimize the computations 9 | needed for the combined elements. 10 | 11 | = Resistor-Capacitor Series 12 | Here we create a combined one-port for a resistor and 13 | capacitor in series. Note that with the combined element 14 | we no longer have direct access to the voltage or current 15 | across either the resistor or capacitor, so this approach 16 | doesn't work great if you need to "probe" either element. 17 | 18 | == Underlying Components 19 | A WDF Resistor is defined by: 20 | 21 | $ R_p = R, b = 0 $ 22 | 23 | A WDF Capacitor is defined by: 24 | 25 | $ R_p = T/(2C), b = a[n-1] $ 26 | 27 | And a 3-port WDF Series adaptor: 28 | 29 | $ 30 | R_p = R_1 + R_2 \ 31 | b_0 = -a_1 - a_2 \ 32 | b_1 = -R_1/R_p a_0 33 | + R_2/R_p a_1 34 | - R_1/R_p a_2 \ 35 | b_2 = -R_2/R_p a_0 36 | - R_2/R_p a_1 37 | + R_1/R_p a_2 38 | $ 39 | 40 | We can simplify this into 1-multiply form. First, we 41 | recognize the similarity between the $b_1$ and $b_2$ terms: 42 | $ 43 | b_1 + b_2 = -R_1/R_p a_0 - R_2/R_p a_0 \ 44 | b_1 + b_2 = -((R_1 + R_2)/R_p a_0) 45 | $ 46 | 47 | Recall that $R_p = R_1 + R_2$, so: 48 | $ 49 | b_1 + b_2 = -((R_1 + R_2)/(R_1 + R_2) a_0) \ 50 | b_1 + b_2 = -a_0 \ 51 | b_2 = -(a_0 + b_1) 52 | $ 53 | 54 | So if we can figure out $b_1$ then we can compute $b_2$ 55 | with zero multiplications. 56 | 57 | $ 58 | b_1 = -R_1/R_p a_0 + R_2/R_p a_1 - R_1/R_p a_2 \ 59 | b_1 = -R_1/R_p a_0 + R_2/R_p a_1 - R_1/R_p a_2 60 | + R_1/R_p a_1 - R_1/R_p a_1 \ 61 | b_1 = -R_1/R_p (a_0 + a_1 + a_2) + R_2/R_p a_1 + R_1/R_p a_1 \ 62 | b_1 = a_1 -R_1/R_p (a_0 + a_1 + a_2) 63 | $ 64 | 65 | So the full 1-multiply series adaptor is: 66 | $ 67 | R_p = R_1 + R_2 \ 68 | b_0 = -a_1 - a_2 \ 69 | b_1 = a_1 -R_1/R_p (a_0 + a_1 + a_2) \ 70 | b_2 = -(a_0 + b_1) 71 | $ 72 | 73 | == Combining The Elements 74 | 75 | Now we can combine these elements. We'll use port (1) 76 | of the series adaptor for the capacitor, and port (2) 77 | for the resistor. Remember that $a$ and $b$ are being 78 | described from the reference point of the given element, 79 | so $a_1$ for the series adaptor is actually $b$ from the 80 | capacitor. To avoid duplicated names, we'll use $b_c$ and 81 | $a_c$ for the variables from the perspective of the capacitor 82 | and $b_r$ and $a_r$ for the resistor. In other words: 83 | $a_1 := b_c$, $b_1 := a_c$, and so on. 84 | 85 | First we sub in $b_c$ and $b_r$ for $a_1$ and $a_2$: 86 | $ 87 | R_p = T/(2C) + R \ 88 | R_i := R_1/R_p = (T/(2C))/(T/(2C) + R) 89 | = T/(T + 2 R C) \ 90 | b_0 = -b_c - b_r \ 91 | b_1 = a_c[n-1] - R_i (a_0 + b_c + b_r) \ 92 | b_2 = -(a_0 + b_1) 93 | $ 94 | 95 | And then we can sub in the definitions $b_c = a_c[n-1]$ and $b_r = 0$: 96 | $ 97 | b_0 = -a_c[n-1] - 0 \ 98 | b_1 = a_c[n-1] - R_i (a_0 + a_c[n-1] + 0) \ 99 | b_2 = -(a_0 + b_1) 100 | $ 101 | 102 | Now we can sub in $a_c$ and $a_r$ for $b_1$ and $b_2$: 103 | $ 104 | b_0 = -a_c[n-1] \ 105 | a_c = a_c[n-1] - R_i (a_0 + a_c[n-1]) \ 106 | a_r = -(a_0 + a_c) 107 | $ 108 | 109 | Since the only thing we need to compute is $b_0$, and 110 | $b_0$ only depends on $a_c$, we no longer need to compute 111 | $a_r$, and we can rename $a_c$ to $z$ (since that's 112 | essentially the "state" for this one-port). 113 | $ 114 | b_0 = -z[n-1] \ 115 | z = z[n-1] - R_i (a_0 + z[n-1]) 116 | $ 117 | 118 | #show link: underline 119 | So what's the advantage here? The series adaptor 120 | needs roughly 7 additions/subtractions and 1 multiply. 121 | The combined element needs only 3 additions/subtractions 122 | (and still the 1 multiply), but we've also removed a 123 | layer of complexity that we were hoping the compiler 124 | would optimize through. This results in better generated 125 | assembly (see, e.g., 126 | #link("https://godbolt.org/z/Esodbobh3")), and 127 | probably faster compile times as well. For WDF trees that 128 | are very deep, simplifying these elements also makes it 129 | less likely that the compiler will "give up" part-way 130 | through the optimizing process because of time/memory 131 | constraints. 132 | 133 | = Resistor-Capacitor Parallel 134 | 135 | == Underlying Components 136 | 137 | The 3-port parallel adaptor is defined: 138 | $ 139 | G_p = G_1 + G_2 \ 140 | b_0 = G_1/G_p a_1 + G_2/G_p a_2 \ 141 | b_1 = a_0 - G_2/G_p a_1 + G_2/G_p a_2 \ 142 | b_2 = a_0 + G_2/G_p a_1 - G_2/G_p a_2 143 | $ 144 | 145 | Again, we find a simple relationship between $b_1$ and $b_2$: 146 | $ 147 | b_d := a_2 - a_1 \ 148 | b_1 = a_0 + G_2/G_p b_d \ 149 | b_2 = a_0 - G_1/G_p b_d \ 150 | b_1 - b_2 = G_2/G_p b_d+G_1/G_p b_d \ 151 | b_1 - b_2 = (G_1 + G_2)/G_p b_d \ 152 | b_1 - b_2 = (G_1 + G_2)/(G_1 + G_2) b_d \ 153 | b_1 - b_2 = b_d 154 | $ 155 | 156 | Then solving for $b_0$: 157 | $ 158 | b_0 = G_1/G_p a_1 159 | + G_2/G_p a_2 160 | + G_1/G_p a_2 161 | - G_1/G_p a_2 \ 162 | b_0 = G_1/G_p a_2 + G_2/G_p a_2 163 | + G_1/G_p (a_1 - a_2) \ 164 | b_0 = a_2 - G_1/G_p b_d 165 | $ 166 | 167 | So the full 1-multiply parallel adaptor: 168 | $ 169 | G_p = G_1 + G_2 \ 170 | b_d = a_2 - a_1 \ 171 | b_0 = a_2 - G_1/G_p b_d \ 172 | b_1 = b_2 + b_d \ 173 | b_2 = a_0 - G_1/G_p b_d = b_0 - a_2 + a_0 174 | $ 175 | 176 | == Combining The Elements 177 | 178 | Again, we can combine the elements with the capacitor 179 | at port (1), and resistor at port (2): 180 | 181 | $ 182 | G_p = (2C)/T + 1/R \ 183 | G_i := G_1/G_p = ((2C)/T)/G_p = (2 C R)/(2 C R + T) \ 184 | b_d = b_r - b_c \ 185 | b_0 = b_r - G_i b_d \ 186 | b_1 = b_2 + b_d \ 187 | b_2 = b_0 - b_r + a_0 188 | $ 189 | 190 | $b_d$ condenses very nicely, to $-a_c[n-1]$, so: 191 | 192 | $ 193 | b_0 = 0 + G_i a_c[n-1] \ 194 | b_1 = b_2 - a_c[n-1] \ 195 | b_2 = b_0 - 0 + a_0 196 | $ 197 | 198 | Then substituting in $b_1 -> a_c$ and $b_2 -> a_r$: 199 | $ 200 | b_0 = G_i a_c[n-1] \ 201 | a_c = a_r - a_c[n-1] \ 202 | a_r = b_0 + a_0 \ 203 | $ 204 | 205 | Substituing in for $a_r$, we get: 206 | $ 207 | a_c = b_0 + a_0 - a_c[n-1] 208 | $ 209 | 210 | So the final derivation: 211 | $ 212 | b_0 = G_i z[n-1] \ 213 | z = b_0 + a_0 - z[n-1] 214 | $ 215 | 216 | = Resistive Voltage Source + Capacitor (in Series) 217 | 218 | == Underlying Components 219 | 220 | A WDF Resistive Voltage source is defined: 221 | 222 | $ R_p = R, b = V $ 223 | 224 | == Combining The Elements 225 | 226 | Now we can start again with the 1-multiply series adaptor: 227 | 228 | $ 229 | R_p = R_1 + R_2 \ 230 | b_0 = -a_1 - a_2 \ 231 | b_1 = a_1 - R_1/R_p (a_0 + a_1 + a_2) \ 232 | b_2 = -(a_0 + b_1) 233 | $ 234 | 235 | And start subbing in: 236 | $ 237 | R_p = T/(2C) + R \ 238 | R_i := R_1/R_p = T/(T + 2 R C) \ 239 | b_0 = -a_c[n-1] - V \ 240 | a_c = a_c[n-1] - R_i (a_0 + a_c[n-1] + V) 241 | $ 242 | 243 | Note that as with the previous series combination, 244 | we can basically ignore the $a_r$ term. We can simplify 245 | $a_c$ a little bit more by subbing in the definition of 246 | $b_0$: 247 | $ 248 | a_c = a_c[n-1] - R_i (a_0 - b_0) 249 | $ 250 | 251 | In final form: 252 | $ 253 | b_0 = -z[n-1] - V \ 254 | z = z[n-1] - R_i (a_0 - b_0) 255 | $ 256 | 257 | = Capacitive Voltage Source (Series) 258 | 259 | Let's start with the Kirchoff domain definition 260 | for a Resistive Voltage Source: 261 | $ v(t) - e(t) = i(t) R $ 262 | 263 | Now we replace the resistor term with a capacitor, 264 | and switch to the $s$-domain: 265 | $ V(s) - E(s) = I(s) / (C s) $ 266 | 267 | And then moving to the wave domain: 268 | $ 1/2 (A(s) + B(s)) - E(s) = 1/(2 R_0) (A(s) - B(s))/(C s) $ 269 | $ R_0 C s (A (s) + B(s)) - 2 R_0 C s E(s) = A(s) - B(s) $ 270 | $ B(s) (R_0 C s + 1) = A(s) (1 - R_0 C s) + 2 R_0 C s E(s) $ 271 | $ B(s) = A(s) (1 - R_0 C s)/(1 + R_0 C s) 272 | + E(s) (2 R_0 C s)/(1 + R_0 C s) $ 273 | 274 | Now we apply the bilinear transform: $s -> 2/T (1 - z^(-1))/(1 + z^(-1))$: 275 | $ 276 | B(z) = A(z) (1 - R_0 C 2/T (1 - z^(-1)) 277 | / (1 + z^(-1)))/(1 + R_0 C 2/T (1 - z^(-1))/(1 + z^(-1))) 278 | + E(z) (2 R_0 C 2/T (1 - z^(-1))/(1 + z^(-1))) 279 | / (1 + R_0 C 2/T (1 - z^(-1))/(1 + z^(-1))) 280 | $ 281 | 282 | Multiplying through by $(T (1 + z^(-1)))/(T (1 + z^(-1)))$: 283 | $ 284 | B(z) = A(z) (T (1 + z^(-1)) - 2 R_0 C (1 - z^(-1))) 285 | / (T (1 + z^(-1)) + 2 R_0 C (1 - z^(-1))) 286 | + E(z) (4 R_0 C (1 - z^(-1))) 287 | / (T (1 + z^(-1)) + 2 R_0 C (1 - z^(-1))) 288 | $ 289 | $ 290 | B(z) = A(z) ((T - 2 R_0 C) + (T + 2 R_0 C)z^(-1)) 291 | / ((T + 2 R_0 C) + (T - 2 R_0 C)z^(-1)) 292 | + E(z) (4 R_0 C - 4 R_0 C z^(-1)) 293 | / ((T + 2 R_0 C) + (T - 2 R_0 C)z^(-1)) 294 | $ 295 | 296 | Now we can adapt the port by setting $R_0 = T/(2 C)$ (note 297 | that this is the same port impedance as a singular capacitor). 298 | We can then simplify $2 R_0 C -> T$ 299 | $ B(z) = A(z) (2 T z^(-1))/(2 T) + E(z) (2 - 2 z^(-1))/(2T) $ 300 | $ B(z) = A(z) z^(-1) + E(z) (1 - z^(-1)) $ 301 | 302 | Applying the inverse $z$-transform: 303 | $ b[n] = a[n-1] + e[n] - e[n-1] $ 304 | -------------------------------------------------------------------------------- /tests/BasicCircuitTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace chowdsp::wdf; 5 | 6 | TEST_CASE ("Basic Circuits Test") 7 | { 8 | SECTION ("Voltage Divider") 9 | { 10 | Resistor r1 (10000.0f); 11 | Resistor r2 (10000.0f); 12 | 13 | WDFSeries s1 (&r1, &r2); 14 | PolarityInverter p1 (&s1); 15 | IdealVoltageSource vs { &p1 }; 16 | 17 | vs.setVoltage (10.0f); 18 | vs.incident (p1.reflected()); 19 | p1.incident (vs.reflected()); 20 | 21 | auto vOut = r2.voltage(); 22 | REQUIRE (vOut == 5.0f); 23 | } 24 | 25 | SECTION ("Current Divider Test") 26 | { 27 | Resistor r1 (10000.0f); 28 | Resistor r2 (10000.0f); 29 | 30 | WDFParallel p1 (&r1, &r2); 31 | IdealCurrentSource is (&p1); 32 | 33 | is.setCurrent (1.0f); 34 | is.incident (p1.reflected()); 35 | p1.incident (is.reflected()); 36 | 37 | auto iOut = r2.current(); 38 | REQUIRE (iOut == 0.5f); 39 | } 40 | 41 | SECTION ("Shockley Diode") 42 | { 43 | static constexpr auto saturationCurrent = 1.0e-7; 44 | static constexpr auto thermalVoltage = 25.85e-3; 45 | static constexpr auto voltage = -0.35; 46 | 47 | ResistiveVoltageSource Vs; 48 | PolarityInverter I1 { &Vs }; 49 | Diode D1 { &I1, saturationCurrent, thermalVoltage }; 50 | 51 | Vs.setVoltage (voltage); 52 | D1.incident (I1.reflected()); 53 | I1.incident (D1.reflected()); 54 | 55 | auto expectedCurrent = saturationCurrent * (std::exp (-voltage / thermalVoltage) - 1.0); 56 | REQUIRE (D1.current() == Approx (expectedCurrent).margin (1.0e-3)); 57 | } 58 | 59 | SECTION ("Current Switch") 60 | { 61 | Resistor r1 (10000.0f); 62 | ResistiveCurrentSource Is; 63 | 64 | WDFSeries s1 (&r1, &Is); 65 | Switch sw { &s1 }; 66 | 67 | // run with switch closed 68 | sw.setClosed (true); 69 | Is.setCurrent (1.0f); 70 | sw.incident (s1.reflected()); 71 | s1.incident (sw.reflected()); 72 | 73 | auto currentClosed = r1.current(); 74 | REQUIRE (currentClosed == Approx (-1.0f).margin (1.0e-3f)); 75 | 76 | // run with switch open 77 | sw.setClosed (false); 78 | sw.incident (s1.reflected()); 79 | s1.incident (sw.reflected()); 80 | 81 | auto currentOpen = r1.current(); 82 | REQUIRE (currentOpen == 0.0f); 83 | } 84 | 85 | SECTION ("Y-Parameter Test") 86 | { 87 | static constexpr auto y11 = 0.11; 88 | static constexpr auto y12 = 0.22; 89 | static constexpr auto y21 = 0.33; 90 | static constexpr auto y22 = 0.44; 91 | static constexpr auto voltage = 2.0; 92 | 93 | Resistor res { 10000.0 }; 94 | YParameter yParam { &res, y11, y12, y21, y22 }; 95 | IdealVoltageSource Vs { &yParam }; 96 | 97 | Vs.setVoltage (voltage); 98 | Vs.incident (yParam.reflected()); 99 | yParam.incident (Vs.reflected()); 100 | 101 | REQUIRE (-res.current() == Approx (y11 * res.voltage() + y12 * voltage).margin (1.0e-3)); 102 | REQUIRE (yParam.current() == Approx (y21 * res.voltage() + y22 * voltage).margin (1.0e-3)); 103 | } 104 | 105 | SECTION ("RC Lowpass") 106 | { 107 | static constexpr double fs = 44100.0; 108 | static constexpr double fc = 500.0; 109 | 110 | static constexpr auto capValue = 1.0e-6; 111 | static constexpr auto resValue = 1.0 / ((2 * M_PI) * fc * capValue); 112 | 113 | Capacitor c1 (capValue, fs); 114 | Resistor r1 (resValue); 115 | 116 | WDFSeries s1 (&r1, &c1); 117 | PolarityInverter p1 (&s1); 118 | IdealVoltageSource vs { &p1 }; 119 | 120 | auto testFreq = [&] (double freq, double expectedMagDB) { 121 | c1.reset(); 122 | 123 | double magnitude = 0.0; 124 | for (int n = 0; n < (int) fs; ++n) 125 | { 126 | const auto x = std::sin (2.0 * M_PI * freq * (double) n / fs); 127 | vs.setVoltage (x); 128 | 129 | vs.incident (p1.reflected()); 130 | p1.incident (vs.reflected()); 131 | 132 | const auto y = c1.voltage(); 133 | 134 | if (n > 1000) 135 | magnitude = std::max (magnitude, std::abs (y)); 136 | } 137 | 138 | const auto actualMagnitudeDB = 20.0 * std::log10 (magnitude); 139 | REQUIRE (actualMagnitudeDB == Approx (expectedMagDB).margin (0.1)); 140 | }; 141 | 142 | testFreq (2 * fc, -7.0); 143 | testFreq (fc, -3.0); 144 | testFreq (0.5 * fc, -1.0); 145 | } 146 | 147 | SECTION ("Alpha Transform") 148 | { 149 | static constexpr float fs = 44100.0f; 150 | 151 | // 1 kHz cutoff 2nd-order highpass 152 | static constexpr float R = 300.0f; 153 | static constexpr float C = 1.0e-6f; 154 | static constexpr float L = 0.022f; 155 | 156 | auto testFreq = [&] (float freq, float expectedMagDB, auto& vs, auto& p1, auto& l1) { 157 | float magnitude = 0.0f; 158 | for (int n = 0; n < (int) fs; ++n) 159 | { 160 | const auto x = std::sin (2.0f * (float) M_PI * freq * (float) n / fs); 161 | vs.setVoltage (x); 162 | 163 | vs.incident (p1.reflected()); 164 | p1.incident (vs.reflected()); 165 | 166 | const auto y = l1.voltage(); 167 | 168 | if (n > 1000) 169 | magnitude = std::max (magnitude, std::abs (y)); 170 | } 171 | 172 | const auto actualMagnitudeDB = 20.0f * std::log10 (magnitude); 173 | REQUIRE (actualMagnitudeDB == Approx (expectedMagDB).margin (0.1f)); 174 | }; 175 | 176 | // reference filter 177 | { 178 | Capacitor c1 (C); 179 | Resistor r1 (R); 180 | Inductor l1 (L); 181 | 182 | WDFSeries s1 (&r1, &c1); 183 | WDFSeries s2 (&s1, &l1); 184 | PolarityInverter p1 (&s2); 185 | IdealVoltageSource vs { &p1 }; 186 | 187 | c1.prepare (fs); 188 | l1.prepare (fs); 189 | 190 | testFreq (10.0e3f, 0.0f, vs, p1, l1); 191 | } 192 | 193 | CapacitorAlpha c1 (C); 194 | Resistor r1 (R); 195 | InductorAlpha l1 (L); 196 | 197 | WDFSeries s1 (&r1, &c1); 198 | WDFSeries s2 (&s1, &l1); 199 | PolarityInverter p1 (&s2); 200 | IdealVoltageSource vs { &p1 }; 201 | 202 | // alpha = 1.0 filter 203 | { 204 | static constexpr float alpha = 1.0f; 205 | c1.prepare (fs); 206 | c1.setAlpha (alpha); 207 | l1.prepare (fs); 208 | l1.setAlpha (alpha); 209 | 210 | testFreq (10.0e3f, 0.0f, vs, p1, l1); 211 | } 212 | 213 | // alpha = 0.1 filter 214 | { 215 | static constexpr float alpha = 0.1f; 216 | c1.reset(); 217 | c1.setAlpha (alpha); 218 | l1.reset(); 219 | l1.setAlpha (alpha); 220 | 221 | testFreq (10.0e3f, -1.1f, vs, p1, l1); 222 | } 223 | } 224 | 225 | SECTION ("Impedance Change") 226 | { 227 | static constexpr float fs = 44100.0f; 228 | 229 | auto checkImpedanceChange = [=] (auto component, float value1, float value2, auto changeFunc, auto impedanceCalc) { 230 | REQUIRE (component.wdf.R == impedanceCalc (value1)); 231 | 232 | changeFunc (component, value2); 233 | REQUIRE (component.wdf.R == impedanceCalc (value2)); 234 | }; 235 | 236 | auto checkImpedanceProp = [=] (auto component, float value1, float value2, auto changeFunc, auto impedanceCalc) { 237 | static constexpr float otherR = 5000.0f; 238 | Resistor r2 { otherR }; 239 | WDFSeries s1 (&component, &r2); 240 | IdealCurrentSource is (&s1); 241 | is.setCurrent (1.0f); 242 | 243 | REQUIRE (s1.wdf.R == impedanceCalc (value1) + otherR); 244 | REQUIRE (is.reflected() == 2.0f * s1.wdf.R); 245 | 246 | changeFunc (component, value2); 247 | REQUIRE (s1.wdf.R == impedanceCalc (value2) + otherR); 248 | REQUIRE (is.reflected() == 2.0f * s1.wdf.R); 249 | }; 250 | 251 | auto doImpedanceChecks = [=] (auto... params) { 252 | checkImpedanceChange (params...); 253 | checkImpedanceProp (params...); 254 | }; 255 | 256 | // resistor 257 | doImpedanceChecks ( 258 | Resistor { 1000.0f }, 1000.0f, 2000.0f, [=] (auto& r, float value) { r.setResistanceValue (value); }, [=] (float value) { return value; }); 259 | 260 | // capacitor 261 | doImpedanceChecks ( 262 | Capacitor { 1.0e-6f, fs }, 1.0e-6f, 2.0e-6f, [=] (auto& c, float value) { c.setCapacitanceValue (value); }, [=] (float value) { return 1.0f / (2.0f * value * (float) fs); }); 263 | 264 | // capacitor alpha 265 | doImpedanceChecks ( 266 | CapacitorAlpha { 1.0e-6f, fs, 0.5f }, 1.0e-6f, 2.0e-6f, [=] (auto& c, float value) { c.setCapacitanceValue (value); }, [=] (float value) { return 1.0f / (1.5f * value * (float) fs); }); 267 | 268 | // inductor 269 | doImpedanceChecks ( 270 | Inductor { 1.0f, fs }, 1.0f, 2.0f, [=] (auto& i, float value) { i.setInductanceValue (value); }, [=] (float value) { return 2.0f * value * (float) fs; }); 271 | 272 | // inductor alpha 273 | doImpedanceChecks ( 274 | InductorAlpha { 1.0f, fs, 0.5f }, 1.0f, 2.0f, [=] (auto& i, float value) { i.setInductanceValue (value); }, [=] (float value) { return 1.5f * value * (float) fs; }); 275 | 276 | // resistive voltage source 277 | doImpedanceChecks ( 278 | ResistiveVoltageSource { 1000.0f }, 1000.0f, 2000.0f, [=] (auto& r, float value) { r.setResistanceValue (value); }, [=] (float value) { return value; }); 279 | 280 | // resistive current source 281 | doImpedanceChecks ( 282 | ResistiveCurrentSource { 1000.0f }, 1000.0f, 2000.0f, [=] (auto& r, float value) { r.setResistanceValue (value); }, [=] (float value) { return value; }); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /tests/BassmanToneStackPoly.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 4 | #include 5 | #endif 6 | 7 | #include 8 | 9 | using namespace chowdsp; 10 | 11 | /** Fender Bassman tonestack circuit */ 12 | template 13 | class TonestackPoly 14 | { 15 | public: 16 | TonestackPoly() : R ({ &S1, &S3, &S2, &Cap2, &Res4, &Cap3 }) 17 | { 18 | R.impedanceCalculator = [] (auto& R) { 19 | const auto Ra = R.getPortImpedance (0); 20 | const auto Rb = R.getPortImpedance (1); 21 | const auto Rc = R.getPortImpedance (2); 22 | const auto Rd = R.getPortImpedance (3); 23 | const auto Re = R.getPortImpedance (4); 24 | const auto Rf = R.getPortImpedance (5); 25 | const auto Ga = (FloatType) 1 / Ra; 26 | const auto Gb = (FloatType) 1 / Rb; 27 | const auto Gc = (FloatType) 1 / Rc; 28 | const auto Gd = (FloatType) 1 / Rd; 29 | const auto Ge = (FloatType) 1 / Re; 30 | const auto Gf = (FloatType) 1 / Rf; 31 | 32 | // This scattering matrix was derived using the R-Solver python script (https://github.com/jatinchowdhury18/R-Solver), 33 | // with netlist input: netlists/bassman.txt 34 | R.setSMatrixData ({ { 2 * Ra * (-Ga * Gb * Gc * Gd - Ga * Gb * Gc * Ge - Ga * Gb * Gc * Gf - Ga * Gb * Gd * Ge - Ga * Gb * Ge * Gf - Ga * Gc * Gd * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) + 1, 2 * Ra * (Ga * Gb * Gc * Gd + Ga * Gb * Gc * Ge + Ga * Gb * Gc * Gf + Ga * Gb * Gd * Ge) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Ra * (Ga * Gb * Gc * Gd + Ga * Gb * Gc * Ge + Ga * Gb * Gc * Gf + Ga * Gc * Gd * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Ra * (Ga * Gb * Gd * Ge - Ga * Gc * Gd * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Ra * (-Ga * Gb * Gd * Ge - Ga * Gb * Ge * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Ra * (-Ga * Gb * Ge * Gf - Ga * Gc * Gd * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) }, 35 | { 2 * Rb * (Ga * Gb * Gc * Gd + Ga * Gb * Gc * Ge + Ga * Gb * Gc * Gf + Ga * Gb * Gd * Ge) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rb * (-Ga * Gb * Gc * Gd - Ga * Gb * Gc * Ge - Ga * Gb * Gc * Gf - Ga * Gb * Gd * Ge - Ga * Gb * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gc * Ge * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) + 1, 2 * Rb * (-Ga * Gb * Gc * Gd - Ga * Gb * Gc * Ge - Ga * Gb * Gc * Gf - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rb * (-Ga * Gb * Gd * Ge - Ga * Gb * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rb * (Ga * Gb * Gd * Ge - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rb * (-Ga * Gb * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gc * Ge * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) }, 36 | { 2 * Rc * (Ga * Gb * Gc * Gd + Ga * Gb * Gc * Ge + Ga * Gb * Gc * Gf + Ga * Gc * Gd * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rc * (-Ga * Gb * Gc * Gd - Ga * Gb * Gc * Ge - Ga * Gb * Gc * Gf - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rc * (-Ga * Gb * Gc * Gd - Ga * Gb * Gc * Ge - Ga * Gb * Gc * Gf - Ga * Gc * Gd * Ge - Ga * Gc * Gd * Gf - Gb * Gc * Gd * Ge - Gb * Gc * Ge * Gf - Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) + 1, 2 * Rc * (Ga * Gc * Gd * Ge + Ga * Gc * Gd * Gf + Gb * Gc * Gd * Ge + Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rc * (-Ga * Gc * Gd * Ge - Gb * Gc * Gd * Ge - Gb * Gc * Ge * Gf - Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rc * (Ga * Gc * Gd * Gf - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) }, 37 | { 2 * Rd * (Ga * Gb * Gd * Ge - Ga * Gc * Gd * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rd * (-Ga * Gb * Gd * Ge - Ga * Gb * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rd * (Ga * Gc * Gd * Ge + Ga * Gc * Gd * Gf + Gb * Gc * Gd * Ge + Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rd * (-Ga * Gb * Gd * Ge - Ga * Gb * Gd * Gf - Ga * Gc * Gd * Ge - Ga * Gc * Gd * Gf - Gb * Gc * Gd * Ge - Gb * Gc * Gd * Gf - Gb * Gd * Ge * Gf - Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) + 1, 2 * Rd * (Ga * Gb * Gd * Ge + Ga * Gc * Gd * Ge + Gb * Gc * Gd * Ge + Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rd * (-Ga * Gb * Gd * Gf - Ga * Gc * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) }, 38 | { 2 * Re * (-Ga * Gb * Gd * Ge - Ga * Gb * Ge * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Re * (Ga * Gb * Gd * Ge - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Re * (-Ga * Gc * Gd * Ge - Gb * Gc * Gd * Ge - Gb * Gc * Ge * Gf - Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Re * (Ga * Gb * Gd * Ge + Ga * Gc * Gd * Ge + Gb * Gc * Gd * Ge + Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Re * (-Ga * Gb * Gd * Ge - Ga * Gb * Ge * Gf - Ga * Gc * Gd * Ge - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf - Gb * Gc * Gd * Ge - Gb * Gc * Ge * Gf - Gc * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) + 1, 2 * Re * (-Ga * Gb * Ge * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) }, 39 | { 2 * Rf * (-Ga * Gb * Ge * Gf - Ga * Gc * Gd * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rf * (-Ga * Gb * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gc * Ge * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rf * (Ga * Gc * Gd * Gf - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rf * (-Ga * Gb * Gd * Gf - Ga * Gc * Gd * Gf - Gb * Gc * Gd * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rf * (-Ga * Gb * Ge * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf - Gb * Gc * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf), 2 * Rf * (-Ga * Gb * Gd * Gf - Ga * Gb * Ge * Gf - Ga * Gc * Gd * Gf - Ga * Gc * Ge * Gf - Ga * Gd * Ge * Gf - Gb * Gc * Gd * Gf - Gb * Gc * Ge * Gf - Gb * Gd * Ge * Gf) / (Ga * Gb * Gd + Ga * Gb * Ge + Ga * Gb * Gf + Ga * Gc * Gd + Ga * Gc * Ge + Ga * Gc * Gf + Ga * Gd * Ge + Ga * Gd * Gf + Gb * Gc * Gd + Gb * Gc * Ge + Gb * Gc * Gf + Gb * Gd * Ge + Gb * Ge * Gf + Gc * Gd * Gf + Gc * Ge * Gf + Gd * Ge * Gf) + 1 } }); 40 | }; 41 | } 42 | 43 | void prepare (double sampleRate) 44 | { 45 | Cap1.prepare ((FloatType) sampleRate); 46 | Cap2.prepare ((FloatType) sampleRate); 47 | Cap3.prepare ((FloatType) sampleRate); 48 | } 49 | 50 | FloatType processSample (FloatType inSamp) 51 | { 52 | Vres.setVoltage (inSamp); 53 | R.compute(); 54 | 55 | return wdft::voltage (Res1m) + wdft::voltage (S2) + wdft::voltage (Res3m); 56 | } 57 | 58 | void setParams (FloatType highPot, FloatType lowPot, FloatType midPot) 59 | { 60 | { 61 | using DeferImpedance = chowdsp::wdft::ScopedDeferImpedancePropagation; 62 | DeferImpedance deferImpedance { S1, S3, S4 }; 63 | 64 | Res1m.setResistanceValue (highPot * R1); 65 | Res1p.setResistanceValue (((FloatType) 1 - highPot) * R1); 66 | 67 | Res2.setResistanceValue (((FloatType) 1 - lowPot) * R2); 68 | 69 | Res3m.setResistanceValue (midPot * R3); 70 | Res3p.setResistanceValue (((FloatType) 1 - midPot) * R3); 71 | } 72 | 73 | S2.propagateImpedanceChange(); 74 | } 75 | 76 | private: 77 | wdf::CapacitorAlpha Cap1 { 250e-12 }; 78 | wdf::CapacitorAlpha Cap2 { 20e-9 }; // Port D 79 | wdf::CapacitorAlpha Cap3 { 20e-9 }; // Port F 80 | 81 | wdf::Resistor Res1p { 1.0 }; 82 | wdf::Resistor Res1m { 1.0 }; 83 | wdf::Resistor Res2 { 1.0 }; 84 | wdf::Resistor Res3p { 1.0 }; 85 | wdf::Resistor Res3m { 1.0 }; 86 | wdf::Resistor Res4 { 56e3 }; // Port E 87 | 88 | wdf::ResistiveVoltageSource Vres { 1.0 }; 89 | 90 | // Port A 91 | wdf::WDFSeries S1 { &Vres, &Res3m }; 92 | 93 | // Port B 94 | wdf::WDFSeries S3 { &Res2, &Res3p }; 95 | 96 | // Port C 97 | wdf::WDFSeries S4 { &Res1p, &Res1m }; 98 | wdf::WDFSeries S2 { &Cap1, &S4 }; 99 | 100 | static constexpr double R1 = 250e3; 101 | static constexpr double R2 = 1e6; 102 | static constexpr double R3 = 25e3; 103 | 104 | wdf::RootRtypeAdaptor R; 105 | }; 106 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(chowdsp_wdf_tests) 2 | target_include_directories(chowdsp_wdf_tests PRIVATE .) 3 | target_link_libraries(chowdsp_wdf_tests PRIVATE ${PROJECT_NAME} chowdsp_wdf) 4 | target_sources(chowdsp_wdf_tests 5 | PRIVATE 6 | BasicCircuitTest.cpp 7 | StaticBasicCircuitTest.cpp 8 | OmegaTest.cpp 9 | RTypeTest.cpp 10 | SIMDTest.cpp 11 | CombinedComponentTest.cpp 12 | TestRunner.cpp 13 | ) 14 | 15 | add_custom_command(TARGET chowdsp_wdf_tests 16 | POST_BUILD 17 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 18 | COMMAND ${CMAKE_COMMAND} -E echo "Copying $ to test-binary" 19 | COMMAND ${CMAKE_COMMAND} -E make_directory test-binary 20 | COMMAND ${CMAKE_COMMAND} -E copy "$" test-binary) 21 | 22 | if(NOT ("${CHOWDSP_WDF_TEST_WITH_XSIMD_VERSION}" STREQUAL "")) 23 | target_link_libraries(chowdsp_wdf_tests PRIVATE ${PROJECT_NAME} xsimd) 24 | target_compile_definitions(chowdsp_wdf_tests PRIVATE CHOWDSP_WDF_TEST_WITH_XSIMD=1) 25 | endif() 26 | 27 | option(CHOWDSP_WDF_CODE_COVERAGE "Build tests with code coverage flags" OFF) 28 | if(CHOWDSP_WDF_CODE_COVERAGE) 29 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 30 | # Add required flags (GCC & LLVM/Clang) 31 | message(STATUS "chowdsp_wdf_tests -- Appending code coverage compiler flags: -O0 -g --coverage") 32 | target_compile_options(chowdsp_wdf_tests PUBLIC 33 | -O0 # no optimization 34 | -g # generate debug info 35 | --coverage # sets all required flags 36 | ) 37 | target_link_options(chowdsp_wdf_tests PUBLIC --coverage) 38 | endif() 39 | endif() 40 | 41 | option(CHOWDSP_WDF_RUN_CLANG_TIDY "Run clang-tidy on chowdsp WDF library" OFF) 42 | if(CHOWDSP_WDF_RUN_CLANG_TIDY) 43 | message(STATUS "Configuring clang-tidy target") 44 | add_custom_target(chowdsp_wdf_clang_tidy 45 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 46 | COMMAND rm -f chowdsp_wdf_clang_tidy.cpp 47 | COMMAND ${CMAKE_COMMAND} -E echo "\\#include \\\"include/chowdsp_wdf/chowdsp_wdf.h\\\"" > chowdsp_wdf_clang_tidy.cpp 48 | COMMAND clang-tidy -p build "chowdsp_wdf_clang_tidy.cpp" 49 | ) 50 | endif() 51 | -------------------------------------------------------------------------------- /tests/CombinedComponentTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace chowdsp::wdft; 6 | 7 | TEST_CASE ("Combined Component Test") 8 | { 9 | // SECTION ("Resistor/Capacitor Series") 10 | // { 11 | // static constexpr auto r_val = 2000.0f; 12 | // static constexpr auto c_val = 2.0e-6f; 13 | // 14 | // ResistorT r1 { r_val }; 15 | // CapacitorT c1 { c_val }; 16 | // WDFSeriesT s1 { r1, c1 }; 17 | // 18 | // ResistorCapacitorSeriesT rc1 { r_val, c_val }; 19 | // 20 | // float inputs[] = { 0.0f, 1.0f, -1.0f, 2.0f, -3.0f }; 21 | // for (auto& a : inputs) 22 | // { 23 | // s1.incident (a); 24 | // const auto ref = s1.reflected(); 25 | // 26 | // rc1.incident (a); 27 | // const auto actual = rc1.reflected(); 28 | // 29 | // REQUIRE (ref == Approx { actual }.margin (1.0e-4f)); 30 | // } 31 | // } 32 | // 33 | // SECTION ("Resistor/Capacitor Parallel") 34 | // { 35 | // static constexpr auto r_val = 2000.0f; 36 | // static constexpr auto c_val = 2.0e-6f; 37 | // 38 | // ResistorT r1 { r_val }; 39 | // CapacitorT c1 { c_val }; 40 | // WDFParallelT p1 { r1, c1 }; 41 | // 42 | // ResistorCapacitorParallelT rc1 { r_val, c_val }; 43 | // 44 | // float inputs[] = { 0.0f, 1.0f, -1.0f, 2.0f, -3.0f }; 45 | // for (auto& a : inputs) 46 | // { 47 | // p1.incident (a); 48 | // const auto ref = p1.reflected(); 49 | // 50 | // rc1.incident (a); 51 | // const auto actual = rc1.reflected(); 52 | // 53 | // REQUIRE (ref == Approx { actual }.margin (1.0e-4f)); 54 | // } 55 | // } 56 | // 57 | // SECTION ("Resistor/Capacitor/Voltage Source Series") 58 | // { 59 | // static constexpr auto r_val = 2000.0f; 60 | // static constexpr auto c_val = 2.0e-6f; 61 | // static constexpr auto source_v = 1.5f; 62 | // 63 | // ResistiveVoltageSourceT rv1 { r_val }; 64 | // rv1.setVoltage (source_v); 65 | // CapacitorT c1 { c_val }; 66 | // WDFSeriesT s1 { rv1, c1 }; 67 | // 68 | // ResistiveCapacitiveVoltageSourceT rc1 { r_val, c_val }; 69 | // rc1.setVoltage (source_v); 70 | // rc1.reset(); 71 | // 72 | // float inputs[] = { 0.0f, 1.0f, -1.0f, 2.0f, -3.0f }; 73 | // for (auto& a : inputs) 74 | // { 75 | // s1.incident (a); 76 | // const auto ref = s1.reflected(); 77 | // 78 | // rc1.incident (a); 79 | // const auto actual = rc1.reflected(); 80 | // 81 | // REQUIRE (ref == Approx { actual }.margin (1.0e-4f)); 82 | // } 83 | // } 84 | 85 | SECTION ("Capacitive Voltage Source") 86 | { 87 | static constexpr auto c_val = 2.0e-6f; 88 | static constexpr auto source_v = 1.5f; 89 | 90 | struct Ref 91 | { 92 | ResistiveVoltageSourceT rv1 { 1.0e3f }; 93 | CapacitorT c1 { 1.0e-6f }; 94 | WDFSeriesT s1 { rv1, c1 }; 95 | IdealVoltageSourceT v0 { s1 }; 96 | 97 | void reset() 98 | { 99 | c1.reset(); 100 | v0.setVoltage (0.0f); 101 | } 102 | 103 | float process (float v) 104 | { 105 | rv1.setVoltage (v); 106 | v0.incident (s1.reflected()); 107 | const auto y = voltage (rv1) + voltage (c1); 108 | s1.incident (v0.reflected()); 109 | return y; 110 | } 111 | }; 112 | Ref ref_circuit; 113 | ref_circuit.reset(); 114 | 115 | struct Test 116 | { 117 | CapacitiveVoltageSourceT cv1 { 1.0e-6f }; 118 | ResistorT r1 { 1.0e3f }; 119 | WDFSeriesT s1 { cv1, r1 }; 120 | IdealVoltageSourceT v0 { s1 }; 121 | 122 | void reset() 123 | { 124 | cv1.reset(); 125 | v0.setVoltage (0.0f); 126 | } 127 | 128 | float process (float v) 129 | { 130 | cv1.setVoltage (v); 131 | v0.incident (s1.reflected()); 132 | const auto y = voltage (cv1) + voltage (r1); 133 | s1.incident (v0.reflected()); 134 | return y; 135 | } 136 | }; 137 | Test test_circuit; 138 | test_circuit.reset(); 139 | 140 | float inputs[] = { 0.0f, 1.0f, -1.0f, 2.0f, -3.0f }; 141 | for (auto& a : inputs) 142 | { 143 | const auto ref = ref_circuit.process (a); 144 | const auto actual = test_circuit.process (a); 145 | REQUIRE (ref == Approx { actual }.margin (1.0e-4f)); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tests/OmegaTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 6 | #include 7 | #endif 8 | 9 | #include 10 | 11 | /** Reference values generated from scipy.special */ 12 | std::unordered_map WO_vals { 13 | { -10.0, 4.539786874921544e-05 }, 14 | { -9.5, 7.484622772024869e-05 }, 15 | { -9.0, 0.00012339457692560975 }, 16 | { -8.5, 0.00020342698226408345 }, 17 | { -8.0, 0.000335350149321062 }, 18 | { -7.5, 0.0005527787213627528 }, 19 | { -7.0, 0.0009110515723789146 }, 20 | { -6.5, 0.0015011839473879653 }, 21 | { -6.0, 0.002472630709097278 }, 22 | { -5.5, 0.004070171383753891 }, 23 | { -5.0, 0.0066930004977309955 }, 24 | { -4.5, 0.010987603420879434 }, 25 | { -4.0, 0.017989102828531025 }, 26 | { -3.5, 0.029324711813756815 }, 27 | { -3.0, 0.04747849102486547 }, 28 | { -2.5, 0.07607221340790257 }, 29 | { -2.0, 0.1200282389876412 }, 30 | { -1.5, 0.1853749184489398 }, 31 | { -1.0, 0.27846454276107374 }, 32 | { -0.5, 0.4046738485459385 }, 33 | { 0.0, 0.5671432904097838 }, 34 | { 0.5, 0.7662486081617502 }, 35 | { 1.0, 1.0 }, 36 | { 1.5, 1.2649597201255005 }, 37 | { 2.0, 1.5571455989976113 }, 38 | { 2.5, 1.8726470404165942 }, 39 | { 3.0, 2.207940031569323 }, 40 | { 3.5, 2.559994780412122 }, 41 | { 4.0, 2.926271062443501 }, 42 | { 4.5, 3.3046649181693253 }, 43 | { 5.0, 3.6934413589606496 }, 44 | { 5.5, 4.091169202271799 }, 45 | { 6.0, 4.4966641730061605 }, 46 | { 6.5, 4.908941634486258 }, 47 | { 7.0, 5.327178301371093 }, 48 | { 7.5, 5.750681611147114 }, 49 | { 8.0, 6.178865346308128 }, 50 | { 8.5, 6.611230244734983 }, 51 | { 9.0, 7.047348546597604 }, 52 | { 9.5, 7.486851633496902 }, 53 | { 10.0, 7.9294200950196965 }, 54 | }; 55 | 56 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 57 | template 58 | struct SIMDApproxImpl 59 | { 60 | explicit SIMDApproxImpl (xsimd::batch const& value) : m_value (value) 61 | { 62 | } 63 | 64 | SIMDApproxImpl& margin (T const& newMargin) 65 | { 66 | m_margin = newMargin; 67 | return *this; 68 | } 69 | 70 | friend bool operator== (const xsimd::batch& lhs, SIMDApproxImpl const& rhs) 71 | { 72 | bool result = true; 73 | for (size_t i = 0; i < xsimd::batch::size; ++i) 74 | result &= (lhs.get (i) == Approx (rhs.m_value.get (0)).margin (rhs.m_margin)); 75 | 76 | return result; 77 | } 78 | 79 | private: 80 | xsimd::batch m_value; 81 | T m_margin; 82 | }; 83 | 84 | template 85 | using SIMDApprox = typename std::conditional>::value, SIMDApproxImpl>, Approx>::type; 86 | #else 87 | template 88 | using SIMDApprox = Approx; 89 | #endif 90 | 91 | template 92 | using FuncType = std::function; 93 | 94 | template 95 | struct FunctionTest 96 | { 97 | chowdsp::NumericType low; 98 | chowdsp::NumericType high; 99 | FuncType testFunc; 100 | FuncType> refFunc; 101 | chowdsp::NumericType tol; 102 | }; 103 | 104 | template 105 | void checkFunctionAccuracy (const FunctionTest& funcTest, size_t N = 20) 106 | { 107 | auto step = (funcTest.high - funcTest.low) / (chowdsp::NumericType) N; 108 | for (chowdsp::NumericType x = funcTest.low; x < funcTest.high; x += step) 109 | { 110 | auto expected = funcTest.refFunc (x); 111 | auto actual = funcTest.testFunc (x); 112 | 113 | REQUIRE (actual == SIMDApprox (expected).margin (funcTest.tol)); 114 | } 115 | } 116 | 117 | template 118 | void checkWrightOmega (Func&& omega, chowdsp::NumericType tol) 119 | { 120 | for (auto vals : WO_vals) 121 | { 122 | auto expected = (T) (chowdsp::NumericType) vals.second; 123 | auto actual = omega ((T) (chowdsp::NumericType) vals.first); 124 | 125 | REQUIRE (actual == SIMDApprox (expected).margin (tol)); 126 | } 127 | } 128 | 129 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 130 | TEMPLATE_TEST_CASE ("Omega Test", "", float, double, xsimd::batch, xsimd::batch) 131 | #else 132 | TEMPLATE_TEST_CASE ("Omega Test", "", float, double) 133 | #endif 134 | { 135 | SECTION ("Log2 Test") 136 | { 137 | checkFunctionAccuracy (FunctionTest { 138 | 1.0f, 139 | 2.0f, 140 | [] (auto x) { return chowdsp::Omega::log2_approx> (x); }, 141 | [] (auto x) { return std::log2 (x); }, 142 | 0.008f }); 143 | } 144 | 145 | SECTION ("Log Test") 146 | { 147 | checkFunctionAccuracy (FunctionTest { 148 | 8.0f, 149 | 12.0f, 150 | [] (auto x) { return chowdsp::Omega::log_approx (x); }, 151 | [] (auto x) { return std::log (x); }, 152 | 0.005f }); 153 | } 154 | 155 | SECTION ("Pow2 Test") 156 | { 157 | checkFunctionAccuracy (FunctionTest { 158 | 0.0f, 159 | 1.0f, 160 | [] (auto x) { return chowdsp::Omega::pow2_approx> (x); }, 161 | [] (auto x) { return std::pow (2.0f, x); }, 162 | 0.001f }); 163 | } 164 | 165 | SECTION ("Exp Test") 166 | { 167 | checkFunctionAccuracy (FunctionTest { 168 | -4.0f, 169 | 2.0f, 170 | [] (auto x) { return chowdsp::Omega::exp_approx (x); }, 171 | [] (auto x) { return std::exp (x); }, 172 | 0.03f }); 173 | } 174 | 175 | SECTION ("Omega1 Test") 176 | { 177 | checkWrightOmega ([] (TestType x) { return chowdsp::Omega::omega1 (x); }, 178 | 2.1f); 179 | } 180 | 181 | SECTION ("Omega2 Test") 182 | { 183 | checkWrightOmega ([] (TestType x) { return chowdsp::Omega::omega2 (x); }, 184 | 2.1f); 185 | } 186 | 187 | SECTION ("Omega3 Test") 188 | { 189 | checkWrightOmega ([] (TestType x) { return chowdsp::Omega::omega3 (x); }, 190 | 0.3f); 191 | } 192 | 193 | SECTION ("Omega4 Test") 194 | { 195 | checkWrightOmega ([] (TestType x) { return chowdsp::Omega::omega4 (x); }, 196 | 0.05f); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/RTypeTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "BassmanToneStack.h" 4 | #include "BassmanToneStackPoly.h" 5 | #include "BaxandallEQ.h" 6 | #include "BaxandallEQPoly.h" 7 | 8 | constexpr double fs = 48000.0f; 9 | 10 | void bassmanFreqTest (float lowPot, float highPot, float sineFreq, float expGainDB, float maxErr) 11 | { 12 | Tonestack tonestack; 13 | tonestack.prepare (fs); 14 | tonestack.setParams ((double) highPot, (double) lowPot, 1.0); 15 | 16 | double magnitude = 0.0; 17 | for (int n = 0; n < (int) fs; ++n) 18 | { 19 | const auto x = std::sin (2.0 * M_PI * (double) n * sineFreq / fs); 20 | const auto y = tonestack.processSample (x); 21 | 22 | if (n > 1000) 23 | magnitude = std::max (magnitude, std::abs (y)); 24 | } 25 | 26 | auto actualGainDB = 20.0 * std::log10 (magnitude); 27 | REQUIRE (actualGainDB == Approx (expGainDB).margin (maxErr)); 28 | } 29 | 30 | void bassmanPolyFreqTest (float lowPot, float highPot, float sineFreq, float expGainDB, float maxErr) 31 | { 32 | TonestackPoly tonestack; 33 | tonestack.prepare (fs); 34 | tonestack.setParams ((double) highPot, (double) lowPot, 1.0); 35 | 36 | double magnitude = 0.0; 37 | for (int n = 0; n < (int) fs; ++n) 38 | { 39 | const auto x = std::sin (2.0 * M_PI * (double) n * sineFreq / fs); 40 | const auto y = tonestack.processSample (x); 41 | 42 | if (n > 1000) 43 | magnitude = std::max (magnitude, std::abs (y)); 44 | } 45 | 46 | auto actualGainDB = 20.0 * std::log10 (magnitude); 47 | REQUIRE (actualGainDB == Approx (expGainDB).margin (maxErr)); 48 | } 49 | 50 | void baxandallFreqTest (float bassParam, float trebleParam, float sineFreq, float expGainDB, float maxErr) 51 | { 52 | BaxandallWDF baxandall; 53 | baxandall.prepare (fs); 54 | baxandall.setParams (bassParam, trebleParam); 55 | 56 | float magnitude = 0.0f; 57 | for (int n = 0; n < (int) fs; ++n) 58 | { 59 | const auto x = std::sin (2.0f * (float) M_PI * (float) n * sineFreq / (float) fs); 60 | const auto y = baxandall.processSample (x); 61 | 62 | if (n > 1000) 63 | magnitude = std::max (magnitude, std::abs (y)); 64 | } 65 | 66 | auto actualGainDB = 20.0f * std::log10 (magnitude); 67 | REQUIRE (actualGainDB == Approx (expGainDB).margin (maxErr)); 68 | } 69 | 70 | void baxandallPolyFreqTest (float bassParam, float trebleParam, float sineFreq, float expGainDB, float maxErr) 71 | { 72 | BaxandallWDFPoly baxandall; 73 | baxandall.prepare (fs); 74 | baxandall.setParams (bassParam, trebleParam); 75 | 76 | float magnitude = 0.0f; 77 | for (int n = 0; n < (int) fs; ++n) 78 | { 79 | const auto x = std::sin (2.0f * (float) M_PI * (float) n * sineFreq / (float) fs); 80 | const auto y = baxandall.processSample (x); 81 | 82 | if (n > 1000) 83 | magnitude = std::max (magnitude, std::abs (y)); 84 | } 85 | 86 | auto actualGainDB = 20.0f * std::log10 (magnitude); 87 | REQUIRE (actualGainDB == Approx (expGainDB).margin (maxErr)); 88 | } 89 | 90 | TEST_CASE ("RType Test") 91 | { 92 | SECTION ("Bassman Bass Test") 93 | { 94 | bassmanFreqTest (0.5f, 0.001f, 60.0f, -9.0f, 0.5f); 95 | } 96 | 97 | SECTION ("Bassman Treble Test") 98 | { 99 | bassmanFreqTest (0.999f, 0.999f, 15000.0f, 5.0f, 0.5f); 100 | } 101 | 102 | SECTION ("Bassman Poly Bass Test") 103 | { 104 | bassmanPolyFreqTest (0.5f, 0.001f, 60.0f, -9.0f, 0.5f); 105 | } 106 | 107 | SECTION ("Bassman Poly Treble Test") 108 | { 109 | bassmanPolyFreqTest (0.999f, 0.999f, 15000.0f, 5.0f, 0.5f); 110 | } 111 | 112 | SECTION ("Baxandall Bass Test") 113 | { 114 | baxandallFreqTest (0.0001f, 0.1f, 20.0f, -3.0f, 0.5f); 115 | } 116 | 117 | SECTION ("Baxandall Treble Test") 118 | { 119 | baxandallFreqTest (0.1f, 0.0001f, 20000.0f, -8.0f, 0.5f); 120 | } 121 | 122 | SECTION ("Baxandall Poly Bass Test") 123 | { 124 | baxandallPolyFreqTest (0.0001f, 0.1f, 20.0f, -3.0f, 0.5f); 125 | } 126 | 127 | SECTION ("Baxandall Poly Treble Test") 128 | { 129 | baxandallPolyFreqTest (0.1f, 0.0001f, 20000.0f, -8.0f, 0.5f); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /tests/SIMDTest.cpp: -------------------------------------------------------------------------------- 1 | #if CHOWDSP_WDF_TEST_WITH_XSIMD 2 | 3 | #include 4 | #include 5 | #include "BassmanToneStack.h" 6 | 7 | TEST_CASE ("SIMD Circuits Test") 8 | { 9 | SECTION ("SIMD Signum Test") 10 | { 11 | using chowdsp::signum::signum; 12 | 13 | float testReg alignas (CHOWDSP_WDF_DEFAULT_SIMD_ALIGNMENT)[xsimd::batch::size] { -1.0f, 0.0f, 0.5f, 1.0f }; 14 | float signumReg alignas (CHOWDSP_WDF_DEFAULT_SIMD_ALIGNMENT)[xsimd::batch::size] {}; 15 | xsimd::store_aligned (signumReg, signum (xsimd::load_aligned (testReg))); 16 | 17 | for (size_t i = 0; i < xsimd::batch::size; ++i) 18 | REQUIRE (signumReg[i] == (float) signum (testReg[i])); 19 | } 20 | 21 | SECTION ("Voltage Divider") 22 | { 23 | using v_type = xsimd::batch; 24 | using namespace chowdsp::wdf; 25 | 26 | Resistor r1 (10000.0f); 27 | Resistor r2 (10000.0f); 28 | 29 | WDFSeries s1 (&r1, &r2); 30 | PolarityInverter p1 (&s1); 31 | IdealVoltageSource vs { &p1 }; 32 | 33 | vs.setVoltage (10.0f); 34 | vs.incident (p1.reflected()); 35 | p1.incident (vs.reflected()); 36 | 37 | REQUIRE (xsimd::all (r2.voltage() == v_type (5.0f))); 38 | } 39 | 40 | SECTION ("Current Divider Test") 41 | { 42 | using v_type = xsimd::batch; 43 | using namespace chowdsp::wdf; 44 | 45 | Resistor r1 (10000.0f); 46 | Resistor r2 (10000.0f); 47 | 48 | WDFParallel p1 (&r1, &r2); 49 | IdealCurrentSource is (&p1); 50 | 51 | is.setCurrent (1.0f); 52 | is.incident (p1.reflected()); 53 | p1.incident (is.reflected()); 54 | 55 | REQUIRE (xsimd::all (r2.current() == v_type (0.5f))); 56 | } 57 | 58 | SECTION ("Shockley Diode Test") 59 | { 60 | using v_type = xsimd::batch; 61 | 62 | static const auto saturationCurrent = (v_type) 1.0e-7; 63 | static const auto thermalVoltage = (v_type) 25.85e-3; 64 | static const auto voltage = (v_type) -0.35; 65 | 66 | using namespace chowdsp::wdf; 67 | ResistiveVoltageSource Vs; 68 | PolarityInverter I1 { &Vs }; 69 | Diode D1 { &I1, saturationCurrent, thermalVoltage }; 70 | 71 | Vs.setVoltage (voltage); 72 | D1.incident (I1.reflected()); 73 | I1.incident (D1.reflected()); 74 | 75 | auto expectedCurrent = saturationCurrent * (xsimd::exp (-voltage / thermalVoltage) - (v_type) 1.0); 76 | REQUIRE (xsimd::all ((D1.current() < expectedCurrent + 1.0e-3 && D1.current() > expectedCurrent - 1.0e-3))); 77 | } 78 | 79 | SECTION ("SIMD DIode Clipper Test") 80 | { 81 | using namespace chowdsp::wdf; 82 | constexpr double fs = 44100.0; 83 | 84 | using FloatType = double; 85 | using VType = xsimd::batch; 86 | constexpr auto Cap = (FloatType) 47.0e-9; 87 | constexpr auto Res = (FloatType) 4700.0; 88 | 89 | constexpr int num = 5; 90 | FloatType data1[num] = { 1.0, 0.5, 0.0, -0.5, -1.0 }; 91 | VType data2[num] = { 1.0, 0.5, 0.0, -0.5, -1.0 }; 92 | 93 | // Normal 94 | { 95 | ResistiveVoltageSource Vs {}; 96 | Resistor R1 { Res }; 97 | auto C1 = std::make_unique> (Cap, (FloatType) fs); 98 | 99 | auto S1 = std::make_unique> (&Vs, &R1); 100 | auto P1 = std::make_unique> (S1.get(), C1.get()); 101 | auto I1 = std::make_unique> (P1.get()); 102 | 103 | DiodePair dp { I1.get(), (FloatType) 2.52e-9 }; 104 | dp.setDiodeParameters ((FloatType) 2.52e-9, (FloatType) 0.02585, 1); 105 | 106 | for (int i = 0; i < num; ++i) 107 | { 108 | Vs.setVoltage (data1[i]); 109 | dp.incident (P1->reflected()); 110 | data1[i] = C1->voltage(); 111 | P1->incident (dp.reflected()); 112 | } 113 | } 114 | 115 | // SIMD 116 | { 117 | ResistiveVoltageSource Vs {}; 118 | Resistor R1 { Res }; 119 | auto C1 = std::make_unique> (Cap, (VType) fs); 120 | 121 | auto S1 = std::make_unique> (&Vs, &R1); 122 | auto P1 = std::make_unique> (S1.get(), C1.get()); 123 | auto I1 = std::make_unique> (P1.get()); 124 | 125 | DiodePair dp { I1.get(), (VType) 2.52e-9 }; 126 | 127 | for (int i = 0; i < num; ++i) 128 | { 129 | Vs.setVoltage (data2[i]); 130 | dp.incident (P1->reflected()); 131 | data2[i] = C1->voltage(); 132 | P1->incident (dp.reflected()); 133 | } 134 | } 135 | 136 | for (int i = 0; i < num; ++i) 137 | REQUIRE (xsimd::all ((data2[i] < data1[i] + 1.0e-6 && data2[i] > data1[i] - 1.0e-6))); 138 | } 139 | 140 | SECTION ("Static SIMD WDF Test") 141 | { 142 | using FloatType = float; 143 | using Vec = xsimd::batch; 144 | constexpr double fs = 44100.0; 145 | 146 | constexpr int num = 5; 147 | Vec data1[num] = { 1.0, 0.5, 0.0, -0.5, -1.0 }; 148 | Vec data2[num] = { 1.0, 0.5, 0.0, -0.5, -1.0 }; 149 | 150 | // dynamic 151 | { 152 | using namespace chowdsp::wdf; 153 | using Resistor = Resistor; 154 | using Capacitor = CapacitorAlpha; 155 | using ResVs = ResistiveVoltageSource; 156 | 157 | ResVs Vs { 1.0e-9f }; 158 | Resistor r162 { 4700.0f }; 159 | Resistor r163 { 100000.0f }; 160 | 161 | auto c40 = std::make_unique ((FloatType) 0.015e-6f, (FloatType) fs, (FloatType) 0.029f); 162 | auto P1 = std::make_unique> (c40.get(), &r163); 163 | auto S1 = std::make_unique> (&Vs, P1.get()); 164 | auto I1 = std::make_unique> (&r162); 165 | auto P2 = std::make_unique> (I1.get(), S1.get()); 166 | 167 | Diode d53 { P2.get(), 2.52e-9f }; // 1N4148 diode 168 | d53.setDiodeParameters ((FloatType) 2.52e-9, (FloatType) 0.02585, 1); 169 | 170 | for (int i = 0; i < num; ++i) 171 | { 172 | Vs.setVoltage (data1[i]); 173 | d53.incident (P2->reflected()); 174 | data1[i] = r162.voltage(); 175 | P2->incident (d53.reflected()); 176 | } 177 | } 178 | 179 | // static 180 | { 181 | using namespace chowdsp::wdft; 182 | using Resistor = ResistorT; 183 | using Capacitor = CapacitorAlphaT; 184 | using ResVs = ResistiveVoltageSourceT; 185 | 186 | ResVs Vs { 1.0e-9f }; 187 | Resistor r162 { 4700.0f }; 188 | Resistor r163 { 100000.0f }; 189 | Capacitor c40 { (FloatType) 0.015e-6f, (FloatType) fs, (FloatType) 0.029f }; 190 | 191 | auto P1 = makeParallel (c40, r163); 192 | auto S1 = makeSeries (Vs, P1); 193 | auto I1 = makeInverter (r162); 194 | auto P2 = makeParallel (I1, S1); 195 | DiodeT d53 { P2, 2.52e-9f }; // 1N4148 diode 196 | 197 | for (int i = 0; i < num; ++i) 198 | { 199 | Vs.setVoltage (data2[i]); 200 | d53.incident (P2.reflected()); 201 | data2[i] = voltage (r162); 202 | P2.incident (d53.reflected()); 203 | } 204 | } 205 | 206 | for (int i = 0; i < num; ++i) 207 | REQUIRE (xsimd::all ((data2[i] < data1[i] + 1.0e-6f && data2[i] > data1[i] - 1.0e-6f))); 208 | } 209 | 210 | SECTION ("SIMD R-Type Test") 211 | { 212 | constexpr double fs = 44100.0; 213 | 214 | using FloatType = double; 215 | using VType = xsimd::batch; 216 | 217 | constexpr int num = 5; 218 | FloatType data1[num] = { 1.0, 0.5, 0.0, -0.5, -1.0 }; 219 | VType data2[num] = { 1.0, 0.5, 0.0, -0.5, -1.0 }; 220 | 221 | constexpr double highPot = 0.75; 222 | constexpr double lowPot = 0.25; 223 | 224 | // Normal 225 | { 226 | Tonestack tonestack; 227 | tonestack.prepare (fs); 228 | tonestack.setParams ((FloatType) highPot, (FloatType) lowPot, 1.0); 229 | 230 | for (int i = 0; i < num; ++i) 231 | data1[i] = tonestack.processSample (data1[i]); 232 | } 233 | 234 | // SIMD 235 | { 236 | Tonestack tonestack; 237 | tonestack.prepare (fs); 238 | tonestack.setParams ((VType) highPot, (VType) lowPot, (VType) 1.0); 239 | 240 | for (int i = 0; i < num; ++i) 241 | data2[i] = tonestack.processSample (data2[i]); 242 | } 243 | 244 | for (int i = 0; i < num; ++i) 245 | REQUIRE (xsimd::all ((data2[i] < data1[i] + 1.0e-6 && data2[i] > data1[i] - 1.0e-6))); 246 | } 247 | } 248 | 249 | #endif // CHOWDSP_WDF_TEST_WITH_XSIMD 250 | -------------------------------------------------------------------------------- /tests/StaticBasicCircuitTest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace chowdsp::wdft; 5 | 6 | TEST_CASE ("Static Basic Circuits Test") 7 | { 8 | SECTION ("Voltage Divider") 9 | { 10 | ResistorT r1 (10000.0f); 11 | ResistorT r2 (10000.0f); 12 | 13 | auto s1 = makeSeries (r1, r2); 14 | auto p1 = makeInverter (s1); 15 | IdealVoltageSourceT vs { p1 }; 16 | 17 | vs.setVoltage (10.0f); 18 | vs.incident (p1.reflected()); 19 | p1.incident (vs.reflected()); 20 | 21 | auto vOut = voltage (r2); 22 | REQUIRE (vOut == 5.0f); 23 | } 24 | 25 | SECTION ("Current Divider Test") 26 | { 27 | ResistorT r1 (10000.0f); 28 | ResistorT r2 (10000.0f); 29 | 30 | auto p1 = makeParallel (r1, r2); 31 | IdealCurrentSourceT is (p1); 32 | 33 | is.setCurrent (1.0f); 34 | is.incident (p1.reflected()); 35 | p1.incident (is.reflected()); 36 | 37 | auto iOut = current (r2); 38 | REQUIRE (iOut == 0.5f); 39 | } 40 | 41 | SECTION ("Shockley Diode") 42 | { 43 | constexpr auto saturationCurrent = 1.0e-7; 44 | constexpr auto thermalVoltage = 25.85e-3; 45 | constexpr auto voltage = -0.35; 46 | 47 | ResistiveVoltageSourceT Vs; 48 | auto I1 = makeInverter (Vs); 49 | DiodeT D1 { I1, saturationCurrent, thermalVoltage }; 50 | 51 | Vs.setVoltage (voltage); 52 | D1.incident (I1.reflected()); 53 | I1.incident (D1.reflected()); 54 | 55 | auto expectedCurrent = saturationCurrent * (std::exp (-voltage / thermalVoltage) - 1.0); 56 | REQUIRE (current (D1) == Approx (expectedCurrent).margin (1.0e-3)); 57 | } 58 | 59 | SECTION ("Current Switch") 60 | { 61 | ResistorT r1 (10000.0f); 62 | ResistiveCurrentSourceT Is; 63 | 64 | auto s1 = makeSeries (r1, Is); 65 | SwitchT sw { s1 }; 66 | 67 | // run with switch closed 68 | sw.setClosed (true); 69 | Is.setCurrent (1.0f); 70 | sw.incident (s1.reflected()); 71 | s1.incident (sw.reflected()); 72 | 73 | auto currentClosed = current (r1); 74 | REQUIRE (currentClosed == Approx (-1.0f).margin (1.0e-3f)); 75 | 76 | // run with switch open 77 | sw.setClosed (false); 78 | sw.incident (s1.reflected()); 79 | s1.incident (sw.reflected()); 80 | 81 | auto currentOpen = current (r1); 82 | REQUIRE (currentOpen == 0.0f); 83 | } 84 | 85 | SECTION ("Y-Parameter Test") 86 | { 87 | constexpr auto y11 = 0.11; 88 | constexpr auto y12 = 0.22; 89 | constexpr auto y21 = 0.33; 90 | constexpr auto y22 = 0.44; 91 | constexpr auto V = 2.0; 92 | 93 | ResistorT res { 10000.0 }; 94 | YParameterT yParam { res, y11, y12, y21, y22 }; 95 | IdealVoltageSourceT Vs { yParam }; 96 | 97 | Vs.setVoltage (V); 98 | Vs.incident (yParam.reflected()); 99 | yParam.incident (Vs.reflected()); 100 | 101 | REQUIRE (-current (res) == Approx (y11 * voltage (res) + y12 * V).margin (1.0e-3)); 102 | REQUIRE (current (yParam) == Approx (y21 * voltage (res) + y22 * V).margin (1.0e-3)); 103 | } 104 | 105 | SECTION ("RC Lowpass") 106 | { 107 | constexpr double fs = 44100.0; 108 | constexpr double fc = 500.0; 109 | 110 | constexpr auto capValue = 1.0e-6; 111 | constexpr auto resValue = 1.0 / ((2 * M_PI) * fc * capValue); 112 | 113 | CapacitorT c1 (capValue, fs); 114 | ResistorT r1 (resValue); 115 | 116 | auto s1 = makeSeries (r1, c1); 117 | auto p1 = makeInverter (s1); 118 | IdealVoltageSourceT vs { p1 }; 119 | 120 | auto testFreq = [&] (double freq, double expectedMagDB) { 121 | c1.reset(); 122 | 123 | double magnitude = 0.0; 124 | for (int n = 0; n < (int) fs; ++n) 125 | { 126 | const auto x = std::sin (2.0 * M_PI * freq * (double) n / fs); 127 | vs.setVoltage (x); 128 | 129 | vs.incident (p1.reflected()); 130 | p1.incident (vs.reflected()); 131 | 132 | const auto y = voltage (c1); 133 | 134 | if (n > 1000) 135 | magnitude = std::max (magnitude, std::abs (y)); 136 | } 137 | 138 | const auto actualMagnitudeDB = 20.0 * std::log10 (magnitude); 139 | REQUIRE (actualMagnitudeDB == Approx (expectedMagDB).margin (0.1)); 140 | }; 141 | 142 | testFreq (2 * fc, -7.0); 143 | testFreq (fc, -3.0); 144 | testFreq (0.5 * fc, -1.0); 145 | } 146 | 147 | SECTION ("Alpha Transform") 148 | { 149 | constexpr float fs = 44100.0f; 150 | 151 | // 1 kHz cutoff 2nd-order highpass 152 | constexpr float R = 300.0f; 153 | constexpr float C = 1.0e-6f; 154 | constexpr float L = 0.022f; 155 | 156 | auto testFreq = [&] (float freq, float expectedMagDB, auto& vs, auto& p1, auto& l1) { 157 | float magnitude = 0.0f; 158 | for (int n = 0; n < (int) fs; ++n) 159 | { 160 | const auto x = std::sin (2.0f * (float) M_PI * freq * (float) n / fs); 161 | vs.setVoltage (x); 162 | 163 | vs.incident (p1.reflected()); 164 | p1.incident (vs.reflected()); 165 | 166 | const auto y = voltage (l1); 167 | 168 | if (n > 1000) 169 | magnitude = std::max (magnitude, std::abs (y)); 170 | } 171 | 172 | const auto actualMagnitudeDB = 20.0f * std::log10 (magnitude); 173 | REQUIRE (actualMagnitudeDB == Approx (expectedMagDB).margin (0.1f)); 174 | }; 175 | 176 | // reference filter 177 | { 178 | CapacitorT c1 (C); 179 | ResistorT r1 (R); 180 | InductorT l1 (L); 181 | 182 | auto s1 = makeSeries (r1, c1); 183 | auto s2 = makeSeries (s1, l1); 184 | auto p1 = makeInverter (s2); 185 | IdealVoltageSourceT vs { p1 }; 186 | 187 | c1.prepare (fs); 188 | l1.prepare (fs); 189 | 190 | testFreq (10.0e3f, 0.0f, vs, p1, l1); 191 | } 192 | 193 | CapacitorAlphaT c1 (C); 194 | ResistorT r1 (R); 195 | InductorAlphaT l1 (L); 196 | 197 | auto s1 = makeSeries (r1, c1); 198 | auto s2 = makeSeries (s1, l1); 199 | auto p1 = makeInverter (s2); 200 | IdealVoltageSourceT vs { p1 }; 201 | 202 | // alpha = 1.0 filter 203 | { 204 | constexpr float alpha = 1.0f; 205 | c1.prepare (fs); 206 | c1.setAlpha (alpha); 207 | l1.prepare (fs); 208 | l1.setAlpha (alpha); 209 | 210 | testFreq (10.0e3f, 0.0f, vs, p1, l1); 211 | } 212 | 213 | // alpha = 0.1 filter 214 | { 215 | constexpr float alpha = 0.1f; 216 | c1.reset(); 217 | c1.setAlpha (alpha); 218 | l1.reset(); 219 | l1.setAlpha (alpha); 220 | 221 | testFreq (10.0e3f, -1.1f, vs, p1, l1); 222 | } 223 | } 224 | } 225 | 226 | template 227 | struct ImpedanceChecker 228 | { 229 | float value1 = 0.0f; 230 | float value2 = 0.0f; 231 | std::function changeFunc; 232 | std::function impedanceCalc; 233 | std::function factory; 234 | }; 235 | 236 | template 237 | void checkImpedanceChange (const ImpedanceChecker& checker) 238 | { 239 | auto&& component = checker.factory(); 240 | REQUIRE (component.wdf.R == checker.impedanceCalc (checker.value1)); 241 | 242 | checker.changeFunc (component, checker.value2); 243 | REQUIRE (component.wdf.R == checker.impedanceCalc (checker.value2)); 244 | } 245 | 246 | template 247 | void checkImpedanceProp (const ImpedanceChecker& checker) 248 | { 249 | constexpr float otherR = 5000.0f; 250 | ResistorT r2 { otherR }; 251 | auto&& component = checker.factory(); 252 | auto s1 = makeSeries (component, r2); 253 | IdealCurrentSourceT is (s1); 254 | is.setCurrent (1.0f); 255 | 256 | REQUIRE (s1.wdf.R == checker.impedanceCalc (checker.value1) + otherR); 257 | REQUIRE (is.reflected() == 2.0f * s1.wdf.R); 258 | 259 | checker.changeFunc (component, checker.value2); 260 | REQUIRE (s1.wdf.R == checker.impedanceCalc (checker.value2) + otherR); 261 | REQUIRE (is.reflected() == 2.0f * s1.wdf.R); 262 | } 263 | 264 | template 265 | void doImpedanceChecks (const ImpedanceChecker& checker) 266 | { 267 | checkImpedanceChange (checker); 268 | checkImpedanceProp (checker); 269 | } 270 | 271 | TEST_CASE ("Static Impedance Change Test") 272 | { 273 | SECTION ("Impedance Change") 274 | { 275 | constexpr float fs = 44100.0f; 276 | 277 | // resistor 278 | doImpedanceChecks (ImpedanceChecker> { 279 | 1000.0f, 280 | 2000.0f, 281 | [] (auto& r, float value) { r.setResistanceValue (value); }, 282 | [] (float value) { return value; }, 283 | []() { return ResistorT { 1000.0f }; }, 284 | }); 285 | 286 | // capacitor 287 | doImpedanceChecks (ImpedanceChecker> { 288 | 1.0e-6f, 289 | 2.0e-6f, 290 | [] (auto& c, float value) { c.setCapacitanceValue (value); }, 291 | [_fs = fs] (float value) { return 1.0f / (2.0f * value * (float) _fs); }, 292 | [_fs = fs]() { return CapacitorT { 1.0e-6f, _fs }; }, 293 | }); 294 | 295 | // capacitor alpha 296 | doImpedanceChecks (ImpedanceChecker> { 297 | 1.0e-6f, 298 | 2.0e-6f, 299 | [] (auto& c, float value) { c.setCapacitanceValue (value); }, 300 | [_fs = fs] (float value) { return 1.0f / (1.5f * value * (float) _fs); }, 301 | [_fs = fs]() { return CapacitorAlphaT { 1.0e-6f, _fs, 0.5f }; }, 302 | }); 303 | 304 | // inductor 305 | doImpedanceChecks (ImpedanceChecker> { 306 | 1.0f, 307 | 2.0f, 308 | [] (auto& i, float value) { i.setInductanceValue (value); }, 309 | [_fs = fs] (float value) { return 2.0f * value * (float) _fs; }, 310 | [_fs = fs]() { return InductorT { 1.0f, _fs }; }, 311 | }); 312 | 313 | // inductor alpha 314 | doImpedanceChecks (ImpedanceChecker> { 315 | 1.0f, 316 | 2.0f, 317 | [] (auto& i, float value) { i.setInductanceValue (value); }, 318 | [_fs = fs] (float value) { return 1.5f * value * (float) _fs; }, 319 | [_fs = fs]() { return InductorAlphaT { 1.0f, _fs, 0.5f }; }, 320 | }); 321 | 322 | // resistive voltage source 323 | doImpedanceChecks (ImpedanceChecker> { 324 | 1000.0f, 325 | 2000.0f, 326 | [] (auto& r, float value) { r.setResistanceValue (value); }, 327 | [] (float value) { return value; }, 328 | []() { return ResistiveVoltageSourceT { 1000.0f }; }, 329 | }); 330 | 331 | // resistive current source 332 | doImpedanceChecks (ImpedanceChecker> { 333 | 1000.0f, 334 | 2000.0f, 335 | [] (auto& r, float value) { r.setResistanceValue (value); }, 336 | [] (float value) { return value; }, 337 | []() { return ResistiveCurrentSourceT { 1000.0f }; }, 338 | }); 339 | } 340 | } 341 | -------------------------------------------------------------------------------- /tests/TestRunner.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_RUNNER 2 | 3 | #include "catch2/catch2.hpp" 4 | 5 | int main (int argc, char** argv) 6 | { 7 | int result = Catch::Session().run (argc, argv); 8 | return result; 9 | } 10 | -------------------------------------------------------------------------------- /tools/amalgamate/CHANGES.md: -------------------------------------------------------------------------------- 1 | The following changes have been made to the code with respect to : 2 | 3 | - Resolved inspection results from PyCharm: 4 | - replaced tabs with spaces 5 | - added encoding annotation 6 | - reindented file to remove trailing whitespaces 7 | - unused import `sys` 8 | - membership check 9 | - made function from `_is_within` 10 | - removed unused variable `actual_path` 11 | -------------------------------------------------------------------------------- /tools/amalgamate/README.md: -------------------------------------------------------------------------------- 1 | 2 | # amalgamate.py - Amalgamate C source and header files 3 | 4 | Origin: https://bitbucket.org/erikedlund/amalgamate 5 | 6 | Mirror: https://github.com/edlund/amalgamate 7 | 8 | `amalgamate.py` aims to make it easy to use SQLite-style C source and header 9 | amalgamation in projects. 10 | 11 | For more information, please refer to: http://sqlite.org/amalgamation.html 12 | 13 | ## Here be dragons 14 | 15 | `amalgamate.py` is quite dumb, it only knows the bare minimum about C code 16 | required in order to be able to handle trivial include directives. It can 17 | produce weird results for unexpected code. 18 | 19 | Things to be aware of: 20 | 21 | `amalgamate.py` will not handle complex include directives correctly: 22 | 23 | #define HEADER_PATH "path/to/header.h" 24 | #include HEADER_PATH 25 | 26 | In the above example, `path/to/header.h` will not be included in the 27 | amalgamation (HEADER_PATH is never expanded). 28 | 29 | `amalgamate.py` makes the assumption that each source and header file which 30 | is not empty will end in a new-line character, which is not immediately 31 | preceded by a backslash character (see 5.1.1.2p1.2 of ISO C99). 32 | 33 | `amalgamate.py` should be usable with C++ code, but raw string literals from 34 | C++11 will definitely cause problems: 35 | 36 | R"delimiter(Terrible raw \ data " #include )delimiter" 37 | R"delimiter(Terrible raw \ data " escaping)delimiter" 38 | 39 | In the examples above, `amalgamate.py` will stop parsing the raw string literal 40 | when it encounters the first quotation mark, which will produce unexpected 41 | results. 42 | 43 | ## Installing amalgamate.py 44 | 45 | Python v.2.7.0 or higher is required. 46 | 47 | `amalgamate.py` can be tested and installed using the following commands: 48 | 49 | ./test.sh && sudo -k cp ./amalgamate.py /usr/local/bin/ 50 | 51 | ## Using amalgamate.py 52 | 53 | amalgamate.py [-v] -c path/to/config.json -s path/to/source/dir \ 54 | [-p path/to/prologue.(c|h)] 55 | 56 | * The `-c, --config` option should specify the path to a JSON config file which 57 | lists the source files, include paths and where to write the resulting 58 | amalgamation. Have a look at `test/source.c.json` and `test/include.h.json` 59 | to see two examples. 60 | 61 | * The `-s, --source` option should specify the path to the source directory. 62 | This is useful for supporting separate source and build directories. 63 | 64 | * The `-p, --prologue` option should specify the path to a file which will be 65 | added to the beginning of the amalgamation. It is optional. 66 | -------------------------------------------------------------------------------- /tools/amalgamate/amalgamate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding=utf-8 3 | 4 | # amalgamate.py - Amalgamate C source and header files. 5 | # Copyright (c) 2012, Erik Edlund 6 | # 7 | # Redistribution and use in source and binary forms, with or without modification, 8 | # are permitted provided that the following conditions are met: 9 | # 10 | # * Redistributions of source code must retain the above copyright notice, 11 | # this list of conditions and the following disclaimer. 12 | # 13 | # * Redistributions in binary form must reproduce the above copyright notice, 14 | # this list of conditions and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # 17 | # * Neither the name of Erik Edlund, nor the names of its contributors may 18 | # be used to endorse or promote products derived from this software without 19 | # specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 25 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | from __future__ import division 33 | from __future__ import print_function 34 | from __future__ import unicode_literals 35 | 36 | import argparse 37 | import datetime 38 | import json 39 | import os 40 | import re 41 | 42 | 43 | class Amalgamation(object): 44 | 45 | # Prepends self.source_path to file_path if needed. 46 | def actual_path(self, file_path): 47 | if not os.path.isabs(file_path): 48 | file_path = os.path.join(self.source_path, file_path) 49 | return file_path 50 | 51 | # Search included file_path in self.include_paths and 52 | # in source_dir if specified. 53 | def find_included_file(self, file_path, source_dir): 54 | search_dirs = self.include_paths[:] 55 | if source_dir: 56 | search_dirs.insert(0, source_dir) 57 | 58 | for search_dir in search_dirs: 59 | search_path = os.path.join(search_dir, file_path) 60 | if os.path.isfile(self.actual_path(search_path)): 61 | return search_path 62 | return None 63 | 64 | def __init__(self, args): 65 | with open(args.config, 'r') as f: 66 | config = json.loads(f.read()) 67 | for key in config: 68 | setattr(self, key, config[key]) 69 | 70 | self.verbose = args.verbose == "yes" 71 | self.prologue = args.prologue 72 | self.source_path = args.source_path 73 | self.included_files = [] 74 | 75 | # Generate the amalgamation and write it to the target file. 76 | def generate(self): 77 | amalgamation = "" 78 | 79 | if self.prologue: 80 | with open(self.prologue, 'r') as f: 81 | amalgamation += datetime.datetime.now().strftime(f.read()) 82 | 83 | if self.verbose: 84 | print("Config:") 85 | print(" target = {0}".format(self.target)) 86 | print(" working_dir = {0}".format(os.getcwd())) 87 | print(" include_paths = {0}".format(self.include_paths)) 88 | print("Creating amalgamation:") 89 | for file_path in self.sources: 90 | # Do not check the include paths while processing the source 91 | # list, all given source paths must be correct. 92 | # actual_path = self.actual_path(file_path) 93 | print(" - processing \"{0}\"".format(file_path)) 94 | t = TranslationUnit(file_path, self, True) 95 | amalgamation += t.content 96 | 97 | with open(self.target, 'w') as f: 98 | f.write(amalgamation) 99 | 100 | print("...done!\n") 101 | if self.verbose: 102 | print("Files processed: {0}".format(self.sources)) 103 | print("Files included: {0}".format(self.included_files)) 104 | print("") 105 | 106 | 107 | def _is_within(match, matches): 108 | for m in matches: 109 | if match.start() > m.start() and \ 110 | match.end() < m.end(): 111 | return True 112 | return False 113 | 114 | 115 | class TranslationUnit(object): 116 | # // C++ comment. 117 | cpp_comment_pattern = re.compile(r"//.*?\n") 118 | 119 | # /* C comment. */ 120 | c_comment_pattern = re.compile(r"/\*.*?\*/", re.S) 121 | 122 | # "complex \"stri\\\ng\" value". 123 | string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S) 124 | 125 | # Handle simple include directives. Support for advanced 126 | # directives where macros and defines needs to expanded is 127 | # not a concern right now. 128 | include_pattern = re.compile( 129 | r'#\s*include\s+(<|")(?P.*?)("|>)', re.S) 130 | 131 | # #pragma once 132 | pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S) 133 | 134 | # Search for pattern in self.content, add the match to 135 | # contexts if found and update the index accordingly. 136 | def _search_content(self, index, pattern, contexts): 137 | match = pattern.search(self.content, index) 138 | if match: 139 | contexts.append(match) 140 | return match.end() 141 | return index + 2 142 | 143 | # Return all the skippable contexts, i.e., comments and strings 144 | def _find_skippable_contexts(self): 145 | # Find contexts in the content in which a found include 146 | # directive should not be processed. 147 | skippable_contexts = [] 148 | 149 | # Walk through the content char by char, and try to grab 150 | # skippable contexts using regular expressions when found. 151 | i = 1 152 | content_len = len(self.content) 153 | while i < content_len: 154 | j = i - 1 155 | current = self.content[i] 156 | previous = self.content[j] 157 | 158 | if current == '"': 159 | # String value. 160 | i = self._search_content(j, self.string_pattern, 161 | skippable_contexts) 162 | elif current == '*' and previous == '/': 163 | # C style comment. 164 | i = self._search_content(j, self.c_comment_pattern, 165 | skippable_contexts) 166 | elif current == '/' and previous == '/': 167 | # C++ style comment. 168 | i = self._search_content(j, self.cpp_comment_pattern, 169 | skippable_contexts) 170 | else: 171 | # Skip to the next char. 172 | i += 1 173 | 174 | return skippable_contexts 175 | 176 | # Returns True if the match is within list of other matches 177 | 178 | # Removes pragma once from content 179 | def _process_pragma_once(self): 180 | content_len = len(self.content) 181 | if content_len < len("#include "): 182 | return 0 183 | 184 | # Find contexts in the content in which a found include 185 | # directive should not be processed. 186 | skippable_contexts = self._find_skippable_contexts() 187 | 188 | pragmas = [] 189 | pragma_once_match = self.pragma_once_pattern.search(self.content) 190 | while pragma_once_match: 191 | if not _is_within(pragma_once_match, skippable_contexts): 192 | pragmas.append(pragma_once_match) 193 | 194 | pragma_once_match = self.pragma_once_pattern.search(self.content, 195 | pragma_once_match.end()) 196 | 197 | # Handle all collected pragma once directives. 198 | prev_end = 0 199 | tmp_content = '' 200 | for pragma_match in pragmas: 201 | tmp_content += self.content[prev_end:pragma_match.start()] 202 | prev_end = pragma_match.end() 203 | tmp_content += self.content[prev_end:] 204 | self.content = tmp_content 205 | 206 | # Include all trivial #include directives into self.content. 207 | def _process_includes(self): 208 | content_len = len(self.content) 209 | if content_len < len("#include "): 210 | return 0 211 | 212 | # Find contexts in the content in which a found include 213 | # directive should not be processed. 214 | skippable_contexts = self._find_skippable_contexts() 215 | 216 | # Search for include directives in the content, collect those 217 | # which should be included into the content. 218 | includes = [] 219 | include_match = self.include_pattern.search(self.content) 220 | while include_match: 221 | if not _is_within(include_match, skippable_contexts): 222 | include_path = include_match.group("path") 223 | search_same_dir = include_match.group(1) == '"' 224 | found_included_path = self.amalgamation.find_included_file( 225 | include_path, self.file_dir if search_same_dir else None) 226 | if found_included_path: 227 | includes.append((include_match, found_included_path)) 228 | 229 | include_match = self.include_pattern.search(self.content, 230 | include_match.end()) 231 | 232 | # Handle all collected include directives. 233 | prev_end = 0 234 | tmp_content = '' 235 | for include in includes: 236 | include_match, found_included_path = include 237 | tmp_content += self.content[prev_end:include_match.start()] 238 | tmp_content += "// {0}\n".format(include_match.group(0)) 239 | if found_included_path not in self.amalgamation.included_files: 240 | t = TranslationUnit(found_included_path, self.amalgamation, False) 241 | tmp_content += t.content 242 | prev_end = include_match.end() 243 | tmp_content += self.content[prev_end:] 244 | self.content = tmp_content 245 | 246 | return len(includes) 247 | 248 | # Make all content processing 249 | def _process(self): 250 | if not self.is_root: 251 | self._process_pragma_once() 252 | self._process_includes() 253 | 254 | def __init__(self, file_path, amalgamation, is_root): 255 | self.file_path = file_path 256 | self.file_dir = os.path.dirname(file_path) 257 | self.amalgamation = amalgamation 258 | self.is_root = is_root 259 | 260 | self.amalgamation.included_files.append(self.file_path) 261 | 262 | actual_path = self.amalgamation.actual_path(file_path) 263 | if not os.path.isfile(actual_path): 264 | raise IOError("File not found: \"{0}\"".format(file_path)) 265 | with open(actual_path, 'r') as f: 266 | self.content = f.read() 267 | self._process() 268 | 269 | 270 | def main(): 271 | description = "Amalgamate C source and header files." 272 | usage = " ".join([ 273 | "amalgamate.py", 274 | "[-v]", 275 | "-c path/to/config.json", 276 | "-s path/to/source/dir", 277 | "[-p path/to/prologue.(c|h)]" 278 | ]) 279 | argsparser = argparse.ArgumentParser( 280 | description=description, usage=usage) 281 | 282 | argsparser.add_argument("-v", "--verbose", dest="verbose", 283 | choices=["yes", "no"], metavar="", help="be verbose") 284 | 285 | argsparser.add_argument("-c", "--config", dest="config", 286 | required=True, metavar="", help="path to a JSON config file") 287 | 288 | argsparser.add_argument("-s", "--source", dest="source_path", 289 | required=True, metavar="", help="source code path") 290 | 291 | argsparser.add_argument("-p", "--prologue", dest="prologue", 292 | required=False, metavar="", help="path to a C prologue file") 293 | 294 | amalgamation = Amalgamation(argsparser.parse_args()) 295 | amalgamation.generate() 296 | 297 | 298 | if __name__ == "__main__": 299 | main() 300 | -------------------------------------------------------------------------------- /tools/amalgamate/config_chowdsp_wdf.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": "chowdsp_wdf", 3 | "target": "single_include/chowdsp_wdf/chowdsp_wdf.h", 4 | "sources": [ 5 | "include/chowdsp_wdf/chowdsp_wdf.h" 6 | ], 7 | "include_paths": ["include"] 8 | } 9 | -------------------------------------------------------------------------------- /tools/amalgamate/run_amalgamate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | python3 tools/amalgamate/amalgamate.py -c tools/amalgamate/config_chowdsp_wdf.json -s . --verbose=yes 4 | --------------------------------------------------------------------------------