FastLED 3.9.15
Loading...
Searching...
No Matches

◆ classify()

SoundState animartrix_ring::SoundOrchestrator::classify ( fl::u32 nowMs)
private

Definition at line 101 of file sound_orchestrator.cpp.

101 {
102 if (!mProcessor) return SoundState::Silence;
103
104 const bool isSilent = mProcessor->isSilent();
105
106 // Silence has its own asymmetric hysteresis (enter slow, exit fast) so a
107 // single audio sample can wake us up promptly but a brief gap won't drop
108 // us into ambient mode mid-song.
109 if (isSilent) {
110 if (mSilentSinceMs == 0) mSilentSinceMs = nowMs;
112 } else {
113 if (mNonSilentSinceMs == 0) mNonSilentSinceMs = nowMs;
114 mSilentSinceMs = 0;
115 }
116
117 // --- raw signals ---
118 const float tempoConf = mProcessor->getTempoConfidence();
119 const float beatConf = mProcessor->getBeatConfidence();
120
121 // Silence-exit gate: when we're currently in Silence, require the
122 // configured silenceExitMs of *contiguous* non-silent audio before we
123 // even consider leaving. Without this, mNonSilentSinceMs would be
124 // tracked but never consulted, so a single non-silent frame would
125 // immediately bounce us out -- defeating the asymmetric silence
126 // hysteresis documented on OrchestratorConfig.
127 const bool silenceReleased =
128 !isSilent &&
129 mNonSilentSinceMs != 0 &&
130 (nowMs - mNonSilentSinceMs) >= mCfg.silenceExitMs;
131
132 // --- determine the candidate (instantaneous) state ---
133 SoundState instant;
134 if (isSilent && mSilentSinceMs && (nowMs - mSilentSinceMs) >= mCfg.silenceEnterMs) {
135 instant = SoundState::Silence;
136 } else if (mState == SoundState::Silence && !silenceReleased) {
137 // Hold Silence until the non-silent run reaches silenceExitMs.
138 instant = SoundState::Silence;
139 } else if (!isSilent &&
140 tempoConf >= mCfg.tempoConfidenceEnter &&
141 beatConf >= mCfg.beatConfidenceEnter) {
142 instant = SoundState::BpmLocked;
143 } else if (mState == SoundState::BpmLocked &&
144 (tempoConf >= mCfg.tempoConfidenceExit &&
145 beatConf >= mCfg.beatConfidenceExit)) {
146 // Stay locked: we're below "enter" but above "exit" thresholds.
147 instant = SoundState::BpmLocked;
148 } else if (mState == SoundState::Silence && isSilent) {
149 instant = SoundState::Silence;
150 } else {
151 instant = SoundState::Disorganized;
152 }
153
154 // --- hysteresis: candidate must hold for classifierHysteresisMs AND
155 // current state must have served minDwellMs before we accept the switch.
156 if (instant != mState) {
157 if (instant != mCandidate) {
158 mCandidate = instant;
159 mCandidateSinceMs = nowMs;
160 }
161 const fl::u32 candidateHeld = nowMs - mCandidateSinceMs;
162 const fl::u32 stateHeld = nowMs - mStateEnteredAtMs;
163 if (candidateHeld >= mCfg.classifierHysteresisMs &&
164 stateHeld >= mCfg.minDwellMs) {
165 return instant;
166 }
167 return mState; // not yet -- hold current state
168 }
169
170 // Candidate matches current state; reset candidate tracker.
172 mCandidateSinceMs = nowMs;
173 return mState;
174}
static bool isSilent
fl::shared_ptr< fl::audio::Processor > mProcessor

References animartrix_ring::BpmLocked, animartrix_ring::Disorganized, isSilent, mCandidate, mCandidateSinceMs, mCfg, mNonSilentSinceMs, mProcessor, mSilentSinceMs, mState, mStateEnteredAtMs, and animartrix_ring::Silence.

Referenced by tick().

+ Here is the caller graph for this function: