Audio Recording


#1

Can someone please help explain how to record audio. I’m looking at the demo, but it’s a bit confusing.


#2

The secret of getting good answers on forums is to ask very specific questions… Asking something as vague as this makes it almost impossible for anyone to answer - try breaking your problem down into smaller tasks that you need help with.


#3

Well the current issue I’m having is that I used the demo code but removed all the visual parts as far as seeing the recording goes and I keep getting this:

ld: warning: ignoring file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks//QuickTime.framework/QuickTime.tbd, missing required architecture x86_64 in file /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks//QuickTime.framework/QuickTime.tbd
Undefined symbols for architecture x86_64:
“MainAppWindow::getSharedAudioDeviceManager()”, referenced from:
MainContentComponent::MainContentComponent() in MainComponent.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

MainComponent.cpp:

#ifndef MAINCOMPONENT_H_INCLUDED
#define MAINCOMPONENT_H_INCLUDED

#include “…/JuceLibraryCode/JuceHeader.h”
#include “MainAppWindow.h”
//==============================================================================
/*
This component lives inside our window, and this is where you should put all
your controls and content.
*/

class AudioRecorder : public AudioIODeviceCallback
{
public:
AudioRecorder(AudioThumbnail& thumbnailToUpdate)
: thumbnail (thumbnailToUpdate),
backgroundThread(“Audio Recorder Thread”),
sampleRate (0), nextSampleNum (0), activeWriter(nullptr)
{
backgroundThread.startThread();
}

~AudioRecorder()
{
    stop();
}

void beginRec (const File& file)
{
    stop();
    
    if (sampleRate > 0)
    {
        file.deleteFile();
        ScopedPointer<FileOutputStream> fileStream(file.createOutputStream());
        
        if (fileStream != nullptr)
        {
            WavAudioFormat wavFormat;
            AudioFormatWriter* writer = wavFormat.createWriterFor (fileStream, sampleRate, 1, 16, StringPairArray(), 0);
            
            if (writer != nullptr)
            {
                fileStream.release();
                
                threadedWriter = new AudioFormatWriter::ThreadedWriter
                (writer, backgroundThread, 32768);
                
                thumbnail.reset (writer->getNumChannels(), writer->getSampleRate());
                nextSampleNum = 0;
                
                const ScopedLock sl(writerLock);
                activeWriter = threadedWriter;
            }
        }
    }
}

void stop()
{
    {
        const ScopedLock sl (writerLock);
        activeWriter = nullptr;
    }
    
    threadedWriter = nullptr;
}

bool isRec() const
{
    return activeWriter != nullptr;
}
      
void audioDeviceAboutToStart (AudioIODevice* device) override
{
    sampleRate = device ->getCurrentSampleRate();
}

void audioDeviceStopped() override
{ 
    sampleRate = 0;
}

void audioDeviceIOCallback (const float** inputChannelData, int /*numInputChannels*/, float** outputChannelData, int numOutputChannels, int numSamples) override
{
    const ScopedLock sl (writerLock);
    
    if (activeWriter != nullptr)
    {
        activeWriter->write (inputChannelData, numSamples);
        
        const AudioSampleBuffer buffer(const_cast<float**> (inputChannelData), thumbnail.getNumChannels(), numSamples);
        thumbnail.addBlock (nextSampleNum, buffer, 0, numSamples);
        nextSampleNum += numSamples;
    }
    
    for (int i = 0; i < numOutputChannels; ++i)
        if(outputChannelData[i] !=nullptr)
            FloatVectorOperations::clear(outputChannelData[i], numSamples);
}

private:
AudioThumbnail& thumbnail;
TimeSliceThread backgroundThread;
ScopedPointerAudioFormatWriter::ThreadedWriter threadedWriter;
double sampleRate;
int64 nextSampleNum;

CriticalSection writerLock;
AudioFormatWriter::ThreadedWriter* volatile activeWriter;

};

class RecordingThumbnail : public Component,
private ChangeListener

