Files
klubhaus-doorbell/libraries/FastLED/src/fl/audio.h
2026-02-12 00:45:31 -08:00

170 lines
5.6 KiB
C++
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include "fl/fft.h"
#include "fl/math.h"
#include "fl/memory.h"
#include "fl/span.h"
#include "fl/vector.h"
#include "fl/int.h"
#include <math.h>
#include "fl/stdint.h"
#include "fl/span.h"
namespace fl {
class AudioSampleImpl;
FASTLED_SMART_PTR(AudioSampleImpl);
// AudioSample is a wrapper around AudioSampleImpl, hiding the reference
// counting so that the api object can be simple and have standard object
// semantics.
class AudioSample {
public:
using VectorPCM = fl::vector<fl::i16>;
using const_iterator = VectorPCM::const_iterator;
AudioSample() {}
AudioSample(const AudioSample &other) : mImpl(other.mImpl) {}
AudioSample(AudioSampleImplPtr impl) : mImpl(impl) {}
~AudioSample();
// Constructor that takes raw audio data and handles pooling internally
AudioSample(fl::span<const fl::i16> span, fl::u32 timestamp = 0);
AudioSample &operator=(const AudioSample &other);
bool isValid() const { return mImpl != nullptr; }
fl::size size() const;
// Raw pcm levels.
const VectorPCM &pcm() const;
// Zero crossing factor between 0.0f -> 1.0f, detects "hiss"
// and sounds like cloths rubbing. Useful for sound analysis.
float zcf() const;
float rms() const;
fl::u32 timestamp() const; // Timestamp when sample became valid (millis)
void fft(FFTBins *out) const;
const_iterator begin() const { return pcm().begin(); }
const_iterator end() const { return pcm().end(); }
const fl::i16 &at(fl::size i) const;
const fl::i16 &operator[](fl::size i) const;
operator bool() const { return isValid(); }
bool operator==(const AudioSample &other) const;
bool operator!=(const AudioSample &other) const;
private:
static const VectorPCM &empty();
AudioSampleImplPtr mImpl;
};
// Sound level meter is a persistant measuring class that will auto-tune the
// microphone to real world SPL levels. It will adapt to the noise floor of the
// environment. Note that the microphone only ever outputs DBFS (dB Full Scale)
// values, which are collected over a stream of samples. The sound level meter
// will convert this to SPL (Sound Pressure Level) values, which are the real
// world values.
class SoundLevelMeter {
public:
/// @param spl_floor The SPL (dB SPL) that corresponds to your true
/// noise-floor.
/// @param smoothing_alpha [0…1] how quickly to adapt floor; 0=instant min.
SoundLevelMeter(double spl_floor = 33.0, double smoothing_alpha = 0.0);
/// Process a block of int16 PCM samples.
void processBlock(const fl::i16 *samples, fl::size count);
void processBlock(fl::span<const fl::i16> samples) {
processBlock(samples.data(), samples.size());
}
/// @returns most recent blocks level in dBFS (≤0)
double getDBFS() const { return current_dbfs_; }
/// @returns calibrated estimate in dB SPL
double getSPL() const { return current_spl_; }
/// change your known noise-floor SPL at runtime
void setFloorSPL(double spl_floor) {
spl_floor_ = spl_floor;
offset_ = spl_floor_ - dbfs_floor_global_;
}
/// reset so the next quiet block will re-initialize your floor
void resetFloor() {
dbfs_floor_global_ = INFINITY_DOUBLE; // infinity<double>
offset_ = 0.0;
}
private:
double spl_floor_; // e.g. 33.0 dB SPL
double smoothing_alpha_; // 0 = pure min, >0 = slow adapt
double dbfs_floor_global_; // lowest dBFS seen so far
double offset_; // spl_floor_ dbfs_floor_global_
double current_dbfs_; // last blocks dBFS
double current_spl_; // last blocks estimated SPL
};
// Implementation details.
class AudioSampleImpl {
public:
using VectorPCM = fl::vector<fl::i16>;
~AudioSampleImpl() {}
// template <typename It> void assign(It begin, It end) {
// assign(begin, end, 0); // Default timestamp to 0
// }
template <typename It> void assign(It begin, It end, fl::u32 timestamp) {
mSignedPcm.assign(begin, end);
mTimestamp = timestamp;
// calculate zero crossings
initZeroCrossings();
}
const VectorPCM &pcm() const { return mSignedPcm; }
fl::u32 timestamp() const { return mTimestamp; }
// For object pool - reset internal state for reuse
void reset() {
mSignedPcm.clear();
mZeroCrossings = 0;
mTimestamp = 0;
}
// "Zero crossing factor". High values > .4 indicate hissing
// sounds. For example a microphone rubbing against a clothing.
// These types of signals indicate the audio should be ignored.
// Low zero crossing factors (with loud sound) indicate that there
// is organized sound like that coming from music. This is so cheap
// to calculate it's done automatically. It should be one of the first
// signals to reject or accept a sound signal.
//
// Returns: a value -> [0.0f, 1.0f)
float zcf() const {
const fl::size n = pcm().size();
if (n < 2) {
return 0.f;
}
return float(mZeroCrossings) / static_cast<float>(n - 1);
}
private:
void initZeroCrossings() {
mZeroCrossings = 0;
if (mSignedPcm.size() > 1) {
for (fl::size i = 1; i < mSignedPcm.size(); ++i) {
const bool crossed =
(mSignedPcm[i - 1] < 0 && mSignedPcm[i] >= 0) ||
(mSignedPcm[i - 1] >= 0 && mSignedPcm[i] < 0);
if (crossed) {
++mZeroCrossings;
}
}
}
}
VectorPCM mSignedPcm;
fl::i16 mZeroCrossings = 0;
fl::u32 mTimestamp = 0;
};
} // namespace fl