53 scConfig.
enableNoiseGate = config.noiseGate && config.enableSignalConditioning;
58 nfConfig.
enabled = config.enableNoiseFloorTracking;
68 fbmConfig.
maxFrequency =
static_cast<float>(config.sampleRate) / 2.0f;
79 const float m =
fl::logf(fmax / fmin);
81 for (
int i = 0; i < 16; ++i) {
82 binCenters[i] = fmin *
fl::expf(m *
static_cast<float>(i) / 15.0f);
95 if (config.enableMusicalBeatDetection) {
100 mbdConfig.
minBPM = config.musicalBeatMinBPM;
101 mbdConfig.
maxBPM = config.musicalBeatMaxBPM;
111 if (config.enableMultiBandBeats) {
126 if (config.enableSpectralEqualizer) {
149 mContext->setSampleRate(config.sampleRate);
170 fl::u32 currentTimeMs =
sample.timestamp();
176 if (
mConfig.enableSignalConditioning) {
178 if (!processedSample.
isValid()) {
184 if (
mConfig.enableNoiseFloorTracking) {
185 float rms = processedSample.
rms();
190 mContext->setSample(processedSample);
201 if (
mConfig.enableNoiseFloorTracking) {
202 constexpr float kSilenceRmsThreshold = 10.0f;
203 mContext->setSilent(processedSample.
rms() < kSilenceRmsThreshold);
223 for (
int i = 0; i < 16; ++i) {
245 const bool silent =
mContext->isSilent();
282 const auto& pcmData =
sample.pcm();
283 if (pcmData.empty())
return;
286 auto cachedBins =
mContext->getFFT16();
299 if (rawBins.
empty()) {
300 for (
int i = 0; i < 16; ++i) {
307 for (
int i = 0; i < 16; ++i) {
308 if (i <
static_cast<int>(rawBins.
size())) {
321 float maxMagnitude = 0.0f;
323 for (
int i = 0; i < 16; ++i) {
337 const auto& pcmData =
sample.pcm();
338 if (pcmData.empty()) {
349 float raw =
rms / 23170.0f;
359 float maxSample = 0.0f;
360 for (fl::i16 pcmSample : pcmData) {
361 float absSample = (pcmSample < 0) ? -
static_cast<float>(pcmSample) :
static_cast<float>(pcmSample);
362 maxSample = (maxSample > absSample) ? maxSample : absSample;
382 currentVolume > 0.02f) {
390 float beatAttackRate =
mConfig.attack / 255.0f * 0.5f + 0.1f;
391 float beatDecayRate =
mConfig.decay / 255.0f * 0.3f + 0.05f;
404 float gainMultiplier =
static_cast<float>(
mConfig.gain) / 128.0f;
415 for (
int i = 0; i < 16; ++i) {
422 for (
int i = 0; i < 16; ++i) {
460 float attackFactor = 1.0f - (
mConfig.attack / 255.0f * 0.9f);
461 float decayFactor = 1.0f - (
mConfig.decay / 255.0f * 0.95f);
486 for (
int i = 0; i < 16; ++i) {
575 if (scaled < 0.0f) scaled = 0.0f;
576 if (scaled > 255.0f) scaled = 255.0f;
577 return static_cast<fl::u8>(scaled);
583 return CRGB(index, index, index);
587 if (binIndex >= 16)
return 0;
605 if (!
mConfig.enableSpectralEqualizer) {
610 float equalizedBins[16];
616 for (
int i = 0; i < 16; ++i) {
634 for (
int i = 0; i < 16; ++i) {
643 for (
int i = 0; i < 16; ++i) {
656 !
mConfig.enableMusicalBeatDetection && !
mConfig.enableMultiBandBeats) {
666 bool onsetDetected =
false;
667 float onsetStrength = 0.0f;
684 if (
mConfig.enableMusicalBeatDetection) {
692 }
else if (onsetDetected) {
699 if (
mConfig.enableMultiBandBeats) {
705 }
else if (
mConfig.enableMultiBand) {
738 if (fromBin < 0 || toBin >=
static_cast<int>(
mFFTBins.bands()) || fromBin > toBin) {
743 for (
int i = fromBin; i <= toBin; ++i) {
744 if (i <
static_cast<int>(
mFFTBins.raw().size())) {
749 return sum /
static_cast<float>(toBin - fromBin + 1);
753 if (samples.
empty())
return 0.0f;
755 float sumSquares = 0.0f;
756 for (
const auto&
sample : samples) {
757 float f =
static_cast<float>(
sample);
761 return sqrtf(sumSquares / samples.
size());
776#if SKETCH_HAS_LARGE_MEMORY
778 for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
779 mFluxHistory[i] = 0.0f;
791#if SKETCH_HAS_LARGE_MEMORY
792 for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
793 mFluxHistory[i] = 0.0f;
802#if SKETCH_HAS_LARGE_MEMORY
804 mFluxHistory[mHistoryIndex] = flux;
805 mHistoryIndex = (mHistoryIndex + 1) % mFluxHistory.size();
807 float adaptiveThreshold = calculateAdaptiveThreshold();
808 return flux > adaptiveThreshold;
819 for (
int i = 0; i < 16; ++i) {
820 float diff = currentBins[i] - previousBins[i];
827 for (
int i = 0; i < 16; ++i) {
842#if SKETCH_HAS_LARGE_MEMORY
843float SpectralFluxDetector::calculateAdaptiveThreshold() {
846 for (fl::size i = 0; i < mFluxHistory.size(); ++i) {
847 sum += mFluxHistory[i];
849 float average = sum / mFluxHistory.size();
866#if SKETCH_HAS_LARGE_MEMORY
884 mBassEnergy = (frequencyBins[0] + frequencyBins[1]) / 2.0f;
885 mMidEnergy = (frequencyBins[6] + frequencyBins[7]) / 2.0f;
886 mTrebleEnergy = (frequencyBins[14] + frequencyBins[15]) / 2.0f;
900#if SKETCH_HAS_LARGE_MEMORY
901 bass.setThreshold(bassThresh);
902 mid.setThreshold(midThresh);
903 treble.setThreshold(trebleThresh);
905 combined.setThreshold((bassThresh + midThresh + trebleThresh) / 3.0f);
911#if SKETCH_HAS_LARGE_MEMORY
915#if SKETCH_HAS_LARGE_MEMORY
917 for (fl::size i = 0; i < mLoudnessHistory.size(); ++i) {
918 mLoudnessHistory[i] = 0.0f;
929 for (
int i = 0; i < 16; ++i) {
941 float compensationFactor = 1.0f;
942 if (currentLoudness < referenceLevel) {
944 compensationFactor = 1.0f + (referenceLevel - currentLoudness) / referenceLevel * 0.3f;
945 }
else if (currentLoudness > referenceLevel * 1.5f) {
947 compensationFactor = 1.0f - (currentLoudness - referenceLevel * 1.5f) / (referenceLevel * 2.0f) * 0.2f;
951 for (
int i = 0; i < 16; ++i) {
955#if SKETCH_HAS_LARGE_MEMORY
982 return mConfig.enableSpectralEqualizer;
float rms(fl::span< const int16_t > data)
#define FL_ASSERT(x, MSG)
Get current statistics (for monitoring/debugging)
void applyLoudnessCompensation(Data &data, float referenceLevel) const
PerceptualWeighting() FL_NOEXCEPT
static constexpr float A_WEIGHTING_COEFFS[16]
void applyAWeighting(Data &data) const
~PerceptualWeighting() FL_NOEXCEPT
float getMoodArousal() FL_NOEXCEPT
float getMidLevel() FL_NOEXCEPT
bool isSilent() FL_NOEXCEPT
u32 getSilenceDuration() FL_NOEXCEPT
float getNoteVelocity() FL_NOEXCEPT
float getEnergy() FL_NOEXCEPT
float getDownbeatConfidence() FL_NOEXCEPT
float getChordConfidence() FL_NOEXCEPT
bool isHiHat() FL_NOEXCEPT
u8 getCurrentNote() FL_NOEXCEPT
float getNoteConfidence() FL_NOEXCEPT
u8 getCurrentBeatNumber() FL_NOEXCEPT
float getBackbeatStrength() FL_NOEXCEPT
bool isCrescendo() FL_NOEXCEPT
float getBassLevel() FL_NOEXCEPT
float getMoodValence() FL_NOEXCEPT
float getDropImpact() FL_NOEXCEPT
bool isDiminuendo() FL_NOEXCEPT
float getTrebleLevel() FL_NOEXCEPT
void setGain(float gain) FL_NOEXCEPT
Set a simple digital gain multiplier applied to each sample before detector.
float getDynamicTrend() FL_NOEXCEPT
float getKeyConfidence() FL_NOEXCEPT
float getTransientStrength() FL_NOEXCEPT
float getTempoConfidence() FL_NOEXCEPT
float getPitch() FL_NOEXCEPT
float getBPM() FL_NOEXCEPT
float getMeasurePhase() FL_NOEXCEPT
float getTempoBPM() FL_NOEXCEPT
float getBuildupIntensity() FL_NOEXCEPT
float getBuildupProgress() FL_NOEXCEPT
bool isSnare() FL_NOEXCEPT
float getBeatConfidence() FL_NOEXCEPT
bool isKick() FL_NOEXCEPT
float getBackbeatConfidence() FL_NOEXCEPT
float getPitchConfidence() FL_NOEXCEPT
float getPeakLevel() FL_NOEXCEPT
float getVocalConfidence() FL_NOEXCEPT
float getBackbeatConfidence()
float mapFrequencyBin(int fromBin, int toBin)
float getDownbeatConfidence()
float getMidEnergy() const
void updateSpectralFlux()
void detectEnhancedBeats(fl::u32 currentTimeMs)
float mPinkNoiseGains[16]
float getBackbeatStrength()
void begin(const ReactiveConfig &config=ReactiveConfig{})
fl::u8 volumeToScale255() const
void update(fl::u32 currentTimeMs)
fl::u8 frequencyToScale255(fl::u8 binIndex) const
float getBuildupIntensity()
const SignalConditioner::Stats & getSignalConditionerStats() const
void detectBeat(fl::u32 currentTimeMs)
float getChordConfidence()
void calculateBandEnergies()
float getBassEnergy() const
void processSample(const Sample &sample)
fl::unique_ptr< SpectralFluxDetector > mSpectralFluxDetector
SilenceEnvelope mDominantFrequencyEnvelope
fl::unique_ptr< SpectralEqualizer > mSpectralEqualizer
CRGB volumeToColor(const CRGBPalette16 &palette) const
fl::unique_ptr< detector::MultiBandBeat > mMultiBandBeatDetector
float getTempoConfidence()
float getTrebleEnergy() const
shared_ptr< Context > mContext
fl::unique_ptr< Processor > mAudioProcessor
float getTransientStrength()
bool isSpectralEqualizerEnabled() const
Processor & ensureAudioProcessor()
float getNoteConfidence()
void processFFT(const Sample &sample)
SilenceEnvelope mMagnitudeEnvelope
float getSpectralFlux() const
void setConfig(const ReactiveConfig &config)
SignalConditioner mSignalConditioner
float getBuildupProgress()
NoiseFloorTracker mNoiseFloorTracker
const Data & getSmoothedData() const
void applyLoudnessCompensation()
void mapFFTBinsToFrequencyChannels()
fl::unique_ptr< detector::MusicalBeat > mMusicalBeatDetector
const SpectralEqualizer::Stats & getSpectralEqualizerStats() const
fl::unique_ptr< PerceptualWeighting > mPerceptualWeighting
fl::array< float, 16 > mPreviousMagnitudes
float getVocalConfidence()
const NoiseFloorTracker::Stats & getNoiseFloorStats() const
static constexpr fl::u32 BEAT_COOLDOWN
float getPitchConfidence()
u8 getCurrentBeatNumber()
const Data & getData() const
bool isTrebleBeat() const
float getBeatConfidence()
float computeRMS(const fl::vector< fl::i16 > &samples)
SilenceEnvelope mSpectralFluxEnvelope
FrequencyBinMapper mFrequencyBinMapper
void applySpectralEqualization()
void updateVolumeAndPeak(const Sample &sample)
bool isValid() const FL_NOEXCEPT
float rms() const FL_NOEXCEPT
const VectorPCM & pcm() const FL_NOEXCEPT
Get current statistics (for debugging/monitoring)
Get statistics (for debugging/monitoring)
SpectralFluxDetector() FL_NOEXCEPT
void setThreshold(float threshold)
~SpectralFluxDetector() FL_NOEXCEPT
float calculateSpectralFlux(span< const float, 16 > currentBins, span< const float, 16 > previousBins)
bool detectOnset(span< const float, 16 > currentBins)
float getThreshold() const
fl::array< float, 16 > mPreviousMagnitudes
constexpr bool empty() const FL_NOEXCEPT
constexpr fl::size size() const FL_NOEXCEPT
fl::size size() const FL_NOEXCEPT
bool empty() const FL_NOEXCEPT
High-resolution microphone frequency response data and utilities.
u32 samplesPerFrame
Samples per frame - used for timing calculations.
float midThreshold
Mid beat threshold (0.0-1.0) Energy increase required to trigger mid beat.
u32 sampleRate
Sample rate (Hz) - used for timing calculations.
float maxBPM
Maximum BPM to detect (default: 250 BPM)
float trebleThreshold
Treble beat threshold (0.0-1.0) Energy increase required to trigger treble beat.
float minBeatConfidence
Minimum beat confidence to report a beat (0.0-1.0) Higher values = fewer false positives,...
float minBPM
Minimum BPM to detect (default: 50 BPM)
float bassThreshold
Bass beat threshold (0.0-1.0) Energy increase required to trigger bass beat.
Configuration for musical beat detection.
Configuration for multi-band beat detection.
float computeAudioDt(fl::size pcmSize, int sampleRate) FL_NOEXCEPT
Compute the time delta (in seconds) for an audio buffer.
void computePinkNoiseGains(const float *binCenters, int numBins, float *out)
Compute pink noise compensation gains for all bins.
bool enabled
Enable noise floor tracking.
bool enableDCRemoval
Enable DC offset removal (running average high-pass filter)
bool enableNoiseGate
Enable noise gate with hysteresis.
EqualizationCurve curve
Equalization curve type.
bool useLogSpacing
Use logarithmic spacing (recommended for audio) Logarithmic spacing provides better bass/mid/treble s...
u32 fftBinCount
Number of FFT bins available from FFT output For 512-sample FFT at 22050 Hz: 256 bins (512/2)
bool enableSpikeFilter
Enable spike filtering for I2S glitches.
float minFrequency
Minimum frequency (Hz) - default 20 Hz (bass)
float maxFrequency
Maximum frequency (Hz) - default 16000 Hz (treble)
u32 sampleRate
Sample rate (Hz) - must match FFT sample rate.
size numBands
Number of frequency bands (must match FrequencyBinMapper output)
FrequencyBinMode mode
Number of output frequency bins (16 or 32)
Configuration for frequency bin mapping.
Configuration for signal conditioning pipeline.
Configuration for noise floor tracking.
Configuration for spectral equalizer.
float sqrtf(float value) FL_NOEXCEPT
constexpr int type_rank< T >::value
float expf(float value) FL_NOEXCEPT
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NOEXCEPT
CRGB sample(const CRGB *grid, const XYMap &xyMap, float x, float y, SampleMode mode)
Sample a pixel from a 2D CRGB grid at floating-point coordinates.
shared_ptr< T > make_shared(Args &&... args) FL_NOEXCEPT
float logf(float value) FL_NOEXCEPT
Base definition for an LED controller.
#define SKETCH_HAS_LARGE_MEMORY
Representation of an 8-bit RGB pixel (Red, Green, Blue)
void setThresholds(float bassThresh, float midThresh, float trebleThresh)
~BeatDetectors() FL_NOEXCEPT
void detectBeats(span< const float, 16 > frequencyBins, Data &audioData)
float mPreviousTrebleEnergy
float mPreviousBassEnergy
BeatDetectors() FL_NOEXCEPT
SpectralFluxDetector combined
static float DefaultMaxFrequency() FL_NOEXCEPT
static float DefaultMinFrequency() FL_NOEXCEPT