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; };