├── .github ├── codecov.yml └── workflows │ ├── build-and-test.yml │ ├── publish-doxygen.yml │ └── static-analysis.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Notes └── spinlock-instructions.txt ├── PerformanceLogs ├── 074dcf3_to_6600be6.log ├── 51f0543.log └── d8fe46e.log ├── README.md ├── Scripts ├── BuildAndTest.sh ├── StaticAnalysis.sh └── get_code_cov.sh ├── WECore ├── CarveDSP │ ├── CarveDSPUnit.h │ ├── CarveNoiseFilter.h │ ├── CarveParameters.h │ └── Tests │ │ ├── CarveNoiseFilterTests.cpp │ │ ├── DSPUnitParameterTests.cpp │ │ ├── DSPUnitProcessingTests.cpp │ │ └── TestData.h ├── CoreJUCEPlugin │ ├── CoreAudioProcessor.h │ ├── CoreLookAndFeel.h │ ├── CoreProcessorEditor.h │ ├── CustomParameter.h │ ├── LabelReadoutSlider.h │ ├── LookAndFeelMixins │ │ ├── ButtonV2.h │ │ ├── ComboBoxV2.h │ │ ├── GroupComponentV2.h │ │ ├── LinearSliderV2.h │ │ ├── LookAndFeelMixins.h │ │ ├── MidAnchoredRotarySlider.h │ │ ├── PopupMenuV2.h │ │ └── RotarySliderV2.h │ ├── ParameterUpdateHandler.h │ └── TooltipLabelUpdater.h ├── General │ ├── AudioSpinMutex.h │ ├── CoreMath.h │ ├── ParameterDefinition.h │ └── UpdateChecker.h ├── MONSTRFilters │ └── MONSTRParameters.h ├── RichterLFO │ ├── RichterLFO.h │ ├── RichterLFOPair.h │ ├── RichterParameters.h │ ├── RichterWavetables.h │ ├── Tests │ │ └── RichterLFOPairTests.cpp │ └── UI │ │ └── RichterWaveViewer.h ├── SongbirdFilters │ ├── Formant.h │ ├── SongbirdFilterModule.h │ ├── SongbirdFiltersParameters.h │ ├── SongbirdFormantFilter.h │ └── Tests │ │ ├── SongbirdFilterModuleTests.cpp │ │ └── TestData.h ├── Tests │ ├── PerformanceTests.cpp │ └── catchMain.cpp └── WEFilters │ ├── AREnvelopeFollowerBase.h │ ├── AREnvelopeFollowerFullWave.h │ ├── AREnvelopeFollowerParameters.h │ ├── AREnvelopeFollowerSquareLaw.h │ ├── EffectsProcessor.h │ ├── ModulationSource.h │ ├── PerlinSource.hpp │ ├── SimpleCompressor.h │ ├── SimpleCompressorParameters.h │ ├── StereoWidthProcessor.h │ ├── StereoWidthProcessorParameters.h │ ├── TPTSVFilter.h │ ├── TPTSVFilterParameters.h │ └── Tests │ ├── AREnvelopeFollowerTests.cpp │ ├── SimpleCompressorTests.cpp │ ├── StereoWidthProcessorTests.cpp │ ├── TPTSVFilterTests.cpp │ └── TestUtils.h └── doxygen └── Doxyfile /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "WECore/Tests" 3 | - "DSPFilters" -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | BuildClang: 7 | runs-on: ubuntu-latest 8 | container: jackd13/audioplugins:clang 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: true 14 | - name: RunBuild 15 | run: Scripts/BuildAndTest.sh 16 | - uses: actions/upload-artifact@v2 17 | with: 18 | name: callgrind-clang 19 | path: build/callgrind.out.* 20 | 21 | BuildGCC: 22 | runs-on: ubuntu-latest 23 | container: jackd13/audioplugins:gcc 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | with: 28 | submodules: true 29 | - name: RunBuild 30 | run: Scripts/BuildAndTest.sh 31 | - uses: actions/upload-artifact@v2 32 | with: 33 | name: callgrind-gcc 34 | path: build/callgrind.out.* 35 | -------------------------------------------------------------------------------- /.github/workflows/publish-doxygen.yml: -------------------------------------------------------------------------------- 1 | name: Publish Doxygen 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | PublishDoxygen: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: mattnotmitt/doxygen-action@v1 14 | with: 15 | working-directory: 'doxygen' 16 | - uses: peaceiris/actions-gh-pages@v3 17 | with: 18 | github_token: ${{ secrets.GITHUB_TOKEN }} 19 | publish_dir: doxygen/html 20 | -------------------------------------------------------------------------------- /.github/workflows/static-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static Analysis 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | StaticAnalysis: 7 | runs-on: ubuntu-latest 8 | container: jackd13/audioplugins:clang 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | with: 13 | submodules: true 14 | - name: RunStaticAnalysis 15 | run: Scripts/StaticAnalysis.sh 16 | - uses: actions/upload-artifact@v2 17 | if: failure() 18 | with: 19 | name: clang-tidy 20 | path: clang-tidy.txt 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swift 2 | wecore_performance.log 3 | doxygen/html/* 4 | doxygen/doxygen* 5 | build/* 6 | .vscode/* 7 | .DS_Store 8 | clang-tidy.txt 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(WECore) 3 | 4 | include_directories($ENV{CATCH_PATH} 5 | ${CMAKE_CURRENT_LIST_DIR}/WECore) 6 | 7 | set(CMAKE_CXX_STANDARD 17) 8 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 9 | 10 | set(GCOV_FLAGS -fprofile-arcs -ftest-coverage) 11 | 12 | # Set the GCC and Clang flags 13 | # TODO: work on removing the need for the below Wno flags 14 | # TODO: replace -gdwarf-4 with -g once the build image uses ubuntu mantic - it's a work around for an older valgrind version https://github.com/llvm/llvm-project/issues/56550 15 | set(CXXFLAGS_CLANG -std=c++17 -gdwarf-4 -Wall -Werror -Wextra -Wconversion -Wshadow -Weverything -Wpedantic -Wno-exit-time-destructors -Wno-weak-vtables -Wno-reserved-id-macro -Wno-double-promotion -Wno-unknown-warning-option -Wno-c++98-compat -Wno-padded -Wno-global-constructors) 16 | set(CXXFLAGS_GCC -std=c++17 -g -Wall -Werror -Wextra -Wconversion -Wshadow -pedantic ${GCOV_FLAGS}) 17 | 18 | # Set the flags we'll actually use based on the compiler 19 | set(CXXFLAGS ${CXXFLAGS_CLANG}) 20 | 21 | if (CMAKE_COMPILER_IS_GNUCXX) 22 | set(CXXFLAGS ${CXXFLAGS_GCC}) 23 | endif() 24 | 25 | add_executable(WECoreTest ${CMAKE_CURRENT_LIST_DIR}/WECore/Tests/catchMain.cpp 26 | ${CMAKE_CURRENT_LIST_DIR}/WECore/CarveDSP/Tests/DSPUnitParameterTests.cpp 27 | ${CMAKE_CURRENT_LIST_DIR}/WECore/CarveDSP/Tests/DSPUnitProcessingTests.cpp 28 | ${CMAKE_CURRENT_LIST_DIR}/WECore/CarveDSP/Tests/CarveNoiseFilterTests.cpp 29 | ${CMAKE_CURRENT_LIST_DIR}/WECore/RichterLFO/Tests/RichterLFOPairTests.cpp 30 | ${CMAKE_CURRENT_LIST_DIR}/WECore/SongbirdFilters/Tests/SongbirdFilterModuleTests.cpp 31 | ${CMAKE_CURRENT_LIST_DIR}/WECore/WEFilters/Tests/AREnvelopeFollowerTests.cpp 32 | ${CMAKE_CURRENT_LIST_DIR}/WECore/WEFilters/Tests/SimpleCompressorTests.cpp 33 | ${CMAKE_CURRENT_LIST_DIR}/WECore/WEFilters/Tests/StereoWidthProcessorTests.cpp 34 | ${CMAKE_CURRENT_LIST_DIR}/WECore/WEFilters/Tests/TPTSVFilterTests.cpp) 35 | 36 | 37 | add_executable(WECoreTestPerf ${CMAKE_CURRENT_LIST_DIR}/WECore/Tests/catchMain.cpp 38 | ${CMAKE_CURRENT_LIST_DIR}/WECore/Tests/PerformanceTests.cpp) 39 | 40 | target_compile_options(WECoreTest PRIVATE ${CXXFLAGS}) 41 | target_compile_options(WECoreTestPerf PRIVATE ${CXXFLAGS}) 42 | 43 | if (CMAKE_COMPILER_IS_GNUCXX) 44 | target_link_libraries(WECoreTest gcov) 45 | target_link_libraries(WECoreTestPerf gcov) 46 | endif() -------------------------------------------------------------------------------- /Notes/spinlock-instructions.txt: -------------------------------------------------------------------------------- 1 | https://timur.audio/using-locks-in-real-time-audio-processing-safely 2 | https://www.keil.com/support/man/docs/armasm/armasm_dom1361289926047.htm 3 | https://stackoverflow.com/questions/7086220/what-does-rep-nop-mean-in-x86-assembly-is-it-the-same-as-the-pause-instru 4 | https://stackoverflow.com/questions/12894078/what-is-the-purpose-of-the-pause-instruction-in-x86 5 | https://stackoverflow.com/questions/7488196/what-is-the-different-of-busy-loop-with-sleep0-and-pause-instruction 6 | https://stackoverflow.com/questions/19779569/cross-platform-implementation-of-the-x86-pause-instruction 7 | http://lkml.iu.edu/hypermail/linux/kernel/2107.2/04837.html 8 | https://support.huaweicloud.com/intl/en-us/pinsrcase-kunpengprocs/kunpengprocessor_18_0013.html 9 | https://developer.arm.com/documentation/dui0473/m/arm-and-thumb-instructions/yield 10 | https://developer.arm.com/documentation/ddi0406/b/Application-Level-Architecture/Application-Level-Programmers--Model/Exceptions--debug-events-and-checks/The-Yield-instruction?lang=en 11 | -------------------------------------------------------------------------------- /PerformanceLogs/074dcf3_to_6600be6.log: -------------------------------------------------------------------------------- 1 | 2 | After commit 074dcf3: 3 | **** New Test Run: 2017-06-03 13:32:50 4 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0653 Deviation: 0.00617178 5 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.7171 Deviation: 0.114106 6 | 7 | **** New Test Run: 2017-06-03 13:32:58 8 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0703 Deviation: 0.00451149 9 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.68622 Deviation: 0.068144 10 | 11 | **** New Test Run: 2017-06-03 13:32:59 12 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.07055 Deviation: 0.00516862 13 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.66179 Deviation: 0.0454698 14 | 15 | **** New Test Run: 2017-06-03 13:33:00 16 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.07063 Deviation: 0.00545441 17 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.70494 Deviation: 0.12455 18 | 19 | 20 | After removing making MONSTR vectors static: 21 | **** New Test Run: 2017-06-03 13:35:24 22 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06646 Deviation: 0.00381761 23 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.45164 Deviation: 0.0254561 24 | 25 | **** New Test Run: 2017-06-03 13:38:28 26 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06546 Deviation: 0.00400106 27 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.46164 Deviation: 0.0324235 28 | 29 | **** New Test Run: 2017-06-03 13:38:29 30 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0712 Deviation: 0.00626357 31 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.46671 Deviation: 0.0313653 32 | 33 | **** New Test Run: 2017-06-03 13:38:31 34 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.08019 Deviation: 0.0198706 35 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.48185 Deviation: 0.0501092 36 | 37 | **** New Test Run: 2017-06-03 13:38:33 38 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0697 Deviation: 0.00634767 39 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.47767 Deviation: 0.0470912 40 | 41 | **** New Test Run: 2017-06-03 13:38:34 42 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06977 Deviation: 0.00741804 43 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.47802 Deviation: 0.0425389 44 | 45 | **** New Test Run: 2017-06-03 13:38:37 46 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.07032 Deviation: 0.0071561 47 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.48156 Deviation: 0.0491323 48 | 49 | After swapping vectors for double*'s: 50 | **** New Test Run: 2017-06-03 13:51:02 51 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0648 Deviation: 0.00514634 52 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.44672 Deviation: 0.0812017 53 | 54 | **** New Test Run: 2017-06-03 13:51:03 55 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.07023 Deviation: 0.00759167 56 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.43127 Deviation: 0.0314517 57 | 58 | **** New Test Run: 2017-06-03 13:51:04 59 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06902 Deviation: 0.00698046 60 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.43227 Deviation: 0.0289865 61 | 62 | **** New Test Run: 2017-06-03 13:51:05 63 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.07135 Deviation: 0.00641711 64 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.42754 Deviation: 0.0324029 65 | 66 | **** New Test Run: 2017-06-03 14:24:35 67 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06528 Deviation: 0.00556156 68 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.45609 Deviation: 0.0874974 69 | 70 | **** New Test Run: 2017-06-03 14:24:53 71 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06905 Deviation: 0.00546684 72 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.44807 Deviation: 0.0430762 73 | 74 | **** New Test Run: 2017-06-03 14:24:54 75 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06766 Deviation: 0.0072254 76 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.43608 Deviation: 0.0494041 77 | 78 | **** New Test Run: 2017-06-03 14:24:55 79 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.06698 Deviation: 0.00412428 80 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.43688 Deviation: 0.0589618 81 | 82 | **** New Test Run: 2017-06-03 14:24:56 83 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0674 Deviation: 0.00357036 84 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.45207 Deviation: 0.0776501 85 | -------------------------------------------------------------------------------- /PerformanceLogs/51f0543.log: -------------------------------------------------------------------------------- 1 | **** New Test Run: 2020-12-18 19:39:58 2 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04764 Deviation: 0.00595186 3 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26356 Deviation: 0.0210892 4 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.3113 Deviation: 0.0386004 5 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.72953 Deviation: 0.0442873 6 | 7 | **** New Test Run: 2020-12-18 19:40:01 8 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04466 Deviation: 0.00363268 9 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.28063 Deviation: 0.0555299 10 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.30172 Deviation: 0.036204 11 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.75528 Deviation: 0.0593839 12 | 13 | **** New Test Run: 2020-12-18 19:40:03 14 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04695 Deviation: 0.00563427 15 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26295 Deviation: 0.019233 16 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.29019 Deviation: 0.0233306 17 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.73171 Deviation: 0.04508 18 | 19 | **** New Test Run: 2020-12-18 19:40:04 20 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.05193 Deviation: 0.0117519 21 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27418 Deviation: 0.0287091 22 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.2944 Deviation: 0.0216118 23 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.72984 Deviation: 0.0727392 -------------------------------------------------------------------------------- /PerformanceLogs/d8fe46e.log: -------------------------------------------------------------------------------- 1 | 2 | 3 | **** New Test Run: 2020-12-16 21:58:23 4 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04788 Deviation: 0.00893103 5 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27127 Deviation: 0.0229011 6 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.25976 Deviation: 0.0191934 7 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.26229 Deviation: 0.0219408 8 | 9 | **** New Test Run: 2020-12-16 21:58:30 10 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04854 Deviation: 0.00751889 11 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26414 Deviation: 0.0205559 12 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.25097 Deviation: 0.0212034 13 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.24891 Deviation: 0.0201525 14 | 15 | **** New Test Run: 2020-12-16 21:58:37 16 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.05143 Deviation: 0.00845697 17 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26963 Deviation: 0.0237451 18 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24814 Deviation: 0.0124771 19 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.25147 Deviation: 0.0173505 20 | 21 | **** New Test Run: 2020-12-16 21:58:38 22 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04471 Deviation: 0.00182184 23 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27483 Deviation: 0.0303075 24 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.2525 Deviation: 0.0240834 25 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.25205 Deviation: 0.0159509 26 | 27 | **** New Test Run: 2020-12-16 21:58:40 28 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04958 Deviation: 0.00854953 29 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27292 Deviation: 0.0288058 30 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24879 Deviation: 0.0159945 31 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.24749 Deviation: 0.0213964 32 | 33 | **** New Test Run: 2020-12-16 21:58:40 34 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0521 Deviation: 0.0111876 35 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27309 Deviation: 0.0284332 36 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24601 Deviation: 0.0175364 37 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.25439 Deviation: 0.0272166 38 | 39 | **** New Test Run: 2020-12-16 21:58:42 40 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.05017 Deviation: 0.00894546 41 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27018 Deviation: 0.0279232 42 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24527 Deviation: 0.0163577 43 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.24828 Deviation: 0.0191111 44 | 45 | **** New Test Run: 2020-12-16 21:58:43 46 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04646 Deviation: 0.00757777 47 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26907 Deviation: 0.0249663 48 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.25655 Deviation: 0.0234191 49 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.25833 Deviation: 0.0255762 50 | 51 | **** New Test Run: 2020-12-16 21:58:44 52 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04827 Deviation: 0.00755566 53 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.2679 Deviation: 0.0250053 54 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24938 Deviation: 0.0182656 55 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.25262 Deviation: 0.0247643 56 | 57 | **** New Test Run: 2020-12-16 21:58:45 58 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.0483 Deviation: 0.006792 59 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.27097 Deviation: 0.0238706 60 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24959 Deviation: 0.0182851 61 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.24974 Deviation: 0.0202767 62 | 63 | **** New Test Run: 2020-12-16 21:59:34 64 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.05026 Deviation: 0.00776891 65 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26885 Deviation: 0.0223468 66 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24785 Deviation: 0.0182886 67 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.2508 Deviation: 0.0203197 68 | 69 | **** New Test Run: 2020-12-16 21:59:43 70 | Scenario: Performance: CarveDSPUnit, 100 buffers of 1024 samples each: Average: 0.04663 Deviation: 0.00819664 71 | Scenario: Performance: MONSTRCrossover, 100 buffers of 1024 samples each: Average: 0.26009 Deviation: 0.0183628 72 | Scenario: Performance: SongbirdFilterModule (blend mode), 100 buffers of 1024 samples each: Average: 0.24307 Deviation: 0.0165537 73 | Scenario: Performance: SongbirdFilterModule (freq mode), 100 buffers of 1024 samples each: Average: 0.24857 Deviation: 0.0195605 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build and Test](https://github.com/jd-13/WE-Core/actions/workflows/build-and-test.yml/badge.svg?branch=master)](https://github.com/jd-13/WE-Core/actions/workflows/build-and-test.yml) 2 | [![Static Analysis](https://github.com/jd-13/WE-Core/actions/workflows/static-analysis.yml/badge.svg?branch=master)](https://github.com/jd-13/WE-Core/actions/workflows/static-analysis.yml) 3 | [![codecov](https://codecov.io/gh/jd-13/WE-Core/branch/master/graph/badge.svg)](https://codecov.io/gh/jd-13/WE-Core) 4 | [![](https://img.shields.io/badge/Docs-Over_here!-blueviolet)](https://jd-13.github.io/WE-Core/) 5 | ![](https://img.shields.io/badge/C%2B%2B-17-informational) 6 | ![](https://img.shields.io/badge/license-GPLv3-informational) 7 | [![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](https://open.vscode.dev/jd-13/WE-Core) 8 | 9 | # WE-Core 10 | A set of core libraries for useful DSP related classes that are used by multiple White Elephant 11 | Audio VSTs and Audio Units. 12 | 13 | This is a headers only library, to use the DSP classes in your own projects add the include path: 14 | `/WECore`. 15 | 16 | ## DSP Classes 17 | The naming convention is that each class is prefixed with the product it was developed for. 18 | 19 | ## APIs 20 | * __ModulationSource__ - provides a standard interface for classes which provide a modulation signal 21 | * __EffectsProcessor__ - provides a standard interface for classes which do audio processing on mono and 22 | stereo signals 23 | 24 | ### Modulation: 25 | * __RichterLFO__ - substantial functionality with tempo sync, phase sync, and multiple wave types and 26 | parameters, can accept a ModulationSource to modulate its parameters 27 | * __AREnvelopeFollowerSquareLaw__ & __AREnvelopeFollowerFullWave__ - two different attack/release envelope 28 | follower implementations 29 | 30 | ### Filters: 31 | * __CarveNoiseFilter__ - a simple filter to remove noise at the extremes of human hearing 32 | * __SongbirdFormantFilter__ - Contains multiple SongbirdBandPassFilters, designed to create vowel sounds 33 | * __SongbirdFilterModule__ - Contains two SongbirdFormantFilters which can be blended between, with 34 | multiple supported vowel sounds built in 35 | * __TPTSVFilter__ - Topology preserving filter, configurable as high pass, low pass, or peak 36 | * __MONSTRCrossover__ - A crossover filter made of several MONSTRBand units which can be provided with an 37 | EffectsProcessor to do any form of audio processing on that band 38 | 39 | ### Distortion: 40 | * __CarveDSPUnit__ - A waveshaping distortion module with multiple wave shapes, pre and post gain control, 41 | and a "tweak" control which morphs the selected wave shape 42 | 43 | ### Stereo Processing: 44 | * __StereoWidthProcessor__ - Enables narrowing or widening of the stereo image 45 | 46 | ## Documentation 47 | Documentation is available at: https://jd-13.github.io/WE-Core/ 48 | 49 | ## Required Libraries 50 | Some classes within this library require: 51 | 52 | A Collection of Useful C++ Classes for Digital Signal Processing: https://github.com/vinniefalco/DSPFilters 53 | 54 | LookAndFeel classes are for building UIs using the JUCE library: https://www.juce.com/ 55 | 56 | cURL (for the experimental auto update functionality): https://curl.haxx.se/libcurl/ 57 | 58 | ## Builds and Testing 59 | Each DSP module has its own set of tests, found under the `WECore` directory. The file 60 | `CMakeLists.txt` can be used to create executable binarys which run the tests. This is done as 61 | follows: 62 | 63 | export WECORE_HOME= 64 | export WECORE_SRC=$WECORE_HOME/WECore 65 | cd $WECORE_HOME 66 | mkdir build 67 | cd build 68 | cmake .. 69 | make 70 | 71 | This produces the binaries: 72 | * `WECoreTest` - Contains the standard set of unit tests 73 | * `WECoreTestPerf` - Contains unit tests for measuring performance 74 | -------------------------------------------------------------------------------- /Scripts/BuildAndTest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | GENERATE_COVERAGE=false 6 | 7 | if [ "$CXX" = "/usr/bin/g++-10" ]; then 8 | GENERATE_COVERAGE=true 9 | fi 10 | 11 | echo "=== Compiler is $CXX ===" 12 | echo "=== Setting GENERATE_COVERAGE: $GENERATE_COVERAGE ===" 13 | 14 | if [ $GENERATE_COVERAGE = true ]; then 15 | update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-10 90 16 | fi 17 | 18 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null 2>&1 && pwd )" 19 | export WECORE_HOME="$(cd $SCRIPT_DIR/.. > /dev/null 2>&1 && pwd)" 20 | export WECORE_SRC="$WECORE_HOME/WECore" 21 | 22 | echo "=== Starting build ===" 23 | 24 | cd $WECORE_HOME 25 | mkdir -p build && cd build 26 | cmake .. && make 27 | 28 | echo "=== Starting tests ===" 29 | valgrind --tool=callgrind ./WECoreTest 30 | 31 | if [ $GENERATE_COVERAGE = true ]; then 32 | echo "=== Generating coverage ===" 33 | $WECORE_HOME/Scripts/get_code_cov.sh; 34 | 35 | echo "=== Uploading coverage report ===" 36 | curl -Os https://uploader.codecov.io/latest/codecov-linux 37 | chmod +x codecov-linux 38 | ./codecov-linux 39 | fi 40 | 41 | echo "=== Renaming callgrind output ===" 42 | mv callgrind.out.* callgrind.out.$(git log --pretty=format:'%h' -n 1) 43 | -------------------------------------------------------------------------------- /Scripts/StaticAnalysis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null 2>&1 && pwd )" 6 | export WECORE_HOME="$(cd $SCRIPT_DIR/.. > /dev/null 2>&1 && pwd)" 7 | export WECORE_SRC="$WECORE_HOME/WECore" 8 | 9 | cd $WECORE_HOME 10 | 11 | echo "=== Running clang-tidy ===" 12 | clang-tidy -header-filter=.*WECore.* \ 13 | -checks=bugprone-*,clang-analyzer-*,-clang-diagnostic-c++17-extensions,modernize-*,-modernize-use-trailing-return-type,performance-* \ 14 | $(find $WECORE_SRC -name *.cpp) -- \ 15 | -I$WECORE_SRC \ 16 | -I$CATCH_PATH \ 17 | -I$WECORE_HOME/DSPFilters/shared/DSPFilters/include > clang-tidy.txt 18 | 19 | NUM_CLANG_TIDY_WARNINGS=$(grep "warning:" clang-tidy.txt | wc -l) 20 | 21 | echo "=== clang-tidy warnings: $NUM_CLANG_TIDY_WARNINGS ===" 22 | 23 | if [ $NUM_CLANG_TIDY_WARNINGS != "0" ]; then 24 | exit 1 25 | fi 26 | -------------------------------------------------------------------------------- /Scripts/get_code_cov.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd ${WECORE_SRC} 3 | for filename in `find . | egrep '\.cpp'`; 4 | do 5 | gcov-10 -o ../build/CMakeFiles/WECoreTest.dir/WECore/$filename.o $filename > /dev/null 6 | done 7 | -------------------------------------------------------------------------------- /WECore/CarveDSP/CarveNoiseFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CarveNoiseFilter.h 3 | * 4 | * Version: 2.0.0 5 | * 6 | * Created: 02/06/2016 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "WEFilters/TPTSVFilter.h" 28 | 29 | /** 30 | * A simple bandpass filter which can process mono or stereo signals. 31 | * Was initially created to remove frequencies at the extremes of the human 32 | * hearing range to clean up audio but can fulfil any typical bandpass 33 | * filter purpose. 34 | * 35 | * The cutoff frequencies cannot be changed once the object is constructed. 36 | * 37 | * Has methods for processing either a mono or stereo buffer of samples. 38 | * 39 | * @see setSampleRate - recommended to call before performing any processing 40 | */ 41 | namespace WECore::Carve { 42 | 43 | template 44 | class NoiseFilter { 45 | public: 46 | 47 | /** 48 | * Defaults the sample rate. It is recommended to call setSampleRate manually 49 | * before attempting any processing. 50 | * 51 | * @param lowCutHz Everything below this frequency will be cut 52 | * @param highCutHz Everything above this frequency will be cut 53 | */ 54 | NoiseFilter(double lowCutHz, double highCutHz); 55 | 56 | virtual ~NoiseFilter() {} 57 | 58 | /** 59 | * Configures the filters for the correct sample rate. Ensure this is 60 | * called before attempting to process audio. 61 | * 62 | * @param sampleRate The sample rate the filter should be configured for 63 | */ 64 | inline void setSampleRate(double sampleRate); 65 | 66 | /** 67 | * Resets all filters. 68 | * Call this whenever the audio stream is interrupted (ie. the playhead is moved) 69 | */ 70 | inline void reset(); 71 | 72 | /** 73 | * Applies the filtering to a mono buffer of samples. 74 | * Expect seg faults or other memory issues if arguements passed are incorrect. 75 | * 76 | * @param inSample Pointer to the first sample of the buffer 77 | * @param numSamples Number of samples in the buffer 78 | */ 79 | inline void Process1in1out(T* inSample, size_t numSamples); 80 | 81 | /** 82 | * Applies the filtering to a stereo buffer of samples. 83 | * Expect seg faults or other memory issues if arguements passed are incorrect. 84 | * 85 | * @param inLeftSample Pointer to the first sample of the left channel's buffer 86 | * @param inRightSample Pointer to the first sample of the right channel's buffer 87 | * @param numSamples Number of samples in the buffer. The left and right buffers 88 | * must be the same size. 89 | */ 90 | inline void Process2in2out(T *inLeftSample, T *inRightSample, size_t numSamples); 91 | 92 | private: 93 | WECore::TPTSVF::TPTSVFilter _monoLowCutFilter; 94 | WECore::TPTSVF::TPTSVFilter _leftLowCutFilter; 95 | WECore::TPTSVF::TPTSVFilter _rightLowCutFilter; 96 | 97 | WECore::TPTSVF::TPTSVFilter _monoHighCutFilter; 98 | WECore::TPTSVF::TPTSVFilter _leftHighCutFilter; 99 | WECore::TPTSVF::TPTSVFilter _rightHighCutFilter; 100 | 101 | double _lowCutHz, 102 | _highCutHz; 103 | }; 104 | 105 | template 106 | NoiseFilter::NoiseFilter(double lowCutHz, double highCutHz) : _lowCutHz(lowCutHz), 107 | _highCutHz(highCutHz) { 108 | setSampleRate(44100); 109 | 110 | auto setupLowCutFilter = [lowCutHz](TPTSVF::TPTSVFilter& filter) { 111 | filter.setMode(WECore::TPTSVF::Parameters::ModeParameter::HIGHPASS); 112 | filter.setCutoff(lowCutHz); 113 | filter.setQ(1); 114 | filter.setGain(1); 115 | }; 116 | 117 | auto setupHighCutFilter = [highCutHz](TPTSVF::TPTSVFilter& filter) { 118 | filter.setMode(WECore::TPTSVF::Parameters::ModeParameter::LOWPASS); 119 | filter.setCutoff(highCutHz); 120 | filter.setQ(1); 121 | filter.setGain(1); 122 | }; 123 | 124 | setupLowCutFilter(_monoLowCutFilter); 125 | setupLowCutFilter(_leftLowCutFilter); 126 | setupLowCutFilter(_rightLowCutFilter); 127 | 128 | setupHighCutFilter(_monoHighCutFilter); 129 | setupHighCutFilter(_leftHighCutFilter); 130 | setupHighCutFilter(_rightHighCutFilter); 131 | } 132 | 133 | template 134 | void NoiseFilter::setSampleRate(double sampleRate) { 135 | _monoLowCutFilter.setSampleRate(sampleRate); 136 | _leftLowCutFilter.setSampleRate(sampleRate); 137 | _rightLowCutFilter.setSampleRate(sampleRate); 138 | 139 | _monoHighCutFilter.setSampleRate(sampleRate); 140 | _leftHighCutFilter.setSampleRate(sampleRate); 141 | _rightHighCutFilter.setSampleRate(sampleRate); 142 | } 143 | 144 | template 145 | void NoiseFilter::reset() { 146 | _monoLowCutFilter.reset(); 147 | _leftLowCutFilter.reset(); 148 | _rightLowCutFilter.reset(); 149 | 150 | _monoHighCutFilter.reset(); 151 | _leftHighCutFilter.reset(); 152 | _rightHighCutFilter.reset(); 153 | } 154 | 155 | template 156 | void NoiseFilter::Process1in1out(T* inSample, size_t numSamples) { 157 | _monoLowCutFilter.processBlock(inSample, numSamples); 158 | _monoHighCutFilter.processBlock(inSample, numSamples); 159 | } 160 | 161 | template 162 | void NoiseFilter::Process2in2out(T *inLeftSample, T *inRightSample, size_t numSamples) { 163 | _leftLowCutFilter.processBlock(inLeftSample, numSamples); 164 | _leftHighCutFilter.processBlock(inLeftSample, numSamples); 165 | 166 | _rightLowCutFilter.processBlock(inRightSample, numSamples); 167 | _rightHighCutFilter.processBlock(inRightSample, numSamples); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /WECore/CarveDSP/CarveParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CarveParameters.h 3 | * 4 | * Created: 25/09/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | 26 | namespace WECore::Carve::Parameters { 27 | 28 | class ModeParameter : public ParameterDefinition::BaseParameter { 29 | public: 30 | 31 | ModeParameter() : ParameterDefinition::BaseParameter::BaseParameter( 32 | OFF, CLIPPER, SINE) { } 33 | 34 | static constexpr int OFF = 1, 35 | SINE = 2, 36 | PARABOLIC_SOFT = 3, 37 | PARABOLIC_HARD = 4, 38 | ASYMMETRIC_SINE = 5, 39 | EXPONENT = 6, 40 | CLIPPER = 7; 41 | }; 42 | 43 | const ModeParameter MODE; 44 | 45 | //@{ 46 | /** 47 | * A parameter which can take any float value between the ranges defined. 48 | * The values passed on construction are in the following order: 49 | * minimum value, 50 | * maximum value, 51 | * default value 52 | */ 53 | const ParameterDefinition::RangedParameter PREGAIN(0, 2, 1), 54 | POSTGAIN(0, 2, 0.5), 55 | TWEAK(0, 1, 0); 56 | //@} 57 | } 58 | -------------------------------------------------------------------------------- /WECore/CarveDSP/Tests/CarveNoiseFilterTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CarveNoiseFilterTests.cpp 3 | * 4 | * Created: 20/05/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "CarveDSP/CarveNoiseFilter.h" 24 | 25 | SCENARIO("CarveNoiseFilter: Silence in = silence out") { 26 | GIVEN("A CarveNoiseFilter and a buffer of silent samples") { 27 | std::vector buffer(1024); 28 | WECore::Carve::NoiseFilter mFilter(20, 20000); 29 | mFilter.setSampleRate(48000); 30 | 31 | WHEN("The silence samples are processed") { 32 | // fill the buffer 33 | std::fill(buffer.begin(), buffer.end(), 0); 34 | 35 | mFilter.Process1in1out(&buffer[0], buffer.size()); 36 | 37 | THEN("The output is silence") { 38 | for (size_t iii {0}; iii < buffer.size(); iii++) { 39 | CHECK(buffer[iii] == Approx(0.0f).margin(0.00001)); 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /WECore/CarveDSP/Tests/DSPUnitParameterTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CarveDSPUnitTests.cpp 3 | * 4 | * Created: 26/12/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "CarveDSP/CarveDSPUnit.h" 24 | 25 | SCENARIO("CarveDSPUnit: Parameters can be set and retrieved correctly") { 26 | GIVEN("A new CarveDSPUnit object") { 27 | WECore::Carve::CarveDSPUnit mCarve; 28 | 29 | WHEN("Nothing is changed") { 30 | THEN("Parameters have their default values") { 31 | CHECK(mCarve.getMode() == 2); 32 | CHECK(mCarve.getPreGain() == Approx(1.0)); 33 | CHECK(mCarve.getPostGain() == Approx(0.5)); 34 | CHECK(mCarve.getTweak() == Approx(0.0)); 35 | } 36 | } 37 | 38 | WHEN("All parameters are changed to unique values") { 39 | mCarve.setMode(2); 40 | mCarve.setPreGain(0.02); 41 | mCarve.setPostGain(0.03); 42 | mCarve.setTweak(0.04); 43 | 44 | THEN("They all get their correct unique values") { 45 | CHECK(mCarve.getMode() == 2); 46 | CHECK(mCarve.getPreGain() == Approx(0.02)); 47 | CHECK(mCarve.getPostGain() == Approx(0.03)); 48 | CHECK(mCarve.getTweak() == Approx(0.04)); 49 | } 50 | } 51 | } 52 | } 53 | 54 | SCENARIO("CarveDSPUnit: Parameters enforce their bounds correctly") { 55 | GIVEN("A new CarveDSPUnit object") { 56 | WECore::Carve::CarveDSPUnit mCarve; 57 | 58 | WHEN("All parameter values are too low") { 59 | mCarve.setMode(-5); 60 | mCarve.setPreGain(-5); 61 | mCarve.setPostGain(-5); 62 | mCarve.setTweak(-5); 63 | 64 | THEN("Parameters enforce their lower bounds") { 65 | CHECK(mCarve.getMode() == 1); 66 | CHECK(mCarve.getPreGain() == Approx(0.0)); 67 | CHECK(mCarve.getPostGain() == Approx(0.0)); 68 | CHECK(mCarve.getTweak() == Approx(0.0)); 69 | } 70 | } 71 | 72 | WHEN("All parameter values are too high") { 73 | mCarve.setMode(10); 74 | mCarve.setPreGain(5); 75 | mCarve.setPostGain(5); 76 | mCarve.setTweak(5); 77 | 78 | THEN("Parameters enforce their upper bounds") { 79 | CHECK(mCarve.getMode() == 7); 80 | CHECK(mCarve.getPreGain() == Approx(2.0)); 81 | CHECK(mCarve.getPostGain() == Approx(2.0)); 82 | CHECK(mCarve.getTweak() == Approx(1.0)); 83 | } 84 | } 85 | } 86 | } 87 | 88 | SCENARIO("CarveDSPUnit: Parameter combinations that should result in silence output for any input") { 89 | GIVEN("A new CarveDSPUnit object and a buffer of 0.5fs") { 90 | std::vector buffer(1024); 91 | WECore::Carve::CarveDSPUnit mCarve; 92 | 93 | WHEN("The unit is turned off") { 94 | // fill the buffer 95 | std::fill(buffer.begin(), buffer.end(), 0.5); 96 | 97 | // turn the unit off 98 | mCarve.setMode(1); 99 | 100 | // do processing 101 | for (size_t iii {0}; iii < buffer.size(); iii++) { 102 | buffer[iii] = mCarve.process(buffer[iii]); 103 | } 104 | 105 | THEN("The output is silence") { 106 | for (size_t iii {0}; iii < buffer.size(); iii++) { 107 | CHECK(buffer[iii] == Approx(0.0)); 108 | } 109 | } 110 | } 111 | 112 | WHEN("Unit is on but has 0 pregain") { 113 | // fill the buffer 114 | std::fill(buffer.begin(), buffer.end(), 0.5); 115 | 116 | // turn the unit on, set pregain 117 | mCarve.setMode(2); 118 | mCarve.setPreGain(0); 119 | 120 | // do processing 121 | for (size_t iii {0}; iii < buffer.size(); iii++) { 122 | buffer[iii] = mCarve.process(buffer[iii]); 123 | } 124 | 125 | THEN("The output is silence") { 126 | for (size_t iii {0}; iii < buffer.size(); iii++) { 127 | CHECK(buffer[iii] == Approx(0.0)); 128 | } 129 | } 130 | } 131 | 132 | WHEN("Unit is on but has 0 postgain") { 133 | // fill the buffer 134 | std::fill(buffer.begin(), buffer.end(), 0.5); 135 | 136 | // turn the unit on, set pregain and postgain 137 | mCarve.setMode(2); 138 | mCarve.setPreGain(1); 139 | mCarve.setPostGain(0); 140 | 141 | // do processing 142 | for (size_t iii {0}; iii < buffer.size(); iii++) { 143 | buffer[iii] = mCarve.process(buffer[iii]); 144 | } 145 | 146 | THEN("The output is silence") { 147 | for (size_t iii {0}; iii < buffer.size(); iii++) { 148 | CHECK(buffer[iii] == Approx(0.0)); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/CoreProcessorEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CoreProcessorEditor.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 18/03/2017 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | #include "CoreAudioProcessor.h" 29 | 30 | namespace WECore::JUCEPlugin { 31 | 32 | /** 33 | * This class provides basic functionality that is commonly used by an AudioProcessorEditor in a 34 | * White Elephant plugin. 35 | * 36 | * Classes inheriting from this should: 37 | * - Override _onParameterUpdate and call it in the constructor 38 | */ 39 | class CoreProcessorEditor : public juce::AudioProcessorEditor, 40 | public ParameterUpdateHandler { 41 | public: 42 | ~CoreProcessorEditor() { 43 | dynamic_cast(processor). 44 | removeParameterChangeListener(&_parameterListener); 45 | } 46 | 47 | protected: 48 | CoreProcessorEditor(CoreAudioProcessor& ownerFilter) 49 | : AudioProcessorEditor(ownerFilter) { 50 | dynamic_cast(processor). 51 | addParameterChangeListener(&_parameterListener); 52 | } 53 | 54 | /** 55 | * Sets the look and feel for all child components. 56 | * 57 | * Previously LookAndFeel::setDefaultLookAndFeel(&defaultLookAndFeel); was used but this 58 | * resulted in a bug where upon opening two instances of the same plugin simultaneously, when 59 | * one is closed the other will stop applying defaultLookAndFeel. 60 | */ 61 | void _assignLookAndFeelToAllChildren(juce::LookAndFeel& defaultLookAndFeel) { 62 | for (int iii {0}; iii < getNumChildComponents(); iii++) { 63 | getChildComponent(iii)->setLookAndFeel(&defaultLookAndFeel); 64 | } 65 | } 66 | 67 | /** 68 | * Sets the LookAndFeel for all child components to nullptr. 69 | * 70 | * Must be called before the LookAndFeel is deallocated, normally this is in the derived 71 | * editor's destructor. 72 | */ 73 | void _removeLookAndFeelFromAllChildren() { 74 | for (int iii {0}; iii < getNumChildComponents(); iii++) { 75 | getChildComponent(iii)->setLookAndFeel(nullptr); 76 | } 77 | } 78 | 79 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CoreProcessorEditor) 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/CustomParameter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CustomParameter.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 18/08/2021 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::JUCEPlugin { 30 | /** 31 | * Used to store state that shouldn't be exposed to the host, but would benefit from using the 32 | * save/restore and update mechanism used by conventional parameters. 33 | * 34 | * Derive from this class and add your own setter/getter methods as needed, just call 35 | * _updateListener() to trigger an update. 36 | * 37 | * Provide an implementation for restoreFromXml and writeToXml to enable saving and restoring 38 | * parameter state. 39 | */ 40 | class CustomParameter { 41 | public: 42 | inline CustomParameter(); 43 | virtual ~CustomParameter() = default; 44 | 45 | inline void setListener(juce::AudioProcessorParameter::Listener* listener); 46 | inline void removeListener(); 47 | 48 | virtual void restoreFromXml(juce::XmlElement* element) = 0; 49 | virtual void writeToXml(juce::XmlElement* element) = 0; 50 | 51 | protected: 52 | inline void _updateListener(); 53 | 54 | private: 55 | std::mutex _listenerMutex; 56 | juce::AudioProcessorParameter::Listener* _listener; 57 | }; 58 | 59 | CustomParameter::CustomParameter() : _listener(nullptr) { 60 | } 61 | 62 | void CustomParameter::setListener(juce::AudioProcessorParameter::Listener* listener) { 63 | std::scoped_lock lock(_listenerMutex); 64 | _listener = listener; 65 | } 66 | 67 | void CustomParameter::removeListener() { 68 | std::scoped_lock lock(_listenerMutex); 69 | _listener = nullptr; 70 | } 71 | 72 | void CustomParameter::_updateListener() { 73 | std::scoped_lock lock(_listenerMutex); 74 | if (_listener != nullptr) { 75 | _listener->parameterValueChanged(0, 0); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LabelReadoutSlider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LabelReadoutSlider.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 01/07/2020 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | #include "General/ParameterDefinition.h" 29 | 30 | namespace WECore::JUCEPlugin { 31 | 32 | /** 33 | * Handles mouse events that may indicate that the Slider value has changed. 34 | */ 35 | class SliderLabelUpdater : public juce::Slider { 36 | public: 37 | explicit SliderLabelUpdater(const juce::String& componentName) : Slider(componentName) {} 38 | virtual ~SliderLabelUpdater() = default; 39 | 40 | /** @name Mouse event handlers */ 41 | /** @{ */ 42 | virtual void mouseEnter(const juce::MouseEvent& event) override { 43 | Slider::mouseEnter(event); 44 | _updateLabel(event); 45 | } 46 | 47 | virtual void mouseMove(const juce::MouseEvent& event) override { 48 | Slider::mouseMove(event); 49 | _updateLabel(event); 50 | } 51 | 52 | virtual void mouseExit(const juce::MouseEvent& event) override { 53 | Slider::mouseExit(event); 54 | _resetLabel(); 55 | } 56 | 57 | virtual void mouseDoubleClick(const juce::MouseEvent& event) override { 58 | Slider::mouseDoubleClick(event); 59 | _updateLabel(event); 60 | } 61 | 62 | virtual void mouseDrag(const juce::MouseEvent& event) override { 63 | Slider::mouseDrag(event); 64 | _updateLabel(event); 65 | } 66 | 67 | virtual void mouseWheelMove(const juce::MouseEvent& event, 68 | const juce::MouseWheelDetails& wheel) override { 69 | Slider::mouseWheelMove(event, wheel); 70 | _updateLabel(event); 71 | } 72 | /** @} */ 73 | 74 | private: 75 | /** 76 | * Called when the Slider value may have changed and the Label(s) should be updated. 77 | */ 78 | virtual void _updateLabel(const juce::MouseEvent& event) = 0; 79 | 80 | /** 81 | * Called when the mouse is no longer over the Slider, so the Label(s) can be reset. 82 | */ 83 | virtual void _resetLabel() = 0; 84 | }; 85 | 86 | /** 87 | * Outputs the value of the Slider to a label while hovering over the slider. 88 | */ 89 | template 90 | class LabelReadoutSlider : public SliderLabelUpdater { 91 | public: 92 | explicit LabelReadoutSlider(const juce::String& componentName) : SliderLabelUpdater(componentName), 93 | _isRunning(false), 94 | _valueToString([](T value) { return juce::String(value, 2); }) {} 95 | 96 | virtual ~LabelReadoutSlider() = default; 97 | 98 | /** 99 | * Tells the slider to start writing to the given component on mouse enter events. 100 | * 101 | * Doesn't take ownership of the component. 102 | */ 103 | /** @{ */ 104 | inline void start(juce::Label* targetLabel, juce::String defaultText); 105 | inline void start(juce::TextButton* targetButton, juce::String defaultText); 106 | /** @} */ 107 | 108 | /** 109 | * Tells the slider to stop writing to the label. 110 | * 111 | * Call this in your destructor if the Label or RangedParameter might which we depend on 112 | * might be deallocated before LabelReadoutSlider. 113 | */ 114 | inline void stop(); 115 | 116 | void setValueToString(std::function valueToString) { _valueToString = valueToString; } 117 | 118 | protected: 119 | std::function _targetCallback; 120 | bool _isRunning; 121 | std::function _valueToString; 122 | 123 | private: 124 | juce::String _defaultText; 125 | 126 | static std::function _labelToCallback(juce::Label* label) { 127 | return [label](const juce::String& text) { label->setText(text, juce::dontSendNotification); }; 128 | } 129 | 130 | static std::function _textButtonToCallback(juce::TextButton* button) { 131 | return [button](const juce::String& text) { button->setButtonText(text); }; 132 | } 133 | 134 | inline virtual void _updateLabel(const juce::MouseEvent& event) override; 135 | 136 | inline virtual void _resetLabel() override; 137 | }; 138 | 139 | template 140 | void LabelReadoutSlider::start(juce::Label* targetLabel, juce::String defaultText) { 141 | _targetCallback = _labelToCallback(targetLabel); 142 | _defaultText = defaultText; 143 | _isRunning = true; 144 | } 145 | 146 | template 147 | void LabelReadoutSlider::start(juce::TextButton* targetButton, juce::String defaultText) { 148 | _targetCallback = _textButtonToCallback(targetButton); 149 | _defaultText = defaultText; 150 | _isRunning = true; 151 | } 152 | 153 | template 154 | void LabelReadoutSlider::stop() { 155 | _isRunning = false; 156 | } 157 | 158 | template 159 | void LabelReadoutSlider::_updateLabel(const juce::MouseEvent& /*event*/) { 160 | if (_isRunning) { 161 | _targetCallback(_valueToString(getValue())); 162 | } 163 | } 164 | 165 | template 166 | void LabelReadoutSlider::_resetLabel() { 167 | if (_isRunning) { 168 | _targetCallback(_defaultText); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/ButtonV2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ButtonV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 19/03/2019 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::LookAndFeelMixins { 30 | 31 | /** 32 | * V2 (December 2018) style lookandfeel button mixin. 33 | * 34 | * Uses the following colours: 35 | * -# ** TextButton::buttonOnColourId ** 36 | * -# ** TextButton::buttonColourId ** 37 | * -# ** TextButton::textColourOnId ** 38 | * -# ** TextButton::textColourOffId ** 39 | */ 40 | template 41 | class ButtonV2 : public BASE { 42 | 43 | public: 44 | ButtonV2() = default; 45 | virtual ~ButtonV2() = default; 46 | 47 | /** @{ LookAndFeel overrides */ 48 | inline virtual void drawButtonBackground(juce::Graphics& g, 49 | juce::Button& button, 50 | const juce::Colour& backgroundColour, 51 | bool isMouseOverButton, 52 | bool isButtonDown) override; 53 | 54 | inline virtual void drawButtonText(juce::Graphics& g, 55 | juce::TextButton& textButton, 56 | bool isMouseOverButton, 57 | bool isButtonDown) override; 58 | /** @} */ 59 | 60 | private: 61 | static constexpr float _disabledDarker {0.7f}; 62 | }; 63 | 64 | template 65 | void ButtonV2::drawButtonBackground(juce::Graphics& g, 66 | juce::Button& button, 67 | const juce::Colour& /*backgroundColour*/, 68 | bool /*isMouseOverButton*/, 69 | bool /*isButtonDown*/) { 70 | const int width {button.getWidth()}; 71 | const int height {button.getHeight()}; 72 | 73 | constexpr float indent {2.0f}; 74 | const int cornerSize {juce::jmin(juce::roundToInt(width * 0.4f), 75 | juce::roundToInt(height * 0.4f))}; 76 | 77 | juce::Path p; 78 | juce::PathStrokeType pStroke(1); 79 | 80 | if (button.isEnabled()) { 81 | if (button.getToggleState()) { 82 | g.setColour(button.findColour(juce::TextButton::buttonOnColourId)); 83 | } else { 84 | g.setColour(button.findColour(juce::TextButton::buttonColourId)); 85 | } 86 | } else { 87 | g.setColour(button.findColour(juce::TextButton::buttonColourId).darker(_disabledDarker)); 88 | } 89 | 90 | p.addRoundedRectangle(indent, 91 | indent, 92 | width - 2 * indent, 93 | height - 2 * indent, 94 | static_cast(cornerSize)); 95 | 96 | g.strokePath(p, pStroke); 97 | } 98 | 99 | template 100 | void ButtonV2::drawButtonText(juce::Graphics& g, 101 | juce::TextButton& textButton, 102 | bool /*isMouseOverButton*/, 103 | bool /*isButtonDown*/) { 104 | if (textButton.isEnabled()) { 105 | if (textButton.getToggleState()) { 106 | g.setColour(textButton.findColour(juce::TextButton::textColourOnId)); 107 | } else { 108 | g.setColour(textButton.findColour(juce::TextButton::textColourOffId)); 109 | } 110 | } else { 111 | g.setColour(textButton.findColour(juce::TextButton::textColourOffId).darker(_disabledDarker)); 112 | } 113 | 114 | constexpr int MARGIN {0}; 115 | 116 | juce::Font font; 117 | font.setTypefaceName(BASE::getTypefaceForFont(font)->getName()); 118 | g.setFont(font); 119 | 120 | g.drawFittedText(textButton.getButtonText(), 121 | MARGIN, 122 | 0, 123 | textButton.getWidth() - 2 * MARGIN, 124 | textButton.getHeight(), 125 | juce::Justification::centred, 126 | 0); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/ComboBoxV2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ComboBoxV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 19/03/2019 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::LookAndFeelMixins { 30 | 31 | /** 32 | * V2 (December 2018) style lookandfeel combo box mixin. 33 | * 34 | * Uses the following colours: 35 | * -# ** ComboBox::arrowColourId ** 36 | */ 37 | template 38 | class ComboBoxV2 : public BASE { 39 | 40 | public: 41 | ComboBoxV2() = default; 42 | virtual ~ComboBoxV2() = default; 43 | 44 | /** @{ LookAndFeel overrides */ 45 | inline virtual void drawComboBox(juce::Graphics& g, 46 | int width, 47 | int height, 48 | const bool isButtonDown, 49 | int buttonX, 50 | int buttonY, 51 | int buttonW, 52 | int buttonH, 53 | juce::ComboBox& box) override; 54 | /** @} */ 55 | }; 56 | 57 | template 58 | void ComboBoxV2::drawComboBox(juce::Graphics& g, 59 | int /*width*/, 60 | int /*height*/, 61 | const bool /*isButtonDown*/, 62 | int buttonX, 63 | int buttonY, 64 | int buttonW, 65 | int buttonH, 66 | juce::ComboBox& box) { 67 | 68 | g.setColour(box.findColour(juce::ComboBox::arrowColourId)); 69 | 70 | juce::Path p; 71 | constexpr float LINE_WIDTH {0.5}; 72 | const int arrowMarginX {buttonY / 4}; 73 | const int arrowMarginY {buttonH / 3}; 74 | const int arrowTipX {buttonX + (buttonW / 2)}; 75 | const int arrowTipY {buttonY + buttonH - arrowMarginY}; 76 | 77 | // Left side of arrow 78 | p.addLineSegment(juce::Line(buttonX + arrowMarginX, 79 | buttonY + arrowMarginY, 80 | arrowTipX, 81 | arrowTipY), 82 | LINE_WIDTH); 83 | 84 | // Right side of arrow 85 | p.addLineSegment(juce::Line(buttonX + buttonW - arrowMarginX, 86 | buttonY + arrowMarginY, 87 | arrowTipX, 88 | arrowTipY), 89 | LINE_WIDTH); 90 | 91 | g.strokePath(p, juce::PathStrokeType(1)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/GroupComponentV2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: GroupComponentV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 19/03/2019 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::LookAndFeelMixins { 30 | 31 | /** 32 | * V2 (December 2018) style lookandfeel group mixin. 33 | * 34 | * Uses the following colours: 35 | * -# ** GroupComponent::textColourId ** 36 | */ 37 | template 38 | class GroupComponentV2 : public BASE { 39 | 40 | public: 41 | GroupComponentV2() : _groupFontName("Courier New") {} 42 | virtual ~GroupComponentV2() = default; 43 | 44 | /** @{ LookAndFeel overrides */ 45 | inline virtual void drawGroupComponentOutline(juce::Graphics& g, 46 | int width, 47 | int height, 48 | const juce::String& text, 49 | const juce::Justification& justification, 50 | juce::GroupComponent& group) override; 51 | 52 | /** @} */ 53 | 54 | void setGroupFontName(const char* fontName) { _groupFontName = fontName; } 55 | 56 | private: 57 | const char* _groupFontName; 58 | }; 59 | 60 | template 61 | void GroupComponentV2::drawGroupComponentOutline(juce::Graphics& g, 62 | int width, 63 | int height, 64 | const juce::String& text, 65 | const juce::Justification& /*justification*/, 66 | juce::GroupComponent& group) { 67 | 68 | constexpr int MARGIN {2}; 69 | 70 | g.setColour(group.findColour(juce::GroupComponent::textColourId)); 71 | 72 | juce::Font font; 73 | font.setTypefaceName(_groupFontName); 74 | g.setFont(font); 75 | 76 | g.drawText(text, 77 | MARGIN, 78 | MARGIN, 79 | width, 80 | height, 81 | juce::Justification::topLeft, 82 | true); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/LinearSliderV2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LinearSliderV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 04/07/2021 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::LookAndFeelMixins { 30 | 31 | /** 32 | * V2 (December 2018) style lookandfeel button mixin. 33 | * 34 | * Uses the following colours: 35 | * -# ** Slider::backgroundColourId ** 36 | * -# ** Slider::thumbColourId ** 37 | * -# ** Slider::trackColourId ** 38 | */ 39 | template 40 | class LinearSliderV2 : public BASE { 41 | public: 42 | LinearSliderV2() = default; 43 | virtual ~LinearSliderV2() = default; 44 | 45 | /** @{ LookAndFeel overrides */ 46 | inline virtual void drawLinearSlider(juce::Graphics& g, 47 | int x, 48 | int y, 49 | int width, 50 | int height, 51 | float sliderPos, 52 | float minSliderPos, 53 | float maxSliderPos, 54 | const juce::Slider::SliderStyle style, 55 | juce::Slider& slider) override; 56 | 57 | inline virtual void drawLinearSliderThumb(juce::Graphics& g, 58 | int x, 59 | int y, 60 | int width, 61 | int height, 62 | float sliderPos, 63 | float minSliderPos, 64 | float maxSliderPos, 65 | const juce::Slider::SliderStyle style, 66 | juce::Slider& slider) override; 67 | 68 | inline virtual void drawLinearSliderBackground(juce::Graphics& g, 69 | int x, 70 | int y, 71 | int width, 72 | int height, 73 | float sliderPos, 74 | float minSliderPos, 75 | float maxSliderPos, 76 | const juce::Slider::SliderStyle style, 77 | juce::Slider& slider) override; 78 | /** @} */ 79 | }; 80 | 81 | template 82 | void LinearSliderV2::drawLinearSlider(juce::Graphics& g, 83 | int x, 84 | int y, 85 | int width, 86 | int height, 87 | float sliderPos, 88 | float minSliderPos, 89 | float maxSliderPos, 90 | const juce::Slider::SliderStyle style, 91 | juce::Slider& slider) { 92 | // Draw background first 93 | drawLinearSliderBackground(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); 94 | drawLinearSliderThumb(g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider); 95 | } 96 | 97 | template 98 | void LinearSliderV2::drawLinearSliderThumb(juce::Graphics& g, 99 | int x, 100 | int y, 101 | int width, 102 | int height, 103 | float sliderPos, 104 | float minSliderPos, 105 | float /*maxSliderPos*/, 106 | const juce::Slider::SliderStyle style, 107 | juce::Slider& slider) { 108 | 109 | constexpr float MARGIN {2}; 110 | 111 | if (slider.isEnabled()) { 112 | g.setColour(slider.findColour(juce::Slider::thumbColourId)); 113 | } else { 114 | g.setColour(slider.findColour(juce::Slider::backgroundColourId)); 115 | } 116 | 117 | if (style == juce::Slider::LinearHorizontal) { 118 | // Horizontal rectangle 119 | g.fillRect(juce::Rectangle(minSliderPos, 120 | y + MARGIN, 121 | sliderPos - minSliderPos, 122 | height - 3 * MARGIN)); 123 | } else { 124 | // Vertical rectangle 125 | g.fillRect(juce::Rectangle(x + MARGIN, 126 | y + height, 127 | width - 3 * MARGIN, 128 | -(y + height - sliderPos))); 129 | } 130 | } 131 | 132 | template 133 | void LinearSliderV2::drawLinearSliderBackground(juce::Graphics& g, 134 | int x, 135 | int y, 136 | int width, 137 | int height, 138 | float /*sliderPos*/, 139 | float /*minSliderPos*/, 140 | float /*maxSliderPos*/, 141 | const juce::Slider::SliderStyle style, 142 | juce::Slider& slider) { 143 | 144 | constexpr int MARGIN {2}; 145 | constexpr int LINE_WIDTH {1}; 146 | 147 | if (slider.isEnabled()) { 148 | g.setColour(slider.findColour(juce::Slider::trackColourId)); 149 | } else { 150 | g.setColour(slider.findColour(juce::Slider::backgroundColourId)); 151 | } 152 | 153 | if (style == juce::Slider::LinearHorizontal) { 154 | // Horizontal line 155 | g.fillRect(juce::Rectangle(x, 156 | y + height - MARGIN - (LINE_WIDTH / 2), 157 | width, 158 | LINE_WIDTH)); 159 | } else { 160 | // Vertical line 161 | g.fillRect(juce::Rectangle(x + width - MARGIN - (LINE_WIDTH / 2), 162 | y, 163 | LINE_WIDTH, 164 | height)); 165 | 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/LookAndFeelMixins.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: LookAndFeelMixinsV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 19/03/2019 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "ButtonV2.h" 28 | #include "ComboBoxV2.h" 29 | #include "GroupComponentV2.h" 30 | #include "LinearSliderV2.h" 31 | #include "MidAnchoredRotarySlider.h" 32 | #include "PopupMenuV2.h" 33 | #include "RotarySliderV2.h" 34 | #include "CoreJUCEPlugin/CoreLookAndFeel.h" 35 | 36 | /** 37 | * Mixin LookAndFeel classes that can be used to augment any existing class derived from 38 | * juce::LookAndFeel. 39 | * 40 | * Mixin classes have been provided rather than a single LookAndFeel as it allows different 41 | * LookAndFeels for different components to be mixed and overriden separately. Each mixin class 42 | * handles all the drawing for a particular component. 43 | * 44 | * @section Example Usage 45 | * 46 | * To create a LookAndFeel class which uses juce::LookAndFeel_V4 but with WECore's buttons: 47 | * 48 | * @code 49 | * #include "CoreJUCEPlugin/LookAndFeelMixins/LookAndFeelMixins.h" 50 | * 51 | * using WECore::LookAndFeelMixins; 52 | * typedef ButtonV2 WECoreButtonsLookAndFeel; 53 | * 54 | * WECoreButtonsLookAndFeel _myLookAndFeelInstance; 55 | * @endcode 56 | * 57 | * To create a LookAndFeel class which uses juce::LookAndFeel_V4 but with WECore's buttons *and* 58 | * combo boxes: 59 | * 60 | * @code 61 | * #include "CoreJUCEPlugin/LookAndFeelMixins/LookAndFeelMixins.h" 62 | * 63 | * using WECore::LookAndFeelMixins; 64 | * typedef ButtonV2> WECoreComboLookAndFeel; 65 | * 66 | * WECoreComboLookAndFeel _myLookAndFeelInstance; 67 | * @endcode 68 | * 69 | * You can then futher customise these typedef'd LookAndFeel classes in the same way you would 70 | * customise a normal JUCE LookAndFeelClass: 71 | * 72 | * @code 73 | * #include "CoreJUCEPlugin/LookAndFeelMixins/LookAndFeelMixins.h" 74 | * 75 | * using WECore::LookAndFeelMixins; 76 | * typedef ButtonV2> WECoreComboLookAndFeel; 77 | * 78 | * class MyCustomLookAndFeel : public WECoreComboLookAndFeel { 79 | * ... custom overrides ... 80 | * } 81 | * @endcode 82 | * 83 | * or, if you already have a custom LookAndFeel which you'd like to augment with the mixin classes, 84 | * you can do that too: 85 | * 86 | * @code 87 | * #include "CoreJUCEPlugin/LookAndFeelMixins/LookAndFeelMixins.h" 88 | * 89 | * using WECore::LookAndFeelMixins; 90 | * typedef ButtonV2> NewCustomLookAndFeel; 91 | * 92 | * NewCustomLookAndFeel _myLookAndFeelInstance; 93 | * @endcode 94 | */ 95 | namespace WECore::LookAndFeelMixins { 96 | /** 97 | * typedef'd all V2 mixins over the CoreLookAndFeel for convenience. 98 | */ 99 | typedef ButtonV2< 100 | ComboBoxV2< 101 | GroupComponentV2< 102 | LinearSliderV2< 103 | PopupMenuV2< 104 | RotarySliderV2< 105 | WECore::JUCEPlugin::CoreLookAndFeel 106 | >>>>>> WEV2LookAndFeel; 107 | } 108 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/MidAnchoredRotarySlider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: MidAnchoredRotarySlider.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 21/11/2020 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | #include "General/CoreMath.h" 29 | 30 | namespace WECore::LookAndFeelMixins { 31 | 32 | /** 33 | * Based on RotarySliderV2, but fills the right side of the slider when above half of the 34 | * slider's value, and fills the left half when below. 35 | * 36 | * Uses the following colours: 37 | * -# ** Slider::rotarySliderFillColourId ** 38 | * -# ** Slider::rotarySliderOutlineColourId ** 39 | */ 40 | template 41 | class MidAnchoredRotarySlider : public BASE { 42 | 43 | public: 44 | MidAnchoredRotarySlider() = default; 45 | virtual ~MidAnchoredRotarySlider() = default; 46 | 47 | /** @{ LookAndFeel overrides */ 48 | inline virtual void drawRotarySlider(juce::Graphics& g, 49 | int x, 50 | int y, 51 | int width, 52 | int height, 53 | float sliderPosProportional, 54 | float rotaryStartAngle, 55 | float rotaryEndAngle, 56 | juce::Slider &slider) override; 57 | /** @} */ 58 | }; 59 | 60 | template 61 | void MidAnchoredRotarySlider::drawRotarySlider(juce::Graphics& g, 62 | int /*x*/, 63 | int /*y*/, 64 | int width, 65 | int height, 66 | float /*sliderPosProportional*/, 67 | float /*rotaryStartAngle*/, 68 | float /*rotaryEndAngle*/, 69 | juce::Slider &slider) { 70 | 71 | // Calculate useful constants 72 | constexpr double arcGap {CoreMath::DOUBLE_TAU / 4}; 73 | constexpr double rangeOfMotion {CoreMath::DOUBLE_TAU - arcGap}; 74 | 75 | const double sliderNormalisedValue {(slider.getValue() - slider.getMinimum()) / 76 | (slider.getMaximum() - slider.getMinimum())}; 77 | 78 | double arcStartPoint {0}; 79 | double arcEndPoint {0}; 80 | if (sliderNormalisedValue > 0.5) { 81 | arcStartPoint = CoreMath::DOUBLE_PI; 82 | arcEndPoint = CoreMath::DOUBLE_PI + (sliderNormalisedValue - 0.5) * rangeOfMotion; 83 | } else { 84 | arcStartPoint = CoreMath::DOUBLE_PI + (sliderNormalisedValue - 0.5) * rangeOfMotion; 85 | arcEndPoint = CoreMath::DOUBLE_PI; 86 | } 87 | 88 | constexpr int margin {2}; 89 | juce::Rectangle area = slider.getBounds(); 90 | area.reduce(margin, margin); 91 | const int diameter {std::min(area.getWidth(), area.getHeight())}; 92 | 93 | if (slider.isEnabled()) { 94 | g.setColour(slider.findColour(juce::Slider::rotarySliderFillColourId)); 95 | } else { 96 | g.setColour(slider.findColour(juce::Slider::rotarySliderOutlineColourId)); 97 | } 98 | 99 | juce::Path p; 100 | 101 | // Draw inner ring 102 | constexpr int arcSpacing {3}; 103 | p.addCentredArc(width / 2, 104 | height / 2, 105 | diameter / 2 - arcSpacing, 106 | diameter / 2 - arcSpacing, 107 | CoreMath::DOUBLE_PI, 108 | arcGap / 2, 109 | CoreMath::DOUBLE_TAU - (arcGap / 2), 110 | true); 111 | 112 | g.strokePath(p, juce::PathStrokeType(0.7f)); 113 | 114 | // Draw outer ring 115 | p.clear(); 116 | p.addCentredArc(width / 2, 117 | height / 2, 118 | diameter / 2, 119 | diameter / 2, 120 | CoreMath::DOUBLE_PI, 121 | arcStartPoint, 122 | arcEndPoint, 123 | true); 124 | g.strokePath(p, juce::PathStrokeType(3.0f)); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/PopupMenuV2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: PopupMenuV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 19/03/2019 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::LookAndFeelMixins { 30 | 31 | /** 32 | * V2 (December 2018) style lookandfeel popup ment mixin. 33 | * 34 | * Uses the following colours: 35 | * -# ** PopupMenu::highlightedBackgroundColourId ** 36 | * -# ** PopupMenu::highlightedTextColourId ** 37 | * -# ** PopupMenu::textColourId ** 38 | */ 39 | template 40 | class PopupMenuV2 : public BASE { 41 | 42 | public: 43 | PopupMenuV2() : _popupMenuFontName("Courier New") {} 44 | virtual ~PopupMenuV2() = default; 45 | 46 | /** @{ LookAndFeel overrides */ 47 | inline virtual void drawPopupMenuItem(juce::Graphics& g, 48 | const juce::Rectangle& area, 49 | bool isSeparator, 50 | bool isActive, 51 | bool isHighlighted, 52 | bool isTicked, 53 | bool hasSubMenu, 54 | const juce::String& text, 55 | const juce::String& shortcutKeyText, 56 | const juce::Drawable* icon, 57 | const juce::Colour* textColour) override; 58 | 59 | inline virtual juce::Font getPopupMenuFont() override; 60 | /** @} */ 61 | 62 | void setPopupMenuFontName(const char* fontName) { _popupMenuFontName = fontName; } 63 | 64 | private: 65 | const char* _popupMenuFontName; 66 | }; 67 | 68 | template 69 | void PopupMenuV2::drawPopupMenuItem(juce::Graphics& g, 70 | const juce::Rectangle& area, 71 | bool /*isSeparator*/, 72 | bool /*isActive*/, 73 | bool isHighlighted, 74 | bool isTicked, 75 | bool /*hasSubMenu*/, 76 | const juce::String& text, 77 | const juce::String& /*shortcutKeyText*/, 78 | const juce::Drawable* /*icon*/, 79 | const juce::Colour* /*textColour*/) { 80 | 81 | juce::Rectangle r = area.reduced(1); 82 | 83 | if (isHighlighted) { 84 | g.setColour(BASE::findColour(juce::PopupMenu::highlightedBackgroundColourId)); 85 | g.fillRect(r); 86 | 87 | g.setColour(BASE::findColour(juce::PopupMenu::highlightedTextColourId)); 88 | } else if (isTicked) { 89 | g.setColour(BASE::findColour(juce::PopupMenu::highlightedBackgroundColourId).withAlpha(0.2f)); 90 | g.fillRect(r); 91 | 92 | g.setColour(BASE::findColour(juce::PopupMenu::textColourId)); 93 | } else { 94 | g.setColour(BASE::findColour(juce::PopupMenu::textColourId)); 95 | } 96 | 97 | juce::Font font(getPopupMenuFont()); 98 | 99 | const float maxFontHeight {area.getHeight() / 1.3f}; 100 | 101 | if (font.getHeight() > maxFontHeight) { 102 | font.setHeight(maxFontHeight); 103 | } 104 | 105 | g.setFont(font); 106 | 107 | r.removeFromLeft(3); 108 | g.drawFittedText(text, r, juce::Justification::centredLeft, 1); 109 | } 110 | 111 | template 112 | juce::Font PopupMenuV2::getPopupMenuFont() { 113 | juce::Font comboFont; 114 | comboFont.setTypefaceName(_popupMenuFontName); 115 | return comboFont; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/LookAndFeelMixins/RotarySliderV2.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: RotarySliderV2.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 19/03/2019 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::LookAndFeelMixins { 30 | 31 | /** 32 | * V2 (December 2018) style lookandfeel button mixin. 33 | * 34 | * Uses the following colours: 35 | * -# ** Slider::rotarySliderFillColourId ** 36 | * -# ** Slider::rotarySliderOutlineColourId ** 37 | */ 38 | template 39 | class RotarySliderV2 : public BASE { 40 | 41 | public: 42 | RotarySliderV2() = default; 43 | virtual ~RotarySliderV2() = default; 44 | 45 | /** @{ LookAndFeel overrides */ 46 | inline virtual void drawRotarySlider(juce::Graphics& g, 47 | int x, 48 | int y, 49 | int width, 50 | int height, 51 | float sliderPosProportional, 52 | float rotaryStartAngle, 53 | float rotaryEndAngle, 54 | juce::Slider &slider) override; 55 | /** @} */ 56 | }; 57 | 58 | template 59 | void RotarySliderV2::drawRotarySlider(juce::Graphics& g, 60 | int /*x*/, 61 | int /*y*/, 62 | int width, 63 | int height, 64 | float /*sliderPosProportional*/, 65 | float /*rotaryStartAngle*/, 66 | float /*rotaryEndAngle*/, 67 | juce::Slider &slider) { 68 | 69 | // Calculate useful constants 70 | constexpr double arcGap {CoreMath::DOUBLE_TAU / 4}; 71 | constexpr double rangeOfMotion {CoreMath::DOUBLE_TAU - arcGap}; 72 | 73 | const double sliderNormalisedValue {slider.valueToProportionOfLength(slider.getValue())}; 74 | const double arcEndPoint {sliderNormalisedValue * rangeOfMotion + arcGap / 2}; 75 | 76 | constexpr int margin {2}; 77 | juce::Rectangle area = slider.getBounds(); 78 | area.reduce(margin, margin); 79 | const int diameter {std::min(area.getWidth(), area.getHeight())}; 80 | 81 | if (slider.isEnabled()) { 82 | g.setColour(slider.findColour(juce::Slider::rotarySliderFillColourId)); 83 | } else { 84 | g.setColour(slider.findColour(juce::Slider::rotarySliderOutlineColourId)); 85 | } 86 | 87 | juce::Path p; 88 | 89 | // Draw inner ring 90 | constexpr int arcSpacing {3}; 91 | p.addCentredArc(width / 2, 92 | height / 2, 93 | diameter / 2 - arcSpacing, 94 | diameter / 2 - arcSpacing, 95 | CoreMath::DOUBLE_PI, 96 | arcGap / 2, 97 | CoreMath::DOUBLE_TAU - (arcGap / 2), 98 | true); 99 | 100 | g.strokePath(p, juce::PathStrokeType(0.7f)); 101 | 102 | // Draw outer ring 103 | p.clear(); 104 | p.addCentredArc(width / 2, 105 | height / 2, 106 | diameter / 2, 107 | diameter / 2, 108 | CoreMath::DOUBLE_PI, 109 | arcGap / 2, 110 | arcEndPoint, 111 | true); 112 | g.strokePath(p, juce::PathStrokeType(3.0f)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/ParameterUpdateHandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CoreProcessorEditor.h 3 | * 4 | * Created: 24/03/2021 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | * 21 | */ 22 | 23 | #pragma once 24 | 25 | #include "../JuceLibraryCode/JuceHeader.h" 26 | #include "CoreAudioProcessor.h" 27 | 28 | namespace WECore::JUCEPlugin { 29 | 30 | class ParameterUpdateHandler { 31 | 32 | public: 33 | ParameterUpdateHandler() : _parameterListener(this) { } 34 | virtual ~ParameterUpdateHandler() = default; 35 | 36 | protected: 37 | /** 38 | * Is notified when a parameter has changed and calls _onParameterUpdate. 39 | */ 40 | class ParameterUpdateListener : public juce::ChangeListener { 41 | public: 42 | ParameterUpdateListener(ParameterUpdateHandler* parent) : _parent(parent) {}; 43 | virtual ~ParameterUpdateListener() = default; 44 | 45 | virtual void changeListenerCallback(juce::ChangeBroadcaster* /*source*/) { 46 | _parent->_onParameterUpdate(); 47 | } 48 | 49 | private: 50 | ParameterUpdateHandler* _parent; 51 | }; 52 | 53 | ParameterUpdateListener _parameterListener; 54 | 55 | /** 56 | * This will be called whenever a parameter has been updated. 57 | */ 58 | virtual void _onParameterUpdate() = 0; 59 | 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /WECore/CoreJUCEPlugin/TooltipLabelUpdater.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: TooltipLabelUpdater.h 3 | * 4 | * Version: 1.0.0 5 | * 6 | * Created: 06/06/2021 7 | * 8 | * This file is part of WECore. 9 | * 10 | * WECore is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * WECore is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with WECore. If not, see . 22 | * 23 | */ 24 | 25 | #pragma once 26 | 27 | #include "../JuceLibraryCode/JuceHeader.h" 28 | 29 | namespace WECore::JUCEPlugin { 30 | 31 | /** 32 | * Acts as a MouseListener for multiple components, setting the given Label to display their 33 | * tooltip. 34 | */ 35 | class TooltipLabelUpdater : public juce::MouseListener { 36 | public: 37 | inline TooltipLabelUpdater(); 38 | ~TooltipLabelUpdater() = default; 39 | 40 | /** 41 | * Starts updating the label as necessary displaying an empty string when not showing a 42 | * tooltip. 43 | */ 44 | inline void start(juce::Label* targetLabel); 45 | 46 | /** 47 | * Starts updating the label as necessary, displaying build information when not showing a 48 | * tooltip. 49 | */ 50 | inline void start(juce::Label* targetLabel, juce::AudioProcessor::WrapperType pluginFormat, bool isDemo = false); 51 | 52 | /** 53 | * Must be called before the given label is destructed. 54 | */ 55 | void stop() { _targetLabel = nullptr; } 56 | 57 | inline virtual void mouseEnter(const juce::MouseEvent& event) override; 58 | inline virtual void mouseExit(const juce::MouseEvent& event) override; 59 | 60 | inline void refreshTooltip(juce::Component* component); 61 | 62 | private: 63 | juce::Label* _targetLabel; 64 | juce::String _defaultString; 65 | }; 66 | 67 | TooltipLabelUpdater::TooltipLabelUpdater() : _targetLabel(nullptr) { 68 | } 69 | 70 | void TooltipLabelUpdater::start(juce::Label* targetLabel) { 71 | _targetLabel = targetLabel; 72 | _defaultString = ""; 73 | } 74 | 75 | void TooltipLabelUpdater::start(juce::Label* targetLabel, juce::AudioProcessor::WrapperType pluginFormat, bool isDemo) { 76 | _targetLabel = targetLabel; 77 | 78 | _defaultString = JucePlugin_Name; 79 | _defaultString += " "; 80 | _defaultString += JucePlugin_VersionString; 81 | 82 | // Format 83 | _defaultString += " "; 84 | _defaultString += juce::AudioProcessor::getWrapperTypeDescription(pluginFormat); 85 | 86 | // OS 87 | _defaultString += " "; 88 | #if _WIN32 89 | _defaultString += "Win"; 90 | #elif __APPLE__ 91 | _defaultString += "macOS"; 92 | #elif __linux__ 93 | _defaultString += "Linux"; 94 | #else 95 | #error "Unknown OS" 96 | #endif 97 | 98 | // Arch 99 | _defaultString += " "; 100 | #if defined(__x86_64__) || defined(_M_AMD64) 101 | _defaultString += "x86_64"; 102 | #elif defined(__aarch64__) || defined(_M_ARM64) 103 | _defaultString += "arm64"; 104 | #else 105 | #error "Unknown arch" 106 | #endif 107 | 108 | // Demo 109 | if (isDemo) { 110 | _defaultString += " (DEMO)"; 111 | } 112 | 113 | _targetLabel->setText(_defaultString, juce::dontSendNotification); 114 | } 115 | 116 | void TooltipLabelUpdater::mouseEnter(const juce::MouseEvent& event) { 117 | if (_targetLabel != nullptr) { 118 | juce::TooltipClient* tooltipClient = dynamic_cast(event.eventComponent); 119 | 120 | if (tooltipClient != nullptr) { 121 | const juce::String displayString = tooltipClient->getTooltip().isEmpty() ? _defaultString : tooltipClient->getTooltip(); 122 | _targetLabel->setText(displayString, juce::dontSendNotification); 123 | } 124 | } 125 | } 126 | 127 | void TooltipLabelUpdater::mouseExit(const juce::MouseEvent& /*event*/) { 128 | if (_targetLabel != nullptr) { 129 | _targetLabel->setText(_defaultString, juce::dontSendNotification); 130 | } 131 | } 132 | 133 | void TooltipLabelUpdater::refreshTooltip(juce::Component* component) { 134 | if (_targetLabel != nullptr) { 135 | juce::TooltipClient* tooltipClient = dynamic_cast(component); 136 | 137 | if (tooltipClient != nullptr) { 138 | const juce::String displayString = tooltipClient->getTooltip().isEmpty() ? _defaultString : tooltipClient->getTooltip(); 139 | _targetLabel->setText(displayString, juce::dontSendNotification); 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /WECore/General/AudioSpinMutex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: AudioSpinMutex.h 3 | * 4 | * Created: 22/10/2021 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | // Some useful links about these instructions in notes/splnlock-instructions.txt 29 | #if defined(__x86_64__) || defined(_M_AMD64) 30 | #include 31 | #define CPU_PAUSE _mm_pause(); 32 | #elif defined(__aarch64__) || defined(_M_ARM64) 33 | #define CPU_PAUSE __asm__ __volatile__("yield" ::: "memory"); 34 | #else 35 | #error Unsupported architecture 36 | #endif 37 | 38 | namespace WECore { 39 | 40 | /** 41 | * Provides mutex that the audio thread can try-lock in a realtime safe way. 42 | * 43 | * Based on the implementation described here: 44 | * https://timur.audio/using-locks-in-real-time-audio-processing-safely 45 | */ 46 | class AudioSpinMutex { 47 | public: 48 | AudioSpinMutex() = default; 49 | ~AudioSpinMutex() = default; 50 | 51 | /** 52 | * Call from the non-realtime thread. 53 | * 54 | * Will block until the mutex is locked. 55 | */ 56 | void lock() { 57 | constexpr std::array iterations = {5, 10, 3000}; 58 | 59 | for (int i = 0; i < iterations[0]; ++i) { 60 | if (tryLock()) { 61 | return; 62 | } 63 | } 64 | 65 | for (int i = 0; i < iterations[1]; ++i) { 66 | if (tryLock()) { 67 | return; 68 | } 69 | 70 | CPU_PAUSE 71 | } 72 | 73 | while (true) { 74 | for (int i = 0; i < iterations[2]; ++i) { 75 | if (tryLock()) { 76 | return; 77 | } 78 | 79 | CPU_PAUSE 80 | CPU_PAUSE 81 | CPU_PAUSE 82 | CPU_PAUSE 83 | CPU_PAUSE 84 | CPU_PAUSE 85 | CPU_PAUSE 86 | CPU_PAUSE 87 | CPU_PAUSE 88 | CPU_PAUSE 89 | } 90 | 91 | // Waiting longer than we should, let's give other threads 92 | // a chance to recover 93 | std::this_thread::yield(); 94 | } 95 | } 96 | 97 | /** 98 | * Returns true if the lock was successful. 99 | * 100 | * Is safe to call from the audio thread. 101 | */ 102 | bool tryLock() { 103 | return !flag.test_and_set(std::memory_order_acquire); 104 | } 105 | 106 | /** 107 | * Call to unlock the mutex. 108 | */ 109 | void unlock() { 110 | flag.clear(std::memory_order_release); 111 | } 112 | 113 | private: 114 | std::atomic_flag flag = ATOMIC_FLAG_INIT; 115 | }; 116 | 117 | class AudioSpinLockBase { 118 | public: 119 | AudioSpinLockBase(AudioSpinMutex& mutex) : _mutex(mutex), _isLocked(false) { } 120 | 121 | ~AudioSpinLockBase() { 122 | if (_isLocked) { 123 | _mutex.unlock(); 124 | } 125 | } 126 | 127 | void unlock() { 128 | if (_isLocked) { 129 | _mutex.unlock(); 130 | _isLocked = false; 131 | } 132 | } 133 | 134 | bool isLocked() { return _isLocked; } 135 | 136 | protected: 137 | AudioSpinMutex& _mutex; 138 | 139 | // Keeps track of whether this lock is still holding the mutex. Must always check this 140 | // internally before calling unlock on the mutex, otherwise we might unlock it, then the lock 141 | // is taken by someone else, then we unlock it again. 142 | bool _isLocked; 143 | }; 144 | 145 | /** 146 | * Locks the given AudioSpinMutex on constuction, unlocks it on destruction. 147 | */ 148 | class AudioSpinLock : public AudioSpinLockBase { 149 | public: 150 | explicit AudioSpinLock(AudioSpinMutex& mutex) : AudioSpinLockBase(mutex) { 151 | _mutex.lock(); 152 | _isLocked = true; 153 | } 154 | }; 155 | 156 | /** 157 | * Will try lock the given AudioSpinMutex on construction and unlock on desctruction if needed. 158 | */ 159 | class AudioSpinTryLock : public AudioSpinLockBase { 160 | public: 161 | explicit AudioSpinTryLock(AudioSpinMutex& mutex) : AudioSpinLockBase(mutex) { 162 | _isLocked = _mutex.tryLock(); 163 | } 164 | }; 165 | } 166 | -------------------------------------------------------------------------------- /WECore/General/CoreMath.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: CoreMath.h 3 | * 4 | * Created: 18/03/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #ifndef CoreMath_h 23 | #define CoreMath_h 24 | 25 | #include 26 | #include 27 | 28 | /** 29 | * Contains generic math related consts and functions. 30 | */ 31 | namespace WECore::CoreMath { 32 | /** 33 | * Used for portability since Visual Studio has a different implementation of math.h 34 | */ 35 | constexpr long double LONG_PI {3.14159265358979323846264338327950288}; 36 | constexpr double DOUBLE_PI {static_cast(LONG_PI)}; 37 | 38 | constexpr long double LONG_TAU {2 * LONG_PI}; 39 | constexpr double DOUBLE_TAU {static_cast(LONG_TAU)}; 40 | 41 | constexpr long double LONG_E {2.71828182845904523536028747135266250}; 42 | constexpr double DOUBLE_E {static_cast(LONG_E)}; 43 | 44 | template 45 | bool compareFloatsEqual(T x, T y, T tolerance = std::numeric_limits::epsilon()) { 46 | return std::abs(x - y) < tolerance; 47 | } 48 | 49 | constexpr double MINUS_INF_DB {-200}; 50 | inline double linearTodB(double val) { return val > 0 ? std::fmax(20 * std::log10(val), MINUS_INF_DB) : MINUS_INF_DB; } 51 | inline double dBToLinear(double val) { return std::pow(10.0, val / 20.0); } 52 | } 53 | 54 | #endif /* CoreMath_h */ 55 | -------------------------------------------------------------------------------- /WECore/General/ParameterDefinition.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ParameterDefinition.h 3 | * 4 | * Created: 22/09/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | #include 26 | 27 | /** 28 | * Contains classes that are used for defining parameters. Note that these are not 29 | * intended to define individual parameters (and as such they will not store the 30 | * current value of a parameter), but are intended to define some characteristics 31 | * of a given type of parameter, such as the values that are valid for it and 32 | * provide some methods for performing calculations relating to those characteristics. 33 | */ 34 | namespace ParameterDefinition { 35 | /** 36 | * If the given value is between the minimum and maximum values for this parameter, 37 | * then the value is returned unchanged. If the given value is outside the minimum 38 | * and maximum values for this parameter, the given value is clipped to this range 39 | * and then returned. 40 | * 41 | * @param val Value to clip to minumum and maximum values 42 | * 43 | * @return Clipped value 44 | */ 45 | template 46 | T BoundsCheck(T val, T min, T max) { 47 | if (val < min) val = min; 48 | if (val > max) val = max; 49 | 50 | return val; 51 | } 52 | 53 | class BooleanParameter { 54 | public: 55 | BooleanParameter(bool newDefaultValue) : defaultValue(newDefaultValue) {} 56 | 57 | bool defaultValue; 58 | }; 59 | 60 | /** 61 | * Provides basic functionality that may be useful for building other parameters from. 62 | */ 63 | template 64 | class BaseParameter { 65 | public: 66 | 67 | const T minValue; 68 | const T maxValue; 69 | const T defaultValue; 70 | 71 | BaseParameter() = delete; 72 | virtual ~BaseParameter() = default; 73 | 74 | BaseParameter(T newMinValue, 75 | T newMaxValue, 76 | T newDefaultValue) : minValue(newMinValue), 77 | maxValue(newMaxValue), 78 | defaultValue(newDefaultValue) {} 79 | 80 | /** 81 | * If the given value is between the minimum and maximum values for this parameter, 82 | * then the value is returned unchanged. If the given value is outside the minimum 83 | * and maximum values for this parameter, the given value is clipped to this range 84 | * and then returned. 85 | * 86 | * @param val Value to clip to minumum and maximum values 87 | * 88 | * @return Clipped value 89 | */ 90 | T BoundsCheck(T val) const { 91 | return ParameterDefinition::BoundsCheck(val, minValue, maxValue); 92 | } 93 | }; 94 | 95 | /** 96 | * Provides storage for minimum, maximum and default values for a parameter 97 | * which can contain a continuous value (such as a slider), as well as methods to convert 98 | * between the normalised and internal ranges, and clip a value to the appropriate range. 99 | */ 100 | template 101 | class RangedParameter: public BaseParameter { 102 | public: 103 | 104 | // Inherit the constructor 105 | using BaseParameter::BaseParameter; 106 | 107 | /** 108 | * Translates parameter values from the normalised (0 to 1) range as required 109 | * by VSTs to the range used internally for that parameter 110 | * 111 | * @param val Normalised value of the parameter 112 | * 113 | * @return The value of the parameter in the internal range for that parameter 114 | */ 115 | T NormalisedToInternal(T val) const { 116 | return val * (this->maxValue - this->minValue) + this->minValue; 117 | } 118 | 119 | /** 120 | * Translates parameter values from the range used internally for that 121 | * parameter, to the normalised range (0 to 1) as required by VSTs. 122 | * 123 | * @param val Value of the parameter in the internal range 124 | * 125 | * @return The normalised value of the parameter 126 | */ 127 | T InternalToNormalised(T val) const { 128 | return (val - this->minValue) / (this->maxValue - this->minValue); 129 | } 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /WECore/General/UpdateChecker.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: UpdateChecker.h 3 | * 4 | * Created: 05/05/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #ifndef UpdateChecker_h 23 | #define UpdateChecker_h 24 | 25 | #include 26 | #include 27 | 28 | class UpdateChecker { 29 | public: 30 | UpdateChecker() = default; 31 | 32 | bool checkIsLatestVersion(const char* productName, 33 | const char* productVersion) { 34 | 35 | // cURL setup 36 | CURL *curl; 37 | CURLcode result; 38 | curl = curl_easy_init(); 39 | 40 | // build the URL we'll be sending 41 | std::string requestURL {TARGET_URL}; 42 | requestURL.append("?product="); 43 | requestURL.append(productName); 44 | 45 | std::string response; 46 | 47 | // setup the request 48 | curl_easy_setopt(curl, CURLOPT_URL, requestURL.c_str()); 49 | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, _writeToString); 50 | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); 51 | 52 | // send the request and clean up 53 | result = curl_easy_perform(curl); 54 | curl_easy_cleanup(curl); 55 | 56 | return (productVersion == response); 57 | } 58 | 59 | private: 60 | const std::string TARGET_URL {"https://whiteelephantaudio.com/versionChecker.php"}; 61 | 62 | static size_t _writeToString(char* ptr, size_t size, size_t nmemb, std::string* stream) { 63 | *stream = std::string(ptr); 64 | 65 | return size * nmemb; 66 | } 67 | }; 68 | 69 | #endif /* UpdateChecker_h */ 70 | -------------------------------------------------------------------------------- /WECore/MONSTRFilters/MONSTRParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: MONSTRParameters.h 3 | * 4 | * Created: 08/11/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | 26 | namespace WECore::MONSTR::Parameters { 27 | constexpr bool BANDSWITCH_OFF {false}, 28 | BANDSWITCH_ON {true}, 29 | BANDSWITCH_DEFAULT {BANDSWITCH_ON}, 30 | 31 | BANDMUTED_OFF {false}, 32 | BANDMUTED_ON {true}, 33 | BANDMUTED_DEFAULT {BANDMUTED_OFF}, 34 | 35 | BANDSOLO_OFF {false}, 36 | BANDSOLO_ON {true}, 37 | BANDSOLO_DEFAULT {BANDSOLO_OFF}; 38 | 39 | // constexpr as it initialises some internal memebers 40 | constexpr int _MAX_NUM_BANDS {6}; 41 | constexpr int _DEFAULT_NUM_BANDS {2}; 42 | 43 | //@{ 44 | /** 45 | * A parameter which can take any float value between the ranges defined. 46 | * The values passed on construction are in the following order: 47 | * minimum value, 48 | * maximum value, 49 | * default value 50 | */ 51 | const ParameterDefinition::RangedParameter CROSSOVER_FREQUENCY(40, 19500, 1000); 52 | const ParameterDefinition::RangedParameter NUM_BANDS(2, _MAX_NUM_BANDS, _DEFAULT_NUM_BANDS); 53 | //@} 54 | } 55 | -------------------------------------------------------------------------------- /WECore/RichterLFO/RichterLFOPair.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: RichterLFOPair.h 3 | * 4 | * Created: 18/05/2015 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "RichterLFO.h" 25 | #include "WEFilters/ModulationSource.h" 26 | 27 | namespace WECore::Richter { 28 | /** 29 | * 30 | * A convenience class that allows a simple implementation of an LFO that has 31 | * been paired with a MOD oscillator to modulate its depth and frequency. If you use 32 | * this class, you will never need to interact directly with either of the contained 33 | * LFOs for anything other than getting or setting parameter values. 34 | * 35 | * This class has been created as the LFO relies on the MOD being ready before 36 | * it can perform certain operations, which means there are method calls to 37 | * each oscillator which must be interleaved carefully. 38 | * 39 | * The following example shows how to set up this object for audio at 120bpm and 44.1kHz: 40 | * @code 41 | * RichterLFOPair lfoPair; 42 | * lfoPair.prepareForNextBuffer(120, 0, 44100); 43 | * @endcode 44 | * 45 | * Then the class can be used to process a buffer as follows: 46 | * (where numSamples is the size of the buffer) 47 | * @code 48 | * for (size_t iii {0}; iii < numSamples; iii++) { 49 | * buffer[iii] = buffer[iii] * lfoPair.getNextOutput(); 50 | * } 51 | * @endcode 52 | * 53 | * getNextOutput must be called once for each sample. Even if there is a sample in the buffer 54 | * which you do not wish to apply processing to, getNextOutput must still be called otherwise 55 | * subsequent samples will have the wrong gain calculation applied. 56 | */ 57 | 58 | class RichterLFOPair : public ModulationSource { 59 | public: 60 | inline RichterLFOPair(); 61 | virtual ~RichterLFOPair() override = default; 62 | RichterLFOPair operator= (RichterLFOPair& other) = delete; 63 | RichterLFOPair(RichterLFOPair& other) = delete; 64 | 65 | /** 66 | * Prepares for processing the next buffer of samples. For example if using JUCE, you 67 | * would call this in your processBlock() method before doing any processing. 68 | * 69 | * This calls various protected methods of each of the oscillators in a specific order 70 | * to ensure calculations are done correctly. 71 | * 72 | * @param bpm Current bpm of the host. 73 | * @param timeInSeconds Position of the host DAW's playhead at the start of 74 | * playback. 75 | */ 76 | inline void prepareForNextBuffer(double bpm, double timeInSeconds); 77 | 78 | /** 79 | * Sets the sample rate for both LFOs. 80 | * 81 | * @param sampleRate Current sample rate of the host 82 | */ 83 | inline void setSampleRate(double sampleRate); 84 | 85 | RichterLFO LFO; 86 | std::shared_ptr MOD; 87 | 88 | private: 89 | /** 90 | * Returns a gain value which is intended to be multiplied with a single sample to apply the 91 | * tremolo effect. 92 | * 93 | * Note: Calling this method will advance the oscillators internal counters by one 94 | * sample. Calling this method will return a different value each time. 95 | * 96 | * @return The value of the RichterLFO's output at this moment, a value between 0 and 1. 97 | */ 98 | inline double _getNextOutputImpl(double inSample) override; 99 | 100 | /** 101 | * Call each oscillator's reset method. 102 | */ 103 | inline void _resetImpl() override; 104 | }; 105 | 106 | RichterLFOPair::RichterLFOPair() { 107 | MOD = std::make_shared(); 108 | LFO.addFreqModulationSource(MOD); 109 | LFO.addDepthModulationSource(MOD); 110 | } 111 | 112 | void RichterLFOPair::prepareForNextBuffer(double bpm, 113 | double timeInSeconds) { 114 | LFO.prepareForNextBuffer(bpm, timeInSeconds); 115 | MOD->prepareForNextBuffer(bpm, timeInSeconds); 116 | } 117 | 118 | void RichterLFOPair::setSampleRate(double sampleRate) { 119 | LFO.setSampleRate(sampleRate); 120 | MOD->setSampleRate(sampleRate); 121 | } 122 | 123 | void RichterLFOPair::_resetImpl() { 124 | LFO.reset(); 125 | MOD->reset(); 126 | } 127 | 128 | double RichterLFOPair::_getNextOutputImpl(double /*inSample*/) { 129 | double retVal {1}; 130 | 131 | // Advance the modulation LFO state 132 | MOD->getNextOutput(0); 133 | 134 | // Always call getNextOutput regardless of bypassed state 135 | const double tempGain {LFO.getNextOutput(0)}; 136 | 137 | if (LFO.getBypassSwitch()) { 138 | // The output of the LFO is a value in the range -1:1, we need to convert this into a 139 | // gain in the range 0:1 and make sure the value is 1 when the depth is 0 140 | retVal = (tempGain / 2) + (2 - LFO.getDepth()) / 2; 141 | } 142 | 143 | return retVal; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /WECore/RichterLFO/RichterParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: RichterParameters.h 3 | * 4 | * Created: 25/09/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | #include "RichterWavetables.h" 26 | 27 | namespace WECore::Richter::Parameters { 28 | 29 | class WaveParameter : public ParameterDefinition::BaseParameter { 30 | public: 31 | WaveParameter() : ParameterDefinition::BaseParameter(SINE, SIDECHAIN, SINE) {} 32 | 33 | static constexpr int SINE {1}, 34 | SQUARE {2}, 35 | SAW {3}, 36 | SIDECHAIN {4}; 37 | }; 38 | 39 | const WaveParameter WAVE; 40 | 41 | class OutputModeParameter : public ParameterDefinition::BaseParameter { 42 | public: 43 | OutputModeParameter() : ParameterDefinition::BaseParameter(UNIPOLAR, BIPOLAR, BIPOLAR) {} 44 | 45 | static constexpr int UNIPOLAR {1}, 46 | BIPOLAR {2}; 47 | }; 48 | 49 | const OutputModeParameter OUTPUTMODE; 50 | 51 | const ParameterDefinition::RangedParameter TEMPONUMER(1, 32, 1), 52 | TEMPODENOM(1, 32, 1); 53 | 54 | const ParameterDefinition::RangedParameter DEPTH(0, 1, 0.5), 55 | DEPTHMOD(0, 1, 0), 56 | FREQ(0, 20, 2), 57 | FREQMOD(0, 1, 0), 58 | PHASE(0, 360, 0); 59 | 60 | constexpr bool LFOSWITCH_OFF = false, 61 | LFOSWITCH_ON = true, 62 | LFOSWITCH_DEFAULT = LFOSWITCH_OFF, 63 | 64 | TEMPOSYNC_OFF = false, 65 | TEMPOSYNC_ON = true, 66 | TEMPOSYNC_DEFAULT = TEMPOSYNC_OFF, 67 | 68 | PHASESYNC_OFF = false, 69 | PHASESYNC_ON = true, 70 | PHASESYNC_DEFAULT = PHASESYNC_ON, 71 | 72 | INVERT_OFF = false, 73 | INVERT_ON = true, 74 | INVERT_DEFAULT = INVERT_OFF; 75 | 76 | } 77 | -------------------------------------------------------------------------------- /WECore/RichterLFO/RichterWavetables.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: RichterWavetables.h 3 | * 4 | * Created: 13/07/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/CoreMath.h" 25 | 26 | namespace WECore::Richter { 27 | 28 | /** 29 | * Singleton which contains the wavetables used by the Richter LFOs. 30 | */ 31 | class Wavetables { 32 | public: 33 | 34 | /** 35 | * Number of samples in each of the wavetables. 36 | */ 37 | static constexpr int SIZE {2000}; 38 | 39 | static const Wavetables* getInstance() { 40 | static Wavetables instance; 41 | return &instance; 42 | } 43 | 44 | const double* getSine() const { return _sineTable; } 45 | const double* getSquare() const { return _squareTable; } 46 | const double* getSaw() const { return _sawTable; } 47 | const double* getSidechain() const { return _sidechainTable; } 48 | 49 | private: 50 | double _sineTable[SIZE]; 51 | double _squareTable[SIZE]; 52 | double _sawTable[SIZE]; 53 | double _sidechainTable[SIZE]; 54 | 55 | /** 56 | * Populates the available wavetables 57 | */ 58 | inline Wavetables(); 59 | }; 60 | 61 | Wavetables::Wavetables() { 62 | 63 | // Sine wavetable 64 | for (int idx = 0; idx < Wavetables::SIZE; idx++) { 65 | const double radians {idx * CoreMath::DOUBLE_TAU / Wavetables::SIZE}; 66 | 67 | // Just a conventional sine 68 | _sineTable[idx] = sin(radians); 69 | } 70 | 71 | // Square wavetable 72 | for (int idx = 0; idx < Wavetables::SIZE; idx++) { 73 | const double radians {(idx * CoreMath::DOUBLE_TAU / Wavetables::SIZE) + 0.32}; 74 | 75 | // The fourier series for a square wave produces a very sharp square with some overshoot 76 | // and ripple, so this actually uses slightly lower amplitudes for each harmonic than 77 | // would normally be used. 78 | // 79 | // Because the harmonics are lower amplitude, it needs to be scaled up by 1.2 to reach 80 | // a range of -1 to +1 81 | _squareTable[idx] = 82 | ( 83 | sin(radians) + 84 | (0.3/1.0) * sin(3 * radians) + 85 | (0.3/2.0) * sin(5 * radians) + 86 | (0.3/4.0) * sin(7 * radians) + 87 | (0.3/8.0) * sin(9 * radians) + 88 | (0.3/16.0) * sin(11 * radians) + 89 | (0.3/32.0) * sin(13 * radians) 90 | ) * 1.2; 91 | } 92 | 93 | // Saw wavetable 94 | for (int idx = 0; idx < Wavetables::SIZE; idx++) { 95 | const double radians {(idx * CoreMath::DOUBLE_TAU / Wavetables::SIZE) + CoreMath::DOUBLE_PI}; 96 | 97 | // Conventional fourier series for a saw wave, scaled to fit -1 to 1 98 | _sawTable[idx] = 99 | ( 100 | sin(radians) - 101 | (1.0/2.0) * sin(2 * radians) + 102 | (1.0/3.0) * sin(3 * radians) - 103 | (1.0/4.0) * sin(4 * radians) + 104 | (1.0/6.0) * sin(5 * radians) - 105 | (1.0/8.0) * sin(6 * radians) + 106 | (1.0/12.0) * sin(7 * radians) - 107 | (1.0/16.0) * sin(8 * radians) + 108 | (1.0/24.0) * sin(9 * radians) - 109 | (1.0/32.0) * sin(10 * radians) + 110 | (1.0/48.0) * sin(11 * radians) - 111 | (1.0/64.0) * sin(12 * radians) + 112 | (1.0/96.0) * sin(13 * radians) - 113 | (1.0/128.0) * sin(14 * radians) 114 | ) * (2.0 / 3.0); 115 | } 116 | 117 | // Sidechain wavetable 118 | for (int idx = 0; idx < Wavetables::SIZE; idx++) { 119 | const double radians {idx * CoreMath::DOUBLE_TAU / Wavetables::SIZE}; 120 | 121 | _sidechainTable[idx] = 122 | ( 123 | radians < 0.4497 ? 124 | -2 * sin(pow(0.2 * radians - 0.8245, 6) * 10) + 1 : 125 | -2 * sin(pow(0.15 * radians - 0.802, 6) * 10) + 1 126 | ); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /WECore/RichterLFO/Tests/RichterLFOPairTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: RichterLFOPairTests.cpp 3 | * 4 | * Created: 26/12/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "RichterLFO/RichterLFOPair.h" 24 | 25 | SCENARIO("RichterLFOPair: Parameters can be set and retrieved correctly") { 26 | GIVEN("A new RichterLFOPair object") { 27 | WECore::Richter::RichterLFOPair mLFOPair; 28 | 29 | WHEN("Nothing is changed") { 30 | THEN("Parameters have their default values") { 31 | CHECK(mLFOPair.LFO.getBypassSwitch() == false); 32 | CHECK(mLFOPair.LFO.getPhaseSyncSwitch() == true); 33 | CHECK(mLFOPair.LFO.getTempoSyncSwitch() == false); 34 | CHECK(mLFOPair.LFO.getInvertSwitch() == false); 35 | CHECK(mLFOPair.LFO.getWave() == 1); 36 | CHECK(mLFOPair.LFO.getOutputMode() == 2); 37 | CHECK(mLFOPair.LFO.getDepth() == Approx(0.5)); 38 | CHECK(mLFOPair.LFO.getFreq() == Approx(2.0)); 39 | CHECK(mLFOPair.LFO.getManualPhase() == Approx(0.0)); 40 | CHECK(mLFOPair.LFO.getTempoNumer() == Approx(1.0)); 41 | CHECK(mLFOPair.LFO.getTempoDenom() == Approx(1.0)); 42 | 43 | CHECK(mLFOPair.MOD->getBypassSwitch() == false); 44 | CHECK(mLFOPair.MOD->getPhaseSyncSwitch() == true); 45 | CHECK(mLFOPair.MOD->getTempoSyncSwitch() == false); 46 | CHECK(mLFOPair.MOD->getInvertSwitch() == false); 47 | CHECK(mLFOPair.MOD->getWave() == 1); 48 | CHECK(mLFOPair.MOD->getOutputMode() == 2); 49 | CHECK(mLFOPair.MOD->getDepth() == Approx(0.5)); 50 | CHECK(mLFOPair.MOD->getFreq() == Approx(2.0)); 51 | CHECK(mLFOPair.MOD->getManualPhase() == Approx(0.0)); 52 | CHECK(mLFOPair.MOD->getTempoNumer() == Approx(1.0)); 53 | CHECK(mLFOPair.MOD->getTempoDenom() == Approx(1.0)); 54 | } 55 | } 56 | 57 | WHEN("All parameters are changed to unique values") { 58 | mLFOPair.LFO.setBypassSwitch(true); 59 | mLFOPair.LFO.setPhaseSyncSwitch(false); 60 | mLFOPair.LFO.setTempoSyncSwitch(true); 61 | mLFOPair.LFO.setInvertSwitch(true); 62 | mLFOPair.LFO.setWave(2); 63 | mLFOPair.LFO.setOutputMode(1); 64 | mLFOPair.LFO.setDepth(0.1); 65 | mLFOPair.LFO.setFreq(3); 66 | mLFOPair.LFO.setManualPhase(0.5); 67 | mLFOPair.LFO.setTempoNumer(2); 68 | mLFOPair.LFO.setTempoDenom(3); 69 | 70 | mLFOPair.MOD->setBypassSwitch(true); 71 | mLFOPair.MOD->setPhaseSyncSwitch(true); 72 | mLFOPair.MOD->setTempoSyncSwitch(true); 73 | mLFOPair.MOD->setInvertSwitch(true); 74 | mLFOPair.MOD->setWave(3); 75 | mLFOPair.MOD->setOutputMode(1); 76 | mLFOPair.MOD->setDepth(0.5); 77 | mLFOPair.MOD->setFreq(6); 78 | mLFOPair.MOD->setManualPhase(0.7); 79 | mLFOPair.MOD->setTempoNumer(3); 80 | mLFOPair.MOD->setTempoDenom(4); 81 | 82 | THEN("They all get their correct unique values") { 83 | CHECK(mLFOPair.LFO.getBypassSwitch() == true); 84 | CHECK(mLFOPair.LFO.getPhaseSyncSwitch() == false); 85 | CHECK(mLFOPair.LFO.getTempoSyncSwitch() == true); 86 | CHECK(mLFOPair.LFO.getInvertSwitch() == true); 87 | CHECK(mLFOPair.LFO.getWave() == 2); 88 | CHECK(mLFOPair.LFO.getOutputMode() == 1); 89 | CHECK(mLFOPair.LFO.getDepth() == Approx(0.1)); 90 | CHECK(mLFOPair.LFO.getFreq() == Approx(3.0)); 91 | CHECK(mLFOPair.LFO.getManualPhase() == Approx(0.5)); 92 | CHECK(mLFOPair.LFO.getTempoNumer() == Approx(2.0)); 93 | CHECK(mLFOPair.LFO.getTempoDenom() == Approx(3.0)); 94 | 95 | CHECK(mLFOPair.MOD->getBypassSwitch() == true); 96 | CHECK(mLFOPair.MOD->getPhaseSyncSwitch() == true); 97 | CHECK(mLFOPair.MOD->getTempoSyncSwitch() == true); 98 | CHECK(mLFOPair.MOD->getInvertSwitch() == true); 99 | CHECK(mLFOPair.MOD->getWave() == 3); 100 | CHECK(mLFOPair.MOD->getOutputMode() == 1); 101 | CHECK(mLFOPair.MOD->getDepth() == Approx(0.5)); 102 | CHECK(mLFOPair.MOD->getFreq() == Approx(6.0)); 103 | CHECK(mLFOPair.MOD->getManualPhase() == Approx(0.7)); 104 | CHECK(mLFOPair.MOD->getTempoNumer() == Approx(3.0)); 105 | CHECK(mLFOPair.MOD->getTempoDenom() == Approx(4.0)); 106 | } 107 | } 108 | } 109 | } 110 | 111 | SCENARIO("RichterLFOPair: Parameters enforce their bounds correctly") { 112 | GIVEN("A new RichterLFOPair object") { 113 | WECore::Richter::RichterLFOPair mLFOPair; 114 | 115 | WHEN("All parameter values are too low") { 116 | mLFOPair.LFO.setWave(-5); 117 | mLFOPair.LFO.setOutputMode(-5); 118 | mLFOPair.LFO.setDepth(-5); 119 | mLFOPair.LFO.setFreq(-5); 120 | mLFOPair.LFO.setManualPhase(-5); 121 | mLFOPair.LFO.setTempoNumer(-5); 122 | mLFOPair.LFO.setTempoDenom(-5); 123 | 124 | mLFOPair.MOD->setWave(-5); 125 | mLFOPair.MOD->setOutputMode(-5); 126 | mLFOPair.MOD->setDepth(-5); 127 | mLFOPair.MOD->setFreq(-5); 128 | mLFOPair.MOD->setManualPhase(-5); 129 | mLFOPair.MOD->setTempoNumer(-5); 130 | mLFOPair.MOD->setTempoDenom(-5); 131 | 132 | THEN("Parameters enforce their lower bounds") { 133 | CHECK(mLFOPair.LFO.getWave() == 1); 134 | CHECK(mLFOPair.LFO.getOutputMode() == 1); 135 | CHECK(mLFOPair.LFO.getDepth() == Approx(0.0)); 136 | CHECK(mLFOPair.LFO.getFreq() == Approx(0.0)); 137 | CHECK(mLFOPair.LFO.getManualPhase() == Approx(0.0)); 138 | CHECK(mLFOPair.LFO.getTempoNumer() == Approx(1.0)); 139 | CHECK(mLFOPair.LFO.getTempoDenom() == Approx(1.0)); 140 | 141 | CHECK(mLFOPair.MOD->getWave() == 1); 142 | CHECK(mLFOPair.MOD->getOutputMode() == 1); 143 | CHECK(mLFOPair.MOD->getDepth() == Approx(0.0)); 144 | CHECK(mLFOPair.MOD->getFreq() == Approx(0.0)); 145 | CHECK(mLFOPair.MOD->getManualPhase() == Approx(0.0)); 146 | CHECK(mLFOPair.MOD->getTempoNumer() == Approx(1.0)); 147 | CHECK(mLFOPair.MOD->getTempoDenom() == Approx(1.0)); 148 | } 149 | } 150 | 151 | WHEN("All parameter values are too high") { 152 | mLFOPair.LFO.setWave(100); 153 | mLFOPair.LFO.setOutputMode(100); 154 | mLFOPair.LFO.setDepth(100); 155 | mLFOPair.LFO.setFreq(100); 156 | mLFOPair.LFO.setManualPhase(10000); 157 | mLFOPair.LFO.setTempoNumer(100); 158 | mLFOPair.LFO.setTempoDenom(100); 159 | 160 | mLFOPair.MOD->setWave(100); 161 | mLFOPair.MOD->setOutputMode(100); 162 | mLFOPair.MOD->setDepth(100); 163 | mLFOPair.MOD->setFreq(100); 164 | mLFOPair.MOD->setManualPhase(10000); 165 | mLFOPair.MOD->setTempoNumer(100); 166 | mLFOPair.MOD->setTempoDenom(100); 167 | 168 | THEN("Parameters enforce their upper bounds") { 169 | CHECK(mLFOPair.LFO.getWave() == 4); 170 | CHECK(mLFOPair.LFO.getOutputMode() == 2); 171 | CHECK(mLFOPair.LFO.getDepth() == Approx(1.0)); 172 | CHECK(mLFOPair.LFO.getFreq() == Approx(20.0)); 173 | CHECK(mLFOPair.LFO.getManualPhase() == Approx(360.0)); 174 | CHECK(mLFOPair.LFO.getTempoNumer() == Approx(32.0)); 175 | CHECK(mLFOPair.LFO.getTempoDenom() == Approx(32.0)); 176 | 177 | CHECK(mLFOPair.MOD->getWave() == 4); 178 | CHECK(mLFOPair.MOD->getOutputMode() == 2); 179 | CHECK(mLFOPair.MOD->getDepth() == Approx(1.0)); 180 | CHECK(mLFOPair.MOD->getFreq() == Approx(20.0)); 181 | CHECK(mLFOPair.MOD->getManualPhase() == Approx(360.0)); 182 | CHECK(mLFOPair.MOD->getTempoNumer() == Approx(32.0)); 183 | CHECK(mLFOPair.MOD->getTempoDenom() == Approx(32.0)); 184 | } 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /WECore/RichterLFO/UI/RichterWaveViewer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: RichterWaveViewer.h 3 | * 4 | * Created: 16/07/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with the WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "JuceHeader.h" 25 | #include "RichterLFO/RichterParameters.h" 26 | #include "RichterLFO/RichterWavetables.h" 27 | 28 | namespace WECore::Richter { 29 | 30 | class WaveViewer : public juce::Component, 31 | public juce::SettableTooltipClient { 32 | public: 33 | WaveViewer() : _waveArrayPointer(nullptr), _depth(0), _phaseShift(0), _isInverted(false) {} 34 | 35 | inline void setWave(const double* pointer, double depth, double phaseShift, bool isInverted); 36 | 37 | inline virtual void paint(juce::Graphics& g); 38 | 39 | void stop() { _waveArrayPointer = nullptr; } 40 | 41 | enum ColourIds 42 | { 43 | highlightColourId = 0x1201201 44 | }; 45 | 46 | private: 47 | const double* _waveArrayPointer; 48 | double _depth; 49 | double _phaseShift; 50 | bool _isInverted; 51 | }; 52 | 53 | void WaveViewer::setWave(const double* pointer, double depth, double phaseShift, bool isInverted) { 54 | _waveArrayPointer = pointer; 55 | _depth = depth; 56 | _phaseShift = phaseShift; 57 | _isInverted = isInverted; 58 | } 59 | 60 | void WaveViewer::paint(juce::Graphics &g) { 61 | 62 | // Down sample the wave array 63 | constexpr int NUM_SAMPLES {25}; 64 | constexpr float SCALE {0.4}; 65 | constexpr float MARGIN { (1 - SCALE) / 2 }; 66 | const float INCREMENT {static_cast(Wavetables::SIZE) / NUM_SAMPLES}; 67 | 68 | if (_waveArrayPointer != nullptr) { 69 | juce::Path p; 70 | 71 | for (size_t idx {0}; idx < NUM_SAMPLES; idx++) { 72 | // Calculate the index of the sample accounting for downsampling and phase shift 73 | const int phaseIndexOffset { 74 | static_cast((_phaseShift / Parameters::PHASE.maxValue) * Wavetables::SIZE) 75 | }; 76 | const int sampleIdx {( 77 | (static_cast(idx * INCREMENT + phaseIndexOffset) % Wavetables::SIZE) 78 | )}; 79 | 80 | // Get the sample for this value 81 | const double sample {_waveArrayPointer[sampleIdx] * _depth * (_isInverted ? -1 : 1)}; 82 | 83 | // Invert the wave and scale to the height of this component 84 | const double sampleX {(static_cast(idx) / NUM_SAMPLES) * getWidth()}; 85 | const double sampleY = (0.5 - sample) * getHeight() * SCALE + getHeight() * MARGIN; 86 | 87 | // Add it to the path 88 | if (idx == 0) { 89 | p.startNewSubPath(0, sampleY); 90 | } else { 91 | p.lineTo(sampleX, sampleY); 92 | } 93 | } 94 | 95 | g.setColour(findColour(highlightColourId)); 96 | g.strokePath(p, juce::PathStrokeType(3.0f)); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /WECore/SongbirdFilters/Formant.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: Formant.h 3 | * 4 | * Created: 16/07/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | namespace WECore::Songbird { 25 | /** 26 | * Simple class to hold the data about an individual formant peak. 27 | */ 28 | class Formant { 29 | public: 30 | Formant() : frequency(0), gaindB(0) {} 31 | 32 | Formant(double newFreq, 33 | double newGaindB) : frequency(newFreq), 34 | gaindB(newGaindB) {} 35 | 36 | double frequency; 37 | double gaindB; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /WECore/SongbirdFilters/SongbirdFiltersParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: SongbirdFiltersParameters.h 3 | * 4 | * Created: 02/10/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | 26 | namespace WECore::Songbird::Parameters { 27 | 28 | class VowelParameter : public ParameterDefinition::BaseParameter { 29 | public: 30 | VowelParameter() : ParameterDefinition::BaseParameter(VOWEL_A, VOWEL_U, VOWEL_A) { } 31 | 32 | static constexpr int VOWEL_A {1}, 33 | VOWEL_E {2}, 34 | VOWEL_I {3}, 35 | VOWEL_O {4}, 36 | VOWEL_U {5}; 37 | }; 38 | const VowelParameter VOWEL; 39 | 40 | //@{ 41 | /** 42 | * A parameter which can take any float value between the ranges defined. 43 | * The values passed on construction are in the following order: 44 | * minimum value, 45 | * maximum value, 46 | * default value 47 | */ 48 | const ParameterDefinition::RangedParameter FILTER_POSITION(0, 1, 0.5), 49 | MIX(0, 1, 1), 50 | AIR_GAIN(0, 1, 0.5), 51 | OUTPUTGAIN(0, 2, 1); 52 | //@} 53 | 54 | constexpr bool MODMODE_BLEND = false, 55 | MODMODE_FREQ = true, 56 | MODMODE_DEFAULT = MODMODE_FREQ; 57 | 58 | constexpr int FILTER_ORDER {8}; 59 | 60 | } 61 | -------------------------------------------------------------------------------- /WECore/SongbirdFilters/SongbirdFormantFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: SongbirdFormantFilter.h 3 | * 4 | * Created: 16/07/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "SongbirdFilters/Formant.h" 25 | #include "WEFilters/TPTSVFilter.h" 26 | #include 27 | #include 28 | 29 | namespace WECore::Songbird { 30 | /** 31 | * A class containing a vector of bandpass filters to produce a vowel sound. 32 | * 33 | * Supports only mono audio processing. For stereo processing, you must create 34 | * two objects of this type. (Do not reuse a single object for both channels) 35 | * 36 | * To use this class, simply call setFormants, reset, and process as necessary. 37 | * 38 | * @see setFormants - must be called before performing any processing 39 | * @see Formant - Formant objects are required for operation of this class 40 | */ 41 | template 42 | class SongbirdFormantFilter { 43 | static_assert(std::is_floating_point::value, 44 | "Must be provided with a floating point template type"); 45 | 46 | public: 47 | /** 48 | * Creates and stores the appropriate number of filters. 49 | */ 50 | SongbirdFormantFilter() { 51 | for (size_t iii {0}; iii < NUM_FORMANTS; iii++) { 52 | _filters[iii].setMode(TPTSVF::Parameters::FILTER_MODE.PEAK); 53 | _filters[iii].setQ(10); 54 | } 55 | } 56 | 57 | virtual ~SongbirdFormantFilter() = default; 58 | 59 | /** 60 | * Applies the filtering to a mono buffer of samples. 61 | * Expect seg faults or other memory issues if arguements passed are incorrect. 62 | * 63 | * @param inSample Sample to process 64 | */ 65 | inline T process(T inSample); 66 | 67 | /** 68 | * Sets the properties of each bandpass filter contained in the object. 69 | * 70 | * @param formants An array of Formants, the size of which must equal the 71 | * number of bandpass filters in the object. 72 | * 73 | * @return A boolean value, true if the formants have been applied to the filters 74 | * correctly, false if the operation failed 75 | * 76 | * @see Formant - This object is used to as a convenient container of all the 77 | * parameters which can be supplied to a bandpass filter. 78 | */ 79 | inline bool setFormants(const std::array& formants); 80 | 81 | /** 82 | * Sets the sample rate which the filters will be operating on. 83 | */ 84 | inline void setSampleRate(double val); 85 | 86 | /** 87 | * Resets all filters. 88 | * Call this whenever the audio stream is interrupted (ie. the playhead is moved) 89 | */ 90 | inline void reset(); 91 | 92 | private: 93 | std::array, NUM_FORMANTS> _filters; 94 | }; 95 | 96 | template 97 | T SongbirdFormantFilter::process(T inSample) { 98 | 99 | T outSample {0}; 100 | 101 | // Perform the filtering for each formant peak 102 | for (size_t filterNumber {0}; filterNumber < _filters.size(); filterNumber++) { 103 | 104 | T tempSample {inSample}; 105 | 106 | _filters[filterNumber].processBlock(&tempSample, 1); 107 | 108 | outSample += tempSample; 109 | } 110 | 111 | return outSample; 112 | } 113 | 114 | template 115 | bool SongbirdFormantFilter::setFormants( 116 | const std::array& formants) { 117 | 118 | bool retVal {false}; 119 | 120 | // if the correct number of formants have been supplied, 121 | // apply them to each filter in turn 122 | if (_filters.size() == formants.size()) { 123 | retVal = true; 124 | 125 | for (size_t iii {0}; iii < _filters.size(); iii++) { 126 | _filters[iii].setCutoff(formants[iii].frequency); 127 | 128 | double gainAbs = CoreMath::dBToLinear(formants[iii].gaindB); 129 | _filters[iii].setGain(gainAbs); 130 | } 131 | } 132 | 133 | return retVal; 134 | } 135 | 136 | template 137 | void SongbirdFormantFilter::setSampleRate(double val) { 138 | for (TPTSVF::TPTSVFilter& filter : _filters) { 139 | filter.setSampleRate(val); 140 | } 141 | } 142 | 143 | template 144 | void SongbirdFormantFilter::reset() { 145 | for (TPTSVF::TPTSVFilter& filter : _filters) { 146 | filter.reset(); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /WECore/SongbirdFilters/Tests/SongbirdFilterModuleTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: SongbirdFilterModuleTests.cpp 3 | * 4 | * Created: 07/01/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "SongbirdFilters/SongbirdFilterModule.h" 24 | #include "SongbirdFilters/Tests/TestData.h" 25 | 26 | #include 27 | 28 | SCENARIO("SongbirdFilterModule: Parameters can be set and retrieved correctly") { 29 | GIVEN("A new SongbirdFilterModule object") { 30 | WECore::Songbird::SongbirdFilterModule mSongbird; 31 | 32 | WHEN("Nothing is changed") { 33 | THEN("Parameters have their default values") { 34 | CHECK(mSongbird.getVowel1() == 1); 35 | CHECK(mSongbird.getVowel2() == 2); 36 | CHECK(mSongbird.getFilterPosition() == 0.5); 37 | CHECK(mSongbird.getMix() == 1.0); 38 | CHECK(mSongbird.getAirGain() == 0.5); 39 | CHECK(mSongbird.getModMode() == true); 40 | } 41 | } 42 | 43 | WHEN("All parameters are changed to unique values") { 44 | mSongbird.setVowel1(3); 45 | mSongbird.setVowel2(4); 46 | mSongbird.setFilterPosition(0.03); 47 | mSongbird.setMix(0.04); 48 | mSongbird.setAirGain(0.05); 49 | mSongbird.setModMode(true); 50 | 51 | THEN("They all get their correct unique values") { 52 | CHECK(mSongbird.getVowel1() == 3); 53 | CHECK(mSongbird.getVowel2() == 4); 54 | CHECK(mSongbird.getFilterPosition() == Approx(0.03)); 55 | CHECK(mSongbird.getMix() == Approx(0.04)); 56 | CHECK(mSongbird.getAirGain() == Approx(0.05)); 57 | CHECK(mSongbird.getModMode() == true); 58 | } 59 | } 60 | } 61 | } 62 | 63 | SCENARIO("SongbirdFilterModule: Parameters enforce their bounds correctly") { 64 | GIVEN("A new SongbirdFilterModule object") { 65 | WECore::Songbird::SongbirdFilterModule mSongbird; 66 | 67 | WHEN("All parameter values are too low") { 68 | mSongbird.setVowel1(-5); 69 | mSongbird.setVowel2(-5); 70 | mSongbird.setFilterPosition(-5); 71 | mSongbird.setMix(-5); 72 | mSongbird.setAirGain(-5); 73 | 74 | THEN("Parameters enforce their lower bounds") { 75 | CHECK(mSongbird.getVowel1() == 1); 76 | CHECK(mSongbird.getVowel2() == 1); 77 | CHECK(mSongbird.getFilterPosition() == Approx(0.0)); 78 | CHECK(mSongbird.getMix() == Approx(0.0)); 79 | CHECK(mSongbird.getAirGain() == Approx(0.0)); 80 | } 81 | } 82 | 83 | WHEN("All parameter values are too high") { 84 | mSongbird.setVowel1(1000); 85 | mSongbird.setVowel2(1000); 86 | mSongbird.setFilterPosition(1000); 87 | mSongbird.setMix(1000); 88 | mSongbird.setAirGain(1000); 89 | 90 | THEN("Parameters enforce their upper bounds") { 91 | CHECK(mSongbird.getVowel1() == 5); 92 | CHECK(mSongbird.getVowel2() == 5); 93 | CHECK(mSongbird.getFilterPosition() == Approx(1.0)); 94 | CHECK(mSongbird.getMix() == Approx(1.0)); 95 | CHECK(mSongbird.getAirGain() == Approx(1.0)); 96 | } 97 | } 98 | } 99 | } 100 | 101 | SCENARIO("SongbirdFilterModule: Silence in = silence out") { 102 | GIVEN("A SongbirdFilterModule and a buffer of silent samples") { 103 | std::vector leftBuffer(1024); 104 | std::vector rightBuffer(1024); 105 | WECore::Songbird::SongbirdFilterModule mSongbird; 106 | 107 | // fill the buffer 108 | std::fill(leftBuffer.begin(), leftBuffer.end(), 0); 109 | std::fill(rightBuffer.begin(), rightBuffer.end(), 0); 110 | 111 | WHEN("The silence samples are processed") { 112 | 113 | // do processing 114 | mSongbird.Process2in2out(&leftBuffer[0], &rightBuffer[0], leftBuffer.size()); 115 | 116 | THEN("The output is silence") { 117 | for (size_t iii {0}; iii < leftBuffer.size(); iii++) { 118 | CHECK(leftBuffer[iii] == Approx(0.0)); 119 | CHECK(rightBuffer[iii] == Approx(0.0)); 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | SCENARIO("SongbirdFilterModule: Freq mode") { 127 | GIVEN("A SongbirdFilterModule and a buffer of sine samples") { 128 | std::vector leftBuffer(1024); 129 | std::vector rightBuffer(1024); 130 | const std::vector& expectedOutputLeft = 131 | TestData::Songbird::Data.at(Catch::getResultCapture().getCurrentTestName() + "-left"); 132 | const std::vector& expectedOutputRight = 133 | TestData::Songbird::Data.at(Catch::getResultCapture().getCurrentTestName() + "-right"); 134 | 135 | WECore::Songbird::SongbirdFilterModule mSongbird; 136 | 137 | // Set some parameters for the input signal 138 | constexpr size_t SAMPLE_RATE {44100}; 139 | constexpr size_t SINE_FREQ {1000}; 140 | constexpr double SAMPLES_PER_CYCLE {SAMPLE_RATE / SINE_FREQ}; 141 | 142 | // fill the buffers, phase shift the right one so that they're not identical 143 | std::generate(leftBuffer.begin(), 144 | leftBuffer.end(), 145 | [iii = 0]() mutable {return std::sin(WECore::CoreMath::LONG_TAU * (iii++ / SAMPLES_PER_CYCLE));} ); 146 | std::generate(rightBuffer.begin(), 147 | rightBuffer.end(), 148 | [iii = 0]() mutable {return std::sin(WECore::CoreMath::LONG_TAU * (iii++ / SAMPLES_PER_CYCLE) + WECore::CoreMath::LONG_PI);} ); 149 | WHEN("The parameters are set and samples are processed") { 150 | // Set freq mode 151 | mSongbird.setVowel1(1); 152 | mSongbird.setVowel2(2); 153 | mSongbird.setAirGain(1); 154 | mSongbird.setModMode(true); 155 | 156 | mSongbird.Process2in2out(&leftBuffer[0], &rightBuffer[0], leftBuffer.size()); 157 | 158 | THEN("The output is as expected") { 159 | for (size_t iii {0}; iii < leftBuffer.size(); iii++) { 160 | CHECK(leftBuffer[iii] == Approx(expectedOutputLeft[iii]).margin(0.00001)); 161 | CHECK(rightBuffer[iii] == Approx(expectedOutputRight[iii]).margin(0.00001)); 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | SCENARIO("SongbirdFilterModule: Blend mode") { 169 | GIVEN("A SongbirdFilterModule and a buffer of sine samples") { 170 | std::vector leftBuffer(1024); 171 | std::vector rightBuffer(1024); 172 | const std::vector& expectedOutputLeft = 173 | TestData::Songbird::Data.at(Catch::getResultCapture().getCurrentTestName() + "-left"); 174 | const std::vector& expectedOutputRight = 175 | TestData::Songbird::Data.at(Catch::getResultCapture().getCurrentTestName() + "-right"); 176 | 177 | WECore::Songbird::SongbirdFilterModule mSongbird; 178 | 179 | // Set some parameters for the input signal 180 | constexpr size_t SAMPLE_RATE {44100}; 181 | constexpr size_t SINE_FREQ {1000}; 182 | constexpr double SAMPLES_PER_CYCLE {SAMPLE_RATE / SINE_FREQ}; 183 | 184 | // fill the buffers, phase shift the right one so that they're not identical 185 | std::generate(leftBuffer.begin(), 186 | leftBuffer.end(), 187 | [iii = 0]() mutable {return std::sin(WECore::CoreMath::LONG_TAU * (iii++ / SAMPLES_PER_CYCLE));} ); 188 | std::generate(rightBuffer.begin(), 189 | rightBuffer.end(), 190 | [iii = 0]() mutable {return std::sin(WECore::CoreMath::LONG_TAU * (iii++ / SAMPLES_PER_CYCLE) + WECore::CoreMath::LONG_PI);} ); 191 | WHEN("The parameters are set and samples are processed") { 192 | // Set blend mode 193 | mSongbird.setVowel1(1); 194 | mSongbird.setVowel2(2); 195 | mSongbird.setAirGain(1); 196 | mSongbird.setModMode(false); 197 | 198 | mSongbird.Process2in2out(&leftBuffer[0], &rightBuffer[0], leftBuffer.size()); 199 | 200 | THEN("The output is as expected") { 201 | for (size_t iii {0}; iii < leftBuffer.size(); iii++) { 202 | CHECK(leftBuffer[iii] == Approx(expectedOutputLeft[iii]).margin(0.00001)); 203 | CHECK(rightBuffer[iii] == Approx(expectedOutputRight[iii]).margin(0.00001)); 204 | } 205 | } 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /WECore/Tests/catchMain.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: catchMain.cpp 3 | * 4 | * Created: 26/12/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #define CATCH_CONFIG_MAIN 23 | #include "catch.hpp" 24 | -------------------------------------------------------------------------------- /WECore/WEFilters/AREnvelopeFollowerBase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: AREnveloperFollowerBase.h 3 | * 4 | * Created: 27/05/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "AREnvelopeFollowerParameters.h" 25 | #include "ModulationSource.h" 26 | #include "TPTSVFilter.h" 27 | 28 | namespace WECore::AREnv { 29 | /** 30 | * Base class for an envelope follower with controls for attack and release times, with optional 31 | * low/high cut filters before the envelope stage. 32 | */ 33 | class AREnvelopeFollowerBase : public ModulationSource { 34 | public: 35 | AREnvelopeFollowerBase() : _attackTimeMs(Parameters::ATTACK_MS.defaultValue), 36 | _releaseTimeMs(Parameters::RELEASE_MS.defaultValue), 37 | _filterEnabled(Parameters::FILTER_ENABLED.defaultValue) { 38 | // call this here rather than setting it in initialiser list so that the coefficients get 39 | // setup 40 | reset(); 41 | setSampleRate(44100); 42 | 43 | _lowCutFilter.setCutoff(Parameters::LOW_CUT.defaultValue); 44 | _highCutFilter.setCutoff(Parameters::HIGH_CUT.defaultValue); 45 | _lowCutFilter.setMode(TPTSVF::Parameters::FILTER_MODE.HIGHPASS); 46 | _highCutFilter.setMode(TPTSVF::Parameters::FILTER_MODE.LOWPASS); 47 | } 48 | 49 | virtual ~AREnvelopeFollowerBase() override = default; 50 | 51 | /** @name Setter Methods */ 52 | /** @{ */ 53 | 54 | /** 55 | * Sets the sample rate the envelope will operate at. 56 | * It is recommended that you call this at some point before calling clockUpdateEnvelope. 57 | * 58 | * @param[in] sampleRate The sample rate in Hz 59 | */ 60 | inline void setSampleRate(double sampleRate); 61 | 62 | /** 63 | * Sets the attack time of the envelope. 64 | * 65 | * @see ATTACK_MS for valid values 66 | * 67 | * @param[in] time Attack time in milliseconds 68 | */ 69 | inline void setAttackTimeMs(double time); 70 | 71 | /** 72 | * Sets the release time of the envelope. 73 | * 74 | * @see RELEASE_MS for valid values 75 | * 76 | * @param[in] time Release time in milliseconds 77 | */ 78 | inline void setReleaseTimeMs(double time); 79 | 80 | /** 81 | * Sets enables or disables the filter in front of the envelope. 82 | * 83 | * @see FILTER_ENABLED for valid values 84 | * 85 | * @param[in] isEnabled Set to true to enabled the filter. 86 | */ 87 | void setFilterEnabled(bool isEnabled) { _filterEnabled = isEnabled; } 88 | 89 | /** 90 | * Sets the low cut applied before the envelope. 91 | * 92 | * @see LOW_CUT for valid values 93 | * 94 | * @param[in] freq Cutoff frequency in Hz 95 | */ 96 | void setLowCutHz(double freq) { _lowCutFilter.setCutoff(freq); } 97 | 98 | /** 99 | * Sets the high cut applied before the envelope. 100 | * 101 | * @see HIGH_CUT for valid values 102 | * 103 | * @param[in] freq Cutoff frequency in Hz 104 | */ 105 | void setHighCutHz(double freq) { _highCutFilter.setCutoff(freq); } 106 | /** @} */ 107 | 108 | /** @name Getter Methods */ 109 | /** @{ */ 110 | 111 | /** 112 | * @see setAttackTimeMs 113 | */ 114 | double getAttackTimeMs() const { return _attackTimeMs; } 115 | 116 | /** 117 | * @see setReleaseTimeMs 118 | */ 119 | double getReleaseTimeMs() const { return _releaseTimeMs; } 120 | 121 | /** 122 | * @see setFilterEnabled 123 | */ 124 | bool getFilterEnabled() const { return _filterEnabled; } 125 | 126 | /** 127 | * @see setLowCutHz 128 | */ 129 | double getLowCutHz() const { return _lowCutFilter.getCutoff(); } 130 | 131 | /** 132 | * @see setHighCutHz 133 | */ 134 | double getHighCutHz() const { return _highCutFilter.getCutoff(); } 135 | /** @} */ 136 | 137 | protected: 138 | double _envVal; 139 | 140 | double _attackTimeMs; 141 | double _releaseTimeMs; 142 | 143 | double _attackCoef; 144 | double _releaseCoef; 145 | 146 | bool _filterEnabled; 147 | TPTSVF::TPTSVFilter _lowCutFilter; 148 | TPTSVF::TPTSVFilter _highCutFilter; 149 | 150 | double _sampleRate; 151 | 152 | /** 153 | * Applies the filters if needed, then calls _envGetNextOutputImpl. 154 | */ 155 | inline double _getNextOutputImpl(double inSample) override; 156 | 157 | virtual inline double _envGetNextOutputImpl(double inSample) = 0; 158 | 159 | inline void _resetImpl() override; 160 | 161 | double _calcCoef(double timeMs) { 162 | return exp(log(0.01) / (timeMs * _sampleRate * 0.001)); 163 | } 164 | }; 165 | 166 | void AREnvelopeFollowerBase::setSampleRate(double sampleRate) { 167 | _sampleRate = sampleRate; 168 | _attackCoef = _calcCoef(_attackTimeMs); 169 | _releaseCoef = _calcCoef(_releaseTimeMs); 170 | 171 | _lowCutFilter.setSampleRate(sampleRate); 172 | _highCutFilter.setSampleRate(sampleRate); 173 | } 174 | 175 | void AREnvelopeFollowerBase::setAttackTimeMs(double time) { 176 | _attackTimeMs = Parameters::ATTACK_MS.BoundsCheck(time); 177 | _attackCoef = _calcCoef(_attackTimeMs); 178 | } 179 | 180 | void AREnvelopeFollowerBase::setReleaseTimeMs(double time) { 181 | _releaseTimeMs = Parameters::RELEASE_MS.BoundsCheck(time); 182 | _releaseCoef = _calcCoef(_releaseTimeMs); 183 | } 184 | 185 | double AREnvelopeFollowerBase::_getNextOutputImpl(double inSample) { 186 | if (_filterEnabled) { 187 | _lowCutFilter.processBlock(&inSample, 1); 188 | _highCutFilter.processBlock(&inSample, 1); 189 | } 190 | 191 | return _envGetNextOutputImpl(inSample); 192 | } 193 | 194 | void AREnvelopeFollowerBase::_resetImpl() { 195 | _envVal = 0; 196 | _lowCutFilter.reset(); 197 | _highCutFilter.reset(); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /WECore/WEFilters/AREnvelopeFollowerFullWave.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: AREnveloperFollowerFullWave.h 3 | * 4 | * Created: 17/11/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/CoreMath.h" 25 | #include "AREnvelopeFollowerParameters.h" 26 | #include "AREnvelopeFollowerBase.h" 27 | 28 | namespace WECore::AREnv { 29 | /** 30 | * Implements a full wave rectification envelope follower. 31 | */ 32 | class AREnvelopeFollowerFullWave : public AREnvelopeFollowerBase { 33 | public: 34 | 35 | AREnvelopeFollowerFullWave() = default; 36 | virtual ~AREnvelopeFollowerFullWave() = default; 37 | 38 | /** 39 | * Updates the envelope with the current sample and returns the updated envelope value. Must 40 | * be called for every sample. 41 | * 42 | * @param[in] inSample Sample used to update the envelope state 43 | * 44 | * @return The updated envelope value 45 | */ 46 | virtual double _envGetNextOutputImpl(double inSample) override { 47 | const double tmp = std::abs(inSample); 48 | 49 | if (tmp > _envVal) 50 | { 51 | _envVal = _attackCoef * (_envVal - tmp) + tmp; 52 | } 53 | else 54 | { 55 | _envVal = _releaseCoef * (_envVal - tmp) + tmp; 56 | } 57 | 58 | return _envVal; 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /WECore/WEFilters/AREnvelopeFollowerParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: AREnveloperFollowerParameters.h 3 | * 4 | * Created: 07/05/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | #include "TPTSVFilterParameters.h" 26 | 27 | namespace WECore::AREnv::Parameters { 28 | //@{ 29 | /** 30 | * A parameter which can take any float value between the ranges defined. 31 | * The values passed on construction are in the following order: 32 | * minimum value, 33 | * maximum value, 34 | * default value 35 | */ 36 | const ParameterDefinition::RangedParameter ATTACK_MS(0.1, 10000, 20), 37 | RELEASE_MS(0.1, 10000, 200), 38 | LOW_CUT(TPTSVF::Parameters::CUTOFF.minValue, TPTSVF::Parameters::CUTOFF.maxValue, 0), 39 | HIGH_CUT(TPTSVF::Parameters::CUTOFF.minValue, TPTSVF::Parameters::CUTOFF.maxValue, 20000); 40 | //@} 41 | 42 | const ParameterDefinition::BooleanParameter FILTER_ENABLED(false); 43 | } 44 | -------------------------------------------------------------------------------- /WECore/WEFilters/AREnvelopeFollowerSquareLaw.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: AREnveloperFollowerSquareLaw.h 3 | * 4 | * Created: 17/11/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/CoreMath.h" 25 | #include "AREnvelopeFollowerParameters.h" 26 | #include "AREnvelopeFollowerBase.h" 27 | 28 | namespace WECore::AREnv { 29 | /** 30 | * Implements a real square law envelope follower. 31 | */ 32 | class AREnvelopeFollowerSquareLaw : public AREnvelopeFollowerBase { 33 | public: 34 | 35 | AREnvelopeFollowerSquareLaw() = default; 36 | virtual ~AREnvelopeFollowerSquareLaw() override = default; 37 | 38 | /** 39 | * Updates the envelope with the current sample and returns the updated envelope value. Must 40 | * be called for every sample. 41 | * 42 | * @param[in] inSample Sample used to update the envelope state 43 | * 44 | * @return The updated envelope value 45 | */ 46 | virtual double _envGetNextOutputImpl(double inSample) override { 47 | const double tmp = inSample * inSample; 48 | 49 | if (tmp > _envVal) 50 | { 51 | _envVal = _attackCoef * (_envVal - tmp) + tmp; 52 | } 53 | else 54 | { 55 | _envVal = _releaseCoef * (_envVal - tmp) + tmp; 56 | } 57 | 58 | return std::sqrt(_envVal); 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /WECore/WEFilters/EffectsProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: EffectsProcessor.h 3 | * 4 | * Created: 03/12/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | namespace WECore { 25 | 26 | /** 27 | * Base class for EffectsProcessors with different channel configurations to inherit from. 28 | */ 29 | template 30 | class EffectsProcessorBase { 31 | static_assert(std::is_floating_point::value, 32 | "Must be provided with a floating point template type"); 33 | 34 | public: 35 | EffectsProcessorBase() = default; 36 | virtual ~EffectsProcessorBase() = default; 37 | 38 | /** 39 | * Resets the internal state of the processor. 40 | * 41 | * Override this to reset everything as necessary. 42 | */ 43 | inline virtual void reset() {} 44 | }; 45 | 46 | /** 47 | * Provides a standard interface for effects which process a mono buffer of samples. 48 | */ 49 | template 50 | class EffectsProcessor1in1out : public EffectsProcessorBase { 51 | public: 52 | EffectsProcessor1in1out() = default; 53 | virtual ~EffectsProcessor1in1out() override = default; 54 | 55 | virtual void process1in1out(SampleType* inSamples, size_t numSamples) = 0; 56 | }; 57 | 58 | /** 59 | * Provides a standard interface for effects which process a mono to stereo buffer of samples. 60 | */ 61 | template 62 | class EffectsProcessor1in2out : public EffectsProcessorBase { 63 | public: 64 | EffectsProcessor1in2out() = default; 65 | virtual ~EffectsProcessor1in2out() override = default; 66 | 67 | virtual void process1in2out(SampleType* inSamplesLeft, SampleType* inSamplesRight, size_t numSamples) = 0; 68 | }; 69 | 70 | /** 71 | * Provides a standard interface for effects which process stereo buffers of samples. 72 | */ 73 | template 74 | class EffectsProcessor2in2out : public EffectsProcessorBase { 75 | public: 76 | EffectsProcessor2in2out() = default; 77 | virtual ~EffectsProcessor2in2out() override = default; 78 | 79 | virtual void process2in2out(SampleType* inSamplesLeft, SampleType* inSamplesRight, size_t numSamples) = 0; 80 | }; 81 | } 82 | -------------------------------------------------------------------------------- /WECore/WEFilters/ModulationSource.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: ModulationSource.h 3 | * 4 | * Created: 22/11/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | namespace WECore { 25 | 26 | /** 27 | * Provides a standard interface for modulation sources such as LFOs and envelope followers to 28 | * follow. 29 | * 30 | * This interface only defines how to get output from the modulation source and reset it, and 31 | * say nothing about setting its parameters, sample rate, etc as these will vary. 32 | */ 33 | template 34 | class ModulationSource { 35 | static_assert(std::is_floating_point::value, 36 | "Must be provided with a floating point template type"); 37 | 38 | public: 39 | ModulationSource() : _cachedOutput(0) {} 40 | virtual ~ModulationSource() = default; 41 | 42 | /** 43 | * Given the provided audio sample, calculates the next output value and advances the 44 | * internal state (if applicable). 45 | */ 46 | inline SampleType getNextOutput(SampleType inSample); 47 | 48 | /** 49 | * Returns the most recent output of getNextOutput without advancing the internal state. 50 | */ 51 | virtual SampleType getLastOutput() const { return _cachedOutput; } 52 | 53 | /** 54 | * Resets the internal state of the modulation source. 55 | */ 56 | inline void reset(); 57 | 58 | protected: 59 | SampleType _cachedOutput; 60 | 61 | /** 62 | * Must be overriden by the inheriting class to provide the specific implementation of this 63 | * modulation source. 64 | * 65 | * The implementation may or may not need to use the provided audio sample. 66 | */ 67 | virtual SampleType _getNextOutputImpl(SampleType inSample) = 0; 68 | 69 | /** 70 | * Must be overriden by the inheriting class to reset the internat state as required. 71 | */ 72 | virtual void _resetImpl() = 0; 73 | }; 74 | 75 | template 76 | SampleType ModulationSource::getNextOutput(SampleType inSample) { 77 | _cachedOutput = _getNextOutputImpl(inSample); 78 | return _cachedOutput; 79 | } 80 | 81 | template 82 | void ModulationSource::reset() { 83 | _resetImpl(); 84 | _cachedOutput = 0; 85 | } 86 | 87 | template 88 | struct ModulationSourceWrapper { 89 | std::shared_ptr> source; 90 | double amount; 91 | }; 92 | 93 | template 94 | SampleType calcModValue(const std::vector>& sources) { 95 | SampleType retVal {0}; 96 | 97 | for (const ModulationSourceWrapper& source : sources) { 98 | retVal += source.source->getLastOutput() * source.amount; 99 | } 100 | 101 | return retVal; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /WECore/WEFilters/SimpleCompressorParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: SimpleCompressorParameters.h 3 | * 4 | * Created: 07/05/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | 26 | namespace WECore::SimpleCompressor::Parameters { 27 | //@{ 28 | /** 29 | * A parameter which can take any float value between the ranges defined. 30 | * The values passed on construction are in the following order: 31 | * minimum value, 32 | * maximum value, 33 | * default value 34 | */ 35 | const ParameterDefinition::RangedParameter ATTACK_MS(0.1, 500, 10), 36 | RELEASE_MS(1, 5000, 100), 37 | THRESHOLD(-60, 0, 0), 38 | RATIO(1, 30, 2), 39 | KNEE_WIDTH(1, 10, 2); 40 | 41 | //@} 42 | } 43 | -------------------------------------------------------------------------------- /WECore/WEFilters/StereoWidthProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: StereoWidthProcessor.h 3 | * 4 | * Created: 03/12/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include 25 | 26 | #include "EffectsProcessor.h" 27 | #include "StereoWidthProcessorParameters.h" 28 | 29 | namespace WECore::StereoWidth { 30 | 31 | /** 32 | * Processor which can apply stereo widening or narrowing. 33 | */ 34 | template 35 | class StereoWidthProcessor : public EffectsProcessor2in2out { 36 | public: 37 | StereoWidthProcessor() : _width(Parameters::WIDTH.defaultValue) {} 38 | virtual ~StereoWidthProcessor() override = default; 39 | 40 | /** 41 | * Sets the stereo width. 42 | * 0 = mono 43 | * 1 = neutral, no processing 44 | * 2 = highest width 45 | * 46 | * @param val The width to be applied. 47 | * 48 | * @see WIDTH for valid values 49 | */ 50 | void setWidth(double val) { _width = Parameters::WIDTH.BoundsCheck(val); } 51 | 52 | /** 53 | * @see setWidth 54 | */ 55 | double getWidth() const { return _width; } 56 | 57 | 58 | inline virtual void process2in2out(SampleType* inSamplesLeft, 59 | SampleType* inSamplesRight, 60 | size_t numSamples) override; 61 | 62 | private: 63 | double _width; 64 | }; 65 | 66 | template 67 | void StereoWidthProcessor::process2in2out(SampleType* inSamplesLeft, 68 | SampleType* inSamplesRight, 69 | size_t numSamples) { 70 | 71 | for (size_t index {0}; index < numSamples; index++) { 72 | 73 | double mid {(inSamplesLeft[index] + inSamplesRight[index]) * 0.5}; 74 | double side {(inSamplesRight[index] - inSamplesLeft[index]) * (_width / 2)}; 75 | 76 | inSamplesLeft[index] = mid - side; 77 | inSamplesRight[index] = mid + side; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /WECore/WEFilters/StereoWidthProcessorParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: StereoWidthProcessorParameters.h 3 | * 4 | * Created: 03/12/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | 26 | namespace WECore::StereoWidth::Parameters { 27 | //@{ 28 | /** 29 | * A parameter which can take any float value between the ranges defined. 30 | * The values passed on construction are in the following order: 31 | * minimum value, 32 | * maximum value, 33 | * default value 34 | */ 35 | const ParameterDefinition::RangedParameter WIDTH(0, 2, 1); 36 | //@} 37 | } 38 | -------------------------------------------------------------------------------- /WECore/WEFilters/TPTSVFilter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: TPTSVFilter.h 3 | * 4 | * Created: 22/12/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/CoreMath.h" 25 | #include "WEFilters/TPTSVFilterParameters.h" 26 | 27 | namespace WECore::TPTSVF { 28 | /** 29 | * A state variable filter from a topology-preserving transform. 30 | * 31 | * To use this class, simply call reset, and the process methods as necessary, using the provided 32 | * getter and setter methods to manipulate parameters. 33 | * 34 | * Internally relies on the parameters provided in TPTSVFilterParameters.h 35 | * 36 | * Based on a talk given by Ivan Cohen: https://www.youtube.com/watch?v=esjHXGPyrhg 37 | */ 38 | template 39 | class TPTSVFilter { 40 | static_assert(std::is_floating_point::value, 41 | "Must be provided with a floating point template type"); 42 | 43 | public: 44 | TPTSVFilter() : _sampleRate(44100), 45 | _cutoffHz(Parameters::CUTOFF.defaultValue), 46 | _q(Parameters::Q.defaultValue), 47 | _gain(Parameters::GAIN.defaultValue), 48 | _s1(0), 49 | _s2(0), 50 | _g(0), 51 | _h(0), 52 | _mode(Parameters::FILTER_MODE.BYPASS) { 53 | _calculateCoefficients(); 54 | } 55 | 56 | virtual ~TPTSVFilter() = default; 57 | 58 | /** 59 | * Applies the filtering to a buffer of samples. 60 | * Expect seg faults or other memory issues if arguements passed are incorrect. 61 | * 62 | * @param[out] inSamples Pointer to the first sample of the left channel's buffer 63 | * @param[in] numSamples Number of samples in the buffer 64 | */ 65 | void processBlock(T* inSamples, size_t numSamples); 66 | 67 | /** 68 | * Resets filter coefficients. 69 | * Call this whenever the audio stream is interrupted (ie. the playhead is moved) 70 | */ 71 | void reset() { 72 | _s1 = 0; 73 | _s2 = 0; 74 | } 75 | 76 | /** @name Getter Methods */ 77 | /** @{ */ 78 | 79 | int getMode() const { return _mode; } 80 | double getCutoff() const { return _cutoffHz; } 81 | double getQ() const { return _q; } 82 | double getGain() const { return _gain; } 83 | 84 | /** @} */ 85 | 86 | /** @name Setter Methods */ 87 | /** @{ */ 88 | 89 | void setMode(int val) { _mode = Parameters::FILTER_MODE.BoundsCheck(val); } 90 | inline void setCutoff(double val); 91 | inline void setQ(double val); 92 | void setGain(double val) { _gain = Parameters::GAIN.BoundsCheck(val); } 93 | inline void setSampleRate(double val); 94 | 95 | /** @} */ 96 | 97 | TPTSVFilter clone() const { 98 | return TPTSVFilter(*this); 99 | } 100 | 101 | private: 102 | double _sampleRate, 103 | _cutoffHz, 104 | _q, 105 | _gain; 106 | 107 | T _s1, 108 | _s2, 109 | _g, 110 | _h; 111 | 112 | int _mode; 113 | 114 | void _calculateCoefficients(); 115 | 116 | TPTSVFilter(const TPTSVFilter& other) { 117 | _sampleRate = other._sampleRate; 118 | _cutoffHz = other._cutoffHz; 119 | _q = other._q; 120 | _gain = other._gain; 121 | 122 | _s1 = other._s1; 123 | _s2 = other._s2; 124 | _g = other._g; 125 | _h = other._h; 126 | 127 | _mode = other._mode; 128 | } 129 | }; 130 | 131 | template 132 | void TPTSVFilter::processBlock(T* inSamples, size_t numSamples) { 133 | 134 | if (_mode != Parameters::FILTER_MODE.BYPASS) { 135 | 136 | for (size_t idx {0}; idx < numSamples; idx++) { 137 | const T sample {inSamples[idx]}; 138 | 139 | const T yH {static_cast(_h * (sample - (1.0f / _q + _g) * _s1 - _s2))}; 140 | 141 | const T yB {static_cast(_g * yH + _s1)}; 142 | _s1 = _g * yH + yB; 143 | 144 | const T yL {static_cast(_g * yB + _s2)}; 145 | _s2 = _g * yB + yL; 146 | 147 | switch (_mode) { 148 | case Parameters::ModeParameter::PEAK: 149 | inSamples[idx] = yB * static_cast(_gain); 150 | break; 151 | 152 | case Parameters::ModeParameter::HIGHPASS: 153 | inSamples[idx] = yH * static_cast(_gain); 154 | break; 155 | 156 | default: 157 | inSamples[idx] = yL * static_cast(_gain); 158 | break; 159 | } 160 | } 161 | } 162 | } 163 | 164 | template 165 | void TPTSVFilter::setCutoff(double val) { 166 | _cutoffHz = Parameters::CUTOFF.BoundsCheck(val); 167 | _calculateCoefficients(); 168 | } 169 | 170 | template 171 | void TPTSVFilter::setQ(double val) { 172 | _q = Parameters::Q.BoundsCheck(val); 173 | _calculateCoefficients(); 174 | } 175 | 176 | template 177 | void TPTSVFilter::setSampleRate(double val) { 178 | _sampleRate = val; 179 | _calculateCoefficients(); 180 | } 181 | 182 | template 183 | void TPTSVFilter::_calculateCoefficients() { 184 | _g = static_cast(std::tan(CoreMath::DOUBLE_PI * _cutoffHz / _sampleRate)); 185 | _h = static_cast(1.0 / (1 + _g / _q + _g * _g)); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /WECore/WEFilters/TPTSVFilterParameters.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: TPTSVFilterParameters.h 3 | * 4 | * Created: 25/12/2016 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #pragma once 23 | 24 | #include "General/ParameterDefinition.h" 25 | 26 | namespace WECore::TPTSVF::Parameters { 27 | 28 | class ModeParameter : public ParameterDefinition::BaseParameter { 29 | public: 30 | ModeParameter() : ParameterDefinition::BaseParameter::BaseParameter( 31 | BYPASS, HIGHPASS, BYPASS) { } 32 | 33 | static constexpr int BYPASS = 1, 34 | LOWPASS = 2, 35 | PEAK = 3, 36 | HIGHPASS = 4; 37 | }; 38 | const ModeParameter FILTER_MODE; 39 | 40 | //@{ 41 | /** 42 | * A parameter which can take any float value between the ranges defined. 43 | * The values passed on construction are in the following order: 44 | * minimum value, 45 | * maximum value, 46 | * default value 47 | */ 48 | const ParameterDefinition::RangedParameter CUTOFF(0, 20000, 20), 49 | Q(0.1, 20, 0.5), 50 | GAIN(0, 2, 1); 51 | //@} 52 | 53 | } 54 | -------------------------------------------------------------------------------- /WECore/WEFilters/Tests/AREnvelopeFollowerTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: AREnvelopeFollowerTests.cpp 3 | * 4 | * Created: 03/06/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "WEFilters/AREnvelopeFollowerSquareLaw.h" 24 | 25 | SCENARIO("AREnvelopeFollowerSquareLaw: Parameters can be set and retrieved correctly") { 26 | GIVEN("A new AREnvelopeFollower object") { 27 | WECore::AREnv::AREnvelopeFollowerSquareLaw mEnv; 28 | 29 | WHEN("Nothing is changed") { 30 | THEN("Parameters have their default values") { 31 | CHECK(mEnv.getAttackTimeMs() == Approx(20.0)); 32 | CHECK(mEnv.getReleaseTimeMs() == Approx(200.0)); 33 | CHECK(mEnv.getFilterEnabled() == false); 34 | CHECK(mEnv.getLowCutHz() == Approx(0.0)); 35 | CHECK(mEnv.getHighCutHz() == Approx(20000.0)); 36 | } 37 | } 38 | 39 | WHEN("All parameters are changed to unique values") { 40 | mEnv.setAttackTimeMs(21.0); 41 | mEnv.setReleaseTimeMs(22.0); 42 | mEnv.setFilterEnabled(true); 43 | mEnv.setLowCutHz(23.0); 44 | mEnv.setHighCutHz(24.0); 45 | 46 | THEN("They all get their correct unique values") { 47 | CHECK(mEnv.getAttackTimeMs() == Approx(21.0)); 48 | CHECK(mEnv.getReleaseTimeMs() == Approx(22.0)); 49 | CHECK(mEnv.getFilterEnabled() == true); 50 | CHECK(mEnv.getLowCutHz() == Approx(23.0)); 51 | CHECK(mEnv.getHighCutHz() == Approx(24.0)); 52 | } 53 | } 54 | } 55 | } 56 | 57 | SCENARIO("AREnvelopeFollowerSquareLaw: Parameters enforce their bounds correctly") { 58 | GIVEN("A new AREnvelopeFollower object") { 59 | WECore::AREnv::AREnvelopeFollowerSquareLaw mEnv; 60 | 61 | WHEN("All parameter values are too low") { 62 | mEnv.setAttackTimeMs(0); 63 | mEnv.setReleaseTimeMs(0); 64 | mEnv.setLowCutHz(-1); 65 | mEnv.setHighCutHz(-1); 66 | 67 | THEN("Parameters enforce their lower bounds") { 68 | CHECK(mEnv.getAttackTimeMs() == Approx(0.1)); 69 | CHECK(mEnv.getReleaseTimeMs() == Approx(0.1)); 70 | CHECK(mEnv.getLowCutHz() == Approx(0.0)); 71 | CHECK(mEnv.getHighCutHz() == Approx(0.0)); 72 | } 73 | } 74 | 75 | WHEN("All parameter values are too high") { 76 | mEnv.setAttackTimeMs(10001); 77 | mEnv.setReleaseTimeMs(10001); 78 | mEnv.setLowCutHz(20001); 79 | mEnv.setHighCutHz(20001); 80 | 81 | THEN("Parameters enforce their upper bounds") { 82 | CHECK(mEnv.getAttackTimeMs() == Approx(10000)); 83 | CHECK(mEnv.getReleaseTimeMs() == Approx(10000)); 84 | CHECK(mEnv.getLowCutHz() == Approx(20000.0)); 85 | CHECK(mEnv.getHighCutHz() == Approx(20000.0)); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /WECore/WEFilters/Tests/SimpleCompressorTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: TPTSVFilterTests.cpp 3 | * 4 | * Created: 04/12/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "WEFilters/SimpleCompressor.h" 24 | 25 | namespace Comp = WECore::SimpleCompressor; 26 | 27 | SCENARIO("SimpleCompressor: Parameters can be set and retrieved correctly") { 28 | GIVEN("A new SimpleCompressor object") { 29 | 30 | Comp::SimpleCompressor compressor; 31 | 32 | WHEN("Nothing is changed") { 33 | THEN("Parameters have their default values") { 34 | CHECK(compressor.getDirection() == Comp::Direction::DOWNWARD); 35 | CHECK(compressor.getAttack() == Approx(10.0)); 36 | CHECK(compressor.getRelease() == Approx(100.0)); 37 | CHECK(compressor.getThreshold() == Approx(0.0)); 38 | CHECK(compressor.getRatio() == Approx(2.0)); 39 | CHECK(compressor.getKneeWidth() == Approx(2.0)); 40 | } 41 | } 42 | 43 | WHEN("All parameters are changed to unique values") { 44 | compressor.setDirection(Comp::Direction::UPWARD); 45 | compressor.setAttack(0.11); 46 | compressor.setRelease(1.1); 47 | compressor.setThreshold(-10.0); 48 | compressor.setRatio(1.2); 49 | compressor.setKneeWidth(1.3); 50 | 51 | THEN("They all get their correct unique values") { 52 | CHECK(compressor.getDirection() == Comp::Direction::UPWARD); 53 | CHECK(compressor.getAttack() == Approx(0.11)); 54 | CHECK(compressor.getRelease() == Approx(1.1)); 55 | CHECK(compressor.getThreshold() == Approx(-10.0)); 56 | CHECK(compressor.getRatio() == Approx(1.2)); 57 | CHECK(compressor.getKneeWidth() == Approx(1.3)); 58 | } 59 | } 60 | } 61 | } 62 | 63 | SCENARIO("SimpleCompressor: Parameters enforce their bounds correctly") { 64 | GIVEN("A new SimpleCompressor object") { 65 | 66 | Comp::SimpleCompressor compressor; 67 | 68 | WHEN("All parameter values are too low") { 69 | compressor.setAttack(-10); 70 | compressor.setRelease(-10); 71 | compressor.setThreshold(-100); 72 | compressor.setRatio(-10); 73 | compressor.setKneeWidth(-10); 74 | 75 | THEN("Parameters enforce their lower bounds") { 76 | CHECK(compressor.getAttack() == Approx(0.1)); 77 | CHECK(compressor.getRelease() == Approx(1.0)); 78 | CHECK(compressor.getThreshold() == Approx(-60.0)); 79 | CHECK(compressor.getRatio() == Approx(1.0)); 80 | CHECK(compressor.getKneeWidth() == Approx(1.0)); 81 | } 82 | } 83 | 84 | WHEN("All parameter values are too high") { 85 | compressor.setAttack(1000); 86 | compressor.setRelease(10000); 87 | compressor.setThreshold(10); 88 | compressor.setRatio(100); 89 | compressor.setKneeWidth(100); 90 | 91 | THEN("Parameters enforce their upper bounds") { 92 | CHECK(compressor.getAttack() == Approx(500)); 93 | CHECK(compressor.getRelease() == Approx(5000)); 94 | CHECK(compressor.getThreshold() == Approx(0)); 95 | CHECK(compressor.getRatio() == Approx(30)); 96 | CHECK(compressor.getKneeWidth() == Approx(10)); 97 | } 98 | } 99 | } 100 | } 101 | 102 | SCENARIO("SimpleCompressor: Silence in = silence out") { 103 | GIVEN("A SimpleCompressor and a buffer of silent samples") { 104 | 105 | std::vector buffer(1024); 106 | Comp::SimpleCompressor compressor; 107 | 108 | WHEN("The silence samples are processed") { 109 | // fill the buffer 110 | std::fill(buffer.begin(), buffer.end(), 0); 111 | 112 | // do processing 113 | compressor.process1in1out(&buffer[0], buffer.size()); 114 | 115 | THEN("The output is silence") { 116 | for (size_t iii {0}; iii < buffer.size(); iii++) { 117 | CHECK(buffer[iii] == Approx(0.0)); 118 | } 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /WECore/WEFilters/Tests/StereoWidthProcessorTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: StereoWidthProcessorTests.cpp 3 | * 4 | * Created: 05/12/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "General/CoreMath.h" 24 | #include "TestUtils.h" 25 | #include "WEFilters/StereoWidthProcessor.h" 26 | 27 | SCENARIO("StereoWidthProcessor: Parameters can be set and retrieved correctly") { 28 | GIVEN("A new StereoWidthProcessor object") { 29 | 30 | WECore::StereoWidth::StereoWidthProcessor processor; 31 | 32 | WHEN("Nothing is changed") { 33 | THEN("Parameters have their default values") { 34 | CHECK(processor.getWidth() == Approx(1.0)); 35 | } 36 | } 37 | 38 | WHEN("All parameters are changed to unique values") { 39 | processor.setWidth(1.5); 40 | 41 | THEN("They all get their correct unique values") { 42 | CHECK(processor.getWidth() == Approx(1.5)); 43 | } 44 | } 45 | } 46 | } 47 | 48 | SCENARIO("StereoWidthProcessor: Parameters enforce their bounds correctly") { 49 | GIVEN("A new StereoWidthProcessor object") { 50 | 51 | WECore::StereoWidth::StereoWidthProcessor processor; 52 | 53 | WHEN("All parameter values are too low") { 54 | processor.setWidth(-10); 55 | 56 | THEN("Parameters enforce their lower bounds") { 57 | CHECK(processor.getWidth() == Approx(0.0)); 58 | } 59 | } 60 | 61 | WHEN("All parameter values are too high") { 62 | processor.setWidth(10); 63 | 64 | 65 | THEN("Parameters enforce their upper bounds") { 66 | CHECK(processor.getWidth() == Approx(2.0)); 67 | 68 | } 69 | } 70 | } 71 | } 72 | 73 | SCENARIO("StereoWidthProcessor: Silence in = silence out") { 74 | GIVEN("A StereoWidthProcessor and a buffer of silent samples") { 75 | 76 | std::vector leftBuffer(1024); 77 | std::vector rightBuffer(1024); 78 | WECore::StereoWidth::StereoWidthProcessor processor; 79 | 80 | // Fill the buffer 81 | std::fill(leftBuffer.begin(), leftBuffer.end(), 0); 82 | std::fill(rightBuffer.begin(), rightBuffer.end(), 0); 83 | 84 | WHEN("The silence samples are processed") { 85 | 86 | // Do processing 87 | processor.process2in2out(&leftBuffer[0], &rightBuffer[0], leftBuffer.size()); 88 | 89 | THEN("The output is silence") { 90 | for (size_t index {0}; index < leftBuffer.size(); index++) { 91 | CHECK(leftBuffer[index] == Approx(0.0)); 92 | CHECK(rightBuffer[index] == Approx(0.0)); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | 99 | SCENARIO("StereoWidthProcessor: Neutral width position") { 100 | GIVEN("A StereoWidthProcessor and a buffer of sine samples") { 101 | 102 | std::vector leftBuffer(1024); 103 | std::vector rightBuffer(1024); 104 | WECore::StereoWidth::StereoWidthProcessor processor; 105 | 106 | // Fill the buffers 107 | WECore::TestUtils::generateSine(leftBuffer, 44100, 1000); 108 | std::copy(leftBuffer.begin(), leftBuffer.end() , rightBuffer.begin()); 109 | 110 | // Set the expected output 111 | std::vector expectedOutput(1024); 112 | std::copy(leftBuffer.begin(), leftBuffer.end() , expectedOutput.begin()); 113 | 114 | WHEN("The samples are processed") { 115 | // Do processing 116 | processor.process2in2out(&leftBuffer[0], &rightBuffer[0], leftBuffer.size()); 117 | 118 | THEN("The output is silence") { 119 | for (size_t index {0}; index < leftBuffer.size(); index++) { 120 | CHECK(leftBuffer[index] == Approx(expectedOutput[index])); 121 | CHECK(rightBuffer[index] == Approx(expectedOutput[index])); 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | SCENARIO("StereoWidthProcessor: Stereo to full mono") { 129 | GIVEN("A StereoWidthProcessor and a buffer of sine samples") { 130 | 131 | std::vector leftBuffer(1024); 132 | std::vector rightBuffer(1024); 133 | WECore::StereoWidth::StereoWidthProcessor processor; 134 | 135 | // Fill the buffers with different frequency sines 136 | WECore::TestUtils::generateSine(leftBuffer, 44100, 1000); 137 | WECore::TestUtils::generateSine(leftBuffer, 44100, 1500); 138 | 139 | WHEN("The width is set to mono and samples are processed") { 140 | 141 | processor.setWidth(0); 142 | 143 | // Do processing 144 | processor.process2in2out(&leftBuffer[0], &rightBuffer[0], leftBuffer.size()); 145 | 146 | THEN("The output is the same for each channel") { 147 | for (size_t index {0}; index < leftBuffer.size(); index++) { 148 | CHECK(leftBuffer[index] == Approx(rightBuffer[index])); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /WECore/WEFilters/Tests/TPTSVFilterTests.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * File: TPTSVFilterTests.cpp 3 | * 4 | * Created: 09/05/2017 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include "catch.hpp" 23 | #include "WEFilters/TPTSVFilter.h" 24 | 25 | SCENARIO("TPTSVFilter: Parameters can be set and retrieved correctly") { 26 | GIVEN("A new TPTSVFilter object") { 27 | WECore::TPTSVF::TPTSVFilter mFilter; 28 | 29 | WHEN("Nothing is changed") { 30 | THEN("Parameters have their default values") { 31 | CHECK(mFilter.getMode() == 1); 32 | CHECK(mFilter.getCutoff() == Approx(20.0)); 33 | CHECK(mFilter.getQ() == Approx(0.5)); 34 | CHECK(mFilter.getGain() == Approx(1.0)); 35 | } 36 | } 37 | 38 | WHEN("All parameters are changed to unique values") { 39 | mFilter.setMode(2); 40 | mFilter.setCutoff(21); 41 | mFilter.setQ(0.6); 42 | mFilter.setGain(1.1); 43 | 44 | THEN("They all get their correct unique values") { 45 | CHECK(mFilter.getMode() == 2); 46 | CHECK(mFilter.getCutoff() == Approx(21.0)); 47 | CHECK(mFilter.getQ() == Approx(0.6)); 48 | CHECK(mFilter.getGain() == Approx(1.1)); 49 | } 50 | } 51 | } 52 | } 53 | 54 | SCENARIO("TPTSVFilter: Parameters enforce their bounds correctly") { 55 | GIVEN("A new TPTSVFilter object") { 56 | WECore::TPTSVF::TPTSVFilter mFilter; 57 | 58 | WHEN("All parameter values are too low") { 59 | mFilter.setMode(-1); 60 | mFilter.setCutoff(-1); 61 | mFilter.setQ(0); 62 | mFilter.setGain(-1); 63 | 64 | THEN("Parameters enforce their lower bounds") { 65 | CHECK(mFilter.getMode() == 1); 66 | CHECK(mFilter.getCutoff() == Approx(0.0)); 67 | CHECK(mFilter.getQ() == Approx(0.1)); 68 | CHECK(mFilter.getGain() == Approx(0.0)); 69 | } 70 | } 71 | 72 | WHEN("All parameter values are too high") { 73 | mFilter.setMode(5); 74 | mFilter.setCutoff(20001); 75 | mFilter.setQ(21); 76 | mFilter.setGain(3); 77 | 78 | THEN("Parameters enforce their upper bounds") { 79 | CHECK(mFilter.getMode() == 4); 80 | CHECK(mFilter.getCutoff() == Approx(20000.0)); 81 | CHECK(mFilter.getQ() == Approx(20.0)); 82 | CHECK(mFilter.getGain() == Approx(2.0)); 83 | } 84 | } 85 | } 86 | } 87 | 88 | SCENARIO("TPTSVFilter: Silence in = silence out") { 89 | GIVEN("A TPTSVFilter and a buffer of silent samples") { 90 | std::vector buffer(1024); 91 | WECore::TPTSVF::TPTSVFilter mFilter; 92 | 93 | WHEN("The silence samples are processed") { 94 | // fill the buffer 95 | std::fill(buffer.begin(), buffer.end(), 0); 96 | 97 | // do processing 98 | mFilter.processBlock(&buffer[0], buffer.size()); 99 | 100 | THEN("The output is silence") { 101 | for (size_t iii {0}; iii < buffer.size(); iii++) { 102 | CHECK(buffer[iii] == Approx(0.0)); 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | SCENARIO("TPTSVFilter: Clone works correctly") { 110 | GIVEN("A TPTSVFilter and a buffer of samples") { 111 | auto generateBuffer = []() { 112 | std::vector buffer(1024); 113 | std::fill(buffer.begin(), buffer.end(), 0); 114 | std::fill(buffer.begin() + 300, buffer.end(), 0.5); 115 | std::fill(buffer.begin() + 600, buffer.end(), 0.7); 116 | return buffer; 117 | }; 118 | 119 | std::vector initialBuffer = generateBuffer(); 120 | WECore::TPTSVF::TPTSVFilter filter; 121 | 122 | // Set some unique values so we can test for them later 123 | filter.setMode(WECore::TPTSVF::Parameters::FILTER_MODE.HIGHPASS); 124 | filter.setCutoff(1001); 125 | filter.setQ(1.1); 126 | filter.setGain(0.8); 127 | filter.setSampleRate(48000); 128 | 129 | // Set up some internal state 130 | filter.processBlock(&initialBuffer[0], initialBuffer.size()); 131 | 132 | WHEN("It is cloned") { 133 | WECore::TPTSVF::TPTSVFilter clonedFilter = filter.clone(); 134 | 135 | THEN("The cloned filter is equal to the original") { 136 | CHECK(clonedFilter.getMode() == filter.getMode()); 137 | CHECK(clonedFilter.getCutoff() == Approx(filter.getCutoff())); 138 | CHECK(clonedFilter.getQ() == Approx(filter.getQ())); 139 | CHECK(clonedFilter.getGain() == Approx(filter.getGain())); 140 | // CHECK(clonedFilter.getSampleRate() == Approx(filter.getSampleRate())); 141 | 142 | // Check internal state 143 | std::vector buffer1 = generateBuffer(); 144 | filter.processBlock(&buffer1[0], buffer1.size()); 145 | 146 | std::vector buffer2 = generateBuffer(); 147 | clonedFilter.processBlock(&buffer2[0], buffer2.size()); 148 | 149 | for (size_t index {0}; index < buffer1.size(); index++) { 150 | CHECK(buffer1[index] == Approx(buffer2[index])); 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /WECore/WEFilters/Tests/TestUtils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * File: TestUtils.h 3 | * 4 | * Created: 05/12/2020 5 | * 6 | * This file is part of WECore. 7 | * 8 | * WECore is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * WECore is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with WECore. If not, see . 20 | */ 21 | 22 | #include 23 | 24 | #include "General/CoreMath.h" 25 | 26 | namespace WECore::TestUtils { 27 | static void generateSine(std::vector& buffer, double sampleRate, double frequency) { 28 | 29 | const double samplesPerCycle {sampleRate / frequency}; 30 | 31 | std::generate(buffer.begin(), 32 | buffer.end(), 33 | [index = 0, samplesPerCycle]() mutable {return std::sin(CoreMath::LONG_TAU * (index++ / samplesPerCycle));} ); 34 | } 35 | } 36 | --------------------------------------------------------------------------------