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.
Files changed (224) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +188 -0
  3. package/TunerEngine.podspec +32 -0
  4. package/android/build.gradle +86 -0
  5. package/android/src/main/AndroidManifest.xml +3 -0
  6. package/android/src/main/cpp/CMakeLists.txt +31 -0
  7. package/android/src/main/cpp/OboeAudioSource.cpp +101 -0
  8. package/android/src/main/cpp/OboeAudioSource.h +41 -0
  9. package/android/src/main/cpp/TunerEngineJni.cpp +220 -0
  10. package/android/src/main/java/com/tunerengine/TunerEngineModule.kt +183 -0
  11. package/android/src/main/java/com/tunerengine/TunerEnginePackage.kt +31 -0
  12. package/cpp/CMakeLists.txt +25 -0
  13. package/cpp/build/CMakeCache.txt +347 -0
  14. package/cpp/build/CMakeFiles/4.3.2/CMakeCXXCompiler.cmake +102 -0
  15. package/cpp/build/CMakeFiles/4.3.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  16. package/cpp/build/CMakeFiles/4.3.2/CMakeSystem.cmake +15 -0
  17. package/cpp/build/CMakeFiles/4.3.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +949 -0
  18. package/cpp/build/CMakeFiles/4.3.2/CompilerIdCXX/a.out +0 -0
  19. package/cpp/build/CMakeFiles/4.3.2/CompilerIdCXX/apple-sdk.cpp +1 -0
  20. package/cpp/build/CMakeFiles/CMakeConfigureLog.yaml +1619 -0
  21. package/cpp/build/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
  22. package/cpp/build/CMakeFiles/InstallScripts.json +7 -0
  23. package/cpp/build/CMakeFiles/Makefile.cmake +118 -0
  24. package/cpp/build/CMakeFiles/Makefile2 +122 -0
  25. package/cpp/build/CMakeFiles/TargetDirectories.txt +3 -0
  26. package/cpp/build/CMakeFiles/cmake.check_cache +1 -0
  27. package/cpp/build/CMakeFiles/progress.marks +1 -0
  28. package/cpp/build/CMakeFiles/tuner_engine_core.dir/DependInfo.cmake +36 -0
  29. package/cpp/build/CMakeFiles/tuner_engine_core.dir/build.make +322 -0
  30. package/cpp/build/CMakeFiles/tuner_engine_core.dir/cmake_clean.cmake +37 -0
  31. package/cpp/build/CMakeFiles/tuner_engine_core.dir/cmake_clean_target.cmake +3 -0
  32. package/cpp/build/CMakeFiles/tuner_engine_core.dir/compiler_depend.make +2 -0
  33. package/cpp/build/CMakeFiles/tuner_engine_core.dir/compiler_depend.ts +2 -0
  34. package/cpp/build/CMakeFiles/tuner_engine_core.dir/depend.make +2 -0
  35. package/cpp/build/CMakeFiles/tuner_engine_core.dir/flags.make +12 -0
  36. package/cpp/build/CMakeFiles/tuner_engine_core.dir/link.txt +2 -0
  37. package/cpp/build/CMakeFiles/tuner_engine_core.dir/progress.make +16 -0
  38. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o +0 -0
  39. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o.d +814 -0
  40. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o +0 -0
  41. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o.d +206 -0
  42. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o +0 -0
  43. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o.d +797 -0
  44. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o +0 -0
  45. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o.d +755 -0
  46. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o +0 -0
  47. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o.d +669 -0
  48. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o +0 -0
  49. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o.d +648 -0
  50. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o +0 -0
  51. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o.d +765 -0
  52. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o +0 -0
  53. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o.d +648 -0
  54. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o +0 -0
  55. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o.d +755 -0
  56. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o +0 -0
  57. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o.d +648 -0
  58. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o +0 -0
  59. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o.d +665 -0
  60. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o +0 -0
  61. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o.d +811 -0
  62. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o +0 -0
  63. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o.d +754 -0
  64. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o +0 -0
  65. package/cpp/build/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o.d +755 -0
  66. package/cpp/build/Makefile +532 -0
  67. package/cpp/build/cmake_install.cmake +61 -0
  68. package/cpp/build/libtuner_engine_core.a +0 -0
  69. package/cpp/include/AudioFrameDispatcher.hpp +87 -0
  70. package/cpp/include/BiquadHpf.hpp +22 -0
  71. package/cpp/include/CepstrumPitchDetector.hpp +33 -0
  72. package/cpp/include/EnsembleSelector.hpp +25 -0
  73. package/cpp/include/Fft.hpp +44 -0
  74. package/cpp/include/IPitchDetector.hpp +21 -0
  75. package/cpp/include/InstrumentPresets.hpp +33 -0
  76. package/cpp/include/NoteMapper.hpp +15 -0
  77. package/cpp/include/OnsetDetector.hpp +37 -0
  78. package/cpp/include/Pipeline.hpp +55 -0
  79. package/cpp/include/PitchResult.hpp +24 -0
  80. package/cpp/include/PostProcessor.hpp +51 -0
  81. package/cpp/include/PyinPitchDetector.hpp +35 -0
  82. package/cpp/include/RingBuffer.hpp +72 -0
  83. package/cpp/include/SnrEstimator.hpp +21 -0
  84. package/cpp/include/StringMatcher.hpp +27 -0
  85. package/cpp/include/TunerEngine.hpp +32 -0
  86. package/cpp/include/TuningPresets.hpp +103 -0
  87. package/cpp/include/Window.hpp +13 -0
  88. package/cpp/include/YinPitchDetector.hpp +37 -0
  89. package/cpp/src/AudioFrameDispatcher.cpp +180 -0
  90. package/cpp/src/BiquadHpf.cpp +35 -0
  91. package/cpp/src/CepstrumPitchDetector.cpp +116 -0
  92. package/cpp/src/EnsembleSelector.cpp +91 -0
  93. package/cpp/src/NoteMapper.cpp +47 -0
  94. package/cpp/src/OnsetDetector.cpp +39 -0
  95. package/cpp/src/Pipeline.cpp +133 -0
  96. package/cpp/src/PostProcessor.cpp +111 -0
  97. package/cpp/src/PyinPitchDetector.cpp +134 -0
  98. package/cpp/src/SnrEstimator.cpp +33 -0
  99. package/cpp/src/StringMatcher.cpp +37 -0
  100. package/cpp/src/TunerEngine.cpp +67 -0
  101. package/cpp/src/Window.cpp +21 -0
  102. package/cpp/src/YinPitchDetector.cpp +118 -0
  103. package/cpp/tests/CMakeLists.txt +23 -0
  104. package/cpp/tests/bench.cpp +160 -0
  105. package/cpp/tests/build/CMakeCache.txt +356 -0
  106. package/cpp/tests/build/CMakeFiles/4.3.2/CMakeCXXCompiler.cmake +102 -0
  107. package/cpp/tests/build/CMakeFiles/4.3.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
  108. package/cpp/tests/build/CMakeFiles/4.3.2/CMakeSystem.cmake +15 -0
  109. package/cpp/tests/build/CMakeFiles/4.3.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +949 -0
  110. package/cpp/tests/build/CMakeFiles/4.3.2/CompilerIdCXX/a.out +0 -0
  111. package/cpp/tests/build/CMakeFiles/4.3.2/CompilerIdCXX/apple-sdk.cpp +1 -0
  112. package/cpp/tests/build/CMakeFiles/CMakeConfigureLog.yaml +1619 -0
  113. package/cpp/tests/build/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
  114. package/cpp/tests/build/CMakeFiles/InstallScripts.json +8 -0
  115. package/cpp/tests/build/CMakeFiles/Makefile.cmake +122 -0
  116. package/cpp/tests/build/CMakeFiles/Makefile2 +211 -0
  117. package/cpp/tests/build/CMakeFiles/TargetDirectories.txt +9 -0
  118. package/cpp/tests/build/CMakeFiles/cmake.check_cache +1 -0
  119. package/cpp/tests/build/CMakeFiles/progress.marks +1 -0
  120. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/DependInfo.cmake +23 -0
  121. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/bench.cpp.o +0 -0
  122. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/bench.cpp.o.d +813 -0
  123. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/build.make +114 -0
  124. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/cmake_clean.cmake +11 -0
  125. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/compiler_depend.make +2 -0
  126. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/compiler_depend.ts +2 -0
  127. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/depend.make +2 -0
  128. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/flags.make +12 -0
  129. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/link.txt +1 -0
  130. package/cpp/tests/build/CMakeFiles/tuner_engine_bench.dir/progress.make +3 -0
  131. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/DependInfo.cmake +23 -0
  132. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/build.make +114 -0
  133. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/cmake_clean.cmake +11 -0
  134. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/compiler_depend.make +2 -0
  135. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/compiler_depend.ts +2 -0
  136. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/depend.make +2 -0
  137. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/flags.make +12 -0
  138. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/link.txt +1 -0
  139. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/main.cpp.o +0 -0
  140. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/main.cpp.o.d +823 -0
  141. package/cpp/tests/build/CMakeFiles/tuner_engine_tests.dir/progress.make +3 -0
  142. package/cpp/tests/build/CTestTestfile.cmake +9 -0
  143. package/cpp/tests/build/Makefile +247 -0
  144. package/cpp/tests/build/cmake_install.cmake +66 -0
  145. package/cpp/tests/build/tuner_engine_bench +0 -0
  146. package/cpp/tests/build/tuner_engine_core/CMakeFiles/CMakeDirectoryInformation.cmake +16 -0
  147. package/cpp/tests/build/tuner_engine_core/CMakeFiles/progress.marks +1 -0
  148. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/DependInfo.cmake +36 -0
  149. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/build.make +322 -0
  150. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/cmake_clean.cmake +37 -0
  151. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/cmake_clean_target.cmake +3 -0
  152. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/compiler_depend.make +2 -0
  153. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/compiler_depend.ts +2 -0
  154. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/depend.make +2 -0
  155. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/flags.make +12 -0
  156. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/link.txt +2 -0
  157. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/progress.make +16 -0
  158. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o +0 -0
  159. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/AudioFrameDispatcher.cpp.o.d +814 -0
  160. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o +0 -0
  161. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/BiquadHpf.cpp.o.d +206 -0
  162. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o +0 -0
  163. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/CepstrumPitchDetector.cpp.o.d +797 -0
  164. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o +0 -0
  165. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/EnsembleSelector.cpp.o.d +755 -0
  166. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o +0 -0
  167. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/NoteMapper.cpp.o.d +669 -0
  168. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o +0 -0
  169. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/OnsetDetector.cpp.o.d +648 -0
  170. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o +0 -0
  171. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Pipeline.cpp.o.d +765 -0
  172. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o +0 -0
  173. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PostProcessor.cpp.o.d +648 -0
  174. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o +0 -0
  175. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/PyinPitchDetector.cpp.o.d +755 -0
  176. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o +0 -0
  177. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/SnrEstimator.cpp.o.d +648 -0
  178. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o +0 -0
  179. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/StringMatcher.cpp.o.d +665 -0
  180. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o +0 -0
  181. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/TunerEngine.cpp.o.d +811 -0
  182. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o +0 -0
  183. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/Window.cpp.o.d +754 -0
  184. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o +0 -0
  185. package/cpp/tests/build/tuner_engine_core/CMakeFiles/tuner_engine_core.dir/src/YinPitchDetector.cpp.o.d +755 -0
  186. package/cpp/tests/build/tuner_engine_core/Makefile +544 -0
  187. package/cpp/tests/build/tuner_engine_core/cmake_install.cmake +45 -0
  188. package/cpp/tests/build/tuner_engine_core/libtuner_engine_core.a +0 -0
  189. package/cpp/tests/build/tuner_engine_tests +0 -0
  190. package/cpp/tests/main.cpp +624 -0
  191. package/ios/IosAudioSource.h +23 -0
  192. package/ios/IosAudioSource.mm +174 -0
  193. package/ios/TunerBridge.h +24 -0
  194. package/ios/TunerBridge.mm +136 -0
  195. package/ios/TunerEngine.h +6 -0
  196. package/ios/TunerEngine.mm +144 -0
  197. package/lib/module/NativeTunerEngine.js +5 -0
  198. package/lib/module/NativeTunerEngine.js.map +1 -0
  199. package/lib/module/TunerEngine.js +43 -0
  200. package/lib/module/TunerEngine.js.map +1 -0
  201. package/lib/module/index.js +5 -0
  202. package/lib/module/index.js.map +1 -0
  203. package/lib/module/package.json +1 -0
  204. package/lib/module/types.js +2 -0
  205. package/lib/module/types.js.map +1 -0
  206. package/lib/module/useTuner.js +55 -0
  207. package/lib/module/useTuner.js.map +1 -0
  208. package/lib/typescript/package.json +1 -0
  209. package/lib/typescript/src/NativeTunerEngine.d.ts +17 -0
  210. package/lib/typescript/src/NativeTunerEngine.d.ts.map +1 -0
  211. package/lib/typescript/src/TunerEngine.d.ts +18 -0
  212. package/lib/typescript/src/TunerEngine.d.ts.map +1 -0
  213. package/lib/typescript/src/index.d.ts +4 -0
  214. package/lib/typescript/src/index.d.ts.map +1 -0
  215. package/lib/typescript/src/types.d.ts +86 -0
  216. package/lib/typescript/src/types.d.ts.map +1 -0
  217. package/lib/typescript/src/useTuner.d.ts +15 -0
  218. package/lib/typescript/src/useTuner.d.ts.map +1 -0
  219. package/package.json +141 -0
  220. package/src/NativeTunerEngine.ts +17 -0
  221. package/src/TunerEngine.ts +62 -0
  222. package/src/index.tsx +11 -0
  223. package/src/types.ts +109 -0
  224. 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)