Wav File Header Data not being created properly

Hi there, I hope someone can help me.

I am trying to save my bufferToFill in getnextAudioBlock to a .wav file so that I can use it in another program. I have written a simple audio Application that generates a sin wave and then stores it in a .wav file I create. My code runs and I can listen to the generated .wav file using windows media player and everything sounds right however I’ve noticed the header of my .wav file is incorrect and missing all of the metadata. I assumed this is because I did not release the writer but when I try to I get a memory leak error when I close the program(The header is still messed up even if I click to continue in VS).

Thank you

This is the hex output of the wav file I generated

My code is as follows:

#pragma once
#include <JuceHeader.h>

class MainComponent   : public AudioAppComponent
{
public:
    //==============================================================================
    MainComponent()
    {
		//setup MOTU Hardware
		deviceManager.setCurrentAudioDeviceType("ASIO", true);
		setAudioChannels(0, 0);

		auto setup = deviceManager.getAudioDeviceSetup();

		setup.outputDeviceName = "MOTU Audio ASIO";
		setup.inputDeviceName = "MOTU Audio ASIO";
		setup.sampleRate = 0;
		setup.bufferSize = 0;

		//set active inputs
		BigInteger bigIntInput = 256;
		bigIntInput.clear();
		bigIntInput.setRange(2, 8, true);

		setup.inputChannels = bigIntInput;
		setup.useDefaultInputChannels = false;

		//set active outputs
		BigInteger bigIntOutput = 256;
		bigIntOutput.clear();
		bigIntOutput.setBit(2);

		setup.outputChannels = bigIntOutput;
		setup.useDefaultOutputChannels = false;

		//apply the new device system settings
		deviceManager.setAudioDeviceSetup(setup, true);
		//setup sin wave
		addAndMakeVisible(frequencySlider);
		frequencySlider.setRange(50.0, 50000.0);
		frequencySlider.setValue(1000);
		frequencySlider.onValueChange = [this]
		{
			if (currentSampleRate > 0.0)
				updateAngleDelta();
		};

		//draw screen
		setSize(800, 600);

    }

    ~MainComponent()
    {
        // This shuts down the audio device and clears the audio source.
        shutdownAudio();
    }

    //==============================================================================
	void prepareToPlay(int, double sampleRate) override
	{
		//Prep sin wave
		currentSampleRate = sampleRate;
		updateAngleDelta();

		//Reset the file
		file.deleteFile();
		//set the writer
		writer.reset(format.createWriterFor(new FileOutputStream(file),
			currentSampleRate,
			8,
			24,
			{},
			0));
	}

	void getNextAudioBlock(const juce::AudioSourceChannelInfo& bufferToFill) override
	{
		//play sin tone to the left buffer
		auto level = 0.125f;
		auto* leftBuffer = bufferToFill.buffer->getWritePointer(0, bufferToFill.startSample);

		for (auto sample = 0; sample < bufferToFill.numSamples; ++sample)
		{
			auto currentSample = (float)std::sin(currentAngle);
			currentAngle += angleDelta;
			leftBuffer[sample] = currentSample * level;
		}

		//save Wav Data before buffer gets modified
		if (writer != nullptr) {
			writer->writeFromAudioSampleBuffer(*bufferToFill.buffer, 0, bufferToFill.buffer->getNumSamples());
		}
	}

    void releaseResources() override
    { 
		writer.release();
	}

    //==============================================================================
    void paint (Graphics& g) override
    {
        g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
    }

    void resized() override
    {
		frequencySlider.setBounds(10, 10, getWidth() - 20, 20);
    }

	void updateAngleDelta()
	{
		auto cyclesPerSample = frequencySlider.getValue() / currentSampleRate;         // [2]
		angleDelta = cyclesPerSample * 2.0 * juce::MathConstants<double>::pi;          // [3]
	}

	public: 
		juce::Slider frequencySlider;
		double currentSampleRate = 0.0, currentAngle = 0.0, angleDelta = 0.0; // [1]

		File file = File::getCurrentWorkingDirectory().getParentDirectory().getParentDirectory().getChildFile("Files").getChildFile("SoundFiles").getChildFile("test.wav");
		WavAudioFormat format;
		std::unique_ptr<AudioFormatWriter> writer;

	private:
    //==============================================================================
    // Your private member variables go here...

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

Your writer is defined as a std::unique_ptr, but you don’t create it using std::make_unique.

It’s not reference-counted, so calling release on it seems wrong, too. Shouldn’t you just set it to nullptr?

But why make it a std::unique_ptr in the first place? Doesn’t createWriterFor return a raw pointer?

Regarding the leak, don’t you have to release the FileOutputStream? In my code, I create the stream, then if createWriteFor() fails, I release the stream myself. Not sure if that matters, though, since there are always multiple ways to accomplish a task.

std::unique_ptr::release() doesn’t delete the object. It sets the pointer to null and returns the original raw pointer, which you are then supposed to delete when necessary. If you want the object to be deleted directly with the unique_ptr, use reset(nullptr) on it.

I have been following the code laid out in this forum post and referenced on this git and TBH I’m a noob and am not really sure why to use a unique ptr object. when I define a file output stream as a member variable and reference it in my writer like this

FileOutputStream* outputTo = file.createOutputStream(); //in membervariables
//in same place as before
writer.reset(format.createWriterFor(outputTo, 
			currentSampleRate,
			8,
			24,
			{},
			0));

