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

◆ computePCMTimeDomainFeatures()

void fl::audio::detector::Vocal::computePCMTimeDomainFeatures ( span< const i16 > pcm)
private

Definition at line 295 of file vocal.cpp.hpp.

295 {
296 // Fused single-pass computation of envelope jitter + shimmer AND
297 // zero-crossing CV. Previously two separate PCM traversals; now one.
298 // Saves ~2-3 us by eliminating redundant PCM reads and cache misses.
299 const int n = static_cast<int>(pcm.size());
300 if (n < 44) {
301 mEnvelopeJitter = 0.0f;
302 mZeroCrossingCV = 0.0f;
303 return;
304 }
305
306 const float normFactor = 1.0f / 32768.0f;
307 const int halfWin = fl::max(2, mSampleRate / 4000); // ~11 samples at 44100
308 const int winSize = 2 * halfWin + 1;
309 const float invWinSize = 1.0f / static_cast<float>(winSize);
310
311 // Seed the sliding window sum
312 float windowSum = 0.0f;
313 for (int j = 0; j < winSize && j < n; ++j) {
314 windowSum += fl::abs(static_cast<float>(pcm[j])) * normFactor;
315 }
316
317 // Envelope jitter accumulators
318 float sumEnv = 0.0f;
319 float sumDev = 0.0f;
320 int count = 0;
321 float sumPeaks = 0.0f;
322 float sumSqPeaks = 0.0f;
323 int numCycles = 0;
324 float currentPeak = 0.0f;
325 bool wasPositive = pcm[halfWin] >= 0;
326
327 // Zero-crossing CV accumulators (fused into same loop)
328 int prevCrossing = -1;
329 int numIntervals = 0;
330 float sumIntervals = 0.0f;
331 float sumSqIntervals = 0.0f;
332
333 for (int i = halfWin; i < n - halfWin; ++i) {
334 float absVal = fl::abs(static_cast<float>(pcm[i])) * normFactor;
335 float smoothed = windowSum * invWinSize;
336
337 sumEnv += smoothed;
338 sumDev += fl::abs(absVal - smoothed);
339 ++count;
340
341 // Shimmer + zero-crossing detection (shared)
342 currentPeak = fl::max(currentPeak, absVal);
343 bool isPositive = pcm[i] >= 0;
344 if (isPositive != wasPositive) {
345 // Shimmer: track peaks between zero crossings
346 if (currentPeak > 0.01f) {
347 sumPeaks += currentPeak;
348 sumSqPeaks += currentPeak * currentPeak;
349 ++numCycles;
350 }
351 currentPeak = 0.0f;
352
353 // ZC CV: track interval statistics
354 if (prevCrossing >= 0) {
355 float interval = static_cast<float>(i - prevCrossing);
356 sumIntervals += interval;
357 sumSqIntervals += interval * interval;
358 ++numIntervals;
359 }
360 prevCrossing = i;
361 }
362 wasPositive = isPositive;
363
364 // Slide window
365 if (i + 1 < n - halfWin) {
366 windowSum -= fl::abs(static_cast<float>(pcm[i - halfWin])) * normFactor;
367 windowSum += fl::abs(static_cast<float>(pcm[i + halfWin + 1])) * normFactor;
368 }
369 }
370
371 // --- Envelope jitter result ---
372 if (sumEnv < 1e-6f || count == 0) {
373 mEnvelopeJitter = 0.0f;
374 } else {
375 float envelopeJitter = (sumDev / static_cast<float>(count))
376 / (sumEnv / static_cast<float>(count));
377
378 float shimmer = 0.0f;
379 if (numCycles >= 3) {
380 float meanPeak = sumPeaks / static_cast<float>(numCycles);
381 if (meanPeak > 0.01f) {
382 float variance = sumSqPeaks / static_cast<float>(numCycles)
383 - meanPeak * meanPeak;
384 if (variance < 0.0f) variance = 0.0f;
385 shimmer = fl::sqrtf(variance) / meanPeak;
386 }
387 }
388 mEnvelopeJitter = envelopeJitter + shimmer * 0.5f;
389 }
390
391 // --- Zero-crossing CV result ---
392 if (numIntervals < 2) {
393 mZeroCrossingCV = 0.0f;
394 } else {
395 float mean = sumIntervals / static_cast<float>(numIntervals);
396 if (mean < 1e-6f) {
397 mZeroCrossingCV = 0.0f;
398 } else {
399 float variance = sumSqIntervals / static_cast<float>(numIntervals)
400 - mean * mean;
401 if (variance < 0.0f) variance = 0.0f;
402 mZeroCrossingCV = fl::sqrtf(variance) / mean;
403 }
404 }
405}
float sqrtf(float value) FL_NOEXCEPT
Definition math.h:453
constexpr common_type_t< T, U > max(T a, U b) FL_NOEXCEPT
Definition math.h:75
constexpr enable_if< is_fixed_point< T >::value, T >::type abs(T x) FL_NOEXCEPT

References fl::abs(), fl::max(), mEnvelopeJitter, mSampleRate, mZeroCrossingCV, fl::span< T, Extent >::size(), and fl::sqrtf().

Referenced by update().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: