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
static constexpr float A_WEIGHTING_COEFFS[16]
~PerceptualWeighting() FL_NO_EXCEPT
void applyAWeighting(Data &data) const
PerceptualWeighting() FL_NO_EXCEPT
float getBPM() FL_NO_EXCEPT
float getChordConfidence() FL_NO_EXCEPT
float getVocalConfidence() FL_NO_EXCEPT
float getPitch() FL_NO_EXCEPT
float getMoodArousal() FL_NO_EXCEPT
float getPitchConfidence() FL_NO_EXCEPT
float getNoteVelocity() FL_NO_EXCEPT
float getMidLevel() FL_NO_EXCEPT
float getTempoBPM() FL_NO_EXCEPT
float getDropImpact() FL_NO_EXCEPT
float getDownbeatConfidence() FL_NO_EXCEPT
float getDynamicTrend() FL_NO_EXCEPT
bool isSnare() FL_NO_EXCEPT
void setGain(float gain) FL_NO_EXCEPT
Set a simple digital gain multiplier applied to each sample before detector.
float getTrebleLevel() FL_NO_EXCEPT
bool isDiminuendo() FL_NO_EXCEPT
float getMeasurePhase() FL_NO_EXCEPT
float getTransientStrength() FL_NO_EXCEPT
float getNoteConfidence() FL_NO_EXCEPT
float getBassLevel() FL_NO_EXCEPT
bool isKick() FL_NO_EXCEPT
float getKeyConfidence() FL_NO_EXCEPT
float getBackbeatConfidence() FL_NO_EXCEPT
float getPeakLevel() FL_NO_EXCEPT
u32 getSilenceDuration() FL_NO_EXCEPT
u8 getCurrentBeatNumber() FL_NO_EXCEPT
bool isCrescendo() FL_NO_EXCEPT
float getTempoConfidence() FL_NO_EXCEPT
float getEnergy() FL_NO_EXCEPT
float getBuildupProgress() FL_NO_EXCEPT
bool isSilent() FL_NO_EXCEPT
bool isTom() FL_NO_EXCEPT
float getBackbeatStrength() FL_NO_EXCEPT
float getBeatConfidence() FL_NO_EXCEPT
float getMoodValence() FL_NO_EXCEPT
bool isHiHat() FL_NO_EXCEPT
float getBuildupIntensity() FL_NO_EXCEPT
u8 getCurrentNote() FL_NO_EXCEPT
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)
const VectorPCM & pcm() const FL_NO_EXCEPT
bool isValid() const FL_NO_EXCEPT
float rms() const FL_NO_EXCEPT
Get current statistics (for debugging/monitoring)
Get statistics (for debugging/monitoring)
~SpectralFluxDetector() FL_NO_EXCEPT
void setThreshold(float threshold)
float calculateSpectralFlux(span< const float, 16 > currentBins, span< const float, 16 > previousBins)
bool detectOnset(span< const float, 16 > currentBins)
SpectralFluxDetector() FL_NO_EXCEPT
float getThreshold() const
fl::array< float, 16 > mPreviousMagnitudes
constexpr bool empty() const FL_NO_EXCEPT
constexpr fl::size size() const FL_NO_EXCEPT
fl::size size() const FL_NO_EXCEPT
bool empty() const FL_NO_EXCEPT
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_NO_EXCEPT
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 logf(float value) FL_NO_EXCEPT
constexpr int type_rank< T >::value
fl::enable_if<!fl::is_array< T >::value, unique_ptr< T > >::type make_unique(Args &&... args) FL_NO_EXCEPT
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_NO_EXCEPT
InputGamut g FL_NO_EXCEPT
float sqrtf(float value) FL_NO_EXCEPT
float expf(float value) FL_NO_EXCEPT
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)
void detectBeats(span< const float, 16 > frequencyBins, Data &audioData)
~BeatDetectors() FL_NO_EXCEPT
float mPreviousTrebleEnergy
float mPreviousBassEnergy
BeatDetectors() FL_NO_EXCEPT
SpectralFluxDetector combined
static float DefaultMinFrequency() FL_NO_EXCEPT
static float DefaultMaxFrequency() FL_NO_EXCEPT