| 170 | } |
| 171 | |
| 172 | void TempoAnalyzer::updateHypotheses(u32 timestamp) { |
| 173 | // For each existing onset, create/update hypotheses |
| 174 | for (size i = 0; i < mOnsetTimes.size(); i++) { |
| 175 | if (i == mOnsetTimes.size() - 1) continue; // Skip the just-added onset |
| 176 | |
| 177 | u32 interval = timestamp - mOnsetTimes[i]; |
| 178 | |
| 179 | // Check if interval is in valid BPM range |
| 180 | if (interval < MIN_BEAT_INTERVAL_MS || interval > MAX_BEAT_INTERVAL_MS) { |
| 181 | continue; |
| 182 | } |
| 183 | |
| 184 | float bpm = 60000.0f / static_cast<float>(interval); |
| 185 | |
| 186 | // Check if this BPM is within user-defined range |
| 187 | if (bpm < mMinBPM || bpm > mMaxBPM) { |
| 188 | continue; |
| 189 | } |
| 190 | |
| 191 | // Check if we already have a similar hypothesis |
| 192 | bool foundExisting = false; |
| 193 | for (size j = 0; j < mHypotheses.size(); j++) { |
| 194 | float bpmDiff = fl::abs(mHypotheses[j].bpm - bpm); |
| 195 | if (bpmDiff < 3.0f) { // Within 3 BPM tolerance |
| 196 | // Update existing hypothesis |
| 197 | mHypotheses[j].bpm = (mHypotheses[j].bpm + bpm) * 0.5f; |
| 198 | mHypotheses[j].score += calculateIntervalScore(interval); |
| 199 | mHypotheses[j].lastOnsetTime = timestamp; |
| 200 | mHypotheses[j].onsetCount++; |
| 201 | foundExisting = true; |
| 202 | break; |
| 203 | } |
| 204 | } |
| 205 | |
| 206 | // Create new hypothesis if not found and we have room |
| 207 | if (!foundExisting && mHypotheses.size() < MAX_HYPOTHESES) { |
| 208 | TempoHypothesis hyp; |
| 209 | hyp.bpm = bpm; |
| 210 | hyp.score = calculateIntervalScore(interval); |
| 211 | hyp.lastOnsetTime = timestamp; |
| 212 | hyp.onsetCount = 1; |
| 213 | mHypotheses.push_back(hyp); |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | |
| 218 | void TempoAnalyzer::pruneHypotheses() { |
| 219 | // Remove hypotheses that haven't been updated recently |