Я смотрел эту демонстрацию веб-аудио API, часть эта хорошая книга
Если вы посмотрите на демонстрацию, пики fft падают плавно. Я пытаюсь сделать то же самое с обработкой в режиме Java, используя библиотеку minim. Я посмотрел, как это делается с помощью веб-аудио api в doFFTAnalysis() и попытался реплицировать это с помощью minim. Я также попытался передать, как abs() работает со сложным типом:
/ 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
В настоящее время я делаю быстрый прототип с использованием Processing (java framework/library). Мой код выглядит следующим образом:
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);
}
Я не получаю ошибок, что хорошо, но я не вижу ожидаемого поведения, которое плохо. Я ожидал, что пики будут падать медленнее, если сглаживание (k) близко к 1, но насколько я могу сказать только мой код масштабирует полосы.
К сожалению, математика и звук - это не моя сильная сторона, поэтому я ношу в темноте. Как я могу воспроизвести приятную визуализацию из демонстрации веб-аудио API?
У меня возникнет соблазн сказать, что это может быть языковым агностиком, но использование javascript, например, не применимо:). Тем не менее, я рад попробовать любую другую библиотеку java, которая выполняет анализ FFT.
UPDATE
У меня есть простое решение для сглаживания (непрерывно уменьшайте значения каждого предыдущего fft-диапазона, если текущий fft-диапазон не выше:
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;
}
Увядший граф - сглаженный, а полностью насыщенный - живой.
Тем не менее, я все еще что-то пропущу, по сравнению с демонстрацией веб-аудио API:
Я думаю, что API веб-аудио может принимать во внимание, что средние и более высокие частоты необходимо масштабировать, чтобы быть ближе к тому, что мы воспринимаем, но я не уверен, как это решить.
Я пытался больше узнать о том, как класс RealtimeAnalyser делает это для WebAudioAPI, но кажется класс FFTFrame doFFT
метод может сделать логарифмическое масштабирование. Я еще не понял, как работает makeFFT.
Как я могу масштабировать необработанный график FFT с логарифмической шкалой для учета восприятия? Моя цель - сделать приличную визуализацию, и я предполагаю, что мне нужно:
- плавные значения, в противном случае элементы ожидают быстрый /twitchy
- шкала Бункер/полосы FFT для получения лучших данных для средних/высоких частот
- map обрабатывает значения FFT для визуальных элементов (найдите максимальные значения/границы)
Любые подсказки о том, как я могу это достичь?
ОБНОВЛЕНИЕ 2
Я предполагаю, что эта часть выполняет сглаживание и масштабирование, которое я получаю в API веб-аудио: // Нормализуем так, как входная синусоидальная волна при 0dBfs регистрируется как 0dBfs (отменить коэффициент масштабирования FFT). const double величинаScale = 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);
}
Кажется, что масштабирование выполняется путем принятия абсолютного значения комплексного значения. Этот пост указывает в том же направлении. Я попытался использовать абс комплексного числа с помощью Minim и использовать различные функции окна, но он по-прежнему не похож на то, к чему я стремлюсь (Web Audio API):
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]);
}
Я не уверен, что правильно использую функции окна, потому что не вижу огромной разницы между ними. Правильно ли значение абс комплексного значения? Как я могу получить визуализацию ближе к моей цели?
ОБНОВЛЕНИЕ 3
Я попытался применить полезные советы @wakjah, например:
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++;
}
Я не уверен, что я применил подсказки по своему усмотрению. Вот как выглядит мой вывод:
но я не думаю, что я еще там, если я сравню это с визуализацией, к которой я стремился:
в медиаплеере Windows
в проигрывателе VLC
Я не уверен, что я правильно применил масштаб журнала. Мои предположения состояли в том, что я бы сделал сюжет, похожий на то, к чему я стремился после использования log10 (игнорируя сглаживание на данный момент).
ОБНОВЛЕНИЕ 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++;
}
производит следующее:
В цикле рисования я рисую из центра, так как масштаб теперь отрицательный. Если я масштабирую значения, результат начинает выглядеть случайным.
UPDATE6
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");
}
Это так близко:
Это выглядит намного лучше, чем в предыдущей версии, но некоторые значения в нижней/левой части спектра выглядят немного, и форма кажется очень быстрой. (сглаженные значения имеют нулевые значения)