| 392 | } |
| 393 | |
| 394 | float Backbeat::calculatePatternConfidence(const fft::Bins& fft) { |
| 395 | // Calculate spectral correlation between current spectrum and learned profile |
| 396 | // Returns 0-1 indicating how well current spectrum matches typical backbeat |
| 397 | |
| 398 | // If profile is not yet learned, return neutral confidence |
| 399 | bool profileLearned = false; |
| 400 | for (size i = 0; i < mBackbeatSpectralProfile.size(); i++) { |
| 401 | if (mBackbeatSpectralProfile[i] > 1e-6f) { |
| 402 | profileLearned = true; |
| 403 | break; |
| 404 | } |
| 405 | } |
| 406 | |
| 407 | if (!profileLearned) { |
| 408 | return 0.5f; // Neutral confidence |
| 409 | } |
| 410 | |
| 411 | // Calculate normalized correlation |
| 412 | size compareSize = fl::min(mBackbeatSpectralProfile.size(), fft.raw().size()); |
| 413 | |
| 414 | float dotProduct = 0.0f; |
| 415 | float profileMag = 0.0f; |
| 416 | float currentMag = 0.0f; |
| 417 | |
| 418 | for (size i = 0; i < compareSize; i++) { |
| 419 | dotProduct += mBackbeatSpectralProfile[i] * fft.raw()[i]; |
| 420 | profileMag += mBackbeatSpectralProfile[i] * mBackbeatSpectralProfile[i]; |
| 421 | currentMag += fft.raw()[i] * fft.raw()[i]; |
| 422 | } |
| 423 | |
| 424 | // Cosine similarity |
| 425 | const float epsilon = 1e-6f; |
| 426 | float denominator = fl::sqrt(profileMag * currentMag); |
| 427 | |
| 428 | if (denominator < epsilon) { |
| 429 | return 0.5f; // Neutral if magnitudes too small |
| 430 | } |
| 431 | |
| 432 | float correlation = dotProduct / denominator; |
| 433 | |
| 434 | // Clamp to 0-1 range |
| 435 | return fl::clamp(correlation, 0.0f, 1.0f); |
| 436 | } |
| 437 | |
| 438 | } // namespace detector |
| 439 | } // namespace audio |