| 116 | } |
| 117 | |
| 118 | void KeyDetector::update(shared_ptr<Context> context) { |
| 119 | // Get fft::FFT data |
| 120 | mRetainedFFT = context->getFFT(32); // Use more bins for better pitch resolution |
| 121 | const fft::Bins& fft = *mRetainedFFT; |
| 122 | u32 timestamp = context->getTimestamp(); |
| 123 | |
| 124 | // Extract chroma features |
| 125 | float chroma[12] = {0}; |
| 126 | extractChroma(fft, chroma); |
| 127 | normalizeChroma(chroma); |
| 128 | |
| 129 | // Update temporal averaging |
| 130 | updateChromaHistory(chroma); |
| 131 | |
| 132 | // Get averaged chroma for stable detection |
| 133 | float avgChroma[12] = {0}; |
| 134 | getAveragedChroma(avgChroma); |
| 135 | |
| 136 | // Detect key from averaged chroma |
| 137 | Key detectedKey = detectKey(avgChroma, timestamp); |
| 138 | |
| 139 | // Update key duration if same key |
| 140 | if (mKeyActive && detectedKey == mCurrentKey) { |
| 141 | mCurrentKey.duration = timestamp - mKeyStartTime; |
| 142 | } |
| 143 | |
| 144 | // Check for key change |
| 145 | if (detectedKey != mCurrentKey) { |
| 146 | // Only accept key change if: |
| 147 | // 1. Confidence is above threshold |
| 148 | // 2. Previous key was held for minimum duration OR new key is much stronger |
| 149 | bool acceptChange = false; |
| 150 | |
| 151 | if (detectedKey.confidence >= mConfidenceThreshold) { |
| 152 | if (!mKeyActive) { |
| 153 | // No previous key, accept new key |
| 154 | acceptChange = true; |
| 155 | } else if (mCurrentKey.duration >= mMinKeyDuration) { |
| 156 | // Previous key held long enough, allow change |
| 157 | acceptChange = true; |
| 158 | } else if (detectedKey.confidence > mCurrentKey.confidence * 1.2f) { |
| 159 | // New key is significantly stronger, allow early change |
| 160 | acceptChange = true; |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | if (acceptChange) { |
| 165 | mPreviousKey = mCurrentKey; |
| 166 | mCurrentKey = detectedKey; |
| 167 | mKeyStartTime = timestamp; |
| 168 | mKeyActive = true; |
| 169 | |
| 170 | FL_DBG("Key change: " << mCurrentKey.getRootName() << " " |
| 171 | << mCurrentKey.getQuality() << " (confidence: " |
| 172 | << mCurrentKey.confidence << ")"); |
| 173 | |
| 174 | mFireKeyChange = true; |
| 175 | } |
no test coverage detected