Spectrum Analyser
Key Features:
- Done with JUCE framework by using filter processor duplicator (IIR filter, juce::dsp::ProcessorDuplicator)
- Balanced log-linear spectrum, audio waveform, peak and RMS level meters
- Volume and low-high-band pass filters (with 0.707 Q value) for simple modification test purposes
- Mouse draggable playing position bar on audio waveform window
- Gradual spacing increase at high frequencies on 99 bands spectrum
- Smooth levelling applied to spectrum and peak-RMS level meters
- Mostly fixed hard-coded GUI style for components
Improvement Areas:
- General refactoring to reduce the complexity
- Higher resolution in low frequencies might be preference
- DSP setup structure should be improved (not optimised)
- Stereo correlation might be added
- Some tweaks for dB level measurements
Download Link:
- SpectrumAnalyserTest.zip (zipped)
- Only DSP code published here to keep simple
References:
- JUCE Tutorial: Visualise the frequencies of a signal in real time
- Drawing Audio Thumbnail
- Drawing Level Meters

DSPSetup.h
#pragma once
#include "SpectrumComponent.h"
#include "LevelMeterComponent.h"
//==============================================================================
struct DSPProcessBase
{
void prepare(const juce::dsp::ProcessSpec& spec)
{
jassert(spec.sampleRate > 0.0f && spec.sampleRate * 0.5f > 20000.0f);
sampleRate = spec.sampleRate;
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeLowPass(sampleRate, 20000.0f, Q);
filterProcessor.prepare(spec);
gainProcessor.prepare(spec);
gainProcessor.setGainLinear(1.0f);
}
void process(const juce::dsp::ProcessContextReplacing<float>& context)
{
juce::ScopedNoDenormals noDenormals;
filterProcessor.process(context);
gainProcessor.process(context);
}
void reset()
{
filterProcessor.reset();
gainProcessor.reset();
}
//==============================================================================
enum FilterType
{
LowPass = 1,
HighPass = 2,
BandPass = 3
};
void updateFilterType(const int type)
{
switch (type)
{
case LowPass:
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeLowPass(sampleRate, filterCutOff, Q);
filterType = FilterType::LowPass;
break;
case HighPass:
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeHighPass(sampleRate, filterCutOff, Q);
filterType = FilterType::HighPass;
break;
case BandPass:
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeBandPass(sampleRate, filterCutOff, Q);
filterType = FilterType::BandPass;
break;
default:
break;
}
}
void updateFilterValue(const float cutoffValue)
{
if (cutoffValue < 1.0f) return;
filterCutOff = cutoffValue;
switch (filterType)
{
case LowPass:
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeLowPass(sampleRate, filterCutOff, Q);
break;
case HighPass:
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeHighPass(sampleRate, filterCutOff, Q);
break;
case BandPass:
*filterProcessor.state = juce::dsp::IIR::ArrayCoefficients<float>::makeBandPass(sampleRate, filterCutOff, Q);
break;
default:
break;
}
}
void updateGain(const float volumeLevel)
{
gainProcessor.setGainLinear(juce::jlimit(0.0f, 1.0f, volumeLevel));
}
//==============================================================================
private:
// IIR::Filter chosen as a mono processor type and process would be in the "float" numeric domain
juce::dsp::ProcessorDuplicator<juce::dsp::IIR::Filter<float>, juce::dsp::IIR::Coefficients<float>> filterProcessor;
juce::dsp::Gain<float> gainProcessor;
FilterType filterType = FilterType::LowPass;
const float Q = 0.707f;
int filterCutOff = 20000;
float sampleRate = 0.0f;
};
//==============================================================================
struct DSPAudioBlocks final : public juce::AudioSource,
public juce::dsp::ProcessorWrapper<DSPProcessBase>
{
DSPAudioBlocks(AudioSource& input) : inputSource(&input)
{
}
void prepareToPlay(int blockSize, double sampleRate) override
{
inputSource->prepareToPlay(blockSize, sampleRate);
this->prepare({ sampleRate, (juce::uint32)blockSize, 2 });
//==============================================================================
// SPECTRUM SETUP
spectrum.CalculateFFTIndices(sampleRate);
spectrum.setSmoothingLevel(sampleRate, 0.1f);
//==============================================================================
//==============================================================================
// LEVEL METERS SETUP
leftPeakMeter.setSmoothingLevel(sampleRate, 0.1f, -100.0f);
rightPeakMeter.setSmoothingLevel(sampleRate, 0.1f, -100.0f);
leftRMSMeter.setSmoothingLevel(sampleRate, 0.5f, -100.0f);
rightRMSMeter.setSmoothingLevel(sampleRate, 0.5f, -100.0f);
//==============================================================================
}
void releaseResources() override
{
inputSource->releaseResources();
}
void getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) override
{
if (bufferToFill.buffer == nullptr)
{
jassertfalse;
return;
}
inputSource->getNextAudioBlock(bufferToFill);
juce::dsp::AudioBlock<float> block(*bufferToFill.buffer, (size_t)bufferToFill.startSample);
juce::ScopedLock audioLock(audioCallbackLock);
this->process(juce::dsp::ProcessContextReplacing<float>(block));
//==============================================================================
// FOR SPECTRUM ANALYSER
spectrum.getNextAudioBlock(bufferToFill); // For modified data
//==============================================================================
//==============================================================================
// FOR LEVEL METERS
leftPeakMeter.setLevelMeter(bufferToFill, LevelMeterComponent::Peak_Level, LevelMeterComponent::Left);
rightPeakMeter.setLevelMeter(bufferToFill, LevelMeterComponent::Peak_Level, LevelMeterComponent::Right);
leftRMSMeter.setLevelMeter(bufferToFill, LevelMeterComponent::RMS_Level, LevelMeterComponent::Left);
rightRMSMeter.setLevelMeter(bufferToFill, LevelMeterComponent::RMS_Level, LevelMeterComponent::Right);
//==============================================================================
}
void updateFilterType(const int filterType)
{
juce::ScopedLock audioLock(audioCallbackLock);
this->processor.updateFilterType(filterType);
}
void updateFilterValue(const float cutoffValue)
{
juce::ScopedLock audioLock(audioCallbackLock);
this->processor.updateFilterValue(cutoffValue);
}
void updateGain(const float volumeLevel)
{
juce::ScopedLock audioLock(audioCallbackLock);
this->processor.updateGain(volumeLevel);
}
SpectrumComponent spectrum;
LevelMeterComponent leftPeakMeter, rightPeakMeter, leftRMSMeter, rightRMSMeter;
private:
juce::CriticalSection audioCallbackLock;
juce::AudioSource* inputSource;
};