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,624 @@
|
|
|
1
|
+
#include "NoteMapper.hpp"
|
|
2
|
+
#include "OnsetDetector.hpp"
|
|
3
|
+
#include "TunerEngine.hpp"
|
|
4
|
+
#include "YinPitchDetector.hpp"
|
|
5
|
+
#include "PyinPitchDetector.hpp"
|
|
6
|
+
#include "CepstrumPitchDetector.hpp"
|
|
7
|
+
#include "EnsembleSelector.hpp"
|
|
8
|
+
#include "BiquadHpf.hpp"
|
|
9
|
+
#include "AudioFrameDispatcher.hpp"
|
|
10
|
+
#include "InstrumentPresets.hpp"
|
|
11
|
+
|
|
12
|
+
#include <cassert>
|
|
13
|
+
#include <cmath>
|
|
14
|
+
#include <chrono>
|
|
15
|
+
#include <iostream>
|
|
16
|
+
#include <thread>
|
|
17
|
+
#include <vector>
|
|
18
|
+
|
|
19
|
+
static constexpr float PI = 3.14159265358979323846f;
|
|
20
|
+
|
|
21
|
+
static void assertNear(float actual, float expected, float tolerance) {
|
|
22
|
+
assert(std::fabs(actual - expected) <= tolerance);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static std::vector<float> generateSine(
|
|
26
|
+
float frequency,
|
|
27
|
+
float sampleRate,
|
|
28
|
+
int frameSize,
|
|
29
|
+
float amplitude = 0.8f
|
|
30
|
+
) {
|
|
31
|
+
std::vector<float> buffer(frameSize);
|
|
32
|
+
|
|
33
|
+
for (int i = 0; i < frameSize; ++i) {
|
|
34
|
+
buffer[i] = amplitude * std::sin(2.0f * PI * frequency * i / sampleRate);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return buffer;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static std::vector<float> generateSilence(int frameSize) {
|
|
41
|
+
return std::vector<float>(frameSize, 0.0f);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static void testNoteMapper() {
|
|
45
|
+
NoteMapper mapper(440.0f);
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
auto result = mapper.map(82.41f, 1.0f, -12.0f);
|
|
49
|
+
assert(result.hasPitch);
|
|
50
|
+
assert(result.noteName == "E");
|
|
51
|
+
assert(result.octave == 2);
|
|
52
|
+
assertNear(result.cents, 0.0f, 1.0f);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
{
|
|
56
|
+
auto result = mapper.map(110.0f, 1.0f, -12.0f);
|
|
57
|
+
assert(result.hasPitch);
|
|
58
|
+
assert(result.noteName == "A");
|
|
59
|
+
assert(result.octave == 2);
|
|
60
|
+
assertNear(result.cents, 0.0f, 1.0f);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
{
|
|
64
|
+
auto result = mapper.map(329.63f, 1.0f, -12.0f);
|
|
65
|
+
assert(result.hasPitch);
|
|
66
|
+
assert(result.noteName == "E");
|
|
67
|
+
assert(result.octave == 4);
|
|
68
|
+
assertNear(result.cents, 0.0f, 1.0f);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
{
|
|
72
|
+
auto result = mapper.map(440.0f, 1.0f, -12.0f);
|
|
73
|
+
assert(result.hasPitch);
|
|
74
|
+
assert(result.noteName == "A");
|
|
75
|
+
assert(result.octave == 4);
|
|
76
|
+
assertNear(result.cents, 0.0f, 1.0f);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static void testYin(float inputFrequency) {
|
|
81
|
+
constexpr float sampleRate = 48000.0f;
|
|
82
|
+
constexpr int frameSize = 4096;
|
|
83
|
+
|
|
84
|
+
auto buffer = generateSine(inputFrequency, sampleRate, frameSize);
|
|
85
|
+
|
|
86
|
+
YinPitchDetector detector(sampleRate, frameSize);
|
|
87
|
+
auto result = detector.detect(buffer.data(), static_cast<int>(buffer.size()));
|
|
88
|
+
|
|
89
|
+
std::cout
|
|
90
|
+
<< "yin input: " << inputFrequency
|
|
91
|
+
<< " detected: " << result.frequency
|
|
92
|
+
<< " confidence: " << result.confidence
|
|
93
|
+
<< std::endl;
|
|
94
|
+
|
|
95
|
+
assert(result.hasPitch);
|
|
96
|
+
assertNear(result.frequency, inputFrequency, 0.5f);
|
|
97
|
+
assert(result.confidence > 0.80f);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
static void testTunerEngine(
|
|
101
|
+
float inputFrequency,
|
|
102
|
+
const std::string& expectedNote,
|
|
103
|
+
int expectedOctave
|
|
104
|
+
) {
|
|
105
|
+
constexpr float sampleRate = 48000.0f;
|
|
106
|
+
constexpr int frameSize = 4096;
|
|
107
|
+
|
|
108
|
+
auto buffer = generateSine(inputFrequency, sampleRate, frameSize);
|
|
109
|
+
|
|
110
|
+
TunerEngine engine(sampleRate, frameSize);
|
|
111
|
+
auto result = engine.process(buffer.data(), static_cast<int>(buffer.size()));
|
|
112
|
+
|
|
113
|
+
std::cout
|
|
114
|
+
<< "engine input: " << inputFrequency
|
|
115
|
+
<< " detected: " << result.frequency
|
|
116
|
+
<< " note: " << result.noteName << result.octave
|
|
117
|
+
<< " cents: " << result.cents
|
|
118
|
+
<< " confidence: " << result.confidence
|
|
119
|
+
<< " rms: " << result.rmsDb
|
|
120
|
+
<< std::endl;
|
|
121
|
+
|
|
122
|
+
assert(result.hasPitch);
|
|
123
|
+
assertNear(result.frequency, inputFrequency, 0.5f);
|
|
124
|
+
assert(result.noteName == expectedNote);
|
|
125
|
+
assert(result.octave == expectedOctave);
|
|
126
|
+
assert(std::fabs(result.cents) < 5.0f);
|
|
127
|
+
assert(result.confidence > 0.75f);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static void testTunerEngineSilence() {
|
|
131
|
+
constexpr float sampleRate = 48000.0f;
|
|
132
|
+
constexpr int frameSize = 4096;
|
|
133
|
+
|
|
134
|
+
auto buffer = generateSilence(frameSize);
|
|
135
|
+
|
|
136
|
+
TunerEngine engine(sampleRate, frameSize);
|
|
137
|
+
auto result = engine.process(buffer.data(), static_cast<int>(buffer.size()));
|
|
138
|
+
|
|
139
|
+
std::cout
|
|
140
|
+
<< "silence hasPitch: " << result.hasPitch
|
|
141
|
+
<< " rms: " << result.rmsDb
|
|
142
|
+
<< std::endl;
|
|
143
|
+
|
|
144
|
+
assert(!result.hasPitch);
|
|
145
|
+
assert(result.rmsDb <= -100.0f);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
static void testTunerEngineQuietSignal() {
|
|
149
|
+
constexpr float sampleRate = 48000.0f;
|
|
150
|
+
constexpr int frameSize = 4096;
|
|
151
|
+
|
|
152
|
+
auto buffer = generateSine(110.0f, sampleRate, frameSize, 0.0001f);
|
|
153
|
+
|
|
154
|
+
TunerEngine engine(sampleRate, frameSize);
|
|
155
|
+
engine.setNoiseGateDb(-55.0f);
|
|
156
|
+
|
|
157
|
+
auto result = engine.process(buffer.data(), static_cast<int>(buffer.size()));
|
|
158
|
+
|
|
159
|
+
std::cout
|
|
160
|
+
<< "quiet signal hasPitch: " << result.hasPitch
|
|
161
|
+
<< " rms: " << result.rmsDb
|
|
162
|
+
<< std::endl;
|
|
163
|
+
|
|
164
|
+
assert(!result.hasPitch);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// --- M2: Pipeline / DSP hardening tests ---
|
|
168
|
+
|
|
169
|
+
static void testPipelineCleanSine(float inputFrequency, const std::string& expectedNote, int expectedOctave) {
|
|
170
|
+
constexpr float sampleRate = 48000.0f;
|
|
171
|
+
constexpr int frameSize = 4096;
|
|
172
|
+
constexpr int numFrames = 6;
|
|
173
|
+
|
|
174
|
+
// Generate a single continuous sine (no phase discontinuity between frames)
|
|
175
|
+
// so the HPF state transitions correctly across frame boundaries.
|
|
176
|
+
auto continuous = generateSine(inputFrequency, sampleRate, frameSize * numFrames);
|
|
177
|
+
|
|
178
|
+
TunerEngine engine(sampleRate, frameSize);
|
|
179
|
+
PitchResult result;
|
|
180
|
+
for (int f = 0; f < numFrames; ++f) {
|
|
181
|
+
result = engine.process(continuous.data() + f * frameSize, frameSize);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
std::cout
|
|
185
|
+
<< "pipeline sine " << inputFrequency << " Hz"
|
|
186
|
+
<< " → " << result.noteName << result.octave
|
|
187
|
+
<< " cents=" << result.cents
|
|
188
|
+
<< " conf=" << result.confidence << "\n";
|
|
189
|
+
|
|
190
|
+
assert(result.hasPitch);
|
|
191
|
+
assert(result.noteName == expectedNote);
|
|
192
|
+
assert(result.octave == expectedOctave);
|
|
193
|
+
assertNear(result.cents, 0.0f, 5.0f);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
static void testPipelineNoisySignal() {
|
|
197
|
+
// Pure noise should NOT produce a stable pitch result
|
|
198
|
+
constexpr float sampleRate = 48000.0f;
|
|
199
|
+
constexpr int frameSize = 4096;
|
|
200
|
+
|
|
201
|
+
TunerEngine engine(sampleRate, frameSize);
|
|
202
|
+
std::vector<float> buffer(frameSize);
|
|
203
|
+
|
|
204
|
+
// Pink-ish noise via simple LFSR-style scramble
|
|
205
|
+
uint32_t seed = 0xDEADBEEF;
|
|
206
|
+
for (int i = 0; i < frameSize; ++i) {
|
|
207
|
+
seed ^= seed << 13; seed ^= seed >> 17; seed ^= seed << 5;
|
|
208
|
+
buffer[i] = static_cast<float>(static_cast<int32_t>(seed)) / 2147483648.0f * 0.3f;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
PitchResult result;
|
|
212
|
+
for (int f = 0; f < 6; ++f) {
|
|
213
|
+
result = engine.process(buffer.data(), static_cast<int>(buffer.size()));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
std::cout
|
|
217
|
+
<< "noisy signal hasPitch=" << result.hasPitch
|
|
218
|
+
<< " conf=" << result.confidence << "\n";
|
|
219
|
+
|
|
220
|
+
// Noisy signal should have low confidence / no stable pitch
|
|
221
|
+
assert(!result.hasPitch || result.confidence < 0.50f);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
static void testPipelineHysteresis() {
|
|
225
|
+
// Play A4 for several frames, then switch to B4 — verify debounce
|
|
226
|
+
constexpr float sampleRate = 48000.0f;
|
|
227
|
+
constexpr int frameSize = 4096;
|
|
228
|
+
constexpr float freqA4 = 440.0f;
|
|
229
|
+
constexpr float freqB4 = 493.88f;
|
|
230
|
+
|
|
231
|
+
TunerEngine engine(sampleRate, frameSize);
|
|
232
|
+
PitchResult result;
|
|
233
|
+
|
|
234
|
+
// Stabilise on A4 — continuous sine
|
|
235
|
+
auto contA4 = generateSine(freqA4, sampleRate, frameSize * 6);
|
|
236
|
+
for (int f = 0; f < 6; ++f) {
|
|
237
|
+
result = engine.process(contA4.data() + f * frameSize, frameSize);
|
|
238
|
+
}
|
|
239
|
+
assert(result.hasPitch);
|
|
240
|
+
assert(result.noteName == "A");
|
|
241
|
+
|
|
242
|
+
// Switch to B4 — continuous sine.
|
|
243
|
+
// Median buffer (5) fills with B4 at frame 3 of the new note.
|
|
244
|
+
// Then hysteresis counter (3) confirms → lock at frame 5 (3 frames where median == B4).
|
|
245
|
+
// Feed 8 frames total to be comfortably past the switch.
|
|
246
|
+
auto contB4 = generateSine(freqB4, sampleRate, frameSize * 8);
|
|
247
|
+
|
|
248
|
+
result = engine.process(contB4.data(), frameSize);
|
|
249
|
+
std::cout
|
|
250
|
+
<< "hysteresis after 1 B4 frame: note=" << result.noteName << "\n";
|
|
251
|
+
|
|
252
|
+
for (int f = 1; f < 8; ++f) {
|
|
253
|
+
result = engine.process(contB4.data() + f * frameSize, frameSize);
|
|
254
|
+
}
|
|
255
|
+
std::cout
|
|
256
|
+
<< "hysteresis after 8 B4 frames: note=" << result.noteName << "\n";
|
|
257
|
+
assert(result.hasPitch);
|
|
258
|
+
assert(result.noteName == "B");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
static void testInstrumentPreset() {
|
|
262
|
+
TunerEngine engine(48000.0f, 4096);
|
|
263
|
+
engine.setInstrument("guitar");
|
|
264
|
+
|
|
265
|
+
// E2 (82 Hz) — within guitar range, continuous sine
|
|
266
|
+
PitchResult result;
|
|
267
|
+
auto contE2 = generateSine(82.41f, 48000.0f, 4096 * 6);
|
|
268
|
+
for (int f = 0; f < 6; ++f) {
|
|
269
|
+
result = engine.process(contE2.data() + f * 4096, 4096);
|
|
270
|
+
}
|
|
271
|
+
std::cout << "guitar preset E2: hasPitch=" << result.hasPitch
|
|
272
|
+
<< " note=" << result.noteName << result.octave << "\n";
|
|
273
|
+
assert(result.hasPitch);
|
|
274
|
+
|
|
275
|
+
// Switch to ukulele — G4 should work, E2 might not (too low)
|
|
276
|
+
engine.setInstrument("ukulele");
|
|
277
|
+
auto contG4 = generateSine(392.0f, 48000.0f, 4096 * 6);
|
|
278
|
+
for (int f = 0; f < 6; ++f) {
|
|
279
|
+
result = engine.process(contG4.data() + f * 4096, 4096);
|
|
280
|
+
}
|
|
281
|
+
std::cout << "ukulele preset G4: hasPitch=" << result.hasPitch
|
|
282
|
+
<< " note=" << result.noteName << result.octave << "\n";
|
|
283
|
+
assert(result.hasPitch);
|
|
284
|
+
assert(result.noteName == "G");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// --- M3: per-detector and ensemble tests ---
|
|
288
|
+
|
|
289
|
+
static void testDetectorOnSine(float freq, const std::string& label) {
|
|
290
|
+
constexpr float sr = 48000.0f;
|
|
291
|
+
constexpr int n = 4096;
|
|
292
|
+
auto buf = generateSine(freq, sr, n);
|
|
293
|
+
|
|
294
|
+
BiquadHpf hpf(sr, 70.0f);
|
|
295
|
+
hpf.process(buf.data(), n);
|
|
296
|
+
|
|
297
|
+
YinPitchDetector yin(sr, n);
|
|
298
|
+
PyinPitchDetector pyin(sr, n);
|
|
299
|
+
|
|
300
|
+
auto ry = yin.detect(buf.data(), n, sr);
|
|
301
|
+
auto rpy = pyin.detect(buf.data(), n, sr);
|
|
302
|
+
|
|
303
|
+
std::cout << "detector " << label << " @ " << freq << " Hz:\n"
|
|
304
|
+
<< " YIN freq=" << ry.frequency << " conf=" << ry.confidence << "\n"
|
|
305
|
+
<< " PYIN freq=" << rpy.frequency << " conf=" << rpy.confidence << "\n";
|
|
306
|
+
|
|
307
|
+
assert(ry.voiced);
|
|
308
|
+
assertNear(ry.frequency, freq, freq * 0.01f); // within 1%
|
|
309
|
+
|
|
310
|
+
assert(rpy.voiced);
|
|
311
|
+
assertNear(rpy.frequency, freq, freq * 0.01f); // PYIN must agree with YIN
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
static void testEnsembleAgreementBonus() {
|
|
315
|
+
// When YIN and PYIN agree, the ensemble should produce confidence > either detector alone.
|
|
316
|
+
constexpr float sr = 48000.0f;
|
|
317
|
+
constexpr int n = 4096;
|
|
318
|
+
constexpr float f0 = 196.0f; // G3
|
|
319
|
+
|
|
320
|
+
auto buf = generateSine(f0, sr, n * 6); // 6 continuous frames for HPF warmup
|
|
321
|
+
|
|
322
|
+
std::vector<std::unique_ptr<IPitchDetector>> detectors;
|
|
323
|
+
detectors.push_back(std::make_unique<YinPitchDetector>(sr, n));
|
|
324
|
+
detectors.push_back(std::make_unique<PyinPitchDetector>(sr, n));
|
|
325
|
+
detectors.push_back(std::make_unique<CepstrumPitchDetector>(sr, n));
|
|
326
|
+
EnsembleSelector ensemble(std::move(detectors));
|
|
327
|
+
|
|
328
|
+
BiquadHpf hpf(sr, 70.0f);
|
|
329
|
+
|
|
330
|
+
DetectorResult result;
|
|
331
|
+
for (int f = 0; f < 6; ++f) {
|
|
332
|
+
std::vector<float> frame(buf.begin() + f * n, buf.begin() + (f + 1) * n);
|
|
333
|
+
hpf.process(frame.data(), n);
|
|
334
|
+
result = ensemble.detect(frame.data(), n, sr);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
std::cout << "ensemble G3: voiced=" << result.voiced
|
|
338
|
+
<< " freq=" << result.frequency
|
|
339
|
+
<< " conf=" << result.confidence << "\n";
|
|
340
|
+
|
|
341
|
+
assert(result.voiced);
|
|
342
|
+
assertNear(result.frequency, f0, f0 * 0.01f);
|
|
343
|
+
assert(result.confidence > 0.85f); // agreement bonus applied
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
static void testEnsembleOctaveSafety() {
|
|
347
|
+
// D3 (146.83 Hz): tau0 and 2*tau0 both fit in the search range.
|
|
348
|
+
// The ensemble must return the fundamental, not the sub-octave.
|
|
349
|
+
constexpr float sr = 48000.0f;
|
|
350
|
+
constexpr int n = 4096;
|
|
351
|
+
|
|
352
|
+
TunerEngine engine(sr, n);
|
|
353
|
+
auto buf = generateSine(146.83f, sr, n * 6);
|
|
354
|
+
|
|
355
|
+
PitchResult result;
|
|
356
|
+
for (int f = 0; f < 6; ++f) {
|
|
357
|
+
result = engine.process(buf.data() + f * n, n);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
std::cout << "ensemble octave safety D3: "
|
|
361
|
+
<< result.noteName << result.octave
|
|
362
|
+
<< " freq=" << result.frequency << "\n";
|
|
363
|
+
|
|
364
|
+
assert(result.hasPitch);
|
|
365
|
+
assert(result.noteName == "D");
|
|
366
|
+
assert(result.octave == 3);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
static void testOnsetDetector() {
|
|
370
|
+
OnsetDetector::Config cfg;
|
|
371
|
+
cfg.thresholdDb = 6.0f;
|
|
372
|
+
cfg.envelopeAlpha = 0.15f;
|
|
373
|
+
cfg.cooldownFrames = 4;
|
|
374
|
+
|
|
375
|
+
OnsetDetector det(cfg);
|
|
376
|
+
|
|
377
|
+
// When disabled, never fires
|
|
378
|
+
assert(!det.detect(-30.0f));
|
|
379
|
+
assert(!det.detect(0.0f)); // 30dB jump — still no onset because disabled
|
|
380
|
+
|
|
381
|
+
// Enable
|
|
382
|
+
det.setEnabled(true);
|
|
383
|
+
det.reset();
|
|
384
|
+
|
|
385
|
+
// Feed quiet frames to establish envelope
|
|
386
|
+
for (int i = 0; i < 10; ++i) {
|
|
387
|
+
det.detect(-40.0f);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Sudden jump — should trigger onset
|
|
391
|
+
bool onset = det.detect(-20.0f); // +20dB rise >> 6dB threshold
|
|
392
|
+
std::cout << "onset on energy spike: " << onset << "\n";
|
|
393
|
+
assert(onset);
|
|
394
|
+
|
|
395
|
+
// Cooldown — should NOT fire even with another jump
|
|
396
|
+
bool duringCooldown = det.detect(-10.0f);
|
|
397
|
+
std::cout << "onset during cooldown: " << duringCooldown << "\n";
|
|
398
|
+
assert(!duringCooldown);
|
|
399
|
+
|
|
400
|
+
// After cooldown expires, can fire again
|
|
401
|
+
for (int i = 0; i < 4; ++i) {
|
|
402
|
+
det.detect(-40.0f);
|
|
403
|
+
}
|
|
404
|
+
bool afterCooldown = det.detect(-20.0f);
|
|
405
|
+
std::cout << "onset after cooldown: " << afterCooldown << "\n";
|
|
406
|
+
assert(afterCooldown);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// --- M8: Adaptive Frame Size & Overlap tests ---
|
|
410
|
+
|
|
411
|
+
static void testSlidingWindowOverlap() {
|
|
412
|
+
// With 75% overlap on a 2048 frame, hopSize should be 512.
|
|
413
|
+
// Feed a continuous 440 Hz sine and verify we get results at hop rate.
|
|
414
|
+
constexpr float sr = 48000.0f;
|
|
415
|
+
constexpr int frameSize = 2048;
|
|
416
|
+
constexpr float overlap = 0.75f;
|
|
417
|
+
|
|
418
|
+
int callbackCount = 0;
|
|
419
|
+
PitchResult lastResult;
|
|
420
|
+
|
|
421
|
+
AudioFrameDispatcher dispatcher(frameSize, sr,
|
|
422
|
+
[&](const PitchResult& r) {
|
|
423
|
+
callbackCount++;
|
|
424
|
+
lastResult = r;
|
|
425
|
+
},
|
|
426
|
+
overlap
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
assert(dispatcher.hopSize() == 512); // 2048 * (1 - 0.75) = 512
|
|
430
|
+
assert(dispatcher.frameSize() == 2048);
|
|
431
|
+
|
|
432
|
+
// Generate enough audio to fill multiple hops
|
|
433
|
+
// First frame needs 2048, then each subsequent needs 512
|
|
434
|
+
constexpr int totalSamples = 2048 + 512 * 7; // 1 first + 7 hops = 8 callbacks
|
|
435
|
+
auto signal = generateSine(440.0f, sr, totalSamples);
|
|
436
|
+
|
|
437
|
+
dispatcher.start();
|
|
438
|
+
dispatcher.push(signal.data(), totalSamples);
|
|
439
|
+
|
|
440
|
+
// Wait for processing
|
|
441
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
442
|
+
dispatcher.stop();
|
|
443
|
+
|
|
444
|
+
std::cout << "sliding window overlap 75%: callbacks=" << callbackCount
|
|
445
|
+
<< " lastFreq=" << lastResult.frequency << "\n";
|
|
446
|
+
|
|
447
|
+
// Should have received multiple callbacks (first frame + hops)
|
|
448
|
+
assert(callbackCount >= 4); // at least some overlap callbacks fired
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
static void testSlidingWindowNoOverlap() {
|
|
452
|
+
// With 0% overlap, behavior should be identical to the old implementation
|
|
453
|
+
constexpr float sr = 48000.0f;
|
|
454
|
+
constexpr int frameSize = 2048;
|
|
455
|
+
|
|
456
|
+
int callbackCount = 0;
|
|
457
|
+
|
|
458
|
+
AudioFrameDispatcher dispatcher(frameSize, sr,
|
|
459
|
+
[&](const PitchResult& r) {
|
|
460
|
+
callbackCount++;
|
|
461
|
+
},
|
|
462
|
+
0.0f
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
assert(dispatcher.hopSize() == 2048); // no overlap → hop = frame
|
|
466
|
+
|
|
467
|
+
auto signal = generateSine(440.0f, sr, 2048 * 4);
|
|
468
|
+
|
|
469
|
+
dispatcher.start();
|
|
470
|
+
dispatcher.push(signal.data(), 2048 * 4);
|
|
471
|
+
|
|
472
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
473
|
+
dispatcher.stop();
|
|
474
|
+
|
|
475
|
+
std::cout << "sliding window no overlap: callbacks=" << callbackCount << "\n";
|
|
476
|
+
assert(callbackCount == 4); // exactly 4 full frames
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static void testAdaptiveFrameSizeBass() {
|
|
480
|
+
// Bass instrument should use 4096 frame for E1 (41 Hz) detection
|
|
481
|
+
constexpr float sr = 48000.0f;
|
|
482
|
+
constexpr int frameSize = 4096;
|
|
483
|
+
constexpr float e1Freq = 41.2f;
|
|
484
|
+
|
|
485
|
+
auto signal = generateSine(e1Freq, sr, frameSize * 8);
|
|
486
|
+
|
|
487
|
+
TunerEngine engine(sr, frameSize);
|
|
488
|
+
engine.setInstrument("bass");
|
|
489
|
+
|
|
490
|
+
PitchResult result;
|
|
491
|
+
for (int f = 0; f < 8; ++f) {
|
|
492
|
+
result = engine.process(signal.data() + f * frameSize, frameSize);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
std::cout << "adaptive bass E1 (41.2 Hz): hasPitch=" << result.hasPitch
|
|
496
|
+
<< " freq=" << result.frequency
|
|
497
|
+
<< " note=" << result.noteName << result.octave
|
|
498
|
+
<< " conf=" << result.confidence << "\n";
|
|
499
|
+
|
|
500
|
+
assert(result.hasPitch);
|
|
501
|
+
assertNear(result.frequency, e1Freq, e1Freq * 0.02f); // within 2%
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
static void testInstrumentRecommendedFrameSize() {
|
|
505
|
+
assert(instrumentRecommendedFrameSize("bass") == 4096);
|
|
506
|
+
assert(instrumentRecommendedFrameSize("cello") == 4096);
|
|
507
|
+
assert(instrumentRecommendedFrameSize("guitar") == 2048);
|
|
508
|
+
assert(instrumentRecommendedFrameSize("ukulele") == 2048);
|
|
509
|
+
assert(instrumentRecommendedFrameSize("violin") == 2048);
|
|
510
|
+
assert(instrumentRecommendedFrameSize("chromatic") == 2048);
|
|
511
|
+
assert(instrumentRecommendedFrameSize("unknown") == 2048);
|
|
512
|
+
std::cout << "instrumentRecommendedFrameSize: all correct\n";
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
static void testDispatcherReconfigure() {
|
|
516
|
+
constexpr float sr = 48000.0f;
|
|
517
|
+
int callbackCount = 0;
|
|
518
|
+
|
|
519
|
+
AudioFrameDispatcher dispatcher(2048, sr,
|
|
520
|
+
[&](const PitchResult& r) {
|
|
521
|
+
callbackCount++;
|
|
522
|
+
},
|
|
523
|
+
0.0f
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
assert(dispatcher.frameSize() == 2048);
|
|
527
|
+
|
|
528
|
+
// Reconfigure to 4096
|
|
529
|
+
dispatcher.reconfigure(4096, sr);
|
|
530
|
+
assert(dispatcher.frameSize() == 4096);
|
|
531
|
+
assert(dispatcher.hopSize() == 4096); // overlap still 0
|
|
532
|
+
|
|
533
|
+
// Push audio and verify it processes with new frame size
|
|
534
|
+
auto signal = generateSine(82.41f, sr, 4096 * 2);
|
|
535
|
+
dispatcher.start();
|
|
536
|
+
dispatcher.push(signal.data(), 4096 * 2);
|
|
537
|
+
|
|
538
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
|
539
|
+
dispatcher.stop();
|
|
540
|
+
|
|
541
|
+
std::cout << "dispatcher reconfigure 2048→4096: callbacks=" << callbackCount << "\n";
|
|
542
|
+
assert(callbackCount == 2);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
static void testSetOverlapRatio() {
|
|
546
|
+
constexpr float sr = 48000.0f;
|
|
547
|
+
|
|
548
|
+
AudioFrameDispatcher dispatcher(2048, sr,
|
|
549
|
+
[](const PitchResult&) {},
|
|
550
|
+
0.0f
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
assert(dispatcher.hopSize() == 2048);
|
|
554
|
+
|
|
555
|
+
dispatcher.setOverlapRatio(0.5f);
|
|
556
|
+
assert(dispatcher.hopSize() == 1024);
|
|
557
|
+
|
|
558
|
+
dispatcher.setOverlapRatio(0.75f);
|
|
559
|
+
assert(dispatcher.hopSize() == 512);
|
|
560
|
+
|
|
561
|
+
// Clamp to max 0.75
|
|
562
|
+
dispatcher.setOverlapRatio(0.9f);
|
|
563
|
+
assert(dispatcher.hopSize() == 512); // clamped to 0.75
|
|
564
|
+
|
|
565
|
+
// Clamp to min 0.0
|
|
566
|
+
dispatcher.setOverlapRatio(-0.5f);
|
|
567
|
+
assert(dispatcher.hopSize() == 2048); // clamped to 0.0
|
|
568
|
+
|
|
569
|
+
std::cout << "setOverlapRatio: all correct\n";
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
int main() {
|
|
573
|
+
testNoteMapper();
|
|
574
|
+
|
|
575
|
+
testYin(82.41f);
|
|
576
|
+
testYin(110.0f);
|
|
577
|
+
testYin(146.83f);
|
|
578
|
+
testYin(196.0f);
|
|
579
|
+
testYin(246.94f);
|
|
580
|
+
testYin(329.63f);
|
|
581
|
+
|
|
582
|
+
testTunerEngine(82.41f, "E", 2);
|
|
583
|
+
testTunerEngine(110.0f, "A", 2);
|
|
584
|
+
testTunerEngine(146.83f, "D", 3);
|
|
585
|
+
testTunerEngine(196.0f, "G", 3);
|
|
586
|
+
testTunerEngine(246.94f, "B", 3);
|
|
587
|
+
testTunerEngine(329.63f, "E", 4);
|
|
588
|
+
|
|
589
|
+
testTunerEngineSilence();
|
|
590
|
+
testTunerEngineQuietSignal();
|
|
591
|
+
|
|
592
|
+
// M3 per-detector and ensemble tests
|
|
593
|
+
testDetectorOnSine(82.41f, "E2");
|
|
594
|
+
testDetectorOnSine(110.0f, "A2");
|
|
595
|
+
testDetectorOnSine(146.83f, "D3");
|
|
596
|
+
testDetectorOnSine(196.0f, "G3");
|
|
597
|
+
testDetectorOnSine(329.63f, "E4");
|
|
598
|
+
testDetectorOnSine(440.0f, "A4");
|
|
599
|
+
testEnsembleAgreementBonus();
|
|
600
|
+
testEnsembleOctaveSafety();
|
|
601
|
+
|
|
602
|
+
// M2 DSP hardening tests
|
|
603
|
+
testPipelineCleanSine(82.41f, "E", 2);
|
|
604
|
+
testPipelineCleanSine(110.0f, "A", 2);
|
|
605
|
+
testPipelineCleanSine(196.0f, "G", 3);
|
|
606
|
+
testPipelineCleanSine(329.63f, "E", 4);
|
|
607
|
+
testPipelineCleanSine(440.0f, "A", 4);
|
|
608
|
+
testPipelineNoisySignal();
|
|
609
|
+
testPipelineHysteresis();
|
|
610
|
+
testInstrumentPreset();
|
|
611
|
+
testOnsetDetector();
|
|
612
|
+
|
|
613
|
+
// M8 Adaptive Frame Size & Overlap tests
|
|
614
|
+
testInstrumentRecommendedFrameSize();
|
|
615
|
+
testSetOverlapRatio();
|
|
616
|
+
testSlidingWindowNoOverlap();
|
|
617
|
+
testSlidingWindowOverlap();
|
|
618
|
+
testDispatcherReconfigure();
|
|
619
|
+
testAdaptiveFrameSizeBass();
|
|
620
|
+
|
|
621
|
+
std::cout << "all tuner engine tests passed." << std::endl;
|
|
622
|
+
|
|
623
|
+
return 0;
|
|
624
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#import <AVFoundation/AVFoundation.h>
|
|
4
|
+
#import <Foundation/Foundation.h>
|
|
5
|
+
|
|
6
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
7
|
+
|
|
8
|
+
typedef void (^AudioSamplesCallback)(const float* samples, int count, float sampleRate);
|
|
9
|
+
|
|
10
|
+
// Manages AVAudioSession + AVAudioEngine mic capture.
|
|
11
|
+
// Delivers interleaved float32 mono samples to the callback on AVAudioEngine's real-time thread.
|
|
12
|
+
// The callback must not block or allocate.
|
|
13
|
+
@interface IosAudioSource : NSObject
|
|
14
|
+
|
|
15
|
+
@property(nonatomic, copy, nullable) AudioSamplesCallback onSamples;
|
|
16
|
+
|
|
17
|
+
- (BOOL)startWithError:(NSError **)error;
|
|
18
|
+
- (void)stop;
|
|
19
|
+
- (float)sampleRate;
|
|
20
|
+
|
|
21
|
+
@end
|
|
22
|
+
|
|
23
|
+
NS_ASSUME_NONNULL_END
|