How to connect getNextAudioBlock() to an AudioTransportSource?

Hi all, mega JUCE-and-C++ noob here with a question.

I’m trying to create a simple GUI that generates a sine wave of variable frequency and amplitude, the values of which can be changed by sliders. I want to incorporate play and stop buttons into this, but I’m unsure how to link the “AudioTransportSource” to the audio buffer in getNextAudioBlock(). The tutorials explain how to use audio transport to play .wav files, but is there a way to do this in real-time?

My goal is to create a gui that will play and plot a sine wave of user-defined frequency and amplitude, with the ability to start and stop. I haven’t done any work in the plotting phase of this yet, though. Here is the code:

Main.cpp:

/*

This file was auto-generated!

It contains the basic startup code for a JUCE application.

*/

#include "../JuceLibraryCode/JuceHeader.h"
#include "MainComponent.h"

class plotSineWavesApplication  : public JUCEApplication
{
public:
    plotSineWavesApplication() {}

    const String getApplicationName() override       { return ProjectInfo::projectName; }
    const String getApplicationVersion() override    { return ProjectInfo::versionString; }
    bool moreThanOneInstanceAllowed() override       { return true; }


    void initialise (const String& commandLine) override
    {
        // This method is where you should put your application's initialisation code..

        mainWindow = new MainWindow (getApplicationName());
    }

    void shutdown() override
    {
        // Add your application's shutdown code here..

        mainWindow = nullptr; // (deletes our window)
    }


    void systemRequestedQuit() override
    {
        // This is called when the app is being asked to quit: you can ignore this
        // request and let the app carry on running, or call quit() to allow the app to close.
        quit();
    }

    void anotherInstanceStarted (const String& commandLine) override
    {
        // When another instance of the app is launched while this one is running,
        // this method is invoked, and the commandLine parameter tells you what
        // the other instance's command-line arguments were.
    }


    /*
        This class implements the desktop window that contains an instance of
        our MainComponent class.
    */
    class MainWindow    : public DocumentWindow
    {
    public:
        MainWindow (String name)  : DocumentWindow (name,
                                                    Desktop::getInstance().getDefaultLookAndFeel()
                                                                          .findColour 
(ResizableWindow::backgroundColourId),
                                                    DocumentWindow::allButtons)
        {
            setUsingNativeTitleBar (true);
            setContentOwned (new MainComponent(), true);
            setResizable (true, true);

            centreWithSize (getWidth(), getHeight());
            setVisible (true);
        }

        void closeButtonPressed() override
        {
            // This is called when the user tries to close this window. Here, we'll just
            // ask the app to quit when this happens, but you can change this to do
            // whatever you need.
            JUCEApplication::getInstance()->systemRequestedQuit();
        }

    /* Note: Be careful if you override any DocumentWindow methods - the base
       class uses a lot of them, so by overriding you might break its functionality.
       It's best to do all your work in your content component instead, but if
       you really have to override any DocumentWindow methods, make sure your
       subclass also calls the superclass's method.
    */

    private:
        JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainWindow)
};

private:
    ScopedPointer<MainWindow> mainWindow;
};

// This macro generates the main() routine that launches the app.
START_JUCE_APPLICATION (plotSineWavesApplication)

MainComponent.h

    /*
This file was auto-generated!

*/

#pragma once

#include "../JuceLibraryCode/JuceHeader.h"

