2016-03-04 8 views
6

Zadałem pytanie podobne do tego wcześniej, ale nie rozwiązało to mojego problemu i zostało źle wyjaśnione. Tym razem zrobiłem ilustracje, które mam nadzieję wyjaśnią lepiej.Uzyskaj logarytmiczny bajtFrequencyData z Audio

Mam prosty analizator widma częstotliwości dla mojego odtwarzacza audio. Częstotliwości są przechowywane w tablicy, która jest aktualizowana po każdej requestAnimationFrame tablica wygląda następująco:

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

Read more about getByteFrequencyData here.

Tak to działa dobrze, jednak chciałbym częstotliwości mają być równomiernie rozmieszczone w całym spektrum. Teraz jest wyświetlanie częstotliwości liniowej:

enter image description here

Jak widać, zakres częstotliwości wyróżniającym jest tutaj tonów (high end), a najbardziej zdominowany zakres częstotliwości jest zakres basów (low end). Chcę, aby mój analizator przedstawiane z równomiernie rozłożonych zakresach częstotliwości tak:

enter image description here

Tu zobaczysz częstotliwości równomiernie rozmieszczonych na analizatorze. czy to możliwe?

Kod I wykorzystywane do generowania analizator wygląda następująco:

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

zwrócić uwagę, że ja pomijam 8 barów (patrz canmultiplier u góry) w pętli for (Jeśli nie zrobisz, druga połowa analizatora jest renderowana poza obszarem roboczym, ponieważ jest zbyt duża.) Nie wiem, czy to też może powodować niespójne zakresy częstotliwości.

Odpowiedz

3

Jeśli ja zrozumiałem, myślę, że to będzie pracować dla Ciebie , choć daleki od doskonałości.

To, co robisz w pętli for, to próbkowanie tablicy, co 8 elementów. Mogę zrobić próbkowanie w sposób logarytmiczny.

Przykład:

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

Mam nadzieję, że wyjaśniłem sobie dobrze. Tutaj masz working demo, który ma pewne błędy graniczne, ale działa tak, jak myślę, że potrzebujesz.

+0

Czy mogę zapytać, dlaczego pierwszy i ostatni indeks nie jest liczbą? –

+0

To prosta kontrola granic, którą pominąłam, aby uczynić kod jaśniejszym. –

+0

Dobra, pozwól mi spróbować tego naprawdę szybko. Jak mam to powiedzieć, aby zignorować ostatni i pierwszy indeks tablicy? –

1

Będziesz musiał ręcznie uśrednić wartości (lub coś w tym stylu), aby przekształcić je w tablicę logarytmiczną; tak właśnie działa algorytm FFT.

+0

Tak, ale jak? Jest to tablica, która zmienia każdą klatkę, więc zastosowanie do niej dużych obliczeń (które próbowałem wcześniej) spowodowało, że moja przeglądarka zatrzymała się. –

+0

Alternatywą byłoby mieć kilka filtrów pasmowoprzepustowych z częstotliwościami środkowymi na pożądanych częstotliwościach. Kwadratowe wyjście każdego filtra i wykreślenie wyjścia filtru (prawdopodobnie za pomocą AnalyserNode, aby uzyskać te dane). –

0

Inne podejście, które może ale nie musi działać. Przełam sygnał, powiedzmy 5 pasm. Zastosuj filtry dolnoprzepustowe i górnoprzepustowe oraz 3 filtry pasmowe, które obejmują cały zakres częstotliwości. Moduluj wyjście wszystkich filtrów (z wyjątkiem dolnego przejścia) do częstotliwości 0 w dół. Dodaj analizator dla każdego z 5 różnych sygnałów. Narysuj odpowiedź każdego z nich, biorąc pod uwagę, że przesunąłeś wyjściowo filtry w dół na częstotliwości.

Dane wyjściowe poszczególnych analizatorów nadal będą jednakowe, ale być może wynik jest wystarczająco zbliżony.

(modulowany w dół do 0 częstotliwości może odbywać się za pomocą węzła zysk lub dwa której zysk jest sinus lub cosinus fali od węzła oscylatora).

+0

Musi być prostszy sposób. Z pewnością możesz po prostu przetworzyć tablicę częstotliwości? –

+0

Prawdopodobnie. Tylko nie do końca pewna jak. Możesz zrobić coś takiego, jak wziąć pierwszy pojemnik. Zsumuj następne 2, Potem 4, 8, 16 i tak dalej. Ale wtedy ostatni pojemnik miałby połowę (lub mniej) pasma częstotliwości. –

+0

Albo obliczyć granice co jedną trzecią (na przykład) oktawy. Zsumuj wszystkie pojemniki, które znajdują się w tym zakresie. W przypadku pojemników znajdujących się na granicy należy w jakiś sposób podzielić wkład między granice. Może nawet liniowo wystarczy. –

0

Coś wzdłuż linii to powinno działać:

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

Jeden krok poprawie, owinąć „Update” w funkcji

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

wierzę rozumiem co masz na myśli dokładnie. Problem nie leży w twoim kodzie, to jest z FFT leżącym u podstaw getByteFrequencyData. Podstawowym problemem jest to, że muzyczne nuty są logarytmicznie oddalone od, podczas gdy przedziały częstotliwości FFT są rozstawione liniowo.

Noty są logarytmicznie rozmieszczone: Różnica pomiędzy kolejnymi niskich tonów, np A2 (110 Hz) i A2 # (116,5 Hz), 6,5 Hz, podczas gdy różnica między tymi samymi 2 Uwagi o wyższej oktawy A3 (220 Hz) i A3 # (233,1 Hz) to 13,1 Hz.

kosze FFT są liniowo rozmieszczone: Say pracujemy z 44100 próbek na sekundę, FFT zajmuje okno 1024 próbek (fala), i mnoży ją najpierw z falą dopóki 1024 próbek (powiedzmy, nazwij go: wave1), więc będzie to okres 1024/44100=0.023 seconds, który jest 43.48 Hz, i umieszcza wynikową amplitudę w pierwszym pojemniku. Następnie mnoży ją falą o częstotliwości wave1 * 2, która jest 86.95 Hz, następnie wave1 * 3 = 130.43 Hz. Różnica między częstotliwościami jest liniowa; zawsze jest tak samo = 43,48, w przeciwieństwie do różnic w nutach, które się zmieniają.

Dlatego właśnie niskie częstotliwości będą łączone w tym samym przedziale, podczas gdy wysokie wysokie częstotliwości są rozdzielone. Jest to problem z rozdzielczością częstotliwości FFT. Można go rozwiązać, pobierając okna większe niż 1024 próbki, ale byłby to kompromis w zakresie rozdzielczości czasowej.