Issues saving .wav on Android

Hello!! I’m facing a lot of issues trying to save a .wav file using the stored contents of an AudioBuffer.

void MainComponent::saveMusicButtonClicked()
{



    auto backingTrackSavingBuffer = readBuffer.load();
    int backingTrackSize = backingTrackSavingBuffer->getNumSamples();
    DBG("backingTrackSize: ");
    DBG(backingTrackSize);


    auto voiceSavingBuffer = voiceReadBuffer.load();
    auto voiceSize = voiceSavingBuffer->getNumSamples();
    DBG("voiceSize: ");
    DBG(voiceSize);


    int max_num = std::max(voiceSize, backingTrackSize);
    DBG("Max num: ");
    DBG(max_num);

    backingTrackSavingBuffer->setSize(1,max_num,true);
    voiceSavingBuffer->setSize(1,max_num,true);
    mixedBuffer.setSize(2, max_num, false, true);

    auto channelData = mixedBuffer.getWritePointer(0);

    for (int currentSample = 0; currentSample < max_num; currentSample++) {

        float backingtrackBufferFloat = (backingTrackSavingBuffer->getSample(0, currentSample)) * 0.5;
        DBG(backingtrackBufferFloat);
        float voiceBufferFloat = (voiceSavingBuffer->getSample(0, currentSample)) * 0.5;

        channelData[0] = backingtrackBufferFloat + voiceBufferFloat;
        channelData[1] = backingtrackBufferFloat + voiceBufferFloat;



        mixedBuffer.addSample(0, currentSample, channelData[0]);
        mixedBuffer.addSample(1, currentSample, channelData[1]);



    }


    chooser.launchAsync (  juce::FileBrowserComponent::saveMode
                           | juce::FileBrowserComponent::warnAboutOverwriting,
                           [this] (const juce::FileChooser& c)
                           {
                                       juce::WavAudioFormat wavFormat;

                                       auto file = juce::File(c.getResult());
                                       auto androidDocument = juce::AndroidDocument();
                                       auto androidOutputStream = androidDocument.fromDocument(c.getURLResult()).createOutputStream();
                                       //androidOutputStream->setPosition(0);

                                       auto writer = wavFormat.createWriterFor (androidOutputStream.get(), 44100, 1, 32, {}, 0);
                                       writer->flush();
                                       writer->writeFromAudioSampleBuffer(mixedBuffer,0, mixedBuffer.getNumSamples());

                                       DBG("mixedBuffer size: ");
                                       int finalSizeInSamples = mixedBuffer.getNumSamples();
                                       DBG(finalSizeInSamples);
                                       DBG("Writer sampleRate");
                                       DBG(writer->getSampleRate());
                                       DBG("Writer numChannels");
                                       DBG(writer->getNumChannels());

                                       androidOutput.release();


                           });



}

I’m hitting the following assertion:

I/JUCE: JUCE Assertion failure in juce_WavAudioFormat.cpp:1689

void writeHeader()
    {
        if ((bytesWritten & 1) != 0) // pad to an even length
            output->writeByte (0);

        using namespace WavFileHelpers;

        if (headerPosition != output->getPosition() && ! output->setPosition (headerPosition))
        {
            // if this fails, you've given it an output stream that can't seek! It needs to be
            // able to seek back to go back and write the header after the data has been written.
            jassertfalse;
            return;
        }

I’m not sure what I’m doing wrong! I’ve tried many different methods and each single one of them hitted a different SIGTRAP error and other bad things, this current approach is the one with less errors (I can at least get the file saved with a size, but with 0 samples and zero data, as you can see in the attached image)

image

image

Unfortunately the OutputStream provided on Android isn’t seekable, so we can’t fix up the wav file header after writing the rest of the stream.

As a workaround, you can write the wave file into a MemoryOutputStream (which is seekable), and then write the full memory block to file, which doesn’t require seeking:

const auto flags = FileBrowserComponent::canSelectFiles
                 | FileBrowserComponent::saveMode
                 | FileBrowserComponent::warnAboutOverwriting;
chooser.launchAsync (flags, [buffer] (const FileChooser& c)
{
    const auto wavBlock = [&]
    {
        MemoryBlock block;
        auto stream = std::make_unique<MemoryOutputStream> (block, false);

        const auto writer = rawToUniquePtr (WavAudioFormat().createWriterFor (stream.get(), 44100, 1, 32, {}, 0));

        if (! writer)
        {
            jassertfalse;
            return block;
        }

        stream.release();
        writer->writeFromAudioSampleBuffer (buffer, 0, buffer.getNumSamples());
        return block;
    }();

    auto doc = AndroidDocument::fromDocument (c.getURLResult());

    if (! doc)
    {
        jassertfalse;
        return;
    }

    auto androidOutputStream = doc.createOutputStream();

    if (! androidOutputStream)
    {
        jassertfalse;
        return;
    }

    androidOutputStream->write (wavBlock.getData(), wavBlock.getSize());
});

3 Likes

Hey thanks a lot!!!

I have just two more questions:

  1. When I change your statement “buffer” to my already filled with the right data “mixedBuffer” I get the following error:

'mixedBuffer' in capture list does not name a variable

EDIT: I managed to fix it by changing the first line of the fileChooser to this:

chooser.launchAsync(flags, [mixedBuffer = this->mixedBuffer](const juce::FileChooser& c)
  1. How would someone automatically save the file as .wav without the user having to manually type.wav? (I’ve posted this question here and on discord many times an no one managed to answer :frowning: )

You can just include ‘this’ in the capture list, to access your member variables in the body of the lambda.

  1. After retrieving the file name for saving, check if it has an extension, and append the .wav if needed.