Get AudioStream of a track to convert it to wav file


#1

Hi all,

I am a newbie in JUCE Audio Plugin and i want to know how to simply get the audio stream of the track plugin’s audio to convert it to a wav file later.

First, i loaded an empty JUCE audio plugin. I got a “Hello world” printed in the plugin window. I can modify that but i dont know how to access to audio stream to handle it. (audio recorded on the plugin track)
I saw a function “processBlock” in PluginProcessor and it may be what i’m looking for but i don’t know what this function does.

Can someone help me?

Thank you.


#2

Hi Tomi,

yes, processBlock is the method which does the work in a plugin. You get a buffer with audio data, do something like DSP with the data and return. To understand how this works have a look at the tutorials.

But it is a bad idea what you are up to: the calls to processBlock have to return as fast as possible, because otherwise the audio will sound crackled. And if you want to write into a file the AudioFormatWriter is the class you would use for that. The problem is, that it depends, how your system performs etc. Even though there are helpers to write on a different thread, it is better to use the features of your DAW to write the output of a bus into a file.

Good luck


#3

Hi daniel,

Thank you for your explanations.
I kept on searching and training on Juce, and I checked AudioFormatWriter.
You said “it is better to use the features of your DAW to write the output of a bus into a file”. However, I’d really like to convert the track to a wav file not depending on the DAW I’m using. I’d like to make it work for all of them.

To test, I tried to follow this : Trying to write WAV file
But I have a problem with the function “WriteToAudioWriter” because it’s not defined.
Do you have any ideas ?
Thank you a lot.


#4

Oh wow, that is almost a decade old :wink:
Look into the current docs and choose the best matching command, I’d suggest
AudioFormatWriter::writeFromAudioSampleBuffer(AudioSampleBuffer&, int start, int length)

writer->writeFromAudioSampleBuffer (buffer, 0, buffer.getNumSamples());

It’s up to you and probably a good exercise to play around, but is there a DAW that isn’t able to write wav files…?


#5

Hi Daniel,
Thank you for your advice, I manage to export the wav file. The file is created within a directory. But the file size is 3kb. I think that’s because i put the wrong data into it. Do you have any idea of what I’m doing wrong ?

Here’s my code :

[CODE]
WavAudioFormat* test = new WavAudioFormat();

File outputDirectory = File(“Wav_Export”);

if (!outputDirectory.exists()) {
outputDirectory.createDirectory();
}

File* outputFile = new File(outputDirectory.getFullPathName()+"\track.wav");
if (outputFile->exists()) {
// create another file
}
FileOutputStream* outputTo = outputFile->createOutputStream();
AudioFormatWriter* writer = test->createWriterFor(outputTo, 44100, 1, 32, NULL, 0);
writer->writeFromAudioSampleBuffer(track, 0, track.getNumSamples());
delete writer;
[/CODE]

Thanks a lot :slight_smile:


#6

Hi @Tomi,
the most important information is missing: what is ‘track’?
If it is the AudioSampleBuffer inside your processBlock, then you are writing exactly one block of data in your file. Together with the wav header it might end up at 3kB.
So you will have to create the writer outside the processBlock method (best choice is prepareToPlay) and only call your writer->writeFromAudioSampleBuffer(track, 0, track.getNumSamples()); there.

Also when writing code, make it a habit to avoid raw pointers. Have a read at https://www.juce.com/learn/coding-standards especially the chapter Object lifetime and Ownership. Use ScopedPointer whenever you think you need a delete. Same with new.

HTH


#7

Thank you Daniel for your answer.

Indeed my ‘track’ variable come from the AudioSampleBuffer in the processBlock function. Here’s the details :

[CODE]
AudioSampleBuffer track;

void MyPluginAudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages)
{
const int totalNumInputChannels = getTotalNumInputChannels();
const int totalNumOutputChannels = getTotalNumOutputChannels();

for (int i = totalNumInputChannels; i < totalNumOutputChannels; ++i) {
       buffer.clear (i, 0, buffer.getNumSamples());
    }
    for (int channel = 0; channel < totalNumInputChannels; ++channel) {
	float* channelData = buffer.getWritePointer(channel);
            
           // ..do something to the data...
       }
	
track = buffer;

}
[/CODE]

For the prepareToPlay function, I want to write my file after a click on a button, so can i make the same that the prepareToPlay function in my buttonClicked function which is in MyPluginAudioProcessorEditor ?

Thanks a lot !


#8

That won’t work that way.

  1. the processBlock method of your plugin is like it’s heartbeat. It must not be stopped, or you die. It will continue to be called.
  2. an audio signal consists of a lot of processBlock calls. even if your DAW is on stop, processBlock will be called (with some exceptions, some for good, some for bad)
  3. any allocations must be done outside the processBlock, because they might take to long to finish, and the next heartbeat was already due
  4. the processor must be able to fully function without the editor. Most of there time the editor doesn’t exist.
  5. the processor must cope with commands from different sources, because it can be controlled from your editor or from the DAWs automation system or from a external controller, most times routed through the DAW
  6. the buffer variable changes blocksize/sampleRate times per second (usually less than 20ms). So don’t save the contents to a place outside of the processing code. Especially no reference, as you might read while the driver/DAW replaces the values.
  7. there are two different threads at work at the same time, one frome the driver/DAW (AudioThread) and one from the GUI (MessageThread). You need to make sure, that they don’t change data at the same time.

