How to access the audio stream's sampling rate in juce program?

Hi there,
I want to visualise the frequencies of a signal in real time through the FFT. The incoming audio data will be displayed as a two-dimensional spectrum analyser in the frequency (x-axis) and amplitude (y-axis) domains.When drawing the coordinates of x-axis, it seems that the sample rate of the audio is needed. The maximum frequency the signal could hold is the one half of the sample rate. The function: double AudioProcessor::getSampleRate ( ) const is ok?

Best regard!

There seems is error:

 class AnalyserComponent   : public AudioAppComponent, private Timer, public AudioProcessor
    {
    public:
        AnalyserComponent()
            : forwardFFT (fftOrder),
              window (fftSize, dsp::WindowingFunction<float>::hann)
        {
            setOpaque (true);
            setAudioChannels (2, 2);  // we want a couple of input channels but no outputs
            startTimerHz (30);
            setSize (700, 500);
        }

        ~AnalyserComponent() override
        {
            shutdownAudio();
        }

        //==============================================================================
        void prepareToPlay (int, double) override {}
        void releaseResources() override          {}

        void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
        {
            if (bufferToFill.buffer->getNumChannels() > 0)
            {
                auto* channelData = bufferToFill.buffer->getReadPointer (0, bufferToFill.startSample);

                for (auto i = 0; i < bufferToFill.numSamples; ++i)
                    pushNextSampleIntoFifo (channelData[i]);
            }
        }

        //==============================================================================
        void paint (Graphics& g) override
        {
            g.fillAll (Colours::black);

            g.setOpacity (1.0f);
            g.setColour (Colours::white);
            drawFrame (g);
    		drawCoordiante(g);
        }

        void timerCallback() override
        {
            if (nextFFTBlockReady)
            {
                drawNextFrameOfSpectrum();
                nextFFTBlockReady = false;
                repaint();
            }
        }


    	void drawCoordiante(Graphics& g)
    	{ 
    		auto area = getLocalBounds();
    		g.setFont(12.0f);
    		g.setColour(Colours::gold);
    		g.drawRoundedRectangle(area.toFloat(), 5, 5);
    		for (int i = 0; i < 5; ++i) 
    		{
    			auto freq = getFrequencyForPosition(i);
    			g.setColour(Colours::silver.withAlpha(0.3f));
    			auto x = area.getX() + area.getWidth() * freq;
    			g.drawVerticalLine(roundToInt(x), area.getY(), area.getBottom());

    			g.setColour(Colours::red);
    			g.drawFittedText((target_frequency[i]< 1000.0f) ? String(target_frequency[i]) + " Hz" : String(target_frequency[i] / 1000, 1) + " kHz",
    				roundToInt(x + 3), area.getBottom() - 18, 50, 15, Justification::left, 1);
    		}

    		g.setColour(Colours::silver.withAlpha(0.3f));
    		g.drawHorizontalLine(roundToInt(area.getY()  + 0.2 * area.getHeight()), area.getX(), area.getRight());
    		g.drawHorizontalLine(roundToInt(area.getY()  + 0.4 * area.getHeight()), area.getX(), area.getRight());
    		g.drawHorizontalLine(roundToInt(area.getY()  + 0.6 * area.getHeight()), area.getX(), area.getRight());
    		g.drawHorizontalLine(roundToInt(area.getY()  + 0.8 * area.getHeight()), area.getX(), area.getRight());

    		g.setColour(Colours::red);
    		g.drawFittedText(" 0 dB", area.getX() + 3, area.getY() + 2, 50, 14, Justification::left, 1);
    		g.drawFittedText(" -20 dB", area.getX() + 3, roundToInt(area.getY()  + 0.2 * area.getHeight()), 50, 14, Justification::left, 1);
    		g.drawFittedText(" -40 dB", area.getX() + 3, roundToInt(area.getY()  + 0.4 * area.getHeight()), 50, 14, Justification::left, 1);
    		g.drawFittedText(" -60 dB", area.getX() + 3, roundToInt(area.getY()  + 0.6 * area.getHeight()), 50, 14, Justification::left, 1);
    		g.drawFittedText(" -80 dB", area.getX() + 3, roundToInt(area.getY()  + 0.8 * area.getHeight()), 50, 14, Justification::left, 1);
    		//g.drawFittedText(" -100 dB", area.getX() + 3, roundToInt(area.getY() - 14.0 +  area.getHeight()), 50, 14, Justification::left, 1);
    		

    	}
    	//===============================================
    	float getFrequencyForPosition(int pos)
    	{
    		float SampleRate = getSampleRate();
    		float target_fre = target_frequency[pos];
    		float skewedProportionX = target_fre / SampleRate;
    		return skewedProportionX = 1.0f - std::pow(1.0- skewedProportionX,5.0);
    	}


    	//===============================================

        void pushNextSampleIntoFifo (float sample) noexcept
        {
            // if the fifo contains enough data, set a flag to say
            // that the next frame should now be rendered..
            if (fifoIndex == fftSize)
            {
                if (! nextFFTBlockReady)
                {
                    zeromem (fftData, sizeof (fftData));
                    memcpy (fftData, fifo, sizeof (fifo));
                    nextFFTBlockReady = true;
                }

                fifoIndex = 0;
            }

            fifo[fifoIndex++] = sample;
        }

        void drawNextFrameOfSpectrum()
        {
            // first apply a windowing function to our data
            window.multiplyWithWindowingTable (fftData, fftSize);

            // then render our FFT data..
            forwardFFT.performFrequencyOnlyForwardTransform (fftData);

            auto mindB = -100.0f;
            auto maxdB =    0.0f;

            for (int i = 0; i < scopeSize; ++i)
            {
                auto skewedProportionX = 1.0f - std::exp (std::log (1.0f - i / (float) scopeSize) * 0.2f);
                auto fftDataIndex = jlimit (0, fftSize / 2, (int) (skewedProportionX * fftSize / 2));
                auto level = jmap (jlimit (mindB, maxdB, Decibels::gainToDecibels (fftData[fftDataIndex])
                                                       - Decibels::gainToDecibels ((float) fftSize)),
                                   mindB, maxdB, 0.0f, 1.0f);

                scopeData[i] = level;
            }
        }

        void drawFrame (Graphics& g)
        {
            for (int i = 1; i < scopeSize; ++i)
            {
                auto width  = getLocalBounds().getWidth();
                auto height = getLocalBounds().getHeight();

                g.drawLine ({ (float) jmap (i - 1, 0, scopeSize - 1, 0, width),
                                      jmap (scopeData[i - 1], 0.0f, 1.0f, (float) height, 0.0f),
                              (float) jmap (i,     0, scopeSize - 1, 0, width),
                                      jmap (scopeData[i],     0.0f, 1.0f, (float) height, 0.0f) });
            }
        }

        enum
        {
            fftOrder  = 11,
            fftSize   = 1 << fftOrder,
            scopeSize = 512
        };
    	//=================

    	const String 	getName() override
    	{

    	}
    	void 	prepareToPlay(double sampleRate, int maximumExpectedSamplesPerBlock)override
    	{
    	}
    	//void 	releaseResources()override
    	//{
    	//}
    	void 	processBlock(AudioBuffer< float >& buffer, MidiBuffer& midiMessages)override
    	{
    	}
    	double 	getTailLengthSeconds() const override
    	{
    	}
    	bool 	acceptsMidi() const override
    	{
    	}
    	bool 	producesMidi() const override
    	{
    	}
    	AudioProcessorEditor* createEditor()override
    	{
    	}
    	bool 	hasEditor() const override
    	{
    	}
    	int 	getNumPrograms()override
    	{
    	}
    	int 	getCurrentProgram()override
    	{
    	}
    	void 	setCurrentProgram(int index) override
    	{
    	}
    	const String 	getProgramName(int index)override
    	{
    	}
    	void 	changeProgramName(int index, const String& newName)override
    	{
    	}
    	void 	getStateInformation(juce::MemoryBlock& destData)override
    	{
    	}

    	//================

    private:
        dsp::FFT forwardFFT;
        dsp::WindowingFunction<float> window;

        float fifo [fftSize];
        float fftData [2 * fftSize];
        int fifoIndex = 0;
        bool nextFFTBlockReady = false;
        float scopeData [scopeSize];
    	float target_frequency[5] = {100.0f, 500.0f, 1000.0f, 5000.0f, 10000.0f};

        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyserComponent)
    };

I refered to @daniel 's code

The returned sampling rate is zero:

class AnalyserComponent   : public AudioAppComponent, private Timer,  public AudioProcessorGraph
{
public:
    AnalyserComponent()
        : forwardFFT (fftOrder),
          window (fftSize, dsp::WindowingFunction<float>::hann)
    {
        setOpaque (true);
        setAudioChannels (2, 2);  // we want a couple of input channels but no outputs
        startTimerHz (30);
        setSize (700, 500);
		SampleRate = getSampleRate() / 2.0f;
		DBG("SampleRate ="<< getSampleRate());
    }

Don’t inherit from both AudioProcessor and AudioAppComponent, that’s not going to achieve anything useful. (Nothing is going to automatically update for example the AudioProcessor’s sample rate member. AudioAppComponent is based on AudioSource, not AudioProcessor.)

You can get the samplerate of the AudioAppComponent by using its deviceManager member which is an AudioDeviceManager. Alternatively, you get the sample rate when the prepareToPlay method is called. You can store that into a member variable for later use.

1 Like