| 23 | } |
| 24 | |
| 25 | void MusicalBeat::processSample(bool onsetDetected, float onsetStrength) { |
| 26 | mBeatDetected = false; |
| 27 | mLastBeatConfidence = 0.0f; |
| 28 | mCurrentFrame++; |
| 29 | |
| 30 | if (!onsetDetected) { |
| 31 | return; |
| 32 | } |
| 33 | |
| 34 | mStats.totalOnsets++; |
| 35 | |
| 36 | // Validate if this onset is a true musical beat |
| 37 | if (validateBeat(onsetStrength)) { |
| 38 | mBeatDetected = true; |
| 39 | mStats.validatedBeats++; |
| 40 | |
| 41 | // Calculate inter-beat interval (IBI) |
| 42 | u32 ibiFrames = mCurrentFrame - mLastBeatFrame; |
| 43 | mLastBeatFrame = mCurrentFrame; |
| 44 | |
| 45 | // Convert IBI from frames to seconds |
| 46 | float ibiSeconds = static_cast<float>(ibiFrames * mConfig.samplesPerFrame) / |
| 47 | static_cast<float>(mConfig.sampleRate); |
| 48 | |
| 49 | // Validate IBI is within BPM range |
| 50 | if (isValidIBI(ibiSeconds)) { |
| 51 | // Add to history |
| 52 | if (mIBIHistory.size() >= mConfig.maxIBIHistory) { |
| 53 | // Remove oldest IBI |
| 54 | mIBIHistory.pop_front(); |
| 55 | } |
| 56 | mIBIHistory.push_back(ibiFrames); |
| 57 | |
| 58 | // Calculate beat confidence |
| 59 | mLastBeatConfidence = calculateBeatConfidence(ibiSeconds); |
| 60 | |
| 61 | // Update BPM estimate |
| 62 | updateBPMEstimate(); |
| 63 | |
| 64 | // Update stats |
| 65 | mStats.currentBPM = mCurrentBPM; |
| 66 | mStats.averageIBI = getAverageIBI(); |
| 67 | mStats.ibiCount = static_cast<u32>(mIBIHistory.size()); |
| 68 | } else { |
| 69 | // IBI out of valid range - reject beat |
| 70 | mBeatDetected = false; |
| 71 | mStats.rejectedOnsets++; |
| 72 | } |
| 73 | } else { |
| 74 | mStats.rejectedOnsets++; |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | bool MusicalBeat::isBeat() const { |
| 79 | return mBeatDetected && (mLastBeatConfidence >= mConfig.minBeatConfidence); |