How to use audioDeviceIOCallback, audioDeviceAboutToStart and audioDeviceStopped in a simple audio recorder program


#1

Using Juce, I tried to create a simple audio recorder from the demo runner.
I’ve extracted the AudioRecordingDemo to create audio recorder based on a simple audio project.

When I’m clicking on the start recording button, the sampleRate still have the default value:

void startRecording(const File& file)
	{
		stop();

		if (sampleRate > 0)
		{ 
			// Create an OutputStream to write to our destination file...  

sampleRate = 0.0

In the AudioRecordingDemo, the audioDeviceAboutToStart increment the sample rate. But I don’t have any AudioIODevice in my main component.

void audioDeviceAboutToStart(AudioIODevice* device) override
{
	sampleRate = device->getCurrentSampleRate();

}

The AudioIODeviceCallback that sets up the input and ouput is also never called in my code. I tried to use it in my MainComponent class without success.

I also tried to make the MainComponent class inherits from the AudioIODeviceCallback :

class MainComponent : public Component,
         private AudioIODeviceCallback,
         private MidiInputCallback
    
{
public:
//...

I found this method in the Build a multi-polyphonic synthesiser tutorial.

But when I tried this, I got an overwriting error in the main class.

So here is my problem, how to use the AudioIODeviceCallback, audioDeviceAboutToStart and audioDeviceStopped that are defined in the AudioRecordingDemo class in my project ?

You can find the source code here.


#2

What error did you run into?

That tutorial has everything you need, even if it’s more focussed on MIDI. You should look at the section where you hook an AudioIODeviceCallback up to an audio device:

    audioDeviceManager.addAudioCallback (this);

Have an AudioDeviceManager audioDeviceManager as a member of your class and initialise it.

Once the AudioIODeviceCallback is connected to an audio device, the audio device will call the audioDeviceAboutToStart method.


#3

I’ve updated my code with the audioDeviceManager.addAudioCallback(this);
and I tried to make the MainComponent class inherits from the AudioIODeviceCallback :

class MainComponent   : 
public AudioAppComponent,
private AudioIODeviceCallback
{
public:
//...

I got the following error in the initialise method from the main class :

//==============================================================================
void initialise(const String&) override { 
	mainWindow.reset(new MainWindow("Juce Audio Recorder", new MainComponent(), *this)); 
}

E0322 object of abstract class type “MainComponent” is not allowed : pure virtual function “juce::AudioIODeviceCallback::audioDeviceIOCallback” has no overrider.

Same error for audioDeviceAboutToStart and audioDeviceStopped.

The problem is that I want to use the AudioRecorder class from the DemoRunner that already override audioDeviceIOCallback, audioDeviceAboutToStart and audioDeviceStopped.

How should I create the link between my MainComponent audioDevice and the AudioRecordingDemo audioDevice ?


#4

When you inherit from an abstract class (which is AudioIODeviceCallback), it makes your class abstract until you implement (override) all pure abstract methods. You recognise them in the header from the added =0;
The Linker tells you, that MainComponent does not implement the method audioDeviceIOCallback.
Either you didn’t add the code from the recording demo to the MainComponent class, or you mistyped something, could be also in the argument list.

Hope that brings you a step further.


#5

You shouldn’t be inheriting from both AudioAppComponent and AudioIODeviceCallback, the needed functionality to use the audio hardware is already in AudioAppComponent.


#6

Not quite, the AudioAppComponent aggregates an AudioSourcePlayer, which is indeed anAudioIODeviceCallback, but that is just playing back. You need a different AudioIODeviceCallback to record.

However I agree, thinking beyond a prototype, it will be wise to do that in a separate class and not put it into the AudioAppComponent. This is following the principle “Prefer aggregation over inheritance”.


#7

The AudioAppComponent works for getting the hardware input too, the buffer given to getNextAudioBlock has the input audio. For example the JUCE SimpleFFTDemo uses it like that.


#8

This works fine to record the incoming audio in an AudioAppComponent, AudioIODeviceCallback is not needed for anything :

void MainComponent::prepareToPlay (int samplesPerBlockExpected, double sampleRate)
{
	WavAudioFormat wavf;
	Uuid uid;
	String outname = uid.toString()+".wav";
	File outfile = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getChildFile(outname);
	FileOutputStream* outstream = outfile.createOutputStream();
	m_writer = std::unique_ptr<AudioFormatWriter>(wavf.createWriterFor(
		outstream, sampleRate, 2, 32, {}, 0));
	if (m_writer == nullptr)
		delete outstream;
}

void MainComponent::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill)
{
	if (m_writer)
	{
		m_writer->writeFromAudioSampleBuffer(*bufferToFill.buffer, bufferToFill.startSample, bufferToFill.numSamples);
	}
    bufferToFill.clearActiveBufferRegion();
}

A real application of course shouldn’t directly write to the file from the audio thread, but using the buffered writer with the worker thread wouldn’t change things much.


#9

I fixed my problem by adding the audioDeviceIOCallback to my MainComponent Class :

void audioDeviceIOCallback (const float** /*inputChannelData*/, int /*numInputChannels*/,
                            float** outputChannelData, int numOutputChannels,
                            int numSamples) override
{
    // make buffer
    AudioBuffer<float> buffer (outputChannelData, numOutputChannels, numSamples);

    // clear it to silence
    buffer.clear();
}

Everything is working well, thanks.
The source code up to date : SimpleAudioRecorder