Hi,
well i’m not sure if this is really a mistake because i’m still a beginner, but i found something perculiar. So i was finishing the fft tutorial and transfered the code into a plugin preset to see how it performs in my daw. looked pretty fine to me first, at least visually. but then i tried to get the fundamental frequency from a monophonic audio source by doing this:
const float& getFundamentalFrequency() {
float index = 0.f;
float max = 0.f;
float absSample;
for (auto i = 0; i < m_sizeHalf; ++i) {
absSample = abs(m_fftData[i]);
if (max < absSample) {
max = absSample;
index = i;
}
}
return m_sampleRateSizeMaxDiv * index;
}
(btw m_sizeHalf is half of the size of fftData and m_sampleRateSizeMaxDiv = 1 / sampleRate * (1 / (size - 1)))
however this worked pretty fine and i could hear that it more or less grabbed the right frequency when asigned that to an oscillator. but it also had a lot of other noises in the highend especially and no matter what kinda smoothing i used on the frequency it just wasn’t going to get a lot better. so what i did then was moving the line:
m_forwardFFT.performFrequencyOnlyForwardTransform(m_fftData.data());
from the beginning of the method drawNextLineOfSpectrogram() into the method pushNextSampleIntoFifo() instantly after the lines with memset and memcpy and voila, it creates a perfect representation of the fundamental frequency on my oscillator.
i’m guessing that when this line is in the drawNextLine… method it will try to calculate the forward-fft on an array that already has been transformed. the visuals also became a bit better then btw.
so just for reference this is the code that i then ended up with:
class HDLFFTVisual {
public:
enum WindowType {
NoWindowing,
LinearType,
SineType
};
HDLFFTVisual(int order = 10, int imageWidth = 512, int imageHeight = 512, int windowType = SineType) :
m_order(order),
m_size(1 << m_order),
m_size2(2 * m_size),
m_sizeHalf(m_size / 2),
m_sizeHalfF(float(m_sizeHalf)),
m_sizeDiv(1.f / float(m_size)),
m_sizeMaxDiv(1.f / float(m_size - 1)),
m_sizeMaxDivPi(m_sizeMaxDiv * MathConstants<float>::pi),
m_imageWidth(imageWidth),
m_imageWidthMax(imageWidth - 1),
m_imageHeight(imageHeight),
m_imageHeightF(float(imageHeight)),
m_imageHeightDiv(1.f / m_imageHeightF),
m_imageHeightMaxDiv(1.f / (m_imageHeightF - 1.f)),
m_image(Image::ARGB, imageWidth, imageHeight, true),
m_fifo(m_size),
m_fftData(m_size2),
m_fifoIdx(0),
m_blockReady(false),
m_forwardFFT(order),
m_windowType(windowType),
m_sampleRate(1.f),
m_sampleRateSizeMaxDiv(1.f)
{
}
// in prepareToPlay
void setSampleRate(const double& sampleRate) {
m_sampleRate = float(sampleRate);
m_sampleRateSizeMaxDiv = m_sampleRate * m_sizeMaxDiv;
}
// in resized (optional)
void setImageBounds(Rectangle<int> bounds) { setImageBounds(bounds.getWidth(), bounds.getHeight()); }
void setImageBounds(int width, int height) {
m_imageWidth = width;
m_imageWidthMax = width - 1;
m_imageHeight = height;
m_imageHeightF = float(height);
m_imageHeightDiv = 1.f / m_imageHeightF;
m_imageHeightMaxDiv = 1.f / (m_imageHeightF - 1.f);
m_image = Image(Image::ARGB, width, height, true);
}
// in paint
void draw(Graphics& g, Rectangle<float> bounds){ g.drawImage(m_image, bounds); }
// get sample from processBlock
void pushSampleIntoFifo(const float& sample) {
if (m_fifoIdx == m_size) {
if (!m_blockReady) {
std::fill(std::next(m_fftData.begin(), m_size), m_fftData.end(), 0.f); // this clears shit
std::copy(m_fifo.begin(), m_fifo.end(), m_fftData.begin()); // this copies shit (apparently^^)
m_forwardFFT.performFrequencyOnlyForwardTransform(m_fftData.data());
m_blockReady = true;
}
m_fifoIdx = 0;
}
float windowAmp;
switch (m_windowType) {
case LinearType:
windowAmp = m_fifoIdx < m_sizeHalf ?
float(2 * m_fifoIdx) * m_sizeMaxDiv :
2.f - float(2 * m_fifoIdx) * m_sizeMaxDiv;
break;
case SineType:
windowAmp = std::sin(float(m_fifoIdx) * m_sizeMaxDivPi);
break;
default:
// No Windowing
windowAmp = 1.f;
break;
}
m_fifo[m_fifoIdx] = sample * windowAmp;
++m_fifoIdx;
}
// returns frequency where magnitude is max
const float& getFundamentalFrequency() {
float index = 0.f;
float max = 0.f;
float absSample;
for (auto i = 0; i < m_sizeHalf; ++i) {
absSample = abs(m_fftData[i]);
if (max < absSample) {
max = absSample;
index = i;
}
}
return m_sampleRateSizeMaxDiv * index;
}
// returns true if repaint() should be called from timerCallback() in editor
bool shouldRepaint() {
if (m_blockReady) {
m_blockReady = false;
drawSpectogram();
return true;
}
return false;
}
private:
void drawSpectogram() {
m_image.moveImageSection(0, 0, 1, 0, m_imageWidthMax, m_imageHeight);
auto maxLevel = FloatVectorOperations::findMinAndMax(m_fftData.data(), m_sizeHalf);
for (auto y = 1; y < m_imageHeight; ++y) {
auto yF = float(y);
auto skewedProportionY = 1.f - std::exp(std::log(yF * m_imageHeightDiv) * .2f);
auto fftDataIndex = int(jlimit(0.f, m_sizeHalfF, skewedProportionY * m_sizeHalfF));
auto level = jmap(m_fftData[fftDataIndex], 0.f, jmax(maxLevel.getEnd(), 1e-5f), 0.f, 1.f);
m_image.setPixelAt(m_imageWidthMax, y, Colour::fromHSV(level, 1.f, level, 1.f));
}
}
int m_order, m_size, m_size2, m_sizeHalf;
float m_sizeHalfF, m_sizeDiv, m_sizeMaxDiv, m_sizeMaxDivPi;
int m_imageWidth, m_imageWidthMax, m_imageHeight;
float m_imageHeightF, m_imageHeightDiv, m_imageHeightMaxDiv;
Image m_image;
std::vector<float> m_fifo, m_fftData;
int m_fifoIdx;
bool m_blockReady;
dsp::FFT m_forwardFFT;
int m_windowType;
float m_sampleRate, m_sampleRateSizeMaxDiv;
};