react-native-tuner-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +188 -0
- package/TunerEngine.podspec +32 -0
- package/android/build.gradle +86 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/cpp/CMakeLists.txt +31 -0
- package/android/src/main/cpp/OboeAudioSource.cpp +101 -0
- package/android/src/main/cpp/OboeAudioSource.h +41 -0
- package/android/src/main/cpp/TunerEngineJni.cpp +220 -0
- package/android/src/main/java/com/tunerengine/TunerEngineModule.kt +183 -0
- package/android/src/main/java/com/tunerengine/TunerEnginePackage.kt +31 -0
- package/cpp/CMakeLists.txt +25 -0
- package/cpp/build/CMakeCache.txt +347 -0
- package/cpp/build/CMakeFiles/4.3.2/CMakeCXXCompiler.cmake +102 -0
- package/cpp/build/CMakeFiles/4.3.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
- package/cpp/build/CMakeFiles/4.3.2/CMakeSystem.cmake +15 -0
- package/cpp/build/CMakeFiles/4.3.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +949 -0
- package/cpp/build/CMakeFiles/4.3.2/CompilerIdCXX/a.out +0 -0
- package/cpp/build/CMakeFiles/4.3.2/CompilerIdCXX/apple-sdk.cpp +1 -0
- package/cpp/build/CMakeFiles/CMakeConfigureLog.yaml +1619 -0
- package/cpp/build/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
- package/cpp/build/CMakeFiles/InstallScripts.json +7 -0
- package/cpp/build/CMakeFiles/Makefile.cmake +118 -0
- package/cpp/build/CMakeFiles/Makefile2 +122 -0
- package/cpp/build/CMakeFiles/TargetDirectories.txt +3 -0
- package/cpp/build/CMakeFiles/cmake.check_cache +1 -0
- package/cpp/build/CMakeFiles/progress.marks +1 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/DependInfo.cmake +36 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/build.make +322 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/cmake_clean.cmake +37 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/cmake_clean_target.cmake +3 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/compiler_depend.make +2 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/compiler_depend.ts +2 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/depend.make +2 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/flags.make +12 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/link.txt +2 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/progress.make +16 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o.d +814 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o.d +206 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o.d +797 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o.d +755 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o.d +669 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o.d +648 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o.d +765 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o.d +648 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o.d +755 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o.d +648 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o.d +665 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o.d +811 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o.d +754 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o +0 -0
- package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o.d +755 -0
- package/cpp/build/Makefile +532 -0
- package/cpp/build/cmake_install.cmake +61 -0
- package/cpp/build/libtuner_engine_core.a +0 -0
- package/cpp/include/AudioFrameDispatcher.hpp +87 -0
- package/cpp/include/BiquadHpf.hpp +22 -0
- package/cpp/include/CepstrumPitchDetector.hpp +33 -0
- package/cpp/include/EnsembleSelector.hpp +25 -0
- package/cpp/include/Fft.hpp +44 -0
- package/cpp/include/IPitchDetector.hpp +21 -0
- package/cpp/include/InstrumentPresets.hpp +33 -0
- package/cpp/include/NoteMapper.hpp +15 -0
- package/cpp/include/OnsetDetector.hpp +37 -0
- package/cpp/include/Pipeline.hpp +55 -0
- package/cpp/include/PitchResult.hpp +24 -0
- package/cpp/include/PostProcessor.hpp +51 -0
- package/cpp/include/PyinPitchDetector.hpp +35 -0
- package/cpp/include/RingBuffer.hpp +72 -0
- package/cpp/include/SnrEstimator.hpp +21 -0
- package/cpp/include/StringMatcher.hpp +27 -0
- package/cpp/include/TunerEngine.hpp +32 -0
- package/cpp/include/TuningPresets.hpp +103 -0
- package/cpp/include/Window.hpp +13 -0
- package/cpp/include/YinPitchDetector.hpp +37 -0
- package/cpp/src/AudioFrameDispatcher.cpp +180 -0
- package/cpp/src/BiquadHpf.cpp +35 -0
- package/cpp/src/CepstrumPitchDetector.cpp +116 -0
- package/cpp/src/EnsembleSelector.cpp +91 -0
- package/cpp/src/NoteMapper.cpp +47 -0
- package/cpp/src/OnsetDetector.cpp +39 -0
- package/cpp/src/Pipeline.cpp +133 -0
- package/cpp/src/PostProcessor.cpp +111 -0
- package/cpp/src/PyinPitchDetector.cpp +134 -0
- package/cpp/src/SnrEstimator.cpp +33 -0
- package/cpp/src/StringMatcher.cpp +37 -0
- package/cpp/src/TunerEngine.cpp +67 -0
- package/cpp/src/Window.cpp +21 -0
- package/cpp/src/YinPitchDetector.cpp +118 -0
- package/cpp/tests/CMakeLists.txt +23 -0
- package/cpp/tests/bench.cpp +160 -0
- package/cpp/tests/build/CMakeCache.txt +356 -0
- package/cpp/tests/build/CMakeFiles/4.3.2/CMakeCXXCompiler.cmake +102 -0
- package/cpp/tests/build/CMakeFiles/4.3.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
- package/cpp/tests/build/CMakeFiles/4.3.2/CMakeSystem.cmake +15 -0
- package/cpp/tests/build/CMakeFiles/4.3.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +949 -0
- package/cpp/tests/build/CMakeFiles/4.3.2/CompilerIdCXX/a.out +0 -0
- package/cpp/tests/build/CMakeFiles/4.3.2/CompilerIdCXX/apple-sdk.cpp +1 -0
- package/cpp/tests/build/CMakeFiles/CMakeConfigureLog.yaml +1619 -0
- package/cpp/tests/build/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
- package/cpp/tests/build/CMakeFiles/InstallScripts.json +8 -0
- package/cpp/tests/build/CMakeFiles/Makefile.cmake +122 -0
- package/cpp/tests/build/CMakeFiles/Makefile2 +211 -0
- package/cpp/tests/build/CMakeFiles/TargetDirectories.txt +9 -0
- package/cpp/tests/build/CMakeFiles/cmake.check_cache +1 -0
- package/cpp/tests/build/CMakeFiles/progress.marks +1 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/DependInfo.cmake +23 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/bench.cpp.o +0 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/bench.cpp.o.d +813 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/build.make +114 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/cmake_clean.cmake +11 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/compiler_depend.make +2 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/compiler_depend.ts +2 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/depend.make +2 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/flags.make +12 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/link.txt +1 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/progress.make +3 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/DependInfo.cmake +23 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/build.make +114 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/cmake_clean.cmake +11 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/compiler_depend.make +2 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/compiler_depend.ts +2 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/depend.make +2 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/flags.make +12 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/link.txt +1 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/main.cpp.o +0 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/main.cpp.o.d +823 -0
- package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/progress.make +3 -0
- package/cpp/tests/build/CTestTestfile.cmake +9 -0
- package/cpp/tests/build/Makefile +247 -0
- package/cpp/tests/build/cmake_install.cmake +66 -0
- package/cpp/tests/build/tuner_engine_bench +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/progress.marks +1 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/DependInfo.cmake +36 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/build.make +322 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/cmake_clean.cmake +37 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/cmake_clean_target.cmake +3 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/compiler_depend.make +2 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/compiler_depend.ts +2 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/depend.make +2 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/flags.make +12 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/link.txt +2 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/progress.make +16 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o.d +814 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o.d +206 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o.d +797 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o.d +755 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o.d +669 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o.d +648 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o.d +765 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o.d +648 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o.d +755 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o.d +648 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o.d +665 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o.d +811 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o.d +754 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o +0 -0
- package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o.d +755 -0
- package/cpp/tests/build/tuner_engine_core/Makefile +544 -0
- package/cpp/tests/build/tuner_engine_core/cmake_install.cmake +45 -0
- package/cpp/tests/build/tuner_engine_core/libtuner_engine_core.a +0 -0
- package/cpp/tests/build/tuner_engine_tests +0 -0
- package/cpp/tests/main.cpp +624 -0
- package/ios/IosAudioSource.h +23 -0
- package/ios/IosAudioSource.mm +174 -0
- package/ios/TunerBridge.h +24 -0
- package/ios/TunerBridge.mm +136 -0
- package/ios/TunerEngine.h +6 -0
- package/ios/TunerEngine.mm +144 -0
- package/lib/module/NativeTunerEngine.js +5 -0
- package/lib/module/NativeTunerEngine.js.map +1 -0
- package/lib/module/TunerEngine.js +43 -0
- package/lib/module/TunerEngine.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/useTuner.js +55 -0
- package/lib/module/useTuner.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeTunerEngine.d.ts +17 -0
- package/lib/typescript/src/NativeTunerEngine.d.ts.map +1 -0
- package/lib/typescript/src/TunerEngine.d.ts +18 -0
- package/lib/typescript/src/TunerEngine.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +86 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/useTuner.d.ts +15 -0
- package/lib/typescript/src/useTuner.d.ts.map +1 -0
- package/package.json +141 -0
- package/src/NativeTunerEngine.ts +17 -0
- package/src/TunerEngine.ts +62 -0
- package/src/index.tsx +11 -0
- package/src/types.ts +109 -0
- package/src/useTuner.ts +67 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#include "PostProcessor.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
static constexpr float kA4 = 440.0f;
|
|
7
|
+
|
|
8
|
+
PostProcessor::PostProcessor() : cfg_{} {}
|
|
9
|
+
PostProcessor::PostProcessor(Config cfg) : cfg_(cfg) {
|
|
10
|
+
cfg_.emaAlpha = std::clamp(cfg_.emaAlpha, 0.01f, 1.0f);
|
|
11
|
+
cfg_.hysteresisFrames = std::max(cfg_.hysteresisFrames, 1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
void PostProcessor::reset() {
|
|
15
|
+
std::fill(medianBuf_, medianBuf_ + kMedianLen, 0.0f);
|
|
16
|
+
medianIdx_ = 0;
|
|
17
|
+
medianFill_ = 0;
|
|
18
|
+
smoothedFreq_ = 0.0f;
|
|
19
|
+
lockedMidi_ = -1;
|
|
20
|
+
candidateMidi_ = -1;
|
|
21
|
+
candidateCount_ = 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
void PostProcessor::setConfig(Config cfg) {
|
|
25
|
+
cfg.emaAlpha = std::clamp(cfg.emaAlpha, 0.01f, 1.0f);
|
|
26
|
+
cfg.hysteresisFrames = std::max(cfg.hysteresisFrames, 1);
|
|
27
|
+
cfg_ = cfg;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
float PostProcessor::median5() const {
|
|
31
|
+
// Optimal median-of-5 via sorting network (6 comparisons, no allocation)
|
|
32
|
+
float a = medianBuf_[0], b = medianBuf_[1], c = medianBuf_[2],
|
|
33
|
+
d = medianBuf_[3], e = medianBuf_[4];
|
|
34
|
+
// Sort pairs
|
|
35
|
+
if (a > b) std::swap(a, b);
|
|
36
|
+
if (c > d) std::swap(c, d);
|
|
37
|
+
// Eliminate the lowest of the two pairs
|
|
38
|
+
if (a > c) { std::swap(a, c); std::swap(b, d); }
|
|
39
|
+
// Now a is the global minimum, discard it. Median is among {b, c, d, e}
|
|
40
|
+
// Find median of {b, c, d, e} by eliminating max and min
|
|
41
|
+
if (b > e) std::swap(b, e);
|
|
42
|
+
// Median is middle of {b, c, d}
|
|
43
|
+
if (b > c) std::swap(b, c);
|
|
44
|
+
if (c > d) std::swap(c, d);
|
|
45
|
+
return std::min(c, e);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
int PostProcessor::freqToMidi(float hz) {
|
|
49
|
+
if (hz <= 0.0f) return -1;
|
|
50
|
+
return static_cast<int>(std::round(69.0f + 12.0f * std::log2(hz / kA4)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
float PostProcessor::freqToCentsFromMidi(float hz, int midi) {
|
|
54
|
+
if (hz <= 0.0f || midi < 0) return 0.0f;
|
|
55
|
+
const float targetHz = kA4 * std::pow(2.0f, (midi - 69) / 12.0f);
|
|
56
|
+
return 1200.0f * std::log2(hz / targetHz);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
PostProcessor::Result PostProcessor::process(float frequency, float confidence) {
|
|
60
|
+
// 1. Feed median buffer (treat 0 as "silent frame")
|
|
61
|
+
medianBuf_[medianIdx_] = frequency;
|
|
62
|
+
medianIdx_ = (medianIdx_ + 1) % kMedianLen;
|
|
63
|
+
if (medianFill_ < kMedianLen) ++medianFill_;
|
|
64
|
+
|
|
65
|
+
const float med = (medianFill_ == kMedianLen) ? median5() : frequency;
|
|
66
|
+
|
|
67
|
+
if (med <= 0.0f || confidence <= 0.0f) {
|
|
68
|
+
return Result{ 0.0f, lockedMidi_, 0.0f, false };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 3. Hysteresis — use the raw median for note detection (not the smoothed EMA).
|
|
72
|
+
// This prevents the slow EMA from drifting through enharmonic neighbours during transitions.
|
|
73
|
+
const int newMidi = freqToMidi(med);
|
|
74
|
+
|
|
75
|
+
if (newMidi == lockedMidi_) {
|
|
76
|
+
candidateMidi_ = -1;
|
|
77
|
+
candidateCount_ = 0;
|
|
78
|
+
} else {
|
|
79
|
+
if (newMidi == candidateMidi_) {
|
|
80
|
+
++candidateCount_;
|
|
81
|
+
} else {
|
|
82
|
+
candidateMidi_ = newMidi;
|
|
83
|
+
candidateCount_ = 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (candidateCount_ >= cfg_.hysteresisFrames) {
|
|
87
|
+
lockedMidi_ = candidateMidi_;
|
|
88
|
+
smoothedFreq_ = med; // snap EMA to new note to avoid lag artefacts
|
|
89
|
+
candidateMidi_ = -1;
|
|
90
|
+
candidateCount_ = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (lockedMidi_ < 0) {
|
|
95
|
+
lockedMidi_ = newMidi;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 4. EMA — only for smooth cents/frequency display within the locked note
|
|
99
|
+
smoothedFreq_ = (smoothedFreq_ <= 0.0f)
|
|
100
|
+
? med
|
|
101
|
+
: cfg_.emaAlpha * med + (1.0f - cfg_.emaAlpha) * smoothedFreq_;
|
|
102
|
+
|
|
103
|
+
const float cents = freqToCentsFromMidi(smoothedFreq_, lockedMidi_);
|
|
104
|
+
|
|
105
|
+
return Result{
|
|
106
|
+
smoothedFreq_,
|
|
107
|
+
lockedMidi_,
|
|
108
|
+
cents,
|
|
109
|
+
true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#include "PyinPitchDetector.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
PyinPitchDetector::PyinPitchDetector(float sampleRate, int frameSize)
|
|
7
|
+
: sampleRate_(sampleRate)
|
|
8
|
+
, frameSize_(frameSize)
|
|
9
|
+
{
|
|
10
|
+
diff_.resize(static_cast<size_t>(frameSize_ / 2));
|
|
11
|
+
cmnd_.resize(static_cast<size_t>(frameSize_ / 2));
|
|
12
|
+
candidates_.reserve(32); // typical number of CMND minima
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
void PyinPitchDetector::setFrequencyRange(float minHz, float maxHz) {
|
|
16
|
+
minHz_ = minHz;
|
|
17
|
+
maxHz_ = maxHz;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
void PyinPitchDetector::setThreshold(float threshold) {
|
|
21
|
+
threshold_ = threshold;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
DetectorResult PyinPitchDetector::detect(const float* frame, int n, float sampleRate) {
|
|
25
|
+
const float sr = sampleRate > 0.0f ? sampleRate : sampleRate_;
|
|
26
|
+
|
|
27
|
+
if (!frame || n < frameSize_) return DetectorResult{};
|
|
28
|
+
|
|
29
|
+
const int tauMin = std::max(2, static_cast<int>(sr / maxHz_));
|
|
30
|
+
const int tauMax = std::min(
|
|
31
|
+
frameSize_ / 2 - 1,
|
|
32
|
+
static_cast<int>(sr / minHz_)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (tauMin >= tauMax) return DetectorResult{};
|
|
36
|
+
|
|
37
|
+
// Squared difference function (YIN step 2)
|
|
38
|
+
std::fill(diff_.begin(), diff_.end(), 0.0f);
|
|
39
|
+
for (int tau = 1; tau <= tauMax; ++tau) {
|
|
40
|
+
float sum = 0.0f;
|
|
41
|
+
for (int i = 0; i < frameSize_ - tau; ++i) {
|
|
42
|
+
const float d = frame[i] - frame[i + tau];
|
|
43
|
+
sum += d * d;
|
|
44
|
+
}
|
|
45
|
+
diff_[tau] = sum;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Cumulative mean normalised difference (CMND, YIN step 3)
|
|
49
|
+
cmnd_[0] = 1.0f;
|
|
50
|
+
float runningSum = 0.0f;
|
|
51
|
+
for (int tau = 1; tau <= tauMax; ++tau) {
|
|
52
|
+
runningSum += diff_[tau];
|
|
53
|
+
cmnd_[tau] = (runningSum <= 0.0f)
|
|
54
|
+
? 1.0f
|
|
55
|
+
: diff_[tau] * static_cast<float>(tau) / runningSum;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Collect all local minima of CMND below threshold — this is the PYIN divergence point.
|
|
59
|
+
// YIN stops at the first; PYIN considers all.
|
|
60
|
+
candidates_.clear();
|
|
61
|
+
|
|
62
|
+
auto tryAdd = [&](int tau) {
|
|
63
|
+
if (tau < tauMin || tau > tauMax) return;
|
|
64
|
+
if (cmnd_[tau] < threshold_) {
|
|
65
|
+
candidates_.push_back({tau, 1.0f - cmnd_[tau] / threshold_});
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Interior local minima
|
|
70
|
+
for (int tau = tauMin + 1; tau < tauMax; ++tau) {
|
|
71
|
+
if (cmnd_[tau] < cmnd_[tau - 1] && cmnd_[tau] < cmnd_[tau + 1]) {
|
|
72
|
+
tryAdd(tau);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Boundary checks
|
|
76
|
+
if (cmnd_[tauMin] < cmnd_[tauMin + 1]) tryAdd(tauMin);
|
|
77
|
+
if (cmnd_[tauMax] < cmnd_[tauMax - 1]) tryAdd(tauMax);
|
|
78
|
+
|
|
79
|
+
if (candidates_.empty()) return DetectorResult{};
|
|
80
|
+
|
|
81
|
+
// The CMND formula makes later (longer-period) minima numerically smaller than
|
|
82
|
+
// earlier ones at sub-multiples of the same fundamental period. Without pruning,
|
|
83
|
+
// a pure sine would always produce candidates at tau0, 2*tau0, 3*tau0 … with the
|
|
84
|
+
// highest probability at the LONGEST alias — the wrong (sub-octave) answer.
|
|
85
|
+
//
|
|
86
|
+
// Prune: for any candidate whose tau is within 2% of an integer multiple (≥2×) of
|
|
87
|
+
// a shorter-period candidate, zero its probability. Candidates are already in
|
|
88
|
+
// ascending tau order, so a single forward pass suffices.
|
|
89
|
+
for (int i = 0; i < static_cast<int>(candidates_.size()); ++i) {
|
|
90
|
+
if (candidates_[i].prob == 0.0f) continue;
|
|
91
|
+
for (int j = i + 1; j < static_cast<int>(candidates_.size()); ++j) {
|
|
92
|
+
const float ratio = static_cast<float>(candidates_[j].tau)
|
|
93
|
+
/ static_cast<float>(candidates_[i].tau);
|
|
94
|
+
const float nearest = std::round(ratio);
|
|
95
|
+
if (nearest >= 2.0f && std::fabs(ratio - nearest) / nearest < 0.02f) {
|
|
96
|
+
candidates_[j].prob = 0.0f;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Sum surviving probabilities for voiced confidence
|
|
102
|
+
float totalProb = 0.0f;
|
|
103
|
+
for (const auto& c : candidates_) totalProb += c.prob;
|
|
104
|
+
|
|
105
|
+
// Pick the candidate with the highest (surviving) probability.
|
|
106
|
+
// Strict '>' keeps the first (lowest-tau, highest-frequency) on exact ties.
|
|
107
|
+
const Candidate* winner = nullptr;
|
|
108
|
+
for (const auto& c : candidates_) {
|
|
109
|
+
if (!winner || c.prob > winner->prob) winner = &c;
|
|
110
|
+
}
|
|
111
|
+
if (!winner || winner->prob == 0.0f) return DetectorResult{};
|
|
112
|
+
|
|
113
|
+
const float betterTau = parabolicInterpolation(winner->tau);
|
|
114
|
+
if (betterTau <= 0.0f) return DetectorResult{};
|
|
115
|
+
|
|
116
|
+
// Confidence: voiced probability × how much the winner dominates
|
|
117
|
+
const float voicedProb = std::min(1.0f, totalProb);
|
|
118
|
+
const float winnerShare = (totalProb > 0.0f) ? winner->prob / totalProb : 0.0f;
|
|
119
|
+
const float confidence = voicedProb * (0.5f + 0.5f * winnerShare);
|
|
120
|
+
|
|
121
|
+
return DetectorResult{true, sr / betterTau, confidence};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
float PyinPitchDetector::parabolicInterpolation(int tau) const {
|
|
125
|
+
if (tau <= 0 || tau >= static_cast<int>(cmnd_.size()) - 1) {
|
|
126
|
+
return static_cast<float>(tau);
|
|
127
|
+
}
|
|
128
|
+
const float L = cmnd_[tau - 1];
|
|
129
|
+
const float C = cmnd_[tau];
|
|
130
|
+
const float R = cmnd_[tau + 1];
|
|
131
|
+
const float d = L - 2.0f * C + R;
|
|
132
|
+
if (std::fabs(d) < 1e-6f) return static_cast<float>(tau);
|
|
133
|
+
return tau + 0.5f * (L - R) / d;
|
|
134
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#include "SnrEstimator.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
static constexpr float kMinLinear = 1e-7f; // -140 dBFS floor
|
|
7
|
+
|
|
8
|
+
SnrEstimator::SnrEstimator(float floorInitDb)
|
|
9
|
+
: noiseFloorLinear_(std::pow(10.0f, floorInitDb / 20.0f)) {}
|
|
10
|
+
|
|
11
|
+
float SnrEstimator::update(float rmsLinear) {
|
|
12
|
+
rmsLinear = std::max(rmsLinear, kMinLinear);
|
|
13
|
+
|
|
14
|
+
if (rmsLinear < noiseFloorLinear_) {
|
|
15
|
+
// Signal dropped below floor — pull floor down quickly to track silence
|
|
16
|
+
noiseFloorLinear_ = kAttackAlpha * rmsLinear + (1.0f - kAttackAlpha) * noiseFloorLinear_;
|
|
17
|
+
}
|
|
18
|
+
// Floor never rises during active signal — prevents SNR from collapsing on sustained notes
|
|
19
|
+
|
|
20
|
+
noiseFloorLinear_ = std::max(noiseFloorLinear_, kMinLinear);
|
|
21
|
+
return 20.0f * std::log10(rmsLinear / noiseFloorLinear_);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
float SnrEstimator::snrToWeight(float snrDb) {
|
|
25
|
+
// Sigmoid-like mapping: 0 dB SNR → ~0.0 weight, 20 dB → ~1.0 weight
|
|
26
|
+
if (snrDb <= 0.0f) return 0.0f;
|
|
27
|
+
if (snrDb >= 30.0f) return 1.0f;
|
|
28
|
+
return snrDb / 30.0f;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
void SnrEstimator::reset() {
|
|
32
|
+
noiseFloorLinear_ = std::pow(10.0f, -70.0f / 20.0f);
|
|
33
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#include "StringMatcher.hpp"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
|
|
5
|
+
void StringMatcher::setTuning(const TuningProfile* profile) {
|
|
6
|
+
profile_ = profile;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
bool StringMatcher::hasTuning() const {
|
|
10
|
+
return profile_ != nullptr && profile_->stringCount > 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
std::optional<StringMatch> StringMatcher::match(float frequency,
|
|
14
|
+
float maxDeviationCents) const {
|
|
15
|
+
if (!hasTuning() || frequency <= 0.0f) return std::nullopt;
|
|
16
|
+
|
|
17
|
+
const StringMatch* best = nullptr;
|
|
18
|
+
float bestAbsCents = maxDeviationCents + 1.0f; // sentinel > threshold
|
|
19
|
+
StringMatch bestMatch{};
|
|
20
|
+
|
|
21
|
+
for (int i = 0; i < profile_->stringCount; ++i) {
|
|
22
|
+
const StringTarget& s = profile_->strings[i];
|
|
23
|
+
if (s.frequency <= 0.0f) continue;
|
|
24
|
+
|
|
25
|
+
const float cents = 1200.0f * std::log2(frequency / s.frequency);
|
|
26
|
+
const float absCents = std::fabs(cents);
|
|
27
|
+
|
|
28
|
+
if (absCents < bestAbsCents) {
|
|
29
|
+
bestAbsCents = absCents;
|
|
30
|
+
bestMatch = {s.name, s.stringNumber, s.frequency, cents};
|
|
31
|
+
best = &bestMatch;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!best || bestAbsCents > maxDeviationCents) return std::nullopt;
|
|
36
|
+
return bestMatch;
|
|
37
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#include "TunerEngine.hpp"
|
|
2
|
+
#include "CepstrumPitchDetector.hpp"
|
|
3
|
+
#include "EnsembleSelector.hpp"
|
|
4
|
+
#include "PyinPitchDetector.hpp"
|
|
5
|
+
#include "TuningPresets.hpp"
|
|
6
|
+
#include "YinPitchDetector.hpp"
|
|
7
|
+
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
TunerEngine::TunerEngine(float sampleRate, int frameSize) {
|
|
12
|
+
std::vector<std::unique_ptr<IPitchDetector>> detectors;
|
|
13
|
+
detectors.push_back(std::make_unique<YinPitchDetector>(sampleRate, frameSize));
|
|
14
|
+
detectors.push_back(std::make_unique<PyinPitchDetector>(sampleRate, frameSize));
|
|
15
|
+
detectors.push_back(std::make_unique<CepstrumPitchDetector>(sampleRate, frameSize));
|
|
16
|
+
|
|
17
|
+
auto ensemble = std::make_unique<EnsembleSelector>(std::move(detectors));
|
|
18
|
+
pipeline_ = std::make_unique<Pipeline>(frameSize, sampleRate, std::move(ensemble));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
PitchResult TunerEngine::process(const float* input, int frameCount) {
|
|
22
|
+
return pipeline_->process(input, frameCount);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
void TunerEngine::setA4(float a4) {
|
|
26
|
+
pipeline_->setA4(a4);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
void TunerEngine::setNoiseGateDb(float db) {
|
|
30
|
+
pipeline_->setNoiseGateDb(db);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void TunerEngine::setConfidenceThreshold(float value) {
|
|
34
|
+
pipeline_->setConfidenceThreshold(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void TunerEngine::setFrequencyRange(float minFrequency, float maxFrequency) {
|
|
38
|
+
pipeline_->setFrequencyRange(minFrequency, maxFrequency);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void TunerEngine::setInstrument(const std::string& name) {
|
|
42
|
+
pipeline_->setInstrument(name);
|
|
43
|
+
// Auto-apply the default tuning for this instrument (e.g. "guitar" → "guitar_standard").
|
|
44
|
+
// Can be overridden afterwards with an explicit setTuning() call.
|
|
45
|
+
const std::string defaultTuning = defaultTuningForInstrument(name);
|
|
46
|
+
pipeline_->setTuning(defaultTuning);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void TunerEngine::setTuning(const std::string& name) {
|
|
50
|
+
pipeline_->setTuning(name);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
void TunerEngine::setPostProcessorConfig(PostProcessor::Config cfg) {
|
|
54
|
+
pipeline_->setPostProcessorConfig(cfg);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void TunerEngine::setHpfCutoff(float hz) {
|
|
58
|
+
pipeline_->setHpfCutoff(hz);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void TunerEngine::setOnsetDetectionEnabled(bool enabled) {
|
|
62
|
+
pipeline_->setOnsetDetectionEnabled(enabled);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void TunerEngine::setOnsetConfig(OnsetDetector::Config cfg) {
|
|
66
|
+
pipeline_->setOnsetConfig(cfg);
|
|
67
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#include "Window.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
static constexpr float kPi = 3.14159265358979323846f;
|
|
7
|
+
|
|
8
|
+
HannWindow::HannWindow(int size) : coeffs_(static_cast<size_t>(size)) {
|
|
9
|
+
for (int i = 0; i < size; ++i) {
|
|
10
|
+
coeffs_[static_cast<size_t>(i)] =
|
|
11
|
+
0.5f * (1.0f - std::cos(2.0f * kPi * i / (size - 1)));
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
void HannWindow::apply(float* frame, int n) const {
|
|
16
|
+
if (!frame || n <= 0) return;
|
|
17
|
+
const int len = std::min(n, static_cast<int>(coeffs_.size()));
|
|
18
|
+
for (int i = 0; i < len; ++i) {
|
|
19
|
+
frame[i] *= coeffs_[static_cast<size_t>(i)];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#include "YinPitchDetector.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
YinPitchDetector::YinPitchDetector(float sampleRate, int frameSize)
|
|
7
|
+
: sampleRate_(sampleRate), frameSize_(frameSize) {
|
|
8
|
+
difference_.resize(frameSize_ / 2);
|
|
9
|
+
cmnd_.resize(frameSize_ / 2);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
void YinPitchDetector::setFrequencyRange(float minFrequency, float maxFrequency) {
|
|
13
|
+
minFrequency_ = minFrequency;
|
|
14
|
+
maxFrequency_ = maxFrequency;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
void YinPitchDetector::setThreshold(float threshold) {
|
|
18
|
+
threshold_ = threshold;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
YinResult YinPitchDetector::detect(const float* input, int frameCount) {
|
|
22
|
+
YinResult result;
|
|
23
|
+
|
|
24
|
+
if (input == nullptr || frameCount < frameSize_) {
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const int tauMin = std::max(2, static_cast<int>(sampleRate_ / maxFrequency_));
|
|
29
|
+
const int tauMax = std::min(
|
|
30
|
+
frameSize_ / 2 - 1,
|
|
31
|
+
static_cast<int>(sampleRate_ / minFrequency_)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
std::fill(difference_.begin(), difference_.end(), 0.0f);
|
|
35
|
+
|
|
36
|
+
for (int tau = 1; tau <= tauMax; ++tau) {
|
|
37
|
+
float sum = 0.0f;
|
|
38
|
+
|
|
39
|
+
for (int i = 0; i < frameSize_ - tau; ++i) {
|
|
40
|
+
const float delta = input[i] - input[i + tau];
|
|
41
|
+
sum += delta * delta;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
difference_[tau] = sum;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
cmnd_[0] = 1.0f;
|
|
48
|
+
|
|
49
|
+
float runningSum = 0.0f;
|
|
50
|
+
|
|
51
|
+
for (int tau = 1; tau <= tauMax; ++tau) {
|
|
52
|
+
runningSum += difference_[tau];
|
|
53
|
+
|
|
54
|
+
if (runningSum <= 0.0f) {
|
|
55
|
+
cmnd_[tau] = 1.0f;
|
|
56
|
+
} else {
|
|
57
|
+
cmnd_[tau] = difference_[tau] * tau / runningSum;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
int tauEstimate = -1;
|
|
62
|
+
|
|
63
|
+
for (int tau = tauMin; tau <= tauMax; ++tau) {
|
|
64
|
+
if (cmnd_[tau] < threshold_) {
|
|
65
|
+
while (tau + 1 <= tauMax && cmnd_[tau + 1] < cmnd_[tau]) {
|
|
66
|
+
tau++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
tauEstimate = tau;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (tauEstimate == -1) {
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const float betterTau = parabolicInterpolation(tauEstimate);
|
|
79
|
+
|
|
80
|
+
if (betterTau <= 0.0f) {
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
result.hasPitch = true;
|
|
85
|
+
result.frequency = sampleRate_ / betterTau;
|
|
86
|
+
result.confidence = 1.0f - cmnd_[tauEstimate];
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
DetectorResult YinPitchDetector::detect(const float* frame, int n, float sampleRate) {
|
|
92
|
+
// Store the passed sampleRate temporarily if it differs (Pipeline may have resampled)
|
|
93
|
+
const float savedSr = sampleRate_;
|
|
94
|
+
if (sampleRate > 0.0f && sampleRate != sampleRate_) {
|
|
95
|
+
sampleRate_ = sampleRate;
|
|
96
|
+
}
|
|
97
|
+
YinResult r = detect(frame, n);
|
|
98
|
+
sampleRate_ = savedSr;
|
|
99
|
+
return DetectorResult{ r.hasPitch, r.frequency, r.confidence };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
float YinPitchDetector::parabolicInterpolation(int tau) const {
|
|
103
|
+
if (tau <= 0 || tau >= static_cast<int>(cmnd_.size()) - 1) {
|
|
104
|
+
return static_cast<float>(tau);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const float left = cmnd_[tau - 1];
|
|
108
|
+
const float center = cmnd_[tau];
|
|
109
|
+
const float right = cmnd_[tau + 1];
|
|
110
|
+
|
|
111
|
+
const float denominator = left - 2.0f * center + right;
|
|
112
|
+
|
|
113
|
+
if (std::fabs(denominator) < 1e-6f) {
|
|
114
|
+
return static_cast<float>(tau);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return tau + 0.5f * (left - right) / denominator;
|
|
118
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.20)
|
|
2
|
+
|
|
3
|
+
project(TunerEngineTests LANGUAGES CXX)
|
|
4
|
+
|
|
5
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
6
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
7
|
+
|
|
8
|
+
add_subdirectory(.. ${CMAKE_BINARY_DIR}/tuner_engine_core)
|
|
9
|
+
|
|
10
|
+
add_executable(tuner_engine_tests
|
|
11
|
+
main.cpp
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
target_link_libraries(tuner_engine_tests PRIVATE tuner_engine_core)
|
|
15
|
+
|
|
16
|
+
add_executable(tuner_engine_bench
|
|
17
|
+
bench.cpp
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
target_link_libraries(tuner_engine_bench PRIVATE tuner_engine_core)
|
|
21
|
+
|
|
22
|
+
enable_testing()
|
|
23
|
+
add_test(NAME tuner_engine_tests COMMAND tuner_engine_tests)
|