/*
    This component lives inside our window, and this is where you should put all
    your controls and content.
*/
class MainComponent   : public AudioAppComponent,
    					public Slider::Listener
{
public:

    MainComponent();
    ~MainComponent();


    void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override;
    void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override;
    void releaseResources() override;

    void paint (Graphics& g) override;
    void resized() override;

    void sliderValueChanged(Slider* slider) override
    {
    	if (slider == &frequencySlider)
	    {
		    frequency = frequencySlider.getValue();
	    }
	    else if (slider == &amplitudeSlider)
	    {
		    amplitude = amplitudeSlider.getValue();
	    }
    }

    void updateFrequency()
    {
    	    increment = frequency * waveTableSize / currentSampleRate;
	    phase = fmod(phase + increment, waveTableSize);

    }

private:

    // Your private member variables go here...
    PositionableAudioSource * tempSource;

    enum TransportState
    {
 	    Stopped,
	    Starting,
	    Stopping
    };

    TransportState state;

    void playButtonClicked();
    void stopButtonClicked();

    void transportStateChanged(TransportState newState);

    Array<float> waveTable;

    double frequency;
    double amplitude;
    double phase;
    double waveTableSize;
    double increment;
    double currentSampleRate;

    Slider frequencySlider;
    Label frequencyLabel;
    Slider phaseSlider;
    Label phaseLabel;
    Slider amplitudeSlider;
    Label amplitudeLabel;

    TextButton playButton;
    TextButton stopButton;

    AudioTransportSource transport;


    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

MainComponent.cpp:

/*

This file was auto-generated!
*/

#include "MainComponent.h"

MainComponent::MainComponent() : state(Stopped), playButton("Play"), stopButton("Stop")
{
    // Make sure you set the size of the component after
    // you add any child components.
    setSize (800, 600);

    // specify the number of input and output channels that we want to open
    setAudioChannels (2, 2);

    /* Frequency Slider Information ===========================================================*/

    // Add slider and make it visible
    addAndMakeVisible(frequencySlider);

    // Frequency value initialization
    frequencySlider.setRange(20.0, 20000.0);
    frequencySlider.setValue(440.0);
    frequencySlider.setSkewFactorFromMidPoint(440);

    // Set frequency slider listener
    frequencySlider.addListener(this);

    // Text box
    frequencySlider.setTextBoxStyle(Slider::TextBoxAbove, false, 100, 20);
    frequencySlider.setTextValueSuffix(" Hz");
    frequencyLabel.setText("Frequency ",dontSendNotification);
    frequencyLabel.attachToComponent(&frequencySlider, true);

    /* Phase Slider Information ===============================================================*/

    // Add slider and make it visible
    //addAndMakeVisible(phaseSlider);


    /* Amplitude Slider Information ===========================================================*/

    // Add slider and make it visible
	addAndMakeVisible(amplitudeSlider);

    // Amplitude value initialization
    amplitudeSlider.setValue(0.25);
    amplitudeSlider.setRange(0.0, 1.0);

    // Amplitude slider listener
    amplitudeSlider.addListener(this);

    // Text box
    amplitudeSlider.setTextBoxStyle(Slider::TextBoxAbove, false, 100, 20);
    amplitudeLabel.setText("Amplitude ",dontSendNotification);
    amplitudeLabel.attachToComponent(&amplitudeSlider, true);

    // Play and stop buttons

    playButton.onClick = [this] {playButtonClicked(); };
	addAndMakeVisible(&playButton);
    playButton.setColour(TextButton::buttonColourId, Colours::green);
    playButton.setEnabled(true);

    stopButton.onClick = [this] {stopButtonClicked(); };
	addAndMakeVisible(&stopButton);
    stopButton.setColour(TextButton::buttonColourId, Colours::red);
    stopButton.setEnabled(false);
}

MainComponent::~MainComponent()
{
    // This shuts down the audio device and clears the audio source.
    shutdownAudio();
}

void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
    transport.prepareToPlay(samplesPerBlockExpected, sampleRate);
    transport.setSource(tempSource); // ????

    frequency = 440.0;
    amplitude = amplitudeSlider.getValue();
    phase = 0.0;
    waveTableSize = 1024;
    currentSampleRate = sampleRate;

    // One cycle of a sine wave
    for (int i = 0; i < waveTableSize; i++)
    {
    	waveTable.insert(i, sin(2.0*double_Pi*i / waveTableSize));

    }
}

void MainComponent::playButtonClicked()
{
    state = Starting;
}

void MainComponent::stopButtonClicked()
{
    state = Stopping;
}

void MainComponent::transportStateChanged(TransportState newState)
{
    if (newState != state)
    {
	    state = newState;

	    switch (state) {

	    case Stopped:
		    playButton.setEnabled(true);
		    transport.setPosition(0.0);
		    break;
	
	    case Starting:
		    stopButton.setEnabled(true);
		    playButton.setEnabled(false);
		    transport.start();
		    break;

	    case Stopping:
		    playButton.setEnabled(true);
		    stopButton.setEnabled(false);
		    transport.stop();
	    	break;
        }
    }
}

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
    tempSource = ? ? ? ;

    float* leftSpeaker = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);
    float* rightSpeaker = bufferToFill.buffer->getWritePointer(1, bufferToFill.startSample);

    for (int sample = 0; sample < bufferToFill.numSamples; ++sample)
    {
	    leftSpeaker[sample] = waveTable[(int)phase] * amplitude;
	    rightSpeaker[sample] = waveTable[(int)phase] * amplitude;
    	updateFrequency();
    }
}

void MainComponent::releaseResources()
{
    // This will be called when the audio device stops, or when it is being
    // restarted due to a setting change.

    // For more details, see the help for AudioProcessor::releaseResources()
}

void MainComponent::paint (Graphics& g)
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));

// You can add your drawing code here!
}

void MainComponent::resized()
{
    const int border = 100;
    frequencySlider.setBounds(getWidth() / 4, getHeight() - (3 * border), getWidth() / 2, 50);
    amplitudeSlider.setBounds(getWidth() / 4, getHeight() - (2 * border), getWidth() / 2, 50);

    playButton.setBounds(20, getHeight() - border, getWidth() / 2 - 40, 50);
    stopButton.setBounds(getWidth() / 2 + 20, getHeight() - border, getWidth() / 2 - 40, 50);
}

It doesn’t look like your code really needs an AudioTransportSource at all…?

Just check your playback state in your getNextAudioBlock. If it’s active, generate your waveform into the output buffer. If the state is inactive/stopped, generate silence (zeros) into the output buffer.

edit : The situation would maybe be a bit different if your oscillator itself was an AudioSource subclass. Then you could in principle set your oscillator as the input source of an AudioTransportSource.

1 Like

That makes sense. Like I said, I’m very new, so most of this code is a conglomeration of stuff I’ve seen in tutorials.

EDIT: Your suggestion worked - thanks!