diff --git a/source/dsp/YinPitchDetector.cpp b/source/dsp/YinPitchDetector.cpp index 8230cda..678083d 100644 --- a/source/dsp/YinPitchDetector.cpp +++ b/source/dsp/YinPitchDetector.cpp @@ -6,7 +6,7 @@ void YinPitchDetector::prepare(double sampleRate) { analysisSR = sampleRate; - halfWindow = static_cast(std::ceil(sampleRate / 70.0)); + halfWindow = static_cast(std::ceil(sampleRate / 60.0)); windowSize = 2 * halfWindow; hopSize = static_cast(std::ceil(sampleRate * 0.003)); @@ -120,6 +120,19 @@ void YinPitchDetector::analyse() } } + if (tauEstimate == 0) + { + float minVal = 1.0f; + for (size_t tau = 2; tau < n; ++tau) + { + if (cmndf[tau] < minVal) + { + minVal = cmndf[tau]; + tauEstimate = tau; + } + } + } + if (tauEstimate == 0) { lastResult = { 0.0f, 0.0f }; diff --git a/tests/TestYinPitchDetector.cpp b/tests/TestYinPitchDetector.cpp index fcd74fb..d823ee7 100644 --- a/tests/TestYinPitchDetector.cpp +++ b/tests/TestYinPitchDetector.cpp @@ -7,7 +7,7 @@ static constexpr double twoPi = 6.283185307179586476925; static int computeWindowSize(double sampleRate) { - int halfWindow = static_cast(std::ceil(sampleRate / 70.0)); + int halfWindow = static_cast(std::ceil(sampleRate / 60.0)); return 2 * halfWindow; } @@ -29,6 +29,23 @@ static void feedSine(YinPitchDetector& yin, double sampleRate, float freq, int n } } +static void feedHarmonicComplex(YinPitchDetector& yin, double sampleRate, float freq, int numSamples, float amplitude) +{ + double phase = 0.0; + double inc = twoPi * static_cast(freq) / sampleRate; + + for (int i = 0; i < numSamples; ++i) + { + double s = std::sin(phase) + + 0.8 * std::sin(2.0 * phase) + + 0.6 * std::sin(3.0 * phase) + + 0.5 * std::sin(4.0 * phase) + + 0.3 * std::sin(5.0 * phase); + yin.feedSample(static_cast(s * static_cast(amplitude))); + phase += inc; + } +} + static int feedSineUntilDetection(YinPitchDetector& yin, double sampleRate, float freq, int maxSamples) { double phase = 0.0; @@ -219,3 +236,42 @@ TEST_CASE("YIN: tracks frequency sweep across hop intervals") REQUIRE_THAT(static_cast(resultAfter.frequency), Catch::Matchers::WithinRel(880.0, 0.05)); } + +TEST_CASE("YIN: fallback detects harmonically complex low signal") +{ + YinPitchDetector yin; + yin.prepare(44100.0); + + feedHarmonicComplex(yin, 44100.0, 82.4f, 44100, 0.15f); + + auto result = yin.getResult(); + REQUIRE(result.frequency > 0.0f); + REQUIRE(result.frequency > 40.0f); + REQUIRE(result.frequency < 200.0f); +} + +TEST_CASE("YIN: silence still returns zero with fallback") +{ + YinPitchDetector yin; + yin.prepare(44100.0); + + for (int i = 0; i < 44100; ++i) + yin.feedSample(0.0f); + + auto result = yin.getResult(); + REQUIRE(result.frequency == 0.0f); + REQUIRE(result.confidence == 0.0f); +} + +TEST_CASE("YIN: threshold path still preferred for clean signals") +{ + YinPitchDetector yin; + yin.prepare(44100.0); + + feedSine(yin, 44100.0, 440.0f, 44100); + + auto result = yin.getResult(); + REQUIRE(result.confidence > 0.8f); + REQUIRE_THAT(static_cast(result.frequency), + Catch::Matchers::WithinRel(440.0, 0.01)); +}