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,37 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "IPitchDetector.hpp"
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
struct YinResult {
|
|
7
|
+
bool hasPitch = false;
|
|
8
|
+
float frequency = 0.0f;
|
|
9
|
+
float confidence = 0.0f;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
class YinPitchDetector : public IPitchDetector {
|
|
13
|
+
public:
|
|
14
|
+
YinPitchDetector(float sampleRate, int frameSize);
|
|
15
|
+
|
|
16
|
+
// Legacy interface used by existing tests
|
|
17
|
+
YinResult detect(const float* input, int frameCount);
|
|
18
|
+
|
|
19
|
+
// IPitchDetector — delegates to the above
|
|
20
|
+
DetectorResult detect(const float* frame, int n, float sampleRate) override;
|
|
21
|
+
|
|
22
|
+
void setFrequencyRange(float minFrequency, float maxFrequency) override;
|
|
23
|
+
void setThreshold(float threshold) override;
|
|
24
|
+
|
|
25
|
+
private:
|
|
26
|
+
float sampleRate_;
|
|
27
|
+
int frameSize_;
|
|
28
|
+
|
|
29
|
+
float minFrequency_ = 60.0f;
|
|
30
|
+
float maxFrequency_ = 1200.0f;
|
|
31
|
+
float threshold_ = 0.15f;
|
|
32
|
+
|
|
33
|
+
std::vector<float> difference_;
|
|
34
|
+
std::vector<float> cmnd_;
|
|
35
|
+
|
|
36
|
+
float parabolicInterpolation(int tau) const;
|
|
37
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#include "AudioFrameDispatcher.hpp"
|
|
2
|
+
#include "InstrumentPresets.hpp"
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <chrono>
|
|
6
|
+
#include <cstring>
|
|
7
|
+
|
|
8
|
+
AudioFrameDispatcher::AudioFrameDispatcher(
|
|
9
|
+
int frameSize, float sampleRate, PitchCallback callback, float overlapRatio
|
|
10
|
+
)
|
|
11
|
+
: frameSize_(frameSize)
|
|
12
|
+
, hopSize_(frameSize) // will be recomputed below
|
|
13
|
+
, overlapRatio_(std::clamp(overlapRatio, 0.0f, 0.75f))
|
|
14
|
+
, sampleRate_(sampleRate)
|
|
15
|
+
, callback_(std::move(callback))
|
|
16
|
+
, frameBuffer_(static_cast<size_t>(frameSize), 0.0f)
|
|
17
|
+
, engine_(std::make_unique<TunerEngine>(sampleRate, frameSize))
|
|
18
|
+
{
|
|
19
|
+
recomputeHopSize();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
AudioFrameDispatcher::~AudioFrameDispatcher() {
|
|
23
|
+
stop();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
void AudioFrameDispatcher::recomputeHopSize() {
|
|
27
|
+
hopSize_ = std::max(1, static_cast<int>(
|
|
28
|
+
std::round(frameSize_ * (1.0f - overlapRatio_))
|
|
29
|
+
));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void AudioFrameDispatcher::start() {
|
|
33
|
+
if (running_.exchange(true)) return; // already running
|
|
34
|
+
firstFrame_ = true;
|
|
35
|
+
workerThread_ = std::thread(&AudioFrameDispatcher::workerLoop, this);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void AudioFrameDispatcher::stop() {
|
|
39
|
+
if (!running_.exchange(false)) return; // already stopped
|
|
40
|
+
if (workerThread_.joinable()) {
|
|
41
|
+
workerThread_.join();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void AudioFrameDispatcher::push(const float* samples, int count) {
|
|
46
|
+
ring_.push(samples, count);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
void AudioFrameDispatcher::setSampleRate(float sampleRate) {
|
|
50
|
+
if (sampleRate <= 0.0f) return;
|
|
51
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
52
|
+
sampleRate_ = sampleRate;
|
|
53
|
+
engine_ = std::make_unique<TunerEngine>(sampleRate, frameSize_);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
void AudioFrameDispatcher::setA4(float hz) {
|
|
57
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
58
|
+
if (engine_) engine_->setA4(hz);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void AudioFrameDispatcher::setNoiseGateDb(float db) {
|
|
62
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
63
|
+
if (engine_) engine_->setNoiseGateDb(db);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void AudioFrameDispatcher::setConfidenceThreshold(float value) {
|
|
67
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
68
|
+
if (engine_) engine_->setConfidenceThreshold(value);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
void AudioFrameDispatcher::setFrequencyRange(float minHz, float maxHz) {
|
|
72
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
73
|
+
if (engine_) engine_->setFrequencyRange(minHz, maxHz);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
void AudioFrameDispatcher::setInstrument(const std::string& name) {
|
|
77
|
+
// Check if the instrument's recommended frame size differs
|
|
78
|
+
const int recommended = instrumentRecommendedFrameSize(name);
|
|
79
|
+
if (recommended != frameSize_) {
|
|
80
|
+
reconfigure(recommended, sampleRate_);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
84
|
+
if (engine_) engine_->setInstrument(name);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void AudioFrameDispatcher::setTuning(const std::string& name) {
|
|
88
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
89
|
+
if (engine_) engine_->setTuning(name);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void AudioFrameDispatcher::setPostProcessorConfig(PostProcessor::Config cfg) {
|
|
93
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
94
|
+
if (engine_) engine_->setPostProcessorConfig(cfg);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void AudioFrameDispatcher::setHpfCutoff(float hz) {
|
|
98
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
99
|
+
if (engine_) engine_->setHpfCutoff(hz);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void AudioFrameDispatcher::setOnsetDetectionEnabled(bool enabled) {
|
|
103
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
104
|
+
if (engine_) engine_->setOnsetDetectionEnabled(enabled);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
void AudioFrameDispatcher::setOnsetConfig(OnsetDetector::Config cfg) {
|
|
108
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
109
|
+
if (engine_) engine_->setOnsetConfig(cfg);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
void AudioFrameDispatcher::setOverlapRatio(float ratio) {
|
|
113
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
114
|
+
overlapRatio_ = std::clamp(ratio, 0.0f, 0.75f);
|
|
115
|
+
recomputeHopSize();
|
|
116
|
+
firstFrame_ = true; // reset sliding window state
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void AudioFrameDispatcher::reconfigure(int newFrameSize, float sampleRate) {
|
|
120
|
+
const bool wasRunning = running_.load();
|
|
121
|
+
if (wasRunning) stop();
|
|
122
|
+
|
|
123
|
+
{
|
|
124
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
125
|
+
frameSize_ = newFrameSize;
|
|
126
|
+
sampleRate_ = sampleRate;
|
|
127
|
+
recomputeHopSize();
|
|
128
|
+
frameBuffer_.assign(static_cast<size_t>(frameSize_), 0.0f);
|
|
129
|
+
firstFrame_ = true;
|
|
130
|
+
engine_ = std::make_unique<TunerEngine>(sampleRate_, frameSize_);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (wasRunning) start();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
void AudioFrameDispatcher::workerLoop() {
|
|
137
|
+
while (running_.load(std::memory_order_relaxed)) {
|
|
138
|
+
if (firstFrame_) {
|
|
139
|
+
// Cold start: wait for a full frame before first processing
|
|
140
|
+
if (ring_.available() >= frameSize_) {
|
|
141
|
+
ring_.pop(frameBuffer_.data(), frameSize_);
|
|
142
|
+
firstFrame_ = false;
|
|
143
|
+
|
|
144
|
+
PitchResult result;
|
|
145
|
+
{
|
|
146
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
147
|
+
result = engine_->process(frameBuffer_.data(), frameSize_);
|
|
148
|
+
}
|
|
149
|
+
if (callback_) {
|
|
150
|
+
callback_(result);
|
|
151
|
+
}
|
|
152
|
+
} else {
|
|
153
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// Sliding window: shift old data left, pop hopSize_ new samples at the end
|
|
157
|
+
if (ring_.available() >= hopSize_) {
|
|
158
|
+
// Shift existing samples left by hopSize_
|
|
159
|
+
const int retain = frameSize_ - hopSize_;
|
|
160
|
+
std::memmove(frameBuffer_.data(),
|
|
161
|
+
frameBuffer_.data() + hopSize_,
|
|
162
|
+
static_cast<size_t>(retain) * sizeof(float));
|
|
163
|
+
|
|
164
|
+
// Pop new samples into the tail
|
|
165
|
+
ring_.pop(frameBuffer_.data() + retain, hopSize_);
|
|
166
|
+
|
|
167
|
+
PitchResult result;
|
|
168
|
+
{
|
|
169
|
+
std::lock_guard<std::mutex> lock(engineMutex_);
|
|
170
|
+
result = engine_->process(frameBuffer_.data(), frameSize_);
|
|
171
|
+
}
|
|
172
|
+
if (callback_) {
|
|
173
|
+
callback_(result);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#include "BiquadHpf.hpp"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
|
|
5
|
+
static constexpr float kPi = 3.14159265358979323846f;
|
|
6
|
+
|
|
7
|
+
BiquadHpf::BiquadHpf(float sampleRate, float cutoffHz, float q) {
|
|
8
|
+
// Audio EQ Cookbook — High Pass Filter
|
|
9
|
+
const float w0 = 2.0f * kPi * cutoffHz / sampleRate;
|
|
10
|
+
const float cosW0 = std::cos(w0);
|
|
11
|
+
const float alpha = std::sin(w0) / (2.0f * q);
|
|
12
|
+
const float a0 = 1.0f + alpha;
|
|
13
|
+
|
|
14
|
+
b0_ = (1.0f + cosW0) / 2.0f / a0;
|
|
15
|
+
b1_ = -(1.0f + cosW0) / a0;
|
|
16
|
+
b2_ = (1.0f + cosW0) / 2.0f / a0;
|
|
17
|
+
a1_ = -2.0f * cosW0 / a0;
|
|
18
|
+
a2_ = (1.0f - alpha) / a0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void BiquadHpf::process(float* frame, int n) {
|
|
22
|
+
if (!frame || n <= 0) return;
|
|
23
|
+
for (int i = 0; i < n; ++i) {
|
|
24
|
+
const float x = frame[i];
|
|
25
|
+
const float y = b0_ * x + w1_;
|
|
26
|
+
w1_ = b1_ * x - a1_ * y + w2_;
|
|
27
|
+
w2_ = b2_ * x - a2_ * y;
|
|
28
|
+
frame[i] = y;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void BiquadHpf::reset() {
|
|
33
|
+
w1_ = 0.0f;
|
|
34
|
+
w2_ = 0.0f;
|
|
35
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#include "CepstrumPitchDetector.hpp"
|
|
2
|
+
#include "Fft.hpp"
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <cmath>
|
|
6
|
+
|
|
7
|
+
static constexpr float kEps = 1e-10f;
|
|
8
|
+
static constexpr float kPi = 3.14159265358979323846f;
|
|
9
|
+
|
|
10
|
+
CepstrumPitchDetector::CepstrumPitchDetector(float sampleRate, int frameSize)
|
|
11
|
+
: sampleRate_(sampleRate)
|
|
12
|
+
, frameSize_(frameSize)
|
|
13
|
+
, hann_(static_cast<size_t>(frameSize))
|
|
14
|
+
, fftBuf_(static_cast<size_t>(frameSize))
|
|
15
|
+
, logPow_(static_cast<size_t>(frameSize))
|
|
16
|
+
{
|
|
17
|
+
for (int i = 0; i < frameSize_; ++i) {
|
|
18
|
+
hann_[i] = 0.5f * (1.0f - std::cos(2.0f * kPi * i / (frameSize_ - 1)));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
void CepstrumPitchDetector::setFrequencyRange(float minHz, float maxHz) {
|
|
23
|
+
minHz_ = minHz;
|
|
24
|
+
maxHz_ = maxHz;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void CepstrumPitchDetector::setThreshold(float threshold) {
|
|
28
|
+
threshold_ = threshold;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
DetectorResult CepstrumPitchDetector::detect(const float* frame, int n, float sampleRate) {
|
|
32
|
+
const float sr = sampleRate > 0.0f ? sampleRate : sampleRate_;
|
|
33
|
+
const int len = std::min(n, frameSize_);
|
|
34
|
+
|
|
35
|
+
// Hann-window the frame into the FFT buffer
|
|
36
|
+
for (int i = 0; i < len; ++i) {
|
|
37
|
+
fftBuf_[i] = {frame[i] * hann_[i], 0.0f};
|
|
38
|
+
}
|
|
39
|
+
for (int i = len; i < frameSize_; ++i) {
|
|
40
|
+
fftBuf_[i] = {};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
tuner::fft(fftBuf_);
|
|
44
|
+
|
|
45
|
+
// Log power spectrum — build full two-sided symmetric array in logPow_
|
|
46
|
+
for (int k = 0; k <= frameSize_ / 2; ++k) {
|
|
47
|
+
const float re = fftBuf_[k].real();
|
|
48
|
+
const float im = fftBuf_[k].imag();
|
|
49
|
+
logPow_[k] = std::log(re * re + im * im + kEps);
|
|
50
|
+
}
|
|
51
|
+
for (int k = 1; k < frameSize_ / 2; ++k) {
|
|
52
|
+
logPow_[frameSize_ - k] = logPow_[k];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Load log-power into fftBuf_ (real) and IFFT → real cepstrum
|
|
56
|
+
for (int k = 0; k < frameSize_; ++k) {
|
|
57
|
+
fftBuf_[k] = {logPow_[k], 0.0f};
|
|
58
|
+
}
|
|
59
|
+
tuner::ifft(fftBuf_);
|
|
60
|
+
|
|
61
|
+
// Quefrency search range
|
|
62
|
+
const int tauMin = std::max(1, static_cast<int>(sr / maxHz_));
|
|
63
|
+
const int tauMax = std::min(frameSize_ / 2 - 1, static_cast<int>(sr / minHz_));
|
|
64
|
+
if (tauMin >= tauMax) return DetectorResult{};
|
|
65
|
+
|
|
66
|
+
// Find the quefrency peak
|
|
67
|
+
float maxVal = -1e30f;
|
|
68
|
+
int bestTau = -1;
|
|
69
|
+
for (int q = tauMin; q <= tauMax; ++q) {
|
|
70
|
+
const float v = fftBuf_[q].real();
|
|
71
|
+
if (v > maxVal) { maxVal = v; bestTau = q; }
|
|
72
|
+
}
|
|
73
|
+
if (bestTau < 0) return DetectorResult{};
|
|
74
|
+
|
|
75
|
+
const float prominence = peakProminence(tauMin, tauMax, bestTau);
|
|
76
|
+
if (prominence < threshold_) return DetectorResult{};
|
|
77
|
+
|
|
78
|
+
// Sub-sample refinement via parabolic interpolation
|
|
79
|
+
float peakTau = static_cast<float>(bestTau);
|
|
80
|
+
if (bestTau > tauMin && bestTau < tauMax) {
|
|
81
|
+
const float L = fftBuf_[bestTau - 1].real();
|
|
82
|
+
const float C = fftBuf_[bestTau].real();
|
|
83
|
+
const float R = fftBuf_[bestTau + 1].real();
|
|
84
|
+
const float d = L - 2.0f * C + R;
|
|
85
|
+
if (std::fabs(d) > 1e-6f) {
|
|
86
|
+
peakTau += 0.5f * (L - R) / d;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (peakTau <= 0.0f) return DetectorResult{};
|
|
90
|
+
|
|
91
|
+
return DetectorResult{true, sr / peakTau, prominence};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
float CepstrumPitchDetector::peakProminence(int tauMin, int tauMax, int peakTau) const {
|
|
95
|
+
const float peak = fftBuf_[peakTau].real();
|
|
96
|
+
|
|
97
|
+
// RMS and mean of the quefrency range (excluding the peak itself)
|
|
98
|
+
float sum = 0.0f;
|
|
99
|
+
float sumSq = 0.0f;
|
|
100
|
+
const int count = tauMax - tauMin + 1;
|
|
101
|
+
for (int q = tauMin; q <= tauMax; ++q) {
|
|
102
|
+
const float v = fftBuf_[q].real();
|
|
103
|
+
sum += v;
|
|
104
|
+
sumSq += v * v;
|
|
105
|
+
}
|
|
106
|
+
const float mean = sum / static_cast<float>(count);
|
|
107
|
+
const float rms = std::sqrt(sumSq / static_cast<float>(count));
|
|
108
|
+
|
|
109
|
+
// Confidence: how much the peak exceeds the RMS level.
|
|
110
|
+
// A cepstrum with no clear periodicity (pure sine) has peak ≈ RMS → conf ≈ 0.
|
|
111
|
+
// A strong harmonic signal has peak >> RMS → conf → 1.
|
|
112
|
+
if (rms < kEps) return 0.0f;
|
|
113
|
+
const float snr = (peak - mean) / rms;
|
|
114
|
+
// Map snr range [0, 5] → [0, 1]; cap both sides
|
|
115
|
+
return std::max(0.0f, std::min(1.0f, snr / 5.0f));
|
|
116
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#include "EnsembleSelector.hpp"
|
|
2
|
+
|
|
3
|
+
#include <cmath>
|
|
4
|
+
|
|
5
|
+
EnsembleSelector::EnsembleSelector(std::vector<std::unique_ptr<IPitchDetector>> detectors)
|
|
6
|
+
: detectors_(std::move(detectors))
|
|
7
|
+
{
|
|
8
|
+
resultsBuf_.resize(detectors_.size());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
void EnsembleSelector::reset() {
|
|
12
|
+
for (auto& d : detectors_) d->reset();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
void EnsembleSelector::setFrequencyRange(float minHz, float maxHz) {
|
|
16
|
+
for (auto& d : detectors_) d->setFrequencyRange(minHz, maxHz);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void EnsembleSelector::setThreshold(float threshold) {
|
|
20
|
+
for (auto& d : detectors_) d->setThreshold(threshold);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
bool EnsembleSelector::withinSemitones(float f1, float f2, float tolerance) {
|
|
24
|
+
if (f1 <= 0.0f || f2 <= 0.0f) return false;
|
|
25
|
+
return std::fabs(12.0f * std::log2(f1 / f2)) <= tolerance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
DetectorResult EnsembleSelector::detect(const float* frame, int n, float sampleRate) {
|
|
29
|
+
// Run all detectors into the pre-allocated buffer
|
|
30
|
+
for (int i = 0; i < static_cast<int>(detectors_.size()); ++i) {
|
|
31
|
+
resultsBuf_[i] = detectors_[i]->detect(frame, n, sampleRate);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Work with a small stack-local view of voiced results to avoid heap allocation.
|
|
35
|
+
// Maximum 8 detectors is more than enough for any realistic ensemble.
|
|
36
|
+
struct Entry { int idx; float freq; float conf; int votes; };
|
|
37
|
+
Entry voiced[8];
|
|
38
|
+
int voicedCount = 0;
|
|
39
|
+
|
|
40
|
+
for (int i = 0; i < static_cast<int>(resultsBuf_.size()) && voicedCount < 8; ++i) {
|
|
41
|
+
const auto& r = resultsBuf_[i];
|
|
42
|
+
if (r.voiced && r.confidence > 0.0f) {
|
|
43
|
+
voiced[voicedCount++] = {i, r.frequency, r.confidence, 0};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (voicedCount == 0) return DetectorResult{};
|
|
48
|
+
|
|
49
|
+
// Tally agreement votes between voiced entries
|
|
50
|
+
for (int i = 0; i < voicedCount; ++i) {
|
|
51
|
+
for (int j = i + 1; j < voicedCount; ++j) {
|
|
52
|
+
if (withinSemitones(voiced[i].freq, voiced[j].freq)) {
|
|
53
|
+
++voiced[i].votes;
|
|
54
|
+
++voiced[j].votes;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Pick winner: most votes first, then highest confidence
|
|
60
|
+
const Entry* best = &voiced[0];
|
|
61
|
+
for (int i = 1; i < voicedCount; ++i) {
|
|
62
|
+
const Entry& c = voiced[i];
|
|
63
|
+
if (c.votes > best->votes
|
|
64
|
+
|| (c.votes == best->votes && c.conf > best->conf)) {
|
|
65
|
+
best = &c;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Average the frequency (and confidence) of all detectors that agree with winner
|
|
70
|
+
float freqSum = best->freq;
|
|
71
|
+
float confSum = best->conf;
|
|
72
|
+
int agreeing = 1;
|
|
73
|
+
|
|
74
|
+
for (int i = 0; i < voicedCount; ++i) {
|
|
75
|
+
if (&voiced[i] != best && withinSemitones(voiced[i].freq, best->freq)) {
|
|
76
|
+
freqSum += voiced[i].freq;
|
|
77
|
+
confSum += voiced[i].conf;
|
|
78
|
+
++agreeing;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const float avgFreq = freqSum / static_cast<float>(agreeing);
|
|
83
|
+
float avgConf = confSum / static_cast<float>(agreeing);
|
|
84
|
+
|
|
85
|
+
// Confidence bonus for agreement, penalty for a lone detector
|
|
86
|
+
avgConf = (agreeing > 1)
|
|
87
|
+
? std::min(1.0f, avgConf * 1.1f)
|
|
88
|
+
: avgConf * 0.85f;
|
|
89
|
+
|
|
90
|
+
return DetectorResult{true, avgFreq, avgConf};
|
|
91
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#include "NoteMapper.hpp"
|
|
2
|
+
|
|
3
|
+
#include <array>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
#include <stdexcept>
|
|
6
|
+
|
|
7
|
+
NoteMapper::NoteMapper(float a4) : a4_(a4) {}
|
|
8
|
+
|
|
9
|
+
void NoteMapper::setA4(float value) {
|
|
10
|
+
if (value <= 0.0f) {
|
|
11
|
+
throw std::invalid_argument("A4 must be positive");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
a4_ = value;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
PitchResult NoteMapper::map(float frequency, float confidence, float rmsDb) const {
|
|
18
|
+
PitchResult result;
|
|
19
|
+
|
|
20
|
+
if (frequency <= 0.0f) {
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static const std::array<const char*, 12> names = {
|
|
25
|
+
"C", "C#", "D", "D#", "E", "F",
|
|
26
|
+
"F#", "G", "G#", "A", "A#", "B"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const int midi = static_cast<int>(
|
|
30
|
+
std::round(69.0f + 12.0f * std::log2(frequency / a4_))
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const float target = a4_ * std::pow(2.0f, (midi - 69) / 12.0f);
|
|
34
|
+
const float cents = 1200.0f * std::log2(frequency / target);
|
|
35
|
+
|
|
36
|
+
result.hasPitch = true;
|
|
37
|
+
result.frequency = frequency;
|
|
38
|
+
result.confidence = confidence;
|
|
39
|
+
result.rmsDb = rmsDb;
|
|
40
|
+
result.midiNote = midi;
|
|
41
|
+
result.noteName = names[midi % 12];
|
|
42
|
+
result.octave = midi / 12 - 1;
|
|
43
|
+
result.targetFrequency = target;
|
|
44
|
+
result.cents = cents;
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#include "OnsetDetector.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
|
|
5
|
+
OnsetDetector::OnsetDetector(Config cfg) : cfg_(cfg) {}
|
|
6
|
+
|
|
7
|
+
bool OnsetDetector::detect(float rmsDb) {
|
|
8
|
+
// Fast path: disabled → no work at all.
|
|
9
|
+
if (!enabled_) return false;
|
|
10
|
+
|
|
11
|
+
// Decrement cooldown counter
|
|
12
|
+
if (cooldown_ > 0) {
|
|
13
|
+
--cooldown_;
|
|
14
|
+
// Still update envelope during cooldown so we don't miss the next onset
|
|
15
|
+
envelopeDb_ += cfg_.envelopeAlpha * (rmsDb - envelopeDb_);
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const float rise = rmsDb - envelopeDb_;
|
|
20
|
+
|
|
21
|
+
// Update envelope (exponential follower — tracks slow changes)
|
|
22
|
+
envelopeDb_ += cfg_.envelopeAlpha * (rmsDb - envelopeDb_);
|
|
23
|
+
|
|
24
|
+
if (rise > cfg_.thresholdDb) {
|
|
25
|
+
cooldown_ = cfg_.cooldownFrames;
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void OnsetDetector::setConfig(Config cfg) {
|
|
33
|
+
cfg_ = cfg;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
void OnsetDetector::reset() {
|
|
37
|
+
envelopeDb_ = -100.0f;
|
|
38
|
+
cooldown_ = 0;
|
|
39
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#include "Pipeline.hpp"
|
|
2
|
+
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cmath>
|
|
5
|
+
|
|
6
|
+
static constexpr float kMinLinear = 1e-7f;
|
|
7
|
+
|
|
8
|
+
Pipeline::Pipeline(int frameSize, float sampleRate, std::unique_ptr<IPitchDetector> detector)
|
|
9
|
+
: frameSize_(frameSize)
|
|
10
|
+
, sampleRate_(sampleRate)
|
|
11
|
+
, hpf_(sampleRate, 70.0f) // 70 Hz HPF — removes DC and sub-bass rumble
|
|
12
|
+
, window_(frameSize)
|
|
13
|
+
, detector_(std::move(detector))
|
|
14
|
+
, workBuffer_(static_cast<size_t>(frameSize))
|
|
15
|
+
{}
|
|
16
|
+
|
|
17
|
+
PitchResult Pipeline::process(const float* input, int frameCount) {
|
|
18
|
+
if (!input || frameCount < frameSize_) return PitchResult{};
|
|
19
|
+
|
|
20
|
+
// --- RMS gate ---
|
|
21
|
+
const float rmsLinear = calculateRmsLinear(input, frameSize_);
|
|
22
|
+
const float rmsDb = 20.0f * std::log10(std::max(rmsLinear, kMinLinear));
|
|
23
|
+
|
|
24
|
+
if (rmsDb < noiseGateDb_) {
|
|
25
|
+
PitchResult silent;
|
|
26
|
+
silent.rmsDb = rmsDb;
|
|
27
|
+
return silent;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// --- Onset detection (no-op when disabled — single branch) ---
|
|
31
|
+
if (onsetDetector_.detect(rmsDb)) {
|
|
32
|
+
postProcessor_.reset();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- Working copy: HPF only.
|
|
36
|
+
// Hann windowing is reserved for FFT-based detectors (M3 cepstrum/PYIN).
|
|
37
|
+
// YIN works in the time domain — windowing distorts its difference function.
|
|
38
|
+
std::copy(input, input + frameSize_, workBuffer_.begin());
|
|
39
|
+
hpf_.process(workBuffer_.data(), frameSize_);
|
|
40
|
+
|
|
41
|
+
// --- Pitch detection ---
|
|
42
|
+
DetectorResult det = detector_->detect(workBuffer_.data(), frameSize_, sampleRate_);
|
|
43
|
+
|
|
44
|
+
// --- SNR-weighted confidence ---
|
|
45
|
+
const float snrDb = snr_.update(rmsLinear);
|
|
46
|
+
const float snrWeight = SnrEstimator::snrToWeight(snrDb);
|
|
47
|
+
const float weightedConf = det.confidence * snrWeight;
|
|
48
|
+
|
|
49
|
+
if (!det.voiced || weightedConf < confidenceThreshold_) {
|
|
50
|
+
PitchResult nopit;
|
|
51
|
+
nopit.rmsDb = rmsDb;
|
|
52
|
+
return nopit;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// --- Post-process: median + EMA + hysteresis ---
|
|
56
|
+
PostProcessor::Result pp = postProcessor_.process(det.frequency, weightedConf);
|
|
57
|
+
|
|
58
|
+
if (!pp.isStable || pp.frequency <= 0.0f) {
|
|
59
|
+
PitchResult nopit;
|
|
60
|
+
nopit.rmsDb = rmsDb;
|
|
61
|
+
return nopit;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --- Note mapping ---
|
|
65
|
+
PitchResult result = noteMapper_.map(pp.frequency, weightedConf, rmsDb);
|
|
66
|
+
// Override cents with the hysteresis-stabilised value from PostProcessor
|
|
67
|
+
result.cents = pp.cents;
|
|
68
|
+
|
|
69
|
+
// --- String matching (optional, only when a TuningProfile is active) ---
|
|
70
|
+
if (stringMatcher_.hasTuning()) {
|
|
71
|
+
auto m = stringMatcher_.match(pp.frequency);
|
|
72
|
+
if (m) {
|
|
73
|
+
result.nearestString = m->name;
|
|
74
|
+
result.stringDeviation = m->deviationCents;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
void Pipeline::setA4(float hz) {
|
|
82
|
+
noteMapper_.setA4(hz);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void Pipeline::setNoiseGateDb(float db) {
|
|
86
|
+
noiseGateDb_ = db;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void Pipeline::setConfidenceThreshold(float threshold) {
|
|
90
|
+
confidenceThreshold_ = threshold;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
void Pipeline::setFrequencyRange(float minHz, float maxHz) {
|
|
94
|
+
detector_->setFrequencyRange(minHz, maxHz);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
void Pipeline::setInstrument(const std::string& name) {
|
|
98
|
+
FrequencyRange r = instrumentPreset(name);
|
|
99
|
+
detector_->setFrequencyRange(r.minHz, r.maxHz);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
void Pipeline::setTuning(const std::string& name) {
|
|
103
|
+
stringMatcher_.setTuning(name.empty() ? nullptr : tuningPreset(name));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
void Pipeline::setPostProcessorConfig(PostProcessor::Config cfg) {
|
|
107
|
+
postProcessor_.setConfig(cfg);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void Pipeline::setHpfCutoff(float hz) {
|
|
111
|
+
hpf_ = BiquadHpf(sampleRate_, hz);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
void Pipeline::setOnsetDetectionEnabled(bool enabled) {
|
|
115
|
+
onsetDetector_.setEnabled(enabled);
|
|
116
|
+
if (!enabled) onsetDetector_.reset();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
void Pipeline::setOnsetConfig(OnsetDetector::Config cfg) {
|
|
120
|
+
onsetDetector_.setConfig(cfg);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
float Pipeline::calculateRmsDb(const float* input, int n) const {
|
|
124
|
+
return 20.0f * std::log10(std::max(calculateRmsLinear(input, n), kMinLinear));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
float Pipeline::calculateRmsLinear(const float* input, int n) const {
|
|
128
|
+
float sum = 0.0f;
|
|
129
|
+
for (int i = 0; i < n; ++i) {
|
|
130
|
+
sum += input[i] * input[i];
|
|
131
|
+
}
|
|
132
|
+
return std::sqrt(sum / static_cast<float>(n));
|
|
133
|
+
}
|