Get the logarithmic byteFrequencyData from audio

I asked a question similar to this earlier, but it did not solve my problem and was poorly explained. This time I made illustrations to hopefully explain better.

I have a simple frequency spectrum analyzer for my audio player. Frequencies are stored in an array, which is updated on each requestAnimationFrame , the array looks like this:

 fbc_array = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(fbc_array); 

Read more about getByteFrequencyData here.

So it works great, but I would like the frequencies to be evenly distributed throughout the spectrum. Right now it displays linear frequencies:

enter image description here

As you can see, the dominant frequency range here is Treble (High end), and the most dominated frequency range is the low frequency range. I want my analyzer to represent evenly distributed frequency ranges as follows:

enter image description here

Here you see frequencies evenly distributed over the analyzer. Is it possible?

The code that I used to generate the analyzer looks like this:

 // These variables are dynamically changed, ignore them. var canbars = 737 var canmultiplier = 8 var canspace = 1 // The analyser var canvas, ctx, source, context, analyser, fbc_array, bars, bar_x, bar_width, bar_height; function audioAnalyserFrame() { 'use strict'; var i; canvas.width = $('analyser-').width(); canvas.height = $('analyser-').height(); ctx.imageSmoothingEnabled = false; fbc_array = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(fbc_array); ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas ctx.fillStyle = "white"; // Color of the bars bars = canbars; for (i = 0; i < bars; i += canmultiplier) { bar_x = i * canspace; bar_width = 2; bar_height = -3 - (fbc_array[i] / 2); ctx.fillRect(bar_x, canvas.height, bar_width, bar_height); } window.requestAnimationFrame(audioAnalyserFrame); } function audioAnalyserInitialize() { 'use strict'; var analyserElement = document.getElementById('analyzer'); if (analyserElement !== null && audioViewIsCurrent() === true) { if (analyserInitialized === false) { context = new AudioContext(); source = context.createMediaElementSource(audioSource); } else { analyser.disconnect(); } analyser = context.createAnalyser(); canvas = analyserElement; ctx = canvas.getContext('2d'); source.connect(analyser); analyser.connect(context.destination); if (analyserInitialized === false) { audioAnalyserFrame(); } analyserInitialized = true; analyser.smoothingTimeConstant = 0.7; } } 

Note that I skip 8 bars (see canmultiplier at the top) in the for loop (if I donโ€™t do this, the other half of the analyzer is pulled out of the canvas because it is too big.) I donโ€™t know if this could be the cause of inconsistent ranges frequencies.

+6
source share
5 answers

If I understood you correctly, I think it will work for you, although far from perfect.

What you do in a for loop is fetching an array every 8 elements. What I would do is make the selection in a logarithmic way.

Example:

 //Given a range, transforms a value from linear scale to log scale. var toLog = function(value, min, max){ var exp = (value-min) / (max-min); return min * Math.pow(max/min, exp); } //This would be the frequency array in a linear scale var arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; //In this case i'm using a range from 1 to 20, you would use the size of your array. I'm incrementing 'i' by one each time, but you could also change that for (var i = 1; i < 20; i += 1) { //I'm starting at 1 because 0 and logarithms dont get along var logindex = toLog(i,1,19); //the index we want to sample //As the logindex will probably be decimal, we need to interpolate (in this case linear interpolation) var low = Math.floor(logindex); var high = Math.ceil(logindex); var lv = arr[low]; var hv = arr[high]; var w = (logindex-low)/(high-low); var v = lv + (hv-lv)*w; //the interpolated value of the original array in the logindex index. document.write(v + "<br/>"); //In your case you should draw the bar here or save it in an array for later. } 

I hope I explained myself well. Here you have a working demo that has some border errors, but it works, as it seems to me, for you.

+3
source

I think I understand what you mean. The problem is not with your code, but with the FFT underlying getByteFrequencyData . The main problem is that musical notes are logarithmically spaced , while FFT frequency cells are linearly spaced .

The notes are logarithmically spaced: the difference between consecutive low notes, for example A2 (110 Hz) and A2 # (116.5 Hz), is 6.5 Hz, while the difference between the same 2 notes on a higher octave A3 (220 Hz) ) and A3 # (233.1 Hz) - 13.1 Hz.

