| 28 | MoodAnalyzer::~MoodAnalyzer() FL_NOEXCEPT = default; |
| 29 | |
| 30 | void MoodAnalyzer::update(shared_ptr<Context> context) { |
| 31 | mRetainedFFT = context->getFFT(32); // Higher resolution for mood analysis |
| 32 | const fft::Bins& fft = *mRetainedFFT; |
| 33 | const fft::Bins* prevFFT = context->getHistoricalFFT(1); |
| 34 | |
| 35 | // Extract audio features |
| 36 | mSpectralCentroid = calculateSpectralCentroid(fft); |
| 37 | mSpectralRolloff = calculateSpectralRolloff(fft); |
| 38 | mSpectralFlux = calculateSpectralFlux(fft, prevFFT); |
| 39 | mZeroCrossingRate = context->getZCF(); |
| 40 | mRMSEnergy = context->getRMS(); |
| 41 | |
| 42 | // Calculate mood dimensions |
| 43 | float valence = calculateValence(mSpectralCentroid, mSpectralRolloff, mSpectralFlux); |
| 44 | float arousal = calculateArousal(mRMSEnergy, mZeroCrossingRate, mSpectralFlux); |
| 45 | |
| 46 | // Add to history for temporal averaging |
| 47 | if (static_cast<int>(mValenceHistory.size()) < mAveragingFrames) { |
| 48 | mValenceHistory.push_back(valence); |
| 49 | mArousalHistory.push_back(arousal); |
| 50 | mHistoryIndex = static_cast<int>(mValenceHistory.size()) % mAveragingFrames; |
| 51 | } else { |
| 52 | mValenceHistory[mHistoryIndex] = valence; |
| 53 | mArousalHistory[mHistoryIndex] = arousal; |
| 54 | mHistoryIndex = (mHistoryIndex + 1) % mAveragingFrames; |
| 55 | } |
| 56 | |
| 57 | // Average over history for stability |
| 58 | float avgValence = 0.0f; |
| 59 | float avgArousal = 0.0f; |
| 60 | for (size_t i = 0; i < mValenceHistory.size(); i++) { |
| 61 | avgValence += mValenceHistory[i]; |
| 62 | avgArousal += mArousalHistory[i]; |
| 63 | } |
| 64 | avgValence /= mValenceHistory.size(); |
| 65 | avgArousal /= mArousalHistory.size(); |
| 66 | |
| 67 | // Update current mood |
| 68 | mPreviousMood = mCurrentMood; |
| 69 | mCurrentMood.valence = avgValence; |
| 70 | mCurrentMood.arousal = avgArousal; |
| 71 | mCurrentMood.confidence = calculateConfidence(avgValence, avgArousal); |
| 72 | mCurrentMood.timestamp = context->getTimestamp(); |
| 73 | |
| 74 | // Update duration if mood is stable |
| 75 | if (mPreviousMood.getCategory() == mCurrentMood.getCategory()) { |
| 76 | mCurrentMood.duration = mPreviousMood.duration + |
| 77 | (mCurrentMood.timestamp - mPreviousMood.timestamp); |
| 78 | } else { |
| 79 | mCurrentMood.duration = 0; |
| 80 | } |
| 81 | |
| 82 | // Check for mood changes (store result for fireCallbacks) |
| 83 | mMoodChanged = shouldChangeMood(mCurrentMood); |
| 84 | } |
| 85 | |
| 86 | void MoodAnalyzer::fireCallbacks() { |
| 87 | if (onMood) { |
nothing calls this directly
no test coverage detected