swharden / FftSharp

A .NET Standard library for computing the Fast Fourier Transform (FFT) of real or complex data
https://nuget.org/packages/FftSharp
MIT License
328 stars 47 forks source link

demo request #91

Open ahmadsafi24 opened 1 day ago

ahmadsafi24 commented 1 day ago

The FFT spectrum in the demo example doesn't resemble a realistic frequency spectrum. Is it possible to visualize audio similar to the FL Studio Parametric EQ, considering a logarithmic x-scale and the equal-loudness contour? [https://en.m.wikipedia.org/wiki/Equal-loudness_contour]()

swharden commented 1 day ago

Hi @ahmadsafi24, thank you for this question! It may be sufficient to document how to convert FFT output to equal loudness curve, and not require code change to the FFTsharp library to achieve this goal.

I am currently travelling and am writing from a smartphone so I haven't tested this, but what do you think about the strategy recommended by ChatGPT?


I have a C# function that returns the FFT power spectrum of an audio waveform. How do I convert these values to create an equal-loudness contour? Write a single function that takes the FFT values and returns the contour.

To convert FFT power spectrum values into an equal-loudness contour, you need to apply the equal-loudness weighting based on ISO 226:2003 or another reference that models the frequency-dependent sensitivity of human hearing. This process involves:

  1. Mapping the FFT frequencies to equal-loudness levels.
  2. Adjusting the FFT power values by the corresponding loudness weights.

Here's a C# function to apply equal-loudness weighting:

using System;
using System.Linq;

public static class AudioProcessing
{
    public static double[] ApplyEqualLoudnessContour(double[] fftValues, double sampleRate, int fftSize)
    {
        // Step 1: Calculate the frequencies corresponding to each FFT bin
        double[] frequencies = Enumerable.Range(0, fftValues.Length)
                                         .Select(i => i * sampleRate / fftSize)
                                         .ToArray();

        // Step 2: Get equal-loudness weights using an approximate ISO 226 model
        double[] weights = frequencies.Select(f => EqualLoudnessWeight(f)).ToArray();

        // Step 3: Apply weights to FFT values
        double[] adjustedValues = fftValues.Zip(weights, (fft, weight) => fft * weight).ToArray();

        return adjustedValues;
    }

    private static double EqualLoudnessWeight(double frequency)
    {
        if (frequency <= 0)
            return 0;

        // Approximate implementation of ISO 226 equal-loudness contours (A-weighting-like)
        // Use constants and equations specific to the model
        double f2 = frequency * frequency;
        double f4 = f2 * f2;

        // Empirical formula for A-weighting
        double r = Math.Pow(12200 * 12200 * f4, 2);
        double numerator = r;
        double denominator = ((f2 + 20.6 * 20.6) * Math.Sqrt((f2 + 107.7 * 107.7) * (f2 + 737.9 * 737.9)) * (f2 + 12200 * 12200));
        double aw = numerator / denominator;

        // Scale to human-perceptible range
        return Math.Pow(10, aw / 20);
    }
}

Explanation:

  1. Frequency Calculation: The function calculates the frequency corresponding to each FFT bin using the formula:

Fi = i * sampleRate / fftSize

  1. Equal-Loudness Weighting: The EqualLoudnessWeight function computes a weight for each frequency based on a simplified approximation of the ISO 226 equal-loudness curve.

  2. Apply Weights: The FFT power values are multiplied by the weights to create the equal-loudness contour.

Usage:

double[] fftValues = ...; // Your FFT power spectrum values
double sampleRate = 44100; // For example, 44.1 kHz
int fftSize = 1024; // FFT size

double[] equalLoudnessContour = AudioProcessing.ApplyEqualLoudnessContour(fftValues, sampleRate, fftSize);

Notes:

  1. This implementation uses a simplified equal-loudness weighting approximation. For a more precise model, consider using detailed equal-loudness curves or lookup tables based on ISO 226 data.
  2. Ensure the FFT values are appropriately scaled (e.g., normalized) before applying the weights.