{
public:
RecordingThumbnail()
:thumbnailCache(10),
thumbnail(512, formatManager, thumbnailCache),
displayFullThumb(false)
{
formatManager.registerBasicFormats();
thumbnail.addChangeListener (this);
}

~RecordingThumbnail()
{
    thumbnail.removeChangeListener (this);
}

AudioThumbnail& getAudioThumbnail()
{
    return thumbnail;
}

void setDisplayFullThumbnail(bool displayFull)
{
    displayFullThumb = displayFull;
    repaint();
}

void paint (Graphics& g) override
{
    g.fillAll (Colours::darkgrey);
    g.setColour (Colours::lightgrey);
    
    if (thumbnail.getTotalLength() > 0.0)
    {
        const double endTime = displayFullThumb ? thumbnail.getTotalLength()
        : jmax(30.0, thumbnail.getTotalLength());
        
        Rectangle<int> thumbArea (getLocalBounds());
        thumbnail.drawChannels(g, thumbArea.reduced (2), 0.0, endTime, 1.0f);
    }
    
    else
    {
        g.setFont (14.0f);
        g.drawFittedText ("(No file recorded)", getLocalBounds(), Justification::centred, 2);
    }
}

private:
    AudioFormatManager formatManager;
    AudioThumbnailCache thumbnailCache;
    AudioThumbnail thumbnail;
    bool displayFullThumb;
    
    void changeListenerCallback (ChangeBroadcaster* source) override
{
    if (source == &thumbnail)
        repaint();
}
 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RecordingThumbnail)

};

class MainContentComponent : public AudioAppComponent,
//public Component,
private Button::Listener
{
public:
//==============================================================================
MainContentComponent()

    :deviceManager(MainAppWindow::getSharedAudioDeviceManager()), recorder (recordingThumbnail.getAudioThumbnail())
{
    
    setOpaque(true);
    
    addAndMakeVisible(explanationLabel);
    explanationLabel.setText ("This page demonstrates how to record a wave file from the live audio input..\n\nPressing record will start recording a file in your \"Documents\" folder.", dontSendNotification);
    explanationLabel.setFont(Font (15.00f, Font::plain));
    explanationLabel.setJustificationType(Justification::topLeft);
    explanationLabel.setEditable(false, false, false);
    explanationLabel.setColour(TextButton::buttonColourId, Colour(0xffff5c5c));
    recordButton.setColour(TextButton::textColourOnId, Colours::black);
    
    
    setSize (800, 600);
    
    // specify the number of input and output channels that we want to open
    setAudioChannels (2, 2);
}

~MainContentComponent()
{
    shutdownAudio();
}

//==============================================================================
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override
{
    // This function will be called when the audio device is started, or when
    // its settings (i.e. sample rate, block size, etc) are changed.

    // You can use this function to initialise any resources you might need,
    // but be careful - it will be called on the audio thread, not the GUI thread.

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

void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override
{
    // Your audio-processing code goes here!

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

    // Right now we are not producing any data, in which case we need to clear the buffer
    // (to prevent the output of random noise)
    bufferToFill.clearActiveBufferRegion();
}

void releaseResources() override
{
    // 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 paint (Graphics& g) override
{
    // (Our component is opaque, so we must completely fill the background with a solid colour)
    g.fillAll (Colours::black);


    // You can add your drawing code here!
}

void resized() override
{
    // This is called when the MainContentComponent is resized.
    // If you add any child components, this is where you should
    // update their positions.
    
    Rectangle<int> area (getLocalBounds());
    recordButton.setBounds (area.removeFromTop(80).reduced(8));
    recordButton.setBounds (area.removeFromTop (36).removeFromLeft(140).reduced(8));
    explanationLabel.setBounds(area.reduced(8));
}

private:
//==============================================================================
AudioDeviceManager& deviceManager;
RecordingThumbnail recordingThumbnail;
AudioRecorder recorder;
Label explanationLabel;
TextButton recordButton;

// Your private member variables go here...

void beginRec()
{
    const File file (File::getSpecialLocation (File::userDocumentsDirectory)
    .getNonexistentChildFile("Juce Demo Audio Recording", ".wav"));
    recorder.beginRec (file);
    recordButton.setButtonText("Stop");
}
    

void endRec()
{
    recorder.stop();
    recordButton.setButtonText("Record");
}

void buttonClicked(Button* button) override
{
    if (button==&recordButton)
    {
        if(recorder.isRec())
            endRec();
        else 
            beginRec();
    }
}

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)

};

// (This function is called by the app startup code to create our main component)
Component* createMainContentComponent() { return new MainContentComponent(); }

#endif // MAINCOMPONENT_H_INCLUDED

My main issue is that with all the GUI components that are built in it is making it hard for me to discern what code I should be studying for the recording and what code I shouldn’t.