consider human hearing for "Major Peak" frequency

The "Major Peak" (for double-notes effects) was computed on the raw FFT results.
However the human ear is more sensitive to higher frequencies (pink noise profile).

This code change considers human ear properties for major peak computations.
Effects like "freqmap" will look much more vivid afterwards, as higher frequencies will now have a stronger influence (harmonics are still suppressed properly).
This commit is contained in:
Frank
2023-07-26 13:30:44 +02:00
parent 03de8ac0ad
commit 6521717e90

View File

@@ -23,6 +23,12 @@
#define FFT_PREFER_EXACT_PEAKS // use different FFT wndowing -> results in "sharper" peaks and less "leaking" into other frequencies
//#define SR_STATS
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !defined(CONFIG_IDF_TARGET_ESP32C3)
// this applies "pink noise scaling" to FFT results before computing the major peak for effects.
// currently only for ESP32-S3 and classic ESP32, due to increased runtime
#define FFT_MAJORPEAK_HUMAN_EAR
#endif
// Comment/Uncomment to toggle usb serial debugging
// #define MIC_LOGGER // MIC sampling & sound input debugging (serial plotter)
// #define FFT_SAMPLING_LOG // FFT result debugging
@@ -289,6 +295,13 @@ static float vImag[samplesFFT] = {0.0f}; // imaginary parts
static float windowWeighingFactors[samplesFFT] = {0.0f};
#endif
#ifdef FFT_MAJORPEAK_HUMAN_EAR
static float pinkFactors[samplesFFT] = {0.0f}; // "pink noise" correction factors
constexpr float pinkcenter = 23.66; // sqrt(560) - center freq for scaling is 560 hz.
constexpr float binWidth = SAMPLE_RATE / (float)samplesFFT; // frequency range of each FFT result bin
#endif
// Create FFT object
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
// lib_deps += https://github.com/kosme/arduinoFFT#develop @ 1.9.2
@@ -376,6 +389,17 @@ void FFTcode(void * parameter)
const TickType_t xFrequencyDouble = FFT_MIN_CYCLE * portTICK_PERIOD_MS * 2;
static bool isFirstRun = false;
#ifdef FFT_MAJORPEAK_HUMAN_EAR
// pre-compute pink noise scaling table
for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++) {
float binFreq = binInd * binWidth + binWidth/2.0f;
if (binFreq > (SAMPLE_RATE * 0.42f))
binFreq = (SAMPLE_RATE * 0.42f) - 0.25 * (binFreq - (SAMPLE_RATE * 0.42f)); // supress noise and aliasing
pinkFactors[binInd] = sqrtf(binFreq) / pinkcenter;
}
pinkFactors[0] *= 0.5; // suppress 0-42hz bin
#endif
TickType_t xLastWakeTime = xTaskGetTickCount();
for(;;) {
delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy.
@@ -498,11 +522,28 @@ void FFTcode(void * parameter)
FFT.ComplexToMagnitude(); // Compute magnitudes
#endif
#ifdef FFT_MAJORPEAK_HUMAN_EAR
// scale FFT results
for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++)
vReal[binInd] *= pinkFactors[binInd];
#endif
#ifdef UM_AUDIOREACTIVE_USE_NEW_FFT
FFT.majorPeak(FFT_MajorPeak, FFT_Magnitude); // let the effects know which freq was most dominant
#else
FFT.MajorPeak(&FFT_MajorPeak, &FFT_Magnitude); // let the effects know which freq was most dominant
#endif
#ifdef FFT_MAJORPEAK_HUMAN_EAR
// undo scaling - we want unmodified values for FFTResult[] computations
for(uint_fast16_t binInd = 0; binInd < samplesFFT; binInd++)
vReal[binInd] *= 1.0f/pinkFactors[binInd];
//fix peak magnitude
if ((FFT_MajorPeak > (binWidth/1.25f)) && (FFT_MajorPeak < (SAMPLE_RATE/2.2f)) && (FFT_Magnitude > 4.0f)) {
unsigned peakBin = constrain((int)((FFT_MajorPeak + binWidth/2.0f) / binWidth), 0, samplesFFT -1);
FFT_Magnitude *= fmaxf(1.0f/pinkFactors[peakBin], 1.0f);
}
#endif
FFT_MajorPeak = constrain(FFT_MajorPeak, 1.0f, 11025.0f); // restrict value to range expected by effects
FFT_MajPeakSmth = FFT_MajPeakSmth + 0.42 * (FFT_MajorPeak - FFT_MajPeakSmth); // I like this "swooping peak" look