Skip to content

Recording and bouncing engine output

Igor Zinken edited this page Sep 11, 2022 · 11 revisions

MWEngine can record and bounce audio output to .WAV files out of the box (if you wish to use your own custom implementation, see utilities/diskwriter.h for a basic outline) as this page will document:

Recording snippets

MWEngine does not write audio onto the device's file system during rendering as this will degrade performance, instead MWEngine stores recorded audio within memory, and only writes the contents to disk after recording completes or when the memory allowance for a single snippet is exceeded.

In order to prevent exhausting all available memory the recording thus works in iterations. When triggering the recording state, a maximum amount of buffers to write should be specified. Note this does not specify the total recording length (recording will continue until the recording state is disabled), but specifies the length of the individually recorded snippets.

When the snippets buffer is full, a message is broadcast to the Observers, indicating a snippet is ready to be written onto disk (see below). The recording process will continue in the background using a secondary buffer and will revert to using the first buffer as soon as the secondary buffer is filled.

Once recording completes, all snippets (created in chronological order) will be concatenated into a single .WAV file, creating a seamless recording. The temporary files will be deleted during this process.

We differentiate between recording audio (occurs "live" while the engine is running) and bouncing audio (an "offline" process that can be executed without the engine running). This page will document on how to achieve this either from C++ (using audioengine.h) or from Java (using nl.igorski.audio.lib.MWEngine).

Configuring the engine to write audio to the device storage:

Note that both the example Activity in this repository as well as the engine configuration are by default set up to record and write audio output. The following is just to outline the process:

Make sure that inside global.h the following definition is uncommented :

#define RECORD_TO_DISK

As this ensures that MWEngine will compile with all necessary functions to write audio to the devices storage. Also see Configuration and initialization

Configuring the engine to record audio using the device inputs:

In case you also want to record incoming audio from the Androids microphone / attached input device, make sure that the following definition is uncommented in global.h:

#define RECORD_DEVICE_INPUT

Also be aware of the required permissions that need to be defined within the applications manifest in order to gain access to the microphone.

Recording audio on-the-fly

MWEngine allows you to record the output of the engine while the Sequencer is running. What will be recorded is essence the "master bus", in other words: all summed audio channels with effects applied, what you hear is what you get.

Recording is toggled by the methods described below, as soon as recording is halted, the requested output file will contain the recorded audio in .WAV format. As mentioned above, be aware that during recording snippets must be manually saved onto disk as temporary files (see furhter below).

Recording audio output in C++

void AudioEngine::setRecordOutputToFileState( int maxBuffers, char* outputFilename );
void AudioEngine::unsetRecordOutputToFileState();

Given integer maxBuffers describes the maximum amount of buffers that each recorded snippet should hold. char* outputFilename describes the desired filename of the resulting .WAV file that will be recorded (NOTE : its directory must be created prior to invoking this method).

Recording audio output in Java using the MWEngine class

void MWEngine.startOutputRecording( String outputFilename );
void MWEngine.stopOutputRecording();

String outputFilename describes the desired filename of the resulting .WAV file that will be recorded (NOTE : its directory must be created prior to invoking this method). The value for snippet size will default to the amount of buffers necessary to contain 15 seconds of audio.

Saving snippets

As soon as a snippet has been recorded, the engine will broadcast Notifications::RECORDED_SNIPPET_READY to the Observers along with the index of the snippet buffer as its payload. The snippet is at this moment held in memory and needs to be written to the device storage to free up memory. There are a maximum of two snippets that can exist in memory at any given time (the secondary snippet buffer is used to continue writing the still recording audio while waiting for the first buffer to be saved onto disk). The snippet at the given buffer index must be written to disk before the current snippet is full. Basically initiate the saving as soon as the message has broadcast.

Saving the snippet is not a long-running operation (depending on its size), but it is not desired to do this in the audio render loop as buffer underruns can occur which will lead to stuttering audio. As such, saving the snippet should be a manual action to be executed in a different thread (when in doubt see the example MWEngineActivity).

Once the snippet has been saved onto the device storage (see below), the engine will broadcast Notifications::RECORDED_SNIPPET_SAVED with the number of the snippet as its payload (note: this is not the same as the snippets buffer index, this is the index number of the snippet in chronological order). No further action needs to be taken, the snippets buffer has been unloaded from memory and recording will continue until manually halted / bounce completes. Just keep writing subsequent snippets onto storage as they become available.

Saving a snippet in C++

void AudioEngine::saveRecordedSnippet( int snippetBufferIndex );

Saves the contents of the DiskWriters buffer at given snipperBufferIndex as a .WAV file onto the device's storage. The file will be temporarily stored in the same folder as the specified recording output folder (it will be deleted once the recording stops when it is automatically concatenated into the requested output file). By invoking this method the buffer at given index will be flushed to free up memory and to allow subsequent writing upon recording of the next snippet.

Saving a snippet in Java using MWEngine class

runOnUiThread( new Runnable() {
    public void run() {
        MWEngine.saveRecordedSnippet( int bufferIndex );
    }
});

Same as above. Notice the use of runOnUiThread() as a quick and dirty way to store the snippet without interrupting the audio rendering thread.

Bouncing audio "offline"

As the engine isn't playing back in this mode, bouncing audio will be as fast as the processor can complete the calculations. When bouncing completes (e.g. when the end of the current loop length has been reached) all reserved memory and temporary files will be cleared and the resulting .WAV file will be available at the requested output location. Additionally, Notifications::BOUNCE_COMPLETE- is broadcast to the Observers. As bouncing is a synchronous operation all snippets will automatically be written onto disk without requiring manual action.

In C++

void AudioEngine::setBounceOutputToFileState( int maxBuffers, char* outputFilename, int rangeStart, int rangeEnd );
void AudioEngine::unsetBounceOutputToFileState();

Given integer maxBuffers describes the maximum amount of buffers that each recorded snippet should hold. char* outputFilename describes the desired filename of the resulting .wav file that will be recorded (NOTE : its directory must be created prior to invoking this method). Integers rangeStart and rangeEnd define the offsets within the sequence to render (these are defined in buffer samples).

In Java using the MWEngine class

The bounce will by default use snippets that can hold 15 seconds of audio (consumes roughly 2.6 Mb of memory for stereo output at 44.1 kHz). At the end of the bounce, all snippets will be combined in a single file.

void MWEngine.startBouncing( String outputFilename );
void MWEngine.startBouncing( String outputFilename, int rangeStart, int rangeEnd );
void MWEngine.stopBouncing();

Given String outputFilename describes the desired filename of the resulting .wav file that will be recorded (NOTE : its directory must be created prior to invoking this method). Integers rangeStart and rangeEnd define the offsets within the sequence to render (these are defined in buffer samples).

The overloaded method without the range parameters will by default render the full sequence.

Further reading

You might also be interested in recording audio coming in from the device's input channels.

Clone this wiki locally