FFT bins are linearly spaced: let's say that we work with 44100 samples per second, FFT takes a window with 1024 samples (wave) and first multiplies it by wave until 1024 samples (let me call it wave1 ), so this will be a period of 1024/44100=0.023 seconds , which is 43.48 Hz , and puts the resulting amplitude in the first bit. Then he multiplies it by a wave with a frequency of wave1 * 2, which is 86.95 Hz , then wave1 * 3 = 130.43 Hz . Thus, the frequency difference is linear; it is always the same = 43.48, in contrast to differences in musical notes that change.

That is why close low frequencies will be combined into one and the same bit, while close high frequencies will be divided. This is a problem with the FFT frequency resolution. It can be solved if you accept windows larger than 1024 samples, but this will be a compromise for time resolution.

+2
source

You will have to manually average the values โ€‹โ€‹(or something like that) to turn them into a logarithmic array; just like the FFT algorithm works.

+1
source

Another approach that may or may not work. Break the signal, say, 5 bands. Apply low-pass and high-pass filters and 3 band-pass filters that cover the entire frequency range. Modulate the output of all filters (except the low) to 0 frequency. Add an analyzer for each of 5 different signals. Adjust the response from each of them, taking into account that you have shifted the filter outputs in frequency.

The outputs of individual analyzers will still be the same, but perhaps the result is quite close.

(Modulation to frequency 0 can be done using a node gain of two or more, the gain of which is a sine or cosine wave from a node oscillator.)

0
source

Something like this should work:

 // These variables are dynamically changed, ignore them. var canbars = 737 var canmultiplier = 8 var canspace = 1 // The analyser var canvas, ctx, source, context, analyser, fbc_array, bars, bar_x, bar_width, bar_height; function audioAnalyserFrame() { 'use strict'; var i; canvas.width = $('analyser-').width(); canvas.height = $('analyser-').height(); ctx.imageSmoothingEnabled = false; fbc_array = new Uint8Array(analyser.frequencyBinCount); analyser.getByteFrequencyData(fbc_array); ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas ctx.fillStyle = "white"; // Color of the bars bars = canbars; //Find the center var center = Math.round(bars / 2) - 1; for (i = 0; i < fbc_array.length; i ++) { // Update the spectrum bars, spread evenly. bar_x = (center + (i % 2 == 0 ? -1 : 1) * Math.round(i / 2)); bar_width = 2; bar_height = -3 - (fbc_array[i] / 2); ctx.fillRect(bar_x, canvas.height, bar_width, bar_height); } window.requestAnimationFrame(audioAnalyserFrame); } function audioAnalyserInitialize() { 'use strict'; var analyserElement = document.getElementById('analyzer'); if (analyserElement !== null && audioViewIsCurrent() === true) { if (analyserInitialized === false) { context = new AudioContext(); source = context.createMediaElementSource(audioSource); } else { analyser.disconnect(); } analyser = context.createAnalyser(); canvas = analyserElement; ctx = canvas.getContext('2d'); source.connect(analyser); analyser.connect(context.destination); if (analyserInitialized === false) { audioAnalyserFrame(); } analyserInitialized = true; analyser.smoothingTimeConstant = 0.7; } } 

One step improved, wrap the โ€œupdateโ€ in function

 function audioAnalyserFrame() { 'use strict'; var i; canvas.width = $('analyser-').width(); canvas.height = $('analyser-').height(); ctx.imageSmoothingEnabled = false; fbc_array = new Uint8Array(analyser.frequencyBinCount); ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas ctx.fillStyle = "white"; // Color of the bars bars = canbars; //Find the center var center = Math.round(bars / 2) - 1; (update = function() { window.requestAnimationFrame(update); analyser.getByteFrequencyData(fbc_array); for (i = 0; i < fbc_array.length; i++) { // Update the spectrum bars, spread evenly. bar_x = (center + (i % 2 == 0 ? -1 : 1) * Math.round(i / 2)); bar_width = 2; bar_height = -3 - (fbc_array[i] / 2); ctx.fillRect(bar_x, canvas.height, bar_width, bar_height); } }(); } 
0
source

All Articles