2016-03-04 11 views
6

Ich stellte eine ähnliche Frage wie zuvor, aber es hat mein Problem nicht gelöst und wurde schlecht erklärt. Diesmal habe ich Illustrationen gemacht, um hoffentlich besser zu erklären.Get logarithmic byteFrequencyData von Audio

Ich habe einen einfachen Frequenzspektrumanalysator für meinen Audiospieler. Die Frequenzen in einem Array gespeichert sind, die auf jeder requestAnimationFrame aktualisiert wird, sieht das Array wie folgt:

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

Read more about getByteFrequencyData here.

So funktioniert das gut aber ich würde die Frequenzen wie gleichmäßig über das Spektrum beabstandet sind. Gerade jetzt ist es die Anzeige lineare Frequenzen:

enter image description here

Wie Sie sehen können, der dominierenden Frequenzbereich ist hier die Höhen (High-End) und der meisten dominiertem Frequenzbereich ist der Bassbereich (Low-End). Ich möchte, dass meine Analysator mit gleichmäßig verteilten Frequenzbereiche wie folgt dargestellt:

enter image description here

Hier können Sie die Frequenzen gleichmäßig im Abstand über den Analysator zu sehen. Ist das möglich?

Der Code, den ich für die Erzeugung der Analysator wie folgt aussieht:

// 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; 
    } 
} 

Beachten Sie, dass ich 8 bar am Überspringen (siehe canmultiplier am oberen Rand) in der for-Schleife (Wenn ich nicht tun, die andere Hälfte des Analysators wird außerhalb der Leinwand gerendert, weil es zu groß ist.) Ich weiß nicht, ob dies auch die inkonsistenten Frequenzbereiche verursachen könnte.

Antwort

3

Wenn ich Sie richtig verstanden habe, ich denke, das wird für Sie arbeiten obwohl es bei weitem nicht perfekt ist.

Was Sie in Ihrer for-Schleife tun, ist die Probe, einmal alle 8 Elemente. Was ich tun würde, ist die logarithmische Probenahme.

Ein Beispiel:

//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. 
} 

Ich hoffe, dass ich mich gut erklärt. Hier haben Sie eine working demo, die einige Grenzfehler hat, aber es funktioniert, wie ich denke, dass Sie brauchen.

+0

Darf ich fragen, warum der erste und letzte Index keine Nummer ist? –

+0

Es ist eine einfache Grenze, die ich weggelassen habe, um den Code klarer zu machen. –

+0

Okay, lass mich das wirklich schnell versuchen. Wie kann ich sagen, dass der letzte und erste Index des Arrays ignoriert wird? –

1

Sie müssen die Werte (oder etwas Ähnliches) manuell mitteln, um sie in ein logarithmisches Array umzuwandeln; So funktioniert der FFT-Algorithmus.

+0

Ja, aber wie? Dies ist ein Array, das jeden Frame ändert, also hat das Ausführen von großen Berechnungen (die ich vorher schon ausprobiert habe) meinen Browser zum Erliegen gebracht. –

+0

Eine Alternative wäre ein Bündel von Bandpassfiltern mit Mittenfrequenzen bei den gewünschten Frequenzen. Quadrieren Sie die Ausgabe jedes Filters und zeichnen Sie die Ausgabe des Filters auf (möglicherweise unter Verwendung von AnalyserNode, um diese Daten zu erhalten). –

0

Ein anderer Ansatz, der möglicherweise funktioniert oder nicht funktioniert. Brechen Sie das Signal in, sagen wir 5 Bänder. Verwenden Sie einen Tiefpass- und Hochpassfilter und 3 Bandpassfilter, die den gesamten Frequenzbereich abdecken. Modulieren Sie den Ausgang aller Filter (außer dem Tiefpass) auf die Frequenz des Tiefs 0. Fügen Sie für jedes von 5 verschiedenen Signalen einen Analysator hinzu. Zeichnen Sie die Antwort von jedem dieser Punkte auf, wobei Sie berücksichtigen, dass Sie die Filterausgänge in der Frequenz nach unten verschoben haben.

Die einzelnen Ausgänge des Analysators sind immer noch einheitlich, aber vielleicht ist das Ergebnis nahe genug.

(Stetige auf 0 Frequenz nach unten kann mit einem Verstärkungsknoten oder zwei, dessen Verstärkung eine Sinus- oder Cosinus-Welle, die von einem Oszillator Knoten durchgeführt werden.)

+0

Es muss einen einfacheren Weg geben. Sicher können Sie nur das Frequenz-Bin-Array verarbeiten? –

+0

Wahrscheinlich. Nur nicht genau, wie. Du könntest so etwas wie den ersten Behälter machen. Summiere die nächsten 2, dann 4, 8, 16 und so weiter. Aber dann würde der letzte Behälter die Hälfte (oder so) des Frequenzbandes haben. –

+0

Oder berechnen Sie die Grenzen von jedem Drittel (sagen wir) Oktave. Fasse alle Behälter zusammen, die in diesem Bereich liegen. Bei Bins, die sich an der Grenze befinden, teilen Sie den Beitrag zwischen den Grenzen in irgendeiner Weise auf. Vielleicht ist sogar linear gut genug. –

0

Etwas nach dem Vorbild dieser sollte funktionieren:

// 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; 
    } 
} 

Ein Schritt verbessert, wickeln Sie das „Update“ in einer Funktion

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); 
     } 
    }(); 
    } 
2

Ich glaube, ich verstehe, was Sie genau bedeuten. Das Problem ist nicht mit Ihrem Code, es ist mit der FFT zugrunde liegenden getByteFrequencyData. Das Kernproblem besteht darin, dass musikalische Noten im logarithmischen Abstand sind, während die FFT-Frequenzbins linear beabstandet sind.

Hinweise logarithmisch beabstandet sind: Der Unterschied zwischen aufeinander folgenden tiefen Töne, sagen A2 (110 Hz) und A2 # (116,5 Hz) 6,5 Hz, während der Unterschied zwischen den gleichen 2 Noten auf einer höheren Oktave A3 (220 Hz) und A3 # (233,1 Hz) ist 13,1 Hz.

FFT-Bins sind linear angeordnet: Sagen wir mit 44.100 Samples pro Sekunde, nimmt die FFT ein Fenster von 1024 Proben (eine Welle), und multipliziert sie zunächst mit einer Welle solange 1024 Samples (sie arbeiten Nennen Sie es wave1), das wäre also ein Zeitraum von 1024/44100=0.023 seconds, der 43.48 Hz ist, und legt die resultierende Amplitude in den ersten Bin. Dann multipliziert es es mit einer Welle mit der Frequenz wave1 * 2, die 86.95 Hz ist, dann wave1 * 3 = 130.43 Hz. Der Unterschied zwischen den Frequenzen ist also linear; es ist immer dasselbe = 43.48, anders als der Unterschied in den Noten, der sich ändert.

Dies ist der Grund, warum nahe niedrige Frequenzen in demselben Behälter gebündelt werden, während nahe hohe Frequenzen getrennt sind. Dies ist das Problem mit der Frequenzauflösung von FFT. Es kann gelöst werden, indem Fenster mit mehr als 1024 Samples genommen werden, aber das wäre ein Kompromiss für die Zeitauflösung.