├── .github
├── codecov.yml
└── workflows
│ ├── publish-doxygen.yml
│ ├── static-analysis.yml
│ └── build-and-test.yml
├── .gitignore
├── Scripts
├── get_code_cov.sh
├── StaticAnalysis.sh
└── BuildAndTest.sh
├── WECore
├── Tests
│ └── catchMain.cpp
├── WEFilters
│ ├── Tests
│ │ ├── TestUtils.h
│ │ ├── AREnvelopeFollowerTests.cpp
│ │ ├── SimpleCompressorTests.cpp
│ │ ├── StereoWidthProcessorTests.cpp
│ │ └── TPTSVFilterTests.cpp
│ ├── StereoWidthProcessorParameters.h
│ ├── SimpleCompressorParameters.h
│ ├── AREnvelopeFollowerParameters.h
│ ├── TPTSVFilterParameters.h
│ ├── AREnvelopeFollowerFullWave.h
│ ├── AREnvelopeFollowerSquareLaw.h
│ ├── StereoWidthProcessor.h
│ ├── EffectsProcessor.h
│ ├── ModulationSource.h
│ ├── TPTSVFilter.h
│ └── AREnvelopeFollowerBase.h
├── SongbirdFilters
│ ├── Formant.h
│ ├── SongbirdFiltersParameters.h
│ ├── SongbirdFormantFilter.h
│ └── Tests
│ │ └── SongbirdFilterModuleTests.cpp
├── CarveDSP
│ ├── Tests
│ │ ├── CarveNoiseFilterTests.cpp
│ │ └── DSPUnitParameterTests.cpp
│ ├── CarveParameters.h
│ └── CarveNoiseFilter.h
├── General
│ ├── CoreMath.h
│ ├── UpdateChecker.h
│ ├── ParameterDefinition.h
│ └── AudioSpinMutex.h
├── MONSTRFilters
│ └── MONSTRParameters.h
├── CoreJUCEPlugin
│ ├── ParameterUpdateHandler.h
│ ├── CustomParameter.h
│ ├── LookAndFeelMixins
│ │ ├── GroupComponentV2.h
│ │ ├── ComboBoxV2.h
│ │ ├── LookAndFeelMixins.h
│ │ ├── RotarySliderV2.h
│ │ ├── PopupMenuV2.h
│ │ ├── ButtonV2.h
│ │ ├── MidAnchoredRotarySlider.h
│ │ └── LinearSliderV2.h
│ ├── CoreProcessorEditor.h
│ ├── TooltipLabelUpdater.h
│ └── LabelReadoutSlider.h
└── RichterLFO
│ ├── RichterParameters.h
│ ├── UI
│ └── RichterWaveViewer.h
│ ├── RichterWavetables.h
│ ├── RichterLFOPair.h
│ └── Tests
│ └── RichterLFOPairTests.cpp
├── Notes
└── spinlock-instructions.txt
├── PerformanceLogs
├── 51f0543.log
├── 074dcf3_to_6600be6.log
└── d8fe46e.log
├── CMakeLists.txt
└── README.md
/.github/codecov.yml:
--------------------------------------------------------------------------------
1 | ignore:
2 | - "WECore/Tests"
3 | - "DSPFilters"
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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@v4
17 | if: failure()
18 | with:
19 | name: clang-tidy
20 | path: clang-tidy.txt
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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@v4
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@v4
32 | with:
33 | name: callgrind-gcc
34 | path: build/callgrind.out.*
35 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/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()
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/jd-13/WE-Core/actions/workflows/build-and-test.yml)
2 | [](https://github.com/jd-13/WE-Core/actions/workflows/static-analysis.yml)
3 | [](https://codecov.io/gh/jd-13/WE-Core)
4 | [](https://jd-13.github.io/WE-Core/)
5 | 
6 | 
7 | [](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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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 | #if TARGET_OS_IPHONE
92 | _defaultString += "iOS";
93 | #else
94 | _defaultString += "macOS";
95 | #endif
96 | #elif __linux__
97 | _defaultString += "Linux";
98 | #else
99 | #error "Unknown OS"
100 | #endif
101 |
102 | // Arch
103 | _defaultString += " ";
104 | #if defined(__x86_64__) || defined(_M_AMD64)
105 | _defaultString += "x86_64";
106 | #elif defined(__aarch64__) || defined(_M_ARM64)
107 | _defaultString += "arm64";
108 | #else
109 | #error "Unknown arch"
110 | #endif
111 |
112 | // Demo
113 | if (isDemo) {
114 | _defaultString += " (DEMO)";
115 | }
116 |
117 | _targetLabel->setText(_defaultString, juce::dontSendNotification);
118 | }
119 |
120 | void TooltipLabelUpdater::mouseEnter(const juce::MouseEvent& event) {
121 | if (_targetLabel != nullptr) {
122 | juce::TooltipClient* tooltipClient = dynamic_cast(event.eventComponent);
123 |
124 | if (tooltipClient != nullptr) {
125 | const juce::String displayString = tooltipClient->getTooltip().isEmpty() ? _defaultString : tooltipClient->getTooltip();
126 | _targetLabel->setText(displayString, juce::dontSendNotification);
127 | }
128 | }
129 | }
130 |
131 | void TooltipLabelUpdater::mouseExit(const juce::MouseEvent& /*event*/) {
132 | if (_targetLabel != nullptr) {
133 | _targetLabel->setText(_defaultString, juce::dontSendNotification);
134 | }
135 | }
136 |
137 | void TooltipLabelUpdater::refreshTooltip(juce::Component* component) {
138 | if (_targetLabel != nullptr) {
139 | juce::TooltipClient* tooltipClient = dynamic_cast(component);
140 |
141 | if (tooltipClient != nullptr) {
142 | const juce::String displayString = tooltipClient->getTooltip().isEmpty() ? _defaultString : tooltipClient->getTooltip();
143 | _targetLabel->setText(displayString, juce::dontSendNotification);
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------