Mathematics Stack Exchange is a question and answer site for people studying math at any level and professionals in related fields. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

I guess my problem is related to logarithmic <-> linear scales. I'm trying to create a colored wave form by using a Spectral centroid. So far I got the color but the scaling is incorrect. Why this ? It helps in identifying music, visually ...

A picture is worth a thousand words : here

  1. Reference (Serato Scratch Live)
  2. Reference (Comparisonics Audio Editor)
  3. My current result
  4. Reference (Spectrum of a white noise)

As you can see, on my drawing it is definitely not linear scaling like the first twos. The left red part shouldn't be that wide, etc ... Some details, this is a sine sweep that is 5 seconds long, it starts at 16Hz and goes up to 22050Hz. How do I get the color ? Using HSL, hue being a value between 0 to 360 degrees.

My question is :

How can I match the scaling of my drawing (3) to look like 1 and/or 2 ?

Thanks !!!

share|cite|improve this question
up vote 3 down vote accepted

If you think of your $x$ axis as running from $0$ to $1$, you can plot frequency $f$ at position $x=\frac{\log \frac {f}{16}}{\log \frac{2250}{16}}$. See if that helps

share|cite|improve this answer
I decided to change thread name since it reflects more the content :-) – Aybe Sep 20 '11 at 18:38

UPDATE 4 : Preliminary multi-band slicing, not always correct, yet ... multi-band slicing

UPDATE 3 : Content must be sliced as samples which will be analyzed individually, this will form a nice image.

UPDATE 2 : This seems to work only on a single frequency sound ... I think in order to make it work for typical songs, there should be 3 FFTs, for Low, Medium and High frequencies, do the stuff for each band, then blend the 3 bands, one being red, one green, one blue (color components); these are my thoughts after studying the problem. For extra infos, see Wavelab's loudness envelope. Also, the results as of now are highly dependent on FFT ratio, 2048 gives better results than lower ones ... I'll get back here once my tests are done :-)

UPDATE : I just finished from implementing custom gradients, here are some results : enter image description here enter image description here

It thinks it's right now, just need to create appropriate gradients :-)))

(I have put the gradient code at the end of the post)

Sorry for the long answering, that's quite better !

That's funny because your formula didn't work the first time, it was only from green to blue (still much better than my initial test).

I think I've found out the origin of the problem, image is not the same for a very small sound (5. secs), data needs to be large enough to provide FFT enough space to operate. The result below is for a 50 sec. sound. enter image description here

Any ideas on why the left red part is being disturbed with orange ?

Overall, this is very good but still needs some work on my side, the pictures I posted first of other softwares, there seems to be some manual fixing in the coloring, etc ...

Thanks a lot :-)

P.S. : for people interested in using this,

You need to calculate spectral centroid for each chunk froms FFT chunks out of sound

  • Get FFT and spectral centroid for each chunks :

        var list = new List<float>();
        var fftSize = 256;
        var amps = new float[fftSize];
        var length = (int)(BASSData.BASS_DATA_FFT256 | BASSData.BASS_DATA_FFT_REMOVEDC);
            int data = Bass.BASS_ChannelGetData(handle, amps, length);
            if (data == -1)
            var weightedTotal = 0f;
            var total = 0f;
            for (int i = 0; i < amps.Length / 2; i++)
                var frequency = Utils.FFTIndex2Frequency(i, fftSize, 44100);
                weightedTotal += frequency * amps[i];
                total += amps[i];
            float result = 0;
            if (total > 0) result = weightedTotal / total;
        } while (true);
  • Then you will have to color all that since it'll be B&W :

            // Draw
        for (int index = 0; index < list.Count; index++)
            var f = list[index];
            float x;
            x = (float)(Math.Log10(f / min) / Math.Log10(max / min));
            x = (float)(Math.Log10(f / 20.0f) / Math.Log10(22050 / 20.0f));
            var hue = x * 360f;
            float saturation = 1.0f;
            float lightness = 0.5f;
            // Darken outside colors as black
            if (hue < 0 || hue > 360)
                hue = lightness = 0.0f;
            var hsl = new HSL(hue, saturation, lightness);
            var rgb = RGB.FromHSL(hsl.Hue, hsl.Saturation, hsl.Lightness);
            var fromArgb = Color.FromArgb(rgb.Red, rgb.Green, rgb.Blue);
            using (var pen = new Pen(fromArgb))
                graphics.DrawLine(pen, index, 0, index, bitmap.Height - 1);

On block 2, x has two formulas, first one scales to audio content so beware of division by zero ! Formula 2 is scaled to the usual 20 Hz to 22050 Hz range. Darkening just makes your debugging easier. Then you have your hue, you might want to check bounds of all these. In the end, a chunk of audio equals a vertical line, just like a wave form stripe in audio editing software. If you have a hard time using HSL go to EasyRGB website, all forumlas you ever dreamt of for converting between color spaces. Finally, draw your form using this coloring ... this shouldn't be too hard :-)

In the mean time, you can use BASS audio library if not already, it will fulfill all your audio programming needs (I used it personally for creating a DJ-ing software from scratch.)

Gradient code :

public class Gradient
    private readonly SortedDictionary<double, Color> _steps;

    public Gradient()
        _steps = new SortedDictionary<double, Color>();

    public SortedDictionary<double, Color> Steps
        get { return _steps; }

    public Color GetColor(double offset)
        // Check steps
        if (_steps.Count < 2)
            throw new InvalidOperationException("You need to add at least two gradient steps.");

        // Check offset
        double min = _steps.Min(s => s.Key);
        double max = _steps.Max(s => s.Key);
        if (offset < min || offset > max)
            throw new ArgumentOutOfRangeException("offset");

        // Get left and right colors
        var pair1 = _steps.TakeWhile(s => s.Key <= offset).Last();
        var pair2 = _steps.SkipWhile(s => s.Key < offset).First();
        var offset1 = pair1.Key;
        var offset2 = pair2.Key;
        var sum = offset2 - offset1;
        if (sum <= 0.0d) sum = 1.0d;
        double amount = 1.0d / sum * (offset - offset1);

        var color = Lerp(pair1.Value, pair2.Value, amount);

        return color;

    public static Color Lerp(Color color1, Color color2, double amount)
        var mul = (uint)(amount * 65536);
        var a = (byte)(color1.A + ((color2.A - color1.A) * mul >> 16));
        var r = (byte)(color1.R + ((color2.R - color1.R) * mul >> 16));
        var g = (byte)(color1.G + ((color2.G - color1.G) * mul >> 16));
        var b = (byte)(color1.B + ((color2.B - color1.B) * mul >> 16));
        return Color.FromArgb(a, r, g, b);
share|cite|improve this answer
What frequency are you sampling at? You need two samples per wave minimum. So if you want frequencies up to 22kHz, you need 44kHz sampling. That may be why short samples don't work well. – Ross Millikan Sep 21 '11 at 9:43
I am sampling at 44.1Khz :-) – Aybe Sep 21 '11 at 19:40

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.