por partir ordenar numericamente mostrar mientras hacer filtro filtrar filtrada escribes datos como columnas busqueda java audio fft visualization processing

partir - rowfilter java



¿Cómo filtrar datos FFT(para visualización de audio)? (2)

Dentro de su bucle: necesita agregar un cálculo logarítmico para una escala lg:

stroke(i,100,50); line( i, height, i, height - fftSmooth[i]*8 ); stroke(i,100,100); line( i, height, i, height - band*8 );

Debe cambiarse a:

int l = map(log(map(i ,0 ,specSize,0,100),0,2,0,width). // an estimation, may have to calibrate stroke(i,100,50); line( l, height, l, height - fftSmooth[i]*8 ); stroke(i,100,100); line( l, height, l, height - band*8 );

Estaba viendo esta demo de Web Audio API , parte de este bonito libro.

Si nos fijamos en la demostración, los picos fft caen suavemente. Estoy tratando de hacer lo mismo con el procesamiento en modo Java usando la biblioteca minim. He observado cómo se hace esto con la api de audio web en el método doFFTAnalysis() y doFFTAnalysis() intentado replicarlo con un mínimo. También intenté portar cómo funciona abs () con el tipo complejo:

/ 26.2.7/3 abs(__z): Returns the magnitude of __z. 00565 template<typename _Tp> 00566 inline _Tp 00567 __complex_abs(const complex<_Tp>& __z) 00568 { 00569 _Tp __x = __z.real(); 00570 _Tp __y = __z.imag(); 00571 const _Tp __s = std::max(abs(__x), abs(__y)); 00572 if (__s == _Tp()) // well ... 00573 return __s; 00574 __x /= __s; 00575 __y /= __s; 00576 return __s * sqrt(__x * __x + __y * __y); 00577 } 00578

Actualmente estoy haciendo un prototipo rápido utilizando Processing (un framework / library de java). Mi código se ve así:

import ddf.minim.*; import ddf.minim.analysis.*; private int blockSize = 512; private Minim minim; private AudioInput in; private FFT mfft; private float[] time = new float[blockSize];//time domain private float[] real = new float[blockSize]; private float[] imag = new float[blockSize]; private float[] freq = new float[blockSize];//smoothed freq. domain public void setup() { minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, blockSize); mfft = new FFT( in.bufferSize(), in.sampleRate() ); } public void draw() { background(255); for (int i = 0; i < blockSize; i++) time[i] = in.left.get(i); mfft.forward( time); real = mfft.getSpectrumReal(); imag = mfft.getSpectrumImaginary(); final float magnitudeScale = 1.0 / mfft.specSize(); final float k = (float)mouseX/width; for (int i = 0; i < blockSize; i++) { float creal = real[i]; float cimag = imag[i]; float s = Math.max(creal,cimag); creal /= s; cimag /= s; float absComplex = (float)(s * Math.sqrt(creal*creal + cimag*cimag)); float scalarMagnitude = absComplex * magnitudeScale; freq[i] = (k * mfft.getBand(i) + (1 - k) * scalarMagnitude); line( i, height, i, height - freq[i]*8 ); } fill(0); text("smoothing: " + k,10,10); }

No obtengo errores, lo cual es bueno, pero no obtengo el comportamiento esperado que es malo. Esperaba que los picos cayeran más lentamente cuando el suavizado (k) esté cerca de 1, pero hasta donde puedo decir, mi código solo escala las bandas.

Desafortunadamente, las matemáticas y el sonido no son mi punto fuerte, así que apuñalo en la oscuridad. ¿Cómo puedo replicar la buena visualización de la demostración de la API de audio web?

Estaría tentado de decir que esto puede ser agnóstico del lenguaje, pero el uso de javascript, por ejemplo, no se aplicaría :) Sin embargo, estoy feliz de probar cualquier otra biblioteca java que haga análisis FFT.

ACTUALIZAR

Tengo una solución simple para suavizar (disminuyen continuamente los valores de cada banda de pies cuadrados anterior si la banda de pies cuadrados actual no es más alta:

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; int specSize; void setup(){ size(640, 360, P3D); minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftReal = new float[specSize]; colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.left); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { float band = fft.getBand(i); fftSmooth[i] *= smoothing; if(fftSmooth[i] < band) fftSmooth[i] = band; stroke(i,100,50); line( i, height, i, height - fftSmooth[i]*8 ); stroke(i,100,100); line( i, height, i, height - band*8 ); } text("smoothing: " + (int)(smoothing*100),10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; }

El gráfico descolorido es el suavizado y el totalmente saturado es el vivo.

Sin embargo, todavía me falta algo, en comparación con la demostración de la API de audio web:

Creo que la Web Audio API podría tener en cuenta que las frecuencias medias y altas necesitarán escalarse para estar más cerca de lo que percibimos, pero no estoy seguro de cómo abordar eso.

Estaba tratando de leer más sobre cómo la clase RealtimeAnalyser hace esto para WebAudioAPI, pero parece que el método doFFT de la clase doFFT podría hacer la escala logarítmica. Todavía no he descubierto cómo funciona doFFT.

¿Cómo puedo escalar un gráfico FFT sin procesar con una escala logarítmica para tener en cuenta la percepción? Mi objetivo es hacer una visualización de aspecto decente y supongo que tendré que:

  • valores suaves , de lo contrario los elementos se animarán a rápido / nervioso
  • escale las bandejas / bandas FFT para obtener mejores datos para frecuencias medias / altas
  • mapear valores de FFT de proceso a elementos visuales (encontrar los valores / límites máximos)

¿Alguna pista sobre cómo puedo lograr esto?

ACTUALIZACIÓN 2

Supongo que esta parte hace el suavizado y el escalado que busco en la Web Audio API: // Normalizar para que una onda sinusoidal de entrada en 0dBfs se registre como 0dBfs (deshacer el factor de escala de FFT). const double magnitudeScale = 1.0 / DefaultFFTSize;

// A value of 0 does no averaging with the previous result. Larger values produce slower, but smoother changes. double k = m_smoothingTimeConstant; k = max(0.0, k); k = min(1.0, k); // Convert the analysis data from complex to magnitude and average with the previous result. float* destination = magnitudeBuffer().data(); size_t n = magnitudeBuffer().size(); for (size_t i = 0; i < n; ++i) { Complex c(realP[i], imagP[i]); double scalarMagnitude = abs(c) * magnitudeScale; destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude); }

Parece que la escala se realiza tomando el absoluto del valor complejo. Este post apunta en la misma dirección. He intentado usar el abs del número complejo usando Minim y usando varias funciones de ventana, pero aún no se parece a lo que estoy apuntando (la demostración de la API de audio web ):

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; void setup(){ size(640, 360, P3D); minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftReal = new float[specSize]; colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { float band = fft.getBand(i); //Sw = abs(Sw(1:(1+N/2))); %# abs is sqrt(real^2 + imag^2) float abs = sqrt(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); fftSmooth[i] *= smoothing; if(fftSmooth[i] < abs) fftSmooth[i] = abs; stroke(i,100,50); line( i, height, i, height - fftSmooth[i]*8 ); stroke(i,100,100); line( i, height, i, height - band*8 ); } text("smoothing: " + (int)(smoothing*100)+"/nwindow:"+wlabel[windex],10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; if(key == ''W'' && windex < window.length-1) windex++; if(key == ''w'' && windex > 0) windex--; if(key == ''w'' || key == ''W'') fft.window(window[windex]); }

No estoy seguro de que esté usando las funciones de la ventana correctamente porque no noto una gran diferencia entre ellas. ¿Es correcto el abs del valor complejo? ¿Cómo puedo acercar una visualización a mi objetivo?

ACTUALIZACIÓN 3

He intentado aplicar los consejos útiles de @ wakjah como:

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; float[] fftPrev; float[] fftCurr; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; int scale = 10; void setup(){ minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftPrev = new float[specSize]; fftCurr = new float[specSize]; size(specSize, specSize/2); colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { //float band = fft.getBand(i); //Sw = abs(Sw(1:(1+N/2))); %# abs is sqrt(real^2 + imag^2) //float abs = sqrt(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); //fftSmooth[i] *= smoothing; //if(fftSmooth[i] < abs) fftSmooth[i] = abs; //x_dB = 10 * log10(real(x) ^ 2 + imag(x) ^ 2); fftCurr[i] = scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); //Y[k] = alpha * Y_(t-1)[k] + (1 - alpha) * X[k] fftSmooth[i] = smoothing * fftPrev[i] + ((1 - smoothing) * fftCurr[i]); fftPrev[i] = fftCurr[i];// stroke(i,100,100); line( i, height, i, height - fftSmooth[i]); } text("smoothing: " + (int)(smoothing*100)+"/nwindow:"+wlabel[windex]+"/nscale:"+scale,10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; if(key == ''W'' && windex < window.length-1) windex++; if(key == ''w'' && windex > 0) windex--; if(key == ''w'' || key == ''W'') fft.window(window[windex]); if(keyCode == LEFT && scale > 1) scale--; if(keyCode == RIGHT) scale++; }

No estoy seguro de haber aplicado los consejos según lo previsto. Así es como se ve mi salida:

pero no creo que esté ahí todavía si comparo esto con las visualizaciones a las que apunto:

espectro en windows media player

espectro en reproductor VLC

No estoy seguro de haber aplicado la escala de registro correctamente. Mis suposiciones eran, que haría una trama similar a la que apunto después de usar log10 (ignorando el suavizado por ahora).

ACTUALIZACIÓN 4:

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; float[] fftPrev; float[] fftCurr; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; int scale = 10; void setup(){ minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftPrev = new float[specSize]; fftCurr = new float[specSize]; size(specSize, specSize/2); colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { float maxVal = Math.max(Math.abs(fftReal[i]), Math.abs(fftImag[i])); if (maxVal != 0.0f) { // prevent divide-by-zero // Normalize fftReal[i] = fftReal[i] / maxVal; fftImag[i] = fftImag[i] / maxVal; } fftCurr[i] = -scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); fftSmooth[i] = smoothing * fftSmooth[i] + ((1 - smoothing) * fftCurr[i]); stroke(i,100,100); line( i, height/2, i, height/2 - (mousePressed ? fftSmooth[i] : fftCurr[i])); } text("smoothing: " + (int)(smoothing*100)+"/nwindow:"+wlabel[windex]+"/nscale:"+scale,10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; if(key == ''W'' && windex < window.length-1) windex++; if(key == ''w'' && windex > 0) windex--; if(key == ''w'' || key == ''W'') fft.window(window[windex]); if(keyCode == LEFT && scale > 1) scale--; if(keyCode == RIGHT) scale++; }

produce esto:

En el ciclo de dibujo, estoy dibujando desde el centro ya que la escala ahora es negativa. Si escala los valores, el resultado comienza a parecer aleatorio.

Actualización 6

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput in; FFT fft; float smoothing = 0; float[] fftReal; float[] fftImag; float[] fftSmooth; float[] fftPrev; float[] fftCurr; int specSize; WindowFunction[] window = {FFT.NONE,FFT.HAMMING,FFT.HANN,FFT.COSINE,FFT.TRIANGULAR,FFT.BARTLETT,FFT.BARTLETTHANN,FFT.LANCZOS,FFT.BLACKMAN,FFT.GAUSS}; String[] wlabel = {"NONE","HAMMING","HANN","COSINE","TRIANGULAR","BARTLETT","BARTLETTHANN","LANCZOS","BLACKMAN","GAUSS"}; int windex = 0; int scale = 10; void setup(){ minim = new Minim(this); in = minim.getLineIn(Minim.STEREO, 512); fft = new FFT(in.bufferSize(), in.sampleRate()); fft.window(window[windex]); specSize = fft.specSize(); fftSmooth = new float[specSize]; fftPrev = new float[specSize]; fftCurr = new float[specSize]; size(specSize, specSize/2); colorMode(HSB,specSize,100,100); } void draw(){ background(0); stroke(255); fft.forward( in.mix); fftReal = fft.getSpectrumReal(); fftImag = fft.getSpectrumImaginary(); for(int i = 0; i < specSize; i++) { fftCurr[i] = scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); fftSmooth[i] = smoothing * fftSmooth[i] + ((1 - smoothing) * fftCurr[i]); stroke(i,100,100); line( i, height/2, i, height/2 - (mousePressed ? fftSmooth[i] : fftCurr[i])); } text("smoothing: " + (int)(smoothing*100)+"/nwindow:"+wlabel[windex]+"/nscale:"+scale,10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; if(key == ''W'' && windex < window.length-1) windex++; if(key == ''w'' && windex > 0) windex--; if(key == ''w'' || key == ''W'') fft.window(window[windex]); if(keyCode == LEFT && scale > 1) scale--; if(keyCode == RIGHT) scale++; if(key == ''s'') saveFrame("fftmod.png"); }

Esto se siente tan cerca:

Esto se ve mucho mejor que la versión anterior, pero algunos valores en el lado inferior / izquierdo del espectro se ven un poco apagados y la forma parece cambiar muy rápido. (Los valores suavizados trazan ceros)


No estoy muy seguro de qué tipo de suavizado quieres hacer, pero intentaré proporcionarte alguna información que pueda ayudarte.

Escalado de resultados FFT para la pantalla

Generalmente, cuando toma la transformada de Fourier y desea mostrar un gráfico de la misma , necesita (como usted menciona) escalarla logarítmicamente. Esto se debe a que la magnitud de los valores variará en un amplio rango, muchos órdenes de magnitud, y al comprimir esto en el pequeño espacio observable en un gráfico, los picos principales harán que el resto de la información empequeñezca.

Para hacer realmente esta escala, convertimos los valores a decibelios. Es importante tener en cuenta que los decibelios son una escala y no una unidad; representan una relación entre dos números: por lo general, un valor medido y alguna referencia. La fórmula general para decibelios es

x_dB = 10 * log10((x ^ 2) / (ref ^ 2))

donde log10 es logaritmo en base 10, ^ es el operador de energía y x_ref es su valor de referencia elegido. Como los valores FFT de un archivo de audio no tienen (normalmente) unidades significativas, se suele elegir x_ref para que sea simplemente 1 para esta aplicación. Además, como x es complejo, debes tomar el valor absoluto. Así que la fórmula será

x_dB = 10 * log10(abs(x) ^ 2)

Aquí es posible una pequeña optimización (numérica y de velocidad), ya que estás cuadrando el resultado de una raíz cuadrada:

x_dB = 10 * log10(real(x) ^ 2 + imag(x) ^ 2)

Ponderación perceptiva

La escala de las mediciones en el dominio de la frecuencia se realiza comúnmente al medir la presión del sonido y los niveles de potencia: se elige un tipo de medición específico para la aplicación dada (no entraré en los tipos aquí), y se realiza una grabación del sonido de acuerdo con esta medición tipo. El resultado es FFT y luego se multiplica por una ponderación dada en cada frecuencia, dependiendo de para qué se usará el resultado y qué tipo de sonido se ha grabado. Existen dos ponderaciones de uso común: A y C. C se usa generalmente solo para sonidos de amplitud extremadamente alta.

Tenga en cuenta que este tipo de ponderación no es realmente necesario si solo desea mostrar un gráfico atractivo: se utiliza para asegurarse de que todos en el mundo puedan realizar mediciones (y equipos de medición) que sigan el mismo estándar. Si decide incluir esto, debe realizarse como una multiplicación antes de la conversión a decibeles (o como una adición del valor de decibelios de la ponderación, que es matemáticamente equivalente).

La información sobre la ponderación A está en wikipedia .

Windowing

La rotación se realiza principalmente para reducir el efecto del fenómeno de Gibbs . Nunca podemos deshacernos de él por completo, pero las ventanas ayudan. Desafortunadamente, tiene otros efectos: los picos afilados se amplían y se introducen "lóbulos laterales"; siempre hay un compromiso entre la nitidez máxima y la altura del lóbulo lateral. No voy a entrar en todos los detalles aquí a menos que usted lo solicite específicamente; Hay una explicación bastante larga de las ventanas en este libro en línea gratuito .

Suavizado en el dominio del tiempo de los intervalos de frecuencia individuales

En cuanto a hacer que la línea en cada intervalo de frecuencia decaiga lentamente, aquí hay una idea simple que podría hacer el truco: en cada intervalo de frecuencia, aplique un promedio móvil exponencial simple. Digamos que los resultados de FFT se almacenan en X[k] , donde k es el índice de frecuencia. Deje que su valor de visualización sea Y[k] tal que

Y[k] = alpha * Y_(t-1)[k] + (1 - alpha) * X[k]

donde 0 < alpha < 1 es su factor de suavizado, e Y_(t-1)[k] es el valor de Y[k] en el último paso de tiempo ( t-1 ). Este es en realidad un simple filtro IIR (respuesta de impulso infinito) de paso bajo, y con suerte debería hacer básicamente lo que quieras (quizás con un poco de ajuste). Cuanto más cercano sea el alfa a cero, más rápidamente las nuevas observaciones (entrada X[k] ) afectarán el resultado. Cuanto más cerca esté de uno, más lentamente disminuirá el resultado, pero la entrada también afectará el resultado más lentamente, por lo que puede aparecer "lento". Es posible que desee agregar un condicional alrededor de él para tomar el nuevo valor inmediatamente si es más alto que el valor actual.

Tenga en cuenta que, una vez más, esto debe realizarse antes de la conversión a decibeles.

(Editar) Habiendo visto el código que publicaste un poco más claro, este parece ser el método utilizado en el ejemplo que estás intentando reproducir. Su intento inicial fue cercano, pero tenga en cuenta que el primer término es el coeficiente de suavizado multiplicado por el último valor de visualización , no la entrada actual.

(edición 2) Su tercera actualización está, de nuevo, cerrada, pero hay una ligera traducción errónea de la fórmula en las siguientes líneas

fftSmooth[i] = smoothing * fftPrev[i] + ((1 - smoothing) * fftCurr[i]); fftPrev[i] = fftCurr[i];//

En lugar del valor anterior de los coeficientes de FFT antes de suavizar, desea tomar el valor después de suavizar. (tenga en cuenta que esto significa que en realidad no necesita otra matriz para almacenar el valor anterior)

fftSmooth[i] = smoothing * fftSmooth[i] + ((1 - smoothing) * fftCurr[i]);

Si el smoothing == 0 , esta línea debe tener poco efecto aparte de multiplicar el resultado por un escalar.

Normalización en el cálculo del valor absoluto.

Al observar más de cerca la forma en que calculan el valor absoluto, tienen una normalización allí, de modo que cualquiera de los dos valores complejos es el máximo, se convierte en 1, y el otro se escala en consecuencia. Esto significa que siempre obtendrá un valor absoluto entre 0 y 1, y es probablemente su alternativa a la conversión de decibeles. Realmente, esto no es exactamente lo que sugiere la documentación de su función abs , lo cual es un poco molesto ... pero de todos modos, si lo reproduce, garantizará que sus valores estén siempre en un rango razonable.

Para hacer esto simplemente en tu código, podrías hacer algo como

float maxVal = Math.max(Math.abs(fftReal[i]), Math.abs(fftImag[i])); if (maxVal != 0.0f) { // prevent divide-by-zero // Normalize fftReal[i] = fftReal[i] / maxVal; fftImag[i] = fftImag[i] / maxVal; } fftCurr[i] = scale * (float)Math.log10(fftReal[i]*fftReal[i] + fftImag[i]*fftImag[i]); // ...

Juntándolo todo: Algún código

Después de haberlo intentado durante un tiempo en Processing 2.1, tengo una solución con la que creo que estarás contento con:

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; //AudioInput in; AudioPlayer in; FFT fft; float smoothing = 0.60; final boolean useDB = true; final int minBandwidthPerOctave = 200; final int bandsPerOctave = 10; float[] fftSmooth; int avgSize; float minVal = 0.0; float maxVal = 0.0; boolean firstMinDone = false; void setup(){ minim = new Minim(this); //in = minim.getLineIn(Minim.STEREO, 512); in = minim.loadFile("C://path//to//some//audio//file.ext", 2048); in.loop(); fft = new FFT(in.bufferSize(), in.sampleRate()); // Use logarithmically-spaced averaging fft.logAverages(minBandwidthPerOctave, bandsPerOctave); avgSize = fft.avgSize(); fftSmooth = new float[avgSize]; int myWidth = 500; int myHeight = 250; size(myWidth, myHeight); colorMode(HSB,avgSize,100,100); } float dB(float x) { if (x == 0) { return 0; } else { return 10 * (float)Math.log10(x); } } void draw(){ background(0); stroke(255); fft.forward( in.mix); final int weight = width / avgSize; final float maxHeight = (height / 2) * 0.75; for (int i = 0; i < avgSize; i++) { // Get spectrum value (using dB conversion or not, as desired) float fftCurr; if (useDB) { fftCurr = dB(fft.getAvg(i)); } else { fftCurr = fft.getAvg(i); } // Smooth using exponential moving average fftSmooth[i] = (smoothing) * fftSmooth[i] + ((1 - smoothing) * fftCurr); // Find max and min values ever displayed across whole spectrum if (fftSmooth[i] > maxVal) { maxVal = fftSmooth[i]; } if (!firstMinDone || (fftSmooth[i] < minVal)) { minVal = fftSmooth[i]; } } // Calculate the total range of smoothed spectrum; this will be used to scale all values to range 0...1 final float range = maxVal - minVal; final float scaleFactor = range + 0.00001; // avoid div. by zero for(int i = 0; i < avgSize; i++) { stroke(i,100,100); strokeWeight(weight); // Y-coord of display line; fftSmooth is scaled to range 0...1; this is then multiplied by maxHeight // to make it within display port range float fftSmoothDisplay = maxHeight * ((fftSmooth[i] - minVal) / scaleFactor); // X-coord of display line float x = i * weight; line(x, height / 2, x, height / 2 - fftSmoothDisplay); } text("smoothing: " + (int)(smoothing*100)+"/n",10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; }

Lo anterior utiliza un enfoque ligeramente diferente (promediando el espectro en una serie de contenedores que es más pequeño que el tamaño total del espectro) que produce un resultado más cercano a los WMP que al original.

Mejora: Ahora con una ponderación A

Tengo una versión actualizada del código que aplica la ponderación A en cada banda de frecuencia (aunque solo cuando el modo dB está activado, porque la tabla que tenía estaba en dB :). Active la ponderación A para un resultado más cercano a los WMP, o desactívela para uno más cercano a los VLC.

También hay algunos ajustes menores en la forma en que se muestra: ahora está centrada en la pantalla y mostrará solo hasta una frecuencia central de banda máxima.

Aquí está el código - ¡disfruta!

import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; //AudioInput in; AudioPlayer in; FFT fft; float smoothing = 0.73; final boolean useDB = true; final boolean useAWeighting = true; // only used in dB mode, because the table I found was in dB final boolean resetBoundsAtEachStep = false; final float maxViewportUsage = 0.85; final int minBandwidthPerOctave = 200; final int bandsPerOctave = 10; final float maxCentreFrequency = 18000; float[] fftSmooth; int avgSize; float minVal = 0.0; float maxVal = 0.0; boolean firstMinDone = false; final float[] aWeightFrequency = { 10, 12.5, 16, 20, 25, 31.5, 40, 50, 63, 80, 100, 125, 160, 200, 250, 315, 400, 500, 630, 800, 1000, 1250, 1600, 2000, 2500, 3150, 4000, 5000, 6300, 8000, 10000, 12500, 16000, 20000 }; final float[] aWeightDecibels = { -70.4, -63.4, -56.7, -50.5, -44.7, -39.4, -34.6, -30.2, -26.2, -22.5, -19.1, -16.1, -13.4, -10.9, -8.6, -6.6, -4.8, -3.2, -1.9, -0.8, 0.0, 0.6, 1.0, 1.2, 1.3, 1.2, 1.0, 0.5, -0.1, -1.1, -2.5, -4.3, -6.6, -9.3 }; float[] aWeightDBAtBandCentreFreqs; void setup(){ minim = new Minim(this); //in = minim.getLineIn(Minim.STEREO, 512); in = minim.loadFile("D://Music//Arthur Brown//The Crazy World Of Arthur Brown//1-09 Fire.mp3", 2048); in.loop(); fft = new FFT(in.bufferSize(), in.sampleRate()); // Use logarithmically-spaced averaging fft.logAverages(minBandwidthPerOctave, bandsPerOctave); aWeightDBAtBandCentreFreqs = calculateAWeightingDBForFFTAverages(fft); avgSize = fft.avgSize(); // Only use freqs up to maxCentreFrequency - ones above this may have // values too small that will skew our range calculation for all time while (fft.getAverageCenterFrequency(avgSize-1) > maxCentreFrequency) { avgSize--; } fftSmooth = new float[avgSize]; int myWidth = 500; int myHeight = 250; size(myWidth, myHeight); colorMode(HSB,avgSize,100,100); } float[] calculateAWeightingDBForFFTAverages(FFT fft) { float[] result = new float[fft.avgSize()]; for (int i = 0; i < result.length; i++) { result[i] = calculateAWeightingDBAtFrequency(fft.getAverageCenterFrequency(i)); } return result; } float calculateAWeightingDBAtFrequency(float frequency) { return linterp(aWeightFrequency, aWeightDecibels, frequency); } float dB(float x) { if (x == 0) { return 0; } else { return 10 * (float)Math.log10(x); } } float linterp(float[] x, float[] y, float xx) { assert(x.length > 1); assert(x.length == y.length); float result = 0.0; boolean found = false; if (x[0] > xx) { result = y[0]; found = true; } if (!found) { for (int i = 1; i < x.length; i++) { if (x[i] > xx) { result = y[i-1] + ((xx - x[i-1]) / (x[i] - x[i-1])) * (y[i] - y[i-1]); found = true; break; } } } if (!found) { result = y[y.length-1]; } return result; } void draw(){ background(0); stroke(255); fft.forward( in.mix); final int weight = width / avgSize; final float maxHeight = height * maxViewportUsage; final float xOffset = weight / 2 + (width - avgSize * weight) / 2; if (resetBoundsAtEachStep) { minVal = 0.0; maxVal = 0.0; firstMinDone = false; } for (int i = 0; i < avgSize; i++) { // Get spectrum value (using dB conversion or not, as desired) float fftCurr; if (useDB) { fftCurr = dB(fft.getAvg(i)); if (useAWeighting) { fftCurr += aWeightDBAtBandCentreFreqs[i]; } } else { fftCurr = fft.getAvg(i); } // Smooth using exponential moving average fftSmooth[i] = (smoothing) * fftSmooth[i] + ((1 - smoothing) * fftCurr); // Find max and min values ever displayed across whole spectrum if (fftSmooth[i] > maxVal) { maxVal = fftSmooth[i]; } if (!firstMinDone || (fftSmooth[i] < minVal)) { minVal = fftSmooth[i]; } } // Calculate the total range of smoothed spectrum; this will be used to scale all values to range 0...1 final float range = maxVal - minVal; final float scaleFactor = range + 0.00001; // avoid div. by zero for(int i = 0; i < avgSize; i++) { stroke(i,100,100); strokeWeight(weight); // Y-coord of display line; fftSmooth is scaled to range 0...1; this is then multiplied by maxHeight // to make it within display port range float fftSmoothDisplay = maxHeight * ((fftSmooth[i] - minVal) / scaleFactor); // Artificially impose a minimum of zero (this is mathematically bogus, but whatever) fftSmoothDisplay = max(0.0, fftSmoothDisplay); // X-coord of display line float x = xOffset + i * weight; line(x, height, x, height - fftSmoothDisplay); } text("smoothing: " + (int)(smoothing*100)+"/n",10,10); } void keyPressed(){ float inc = 0.01; if(keyCode == UP && smoothing < 1-inc) smoothing += inc; if(keyCode == DOWN && smoothing > inc) smoothing -= inc; }