class AnalyzerComponent : public juce::Component,
private juce::Timer
{
public:
AnalyzerComponent(SIGMusicFFTDemoAudioProcessor& ap_) : ap(ap_),
forwardFFT(ap.fftOrder),
window(ap.fftSize, juce::dsp::WindowingFunction<float>::hann)
{
// 初始化平滑的数组
smoothedScopeData.assign(ap.scopeSize, 0.0f);
previousFrame.assign(ap.scopeSize, 0.0f);
setOpaque(true);
startTimerHz(24);
}
~AnalyzerComponent() override {}
void timerCallback() override
{
if (ap.nextFFTBlockReady)
{
drawNextFrameOfSpectrum();
ap.nextFFTBlockReady = false;
repaint();
}
}
void paint(juce::Graphics& g) override
{
g.fillAll(juce::Colours::black);
g.setOpacity(1.0f);
g.setColour(juce::Colours::white);
drawFrame(g);
}
float aWeighting(float freq)
{
const float c1 = 12194.217f;
const float c2 = 20.598997f;
const float c3 = 107.65265f;
const float c4 = 737.86223f;
float f = freq * freq;
float num = c1 * c1 * (f * f);
float den = (f + c2 * c2) * std::sqrt((f + c3 * c3) * (f + c4 * c4)) * (f + c1 * c1);
float aWeighted = 1.2589f * num / den;
aWeighted = std::max(0.0f, 2.0f + 20.0f * std::log10(aWeighted));
return aWeighted;
}
void drawNextFrameOfSpectrum()
{
window.multiplyWithWindowingTable(ap.fftData, ap.fftSize);
forwardFFT.performFrequencyOnlyForwardTransform(ap.fftData);
auto mindB = -100.0f;
auto maxdB = 0.0f;
float sampleRate = ap.getSampleRate();
auto frequencyResolution = sampleRate / ap.fftSize;
// 使用对数刻度选择绘图点
std::vector<float> logFreqBins;
float minLogFreq = std::log10(10.0f); // 20 Hz
float maxLogFreq = std::log10(21000.0f); // 20 kHz
float logFreqRange = maxLogFreq - minLogFreq;
int numPoints = ap.scopeSize; // 选择绘图点的数量
// 在对数尺度上生成你想绘制的频率点
for (int i = 0; i < numPoints; ++i)
{
float logFreq = minLogFreq + logFreqRange * (static_cast<float>(i) / (numPoints - 1));
logFreqBins.push_back(std::pow(10.0f, logFreq));
}
for (size_t i = 0; i < ap.scopeSize; ++i)
{
int fftDataIndex = static_cast<int>(logFreqBins[i] / frequencyResolution);
fftDataIndex = std::min(fftDataIndex, ap.fftSize / 2 - 1);
// 获取FFT bin的线性幅度值 (未转换为分贝)
float magnitude = ap.fftData[fftDataIndex];
// 计算并应用A-weighting调整
float aWeightingFactor = std::pow(10.0f, aWeighting(logFreqBins[i]) / 20.0f);
magnitude *= aWeightingFactor;
// 将加权的线性幅度转换为分贝
auto levelDb = juce::Decibels::gainToDecibels(magnitude * 0.0009f, mindB);
// 映射到0-1范围
auto level = juce::jmap(juce::jlimit(mindB, 0.0f, levelDb), mindB, 0.0f, 0.0f, 1.0f);
// 将结果保存在平滑过的数据中
smoothedScopeData[i] = previousFrame[i] * previousWeight + level * currentWeight;
}
previousFrame = smoothedScopeData; // 更新 previousFrame 为当前平滑后的数据
}
void AnalyzerComponent::drawFrame(juce::Graphics& g)
{
auto width = getBounds().getWidth();
auto height = getBounds().getHeight();
juce::Path spectrumPath;
spectrumPath.startNewSubPath(0, height);
// 插值以产生更平滑的曲线
for (int i = 0; i < ap.scopeSize - 1; ++i)
{
float x1 = juce::jmap(i, 0, ap.scopeSize - 1, 0, width);
float y1 = juce::jmap(smoothedScopeData[i], 0.0f, 1.0f, (float)height, 0.f);
// 计算下一个点的位置
float x2 = juce::jmap(i + 1, 0, ap.scopeSize - 1, 0, width);
float y2 = juce::jmap(smoothedScopeData[static_cast<std::vector<float, std::allocator<float>>::size_type>(i) + 1], 0.0f, 1.0f, (float)height, 0.f);
// 创建两点之间的二次Bezier曲线(或使用其他插值方法)
spectrumPath.quadraticTo(x1, y1, (x1 + x2) * 0.5f, (y1 + y2) * 0.5f);
}
// 画出最后一段到最后一个点
spectrumPath.lineTo(width, juce::jmap(smoothedScopeData[ap.scopeSize - 1], 0.0f, 1.0f, (float)height, 0.f));
spectrumPath.lineTo(width, height);
spectrumPath.closeSubPath();
g.setColour(juce::Colours::red);
g.setOpacity(0.3f);
g.fillPath(spectrumPath);
}
private:
std::vector<float> smoothedScopeData;
std::vector<float> previousFrame; // 用于存储上一帧的平滑数据
SIGMusicFFTDemoAudioProcessor& ap;
juce::dsp::FFT forwardFFT;
juce::dsp::WindowingFunction<float> window;
float previousWeight = 0.9f; // 上一帧的权重
float currentWeight = 0.1f; // 当前帧的权重
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(AnalyzerComponent);
};
After drawing my spectrogram, I don’t know if there are rectangular lines in low-frequency areas. How do I apply linear interpolation or linear smoothing? I’m stumped
With a logarithmic frequency scale, you’ll have very low resolution at lower frequencies and so you’re getting less than one frequency per pixel.
int fftDataIndex = static_cast<int>(logFreqBins[i] / frequencyResolution);
This will likely be returning the same index several times in a row as you iterate ap.scopeSize
since the cast will truncate.
An easy fix is to simply ignore cases where the FFT index is the same as the previous one:
int prevFftIndex = -1;
for (size_t i = 0; i < ap.scopeSize; ++i)
{
// ...
int fftDataIndex = static_cast<int>(logFreqBins[i] / frequencyResolution);
fftDataIndex = std::min(fftDataIndex, ap.fftSize / 2 - 1);
if (fftDataIndex == prevFftIndex)
continue;
prevFftIndex = fftDataIndex;
// ...
}
It’s really better now! But the low-frequency is now too sparse
You’ll need a way to not add the skipped points to the path. Maybe initialise them to -1
and only add the points to the path if they’re above 0.
it so hard…
help me master