How to render an Edit to a new file audio file?

I am guessing the needed functionality is in the Renderer class, but I just can’t figure out how to hook it all up correctly. Is it allowed to attempt to render an edit that is currently playing back in real time? If not, is there an easy way to make a temporary clone of the edit for the rendering?

My current code is as follows. Probably completely wrong, but the only way was to do guess work. The code fails when trying to create the rendering audio node, a nullptr is returned :

WavAudioFormat wavformat;
				te::Renderer::Parameters pars{ edit };
				pars.engine = &engine;
				pars.destFile = File("C:\\MusicAudio\\sourcesamples\\dirac\\te_export1.wav");
				pars.audioFormat = &wavformat;
				pars.time = { 0.0,1.0 };
				auto node = te::Renderer::createRenderingAudioNode(pars);
				if (node)
				{
					te::Renderer::RenderTask task{ "foo task",pars,node };
					while (task.runJob() != ThreadPoolJob::jobHasFinished)
					{
						Thread::sleep(10);
					}
					delete node;
					Logger::writeToLog("Render job finished");
				}
				else Logger::writeToLog("Could not create node");

No, you can’t render an edit while it’s playing, mainly because there is only one copy of the plugins loaded, and they can’t be used for both playback and render at the same time.

The easiest way to do a render is with the static function Renderer::renderToFile

Renderer::renderToFile ("Render"), outputFile, edit, { start, end }, tracksToDo);

To create a copy of an edit, first I’d edit.flushState() and then edit.state.createCopy() and pass the ValueTree to a new edit.

1 Like

I’m using the code below, and not having any luck.

			juce::BigInteger tracksToDo{ 0 };

			for (auto i = 0; i < tracks.size(); i++)
				tracksToDo.setBit(i);

			te::EditTimeRange range{ 0.0, 34.0 };

			File renderFile{ File::getSpecialLocation(File::userDesktopDirectory).getChildFile("render.wav") };

			te::Renderer::renderToFile("Render", renderFile, edit, range, tracksToDo);

Th edit has five tracks with a few audio clips each.

I get nothing. No errors. No render file.

What am I missing?

What are you trying to render? Are you sure there is content within that time range?

Have you stepped through the renderToFile method at all? It could be something simple like there is not content on the clips etc.

Yes, all of the clips are in that time range.

On stepping through, I was hitting the jassert for the progress bar. So, instead, I needed to use this syntax;

te::Renderer::renderToFile("Render", renderFile, edit, range, tracksToDo, true, {}, false);

And now I am getting my render file!

So, now, two questions.

  1. What are the downsides to setting useThread = false in te::Renderer::renderToFile?
  2. What is the correct way to set up the progressbar so that I can have useThread = true?

If you don’t use a thread, it will render on the calling thread and you won’t be able to cancel or get progress to it in order to display to your users.

To use a thread you’ll have to implement runTaskWithProgressBar in your UIBehaviour subclass.
There’s an example of this just clearing our CI but you can take a look at a rough idea here: Problem implementing runTaskWithProgressBar(te::ThreadPoolJobWithProgress& job)

Your help is always much appreciated.

I think I’ll wait for the runTaskWithProgressBar example coming through at the moment.

Thank you.

Ok, you can see a minimal example of this now here: https://github.com/Tracktion/tracktion_engine/blob/develop/examples/TestRunner.h

Hi, I’m having issues trying to do the same. Render works, but it only renders first track. Here’s how I’m setting tracks to render:

	juce::BigInteger tracksToDo{ 0 };

	for (auto i = 0; i < numOfTracks; i++) {
		tracksToDo.setBit(i);
	}

Literally the same way, just passing an int for the number of tracks instead of size of an array.
In debugging it shows that there’s indeed correct number of bits set to 1, but rendered file plays as if there’s only the first track.

Found the issue. There were hidden utility tracks at first indexes. And for some reason Rendered doesn’t exclude them by default.

I have a question though. What would be the use case for Clips* parameter and how to use it?

You can choose to render only specific clips from a track.
It’s probably not useful in most cases though, if you did want to use it, just add the clips to the allowedClips member that you want to include. Everything else won’t appear in the render.

1 Like

Just to clarify. I would have to create Renderer::Parameters with populated allowedClips, and use this function instead: static juce::File renderToFile (const juce::String& taskDescription, const Parameters& params); ? Or I can just pass an array of Clip* to this function:
static bool renderToFile (const juce::String& taskDescription, const juce::File& outputFile, Edit& edit, EditTimeRange range, const juce::BigInteger& tracksToDo, bool usePlugins = true, juce::Array<Clip*> clips = {}, bool useThread = true); ?

You can just pass an Array<Clip*> to renderToFile, it does all the rest internally.

1 Like