 void releaseResources() override
    { 
		delete outputTo;
		writer.release();
	}

I still get the memory leak error. Do you have an example of another way to define my AudioFormatWriter?

Oh that makes a lot more sense and seems to have resolved the memory leak. I am still suffering from the headers not being correctly written. Is this a problem with how i destroy the writer or is it related to how the writer was inialized in the first place?

I am not sure what metadata exactly you are expecting there to be? If the file can be played in a player, the header surely has at least the sample rate, bit depth, number of channels and the length of the audio data, and that’s going to be enough. Your code doesn’t appear to be adding any custom info.

There are a few misconceptions about ownership.

1.) createWriterFor will take ownership over the FileOutputStream, so a raw pointer is fine here.
2.) createOutputStream returns a unique_ptr nowadays. You need to release it to use it in the writer.
3.) Only if the writer couldn’t be created, the FileOutputStream will be left over.
4.) No need to call writer.release(). Just let it go out of scope, like Xenakios showed before.

auto* outputTo = file.createOutputStream().release();
writer.reset (format.createWriterFor(outputTo, 
  			currentSampleRate,
			8,
			24,
			{},
			0));

// if no writer was returned we need to delete the stream manually.
if (writer.get() == nullptr)
    delete outputTo;

See createWriterFor

the stream that the data will go to - this will be deleted by the AudioFormatWriter object when it’s no longer needed. If no AudioFormatWriter can be created by this method, the stream will NOT be deleted, so that the caller can re-use it to try to open a different format, etc

Ah i see, I have reverted my code back to the original code that I posted. If my understanding is correct the createWriterFor will take ownership of the FileOutputStream and that way I don’t need a OutputObject Stream floating around.

exactly, except for the case when no writer was returned. Normally you shouldn’t need to call delete anywhere writing modern C++ using RAII, but here the ownership depends on the returned writer, so you need this old method, unfortunately.

1 Like

Xenakios the metadata I am referring to is the .wav header it should follow the structure of Official Structure however juce seems to be adding a junk chunk into the header as seen from the picture above. Programs like windows media player are smart enough to ignore this but the program I want to feed my wave files into isn’t unfortunately and needs the official structure. Is there a way to tell juce to not write this junk subchunk in the header?

I think the other program needs to be fixed, Juce doesn’t provide control over how the WAV header is written. (Proper WAV parser implementations should always be able to skip the chunks they don’t understand.)

I figured as much, it’ll probably be easier for me to write some code to remove the junk chunk rather than them writing an error parser.Thanks a bunch for the help I really appreciated it.

And while there might be a bug in JUCE, the audio file format code is pretty mature. I doubt it is putting ‘junk’ in the file. I would still suspect your code for any improper formatting issues long before I suspected JUCE. :slight_smile:

The “junk chunk” is a real thing in the full WAV format and the Juce writer puts that into the file. It’s not a bug in Juce but a problem for some program the original poster is trying to use.

1 Like

There’s actually a Juce compile time define option to avoid writing the junk header section!

#if ! JUCE_WAV_DO_NOT_PAD_HEADER_SIZE
            /* NB: This junk chunk is added for padding, so that the header is a fixed size
               regardless of whether it's RF64 or not. That way, we can begin recording a file,
               and when it's finished, can go back and write either a RIFF or RF64 header,
               depending on whether more than 2^32 samples were written.

               The JUCE_WAV_DO_NOT_PAD_HEADER_SIZE macro allows you to disable this feature in case
               you need to create files for crappy WAV players with bugs that stop them skipping chunks
               which they don't recognise. But DO NOT USE THIS option unless you really have no choice,
               because it means that if you write more than 2^32 samples to the file, you'll corrupt it.
            */
2 Likes

Just to confirm Juce writes wav file data as uncompressed audio in the linear pulse code modulation (LPCM) format correct?

Yes, it doesn’t support the more esoteric “compressed audio in WAV container” things.

I am now trying to understand how juce writes the wav header. I have been following along with this tutoral
and the canonical wave file format. Ignoring the space taken up by the junk chunk we can see that "fmt " is 66 6d 74 20. Following this I can see that this subchunk has a size of 40 (28hex -> 40dec) the next section should be the AudioFormat. I was expecting to see 01 00 here but instead found fe ff as the hex (does fe ff represent LPCM?). This doesn’t seem to make sense unless the data is compressed in some way. the number of channel and sample rate following the Audioformat section seem to be correct.

My question is what AudioFormat is juce using and is there a way to set it to PCM (ie linear quantization)? Are LPCM and PCM the same thing? And why is the subchunk1ID so long, is it storing extra JUCE related data/ what is it storing? Is there somewhere that explains how juce writes headers?

Thanks in advance I know this is a lot of questions

I think you should just get the program fixed that you are feeding the WAV files into. (Or switch to another application.) I have no problems at all using the Juce produced WAV files with the following applications :

  • Cockos Reaper
  • Ableton Live
  • Ocenaudio
  • Audacity
  • The Mac Os preview functionality
  • Foobar2000 (Windows audio playing software)
  • The Google Chrome browser
  • The Composers Desktop Project (this would have been an expected candidate to fail because it uses quite old code in parts)

So, I would say that regardless of Juce implementing the “official” WAV format correctly or not, it’s writing the files in a way that a variety of applications can handle in any case.

1 Like

I have abandoned the usless program and now im trying to write my own c++ script that can load in the file (Mainly for my understanding). Everything is going fine im just confused on the AudioFormat juce is saving the data as. It gives fe ff as hex for the AudioFormat subsection of the header which implies the data is compressed in some way. I wanna first underst and why it is fe fx instead of the standard 01 00 or 00 01 and wanna make sure that the data in the data chunk is uncompressed.