| 218 | } |
| 219 | |
| 220 | bool Downbeat::detectDownbeat(u32 timestamp, float accent) { |
| 221 | // Downbeat detection strategy: |
| 222 | // 1. Check if we're at the expected measure boundary |
| 223 | // 2. Check if accent is strong enough |
| 224 | // 3. Check if pattern matches metric grouping |
| 225 | |
| 226 | // If we haven't detected any downbeats yet, consider this one |
| 227 | if (mLastDownbeatTime == 0) { |
| 228 | // Use accent strength for initial confidence instead of fixed 0.5 |
| 229 | // This provides a better estimate based on actual audio characteristics |
| 230 | float meanAccent = 1.0f; |
| 231 | if (!mBeatAccents.empty()) { |
| 232 | float sum = 0.0f; |
| 233 | for (size i = 0; i < mBeatAccents.size(); i++) { |
| 234 | sum += mBeatAccents[i]; |
| 235 | } |
| 236 | meanAccent = sum / static_cast<float>(mBeatAccents.size()); |
| 237 | } |
| 238 | |
| 239 | // Calculate accent-based confidence |
| 240 | // If we have accent history, use it; otherwise use raw accent with moderate confidence |
| 241 | float accentConfidence = meanAccent > 0.0f |
| 242 | ? fl::clamp(accent / (meanAccent * mAccentThreshold), 0.0f, 1.0f) |
| 243 | : fl::clamp(accent * 0.5f, 0.3f, 0.7f); // Raw accent scaled to 30-70% range |
| 244 | |
| 245 | mConfidence = accentConfidence; |
| 246 | return true; |
| 247 | } |
| 248 | |
| 249 | // Calculate expected downbeat position |
| 250 | u32 timeSinceDownbeat = timestamp - mLastDownbeatTime; |
| 251 | float beatInterval = mBeatDetector->getBPM() > 0.0f |
| 252 | ? (60000.0f / mBeatDetector->getBPM()) |
| 253 | : 500.0f; |
| 254 | float expectedMeasureDuration = beatInterval * static_cast<float>(mBeatsPerMeasure); |
| 255 | |
| 256 | // Check if we're near expected measure boundary |
| 257 | float timingError = fl::abs(static_cast<float>(timeSinceDownbeat) - expectedMeasureDuration); |
| 258 | float maxTimingError = beatInterval * 0.4f; // Allow 40% timing error |
| 259 | bool nearMeasureBoundary = (timingError < maxTimingError); |
| 260 | |
| 261 | // Check if accent is strong enough |
| 262 | float meanAccent = 1.0f; |
| 263 | if (!mBeatAccents.empty()) { |
| 264 | float sum = 0.0f; |
| 265 | for (size i = 0; i < mBeatAccents.size(); i++) { |
| 266 | sum += mBeatAccents[i]; |
| 267 | } |
| 268 | meanAccent = sum / static_cast<float>(mBeatAccents.size()); |
| 269 | } |
| 270 | |
| 271 | bool strongAccent = (accent > meanAccent * mAccentThreshold); |
| 272 | |
| 273 | // Check if we're at the beat counter boundary |
| 274 | bool atBeatCounterBoundary = (mBeatsSinceDownbeat >= mBeatsPerMeasure - 1); |
| 275 | |
| 276 | // Calculate confidence |
| 277 | float timingConfidence = 1.0f - (timingError / (beatInterval * 2.0f)); |