Sorry, if that seems like too much obstacles. I don’t want to discourage you, but maybe you should start with doing the tutorials. And a video from Timur @ ROLI, which cannor be shared often enough: Timur @ CppCon 2015 and several following, just search youtube or this forum…


#9

Sorry, was a little bit in a rush yesterday, so instead of telling what doesn’t work, here is how it would work:

In prepareToPlay:

MyPluginAudioProcessor {
    MyPluginAudioProcessor () : recording (false), writeThread ("write thread") {}

    void prepareToPlay (double sampleRate, int blockSize) {
        writeThread.startThread();
        stream = new FileOutputStream (File::getSpecialLocation (File::userHomeDirectory()).getChildFile ("test.wav"));
        WavAudioFormat format;
        writer = format.createWriterFor (stream, sampleRate, 2, 24, StringPairArray(), 0);
        threaded = new AudioFormatWriter::ThreadedWriter (writer, writeThread, 16384);
    }

    void processBlock (AudioSampleBuffer& buffer, MidiBuffer&) {
        if (threaded && recording) {
            threaded.write (buffer.getArrayOfReadPointers (), buffer.getNumSamples());
        }
    }

    void releaseResources () {
        recording = false;
        writeThread.stopThread (1000);
        threaded = nullptr;
        writer = nullptr;
    }

    void setRecording (bool rec) {
        recording = rec);
    }

private:
    bool recording;
    TimeSliceTread writeThread;
    ScopedPointer<FileOutputStream> stream;
    ScopedPointer<AudioFormatWriter> writer;
    ScopedPointer<AudioFormatWriter::ThreadedWriter> threaded;
}

I haven’t tested this, but at least you know the rough workflow now.

Good luck


#10

Ho Daniel

Your response was a relief ! I finally manage to have a wav file working (by that i mean vlc read it). The DAW write in the file.
However, althought i have a time, i don’t have any sound on my track. And in my DAW, when I focus on my plugin window, it says that the track is “offline mode”.

Here the code of my button action :

[CODE]
processor.setRecording(!processor.getRecording());

if (processor.getRecording()) {
	importButton.setButtonText("Recording...");
}
else {
	importButton.setButtonText("Click to record");
}

importButton.setBounds(getWidth() / 2 - 50, getHeight() / 2 - 30, 100, 60);
repaint();

[/CODE]

I write the code you gave me in my audioProcessor. And for my file in which i record :

[CODE]
writeThread.startThread();
File outputDirectory = File(“Wav_Export”);

if (!outputDirectory.exists()) {
	outputDirectory.createDirectory();
}

File outputFile = File(outputDirectory.getFullPathName() + "\\track1.wav");

if (outputFile.exists()) {
	File outputFile = File("\\track" + (String)outputDirectory.getNumberOfChildFiles(1, ".wav") + ".wav");
}
stream = new FileOutputStream(outputFile);
WavAudioFormat format;
writer = format.createWriterFor(stream, sampleRate, 2, 24, StringPairArray(), 0);
threaded = new AudioFormatWriter::ThreadedWriter(writer, writeThread, 16384);

[/CODE]

Thanks for all you do for me !


#11

Hi @Tomi,

most important, never concatenate a filename from strings. Instead write:

File outputFile = outputDirectory.getChildFile ("track1.wav");

Otherwise you get problems with path separators, quoting etc. especially when you run your code on a different plattform.

What DAW are you using? To me “offline mode” sounds like the processBlock is not called. Maybe you can verify that either by debugging or by adding a counter to your processBlock that you display in your editor…?

Some DAWs suspend processing of plugins when no audio is on the track… just speculating…


#12

Hi @daniel ,
Thank you for the details and sorry for the delay.
The file “track1.wav” is created in the directory. My DAW is Reaper, and the offline mode is triggered when the focus is lost on the DAW.
I don’t know how to debug my program because to test, i run a DLL that i add to the “plugins” folder of Reapers. Then, I add the audio track.

Here is a screenshot of what’s happening :

Thank you

Tomi


#13

Any updates with this?


#14

What updates are you expecting?

It looks like the original poster was confused on how audio plugins work in general and was also using his plugin in Reaper via bit bridging. (The latter wouldn’t be necessary as he could have built the plugin as 64 bit directly.)

VST and similar plugins can not access the audio of the host’s tracks directly, especially when developing plugins with JUCE. If the plugin needs bigger chunks of the audio from the host’s track for processing, the audio has to first be “recorded” into the plugin. (Like how Melodyne without ARA support works or the GRM Freeze plugin.)