Mixing multiple wav files


#1

I want to mix multiple wave files using JUCE library, and write to a file. I am completely new to JUCE. Could anyone please give some idea on how to do this.


#2

Hi arka86,

For mixing multiple wav files take a look at the MixerAudioSource class.

When you want to write this data to a file, you should create a FileOutputStream object and pass it your destination file in the constructor and then create an AudioFormatWriter using the WavAudioFormat class createWriterFor() method with your output stream as the first argument. You can then write a set of audio samples to the output stream using the AudioFormatWriter::writeFromAudioSampleBuffer() method.

Hope this helps!
Ed


#3

Hi ed,

Thanks a lot. I am using the following code to merge the wav files into one -

String fileName = “D:\output.wav”;
File outputFile = File(fileName);
if (outputFile.exists())
outputFile.deleteFile();
outputFile.create();

	WavAudioFormat wav;
	for each (String filePath in arrFiles)
	{
		File inputFile = File(filePath);
		ScopedPointer <AudioFormatReader> reader(wav.createReaderFor(inputFile.createInputStream(), true));
		if (reader != nullptr)
		{
			ScopedPointer <OutputStream> outStream(outputFile.createOutputStream());
			if (outStream != nullptr)
			{
				StringPairArray metaData = WavAudioFormat::createBWAVMetadata("", "", "", Time::getCurrentTime(), 0, "");
				ScopedPointer <AudioFormatWriter> writer (wav.createWriterFor(outStream, reader->sampleRate, reader->numChannels,
																(int)reader->bitsPerSample, metaData, 0));
				if (writer != nullptr)
				{
                                            outStream.release();
					bool ok = writer->writeFromAudioReader(*reader, 0, -1);
					writer = nullptr;
					reader = nullptr;
				}
			}
		}
	}

But it is writing only one file to the output. I am not understanding what wrong I am doing. Thanks in advance.


#4

Your code doesn’t do any audio mixing. You might want to use the MixerAudioSource like suggested above.

As an unrelated but useful tip : Your output file name shouldn’t use the \ character as it may be interpreted as an escape character within the string. Use \\ or / instead. (/ works fine on Windows too.)


#5

Huh? Does that even compile?


#6

I have declared arrFiles in another place.


#7

Is There any example of mixing using MixerAudioSource? If I just want to merge the files, would I have to use MixerAudioSource? Thanks.


#8

Do you mean: for (String filePath : arrFiles)


#9

arrFiles contains the path of the files I want to merge. And then using a for each loop I was writing the file content to the output file as described above. But the output file is generated with the audio from only one file. Is there anything wrong I am doing?


#10

Oh, I see that for each (... in ...) expression is a non-standard Microsoft extension. (You should probably use a standard way of iterating over these strings.)

Anyway, you really need to get to grips with using AudioSource objects. Then it should be easy to see how to use the MixerAudioSource to do your mixing as Ed suggests.

Take a look at this tutorial:
https://www.juce.com/doc/tutorial_playing_sound_files


#11

Are you saying something like this -

AudioFormatManager formatManager;
MixerAudioSource mixerSource;
for each (String filePath in target.arrFiles)
{
File inputFile = File(filePath);
AudioFormatReader* reader = formatManager.createReaderFor(inputFile);
if (reader != nullptr)
{
ScopedPointer newSource = new AudioFormatReaderSource(reader, true);
mixerSource.addInputSource(newSource, true);
}
}
ScopedPointer outStream(outputFile.createOutputStream());
StringPairArray metaData = WavAudioFormat::createBWAVMetadata("", “”, “”, Time::getCurrentTime(), 0, “”);

Then how to create the writer and how to write the data to the output file? Thanks.


#12

Sorry, could you use preformatted text?


#13

Thanks.

	AudioFormatManager formatManager;
	MixerAudioSource mixerSource;
	for each (String filePath in target.arrFiles)
	{
		File inputFile = File(filePath);
		AudioFormatReader* reader = formatManager.createReaderFor(inputFile);
		if (reader != nullptr)
		{
			ScopedPointer newSource = new AudioFormatReaderSource(reader, true);
			mixerSource.addInputSource(newSource, true);
		}
	}
	ScopedPointer outStream(outputFile.createOutputStream());
	StringPairArray metaData = WavAudioFormat::createBWAVMetadata("", "", "", Time::getCurrentTime(), 0, "");

#14

No, you need to select the code and click this button:


#15

Nearly there… The point of having it preformatted is to avoid the <...> brackets from being stripped as html tags in the forum. Your ScopePointer variables have lost their template type…

But yes, that’s the idea.

  • You’ll need to call prepareToPlay() on the MixerAudioSource object, after adding the sources. Here you can set your destination file sample rate and choose some sensible block size (512 samples for example).
  • All your input files will need to be the same sample rate as the output file, although if you use an ResamplingAudioSource between each AudioFormatReaderSource and the MixerAudioSource, then it will resample for you.
  • You’ll got a double deletion problem for the AudioFormatReaderSource, don’t store that in a ScopedPointer as you’re asking the MixerAudioSource to take ownership.
  • You’ll need to have a loop that calls AudioFormatWriter::writeFromAudioSource() until you’ve outputted all of the samples. To know when to finish you might need to keep an array of your AudioFormatReader objects and determine if the input stream is exhausted.

#16

Thanks a lot Martin. I have modified the code as suggested by you -

                String fileName = "D:/output.wav";
		File outputFile = File(fileName);
		if (outputFile.exists())
			outputFile.deleteFile();
		outputFile.create();
		WavAudioFormat wav;
		MixerAudioSource mixerSource;
		formatManager.registerBasicFormats();
		for each (String filePath in target.arrFiles)
		{
			File inputFile = File(filePath);
			AudioFormatReader* reader = formatManager.createReaderFor(inputFile);
			totalNumSamples += reader->lengthInSamples;
			if (reader != nullptr)
			{
				AudioFormatReaderSource newSource(reader, true);
				mixerSource.addInputSource(&newSource, true);
			}
		}
		mixerSource.prepareToPlay(512, 48000.000000000000);
		ScopedPointer <OutputStream> outStream(outputFile.createOutputStream());
		StringPairArray metaData = WavAudioFormat::createBWAVMetadata("", "", "", Time::getCurrentTime(), 0, "");
		ScopedPointer <AudioFormatWriter> writer(wav.createWriterFor(outStream, 48000.000000000000, 2, 24, metaData, 0));
		if (writer != nullptr)
		{
			outStream.release();
			bool ok = writer->writeFromAudioSource(mixerSource, totalNumSamples, 512);
			writer = nullptr;
		}

But when i execute this, it throws an error “Pure virtual function call” at mixerSource.prepareToPlay(512, 48000.000000000000); this line. Also what about the loop to call AudioFormatWriter::writeFromAudioSource(). On which object I will have to iterate the loop?


#17

(Ironically, I can’t seem to get preformatted text to work right now…!)

This:
if (reader != nullptr) { AudioFormatReaderSource newSource(reader, true); mixerSource.addInputSource(&newSource, true); }

should be:
if (reader != nullptr) mixerSource.addInputSource (new AudioFormatReaderSource (reader, true), true);

Sorry, I didn’t read the docs on writeFromAudioSource() properly: that should work as-is now as it does the iterating for you.


#18

Thanks a lot Martin. It executed without any exception. The duration of the audio file generated is ok. But it played the sound of only one audio file amoung the files that were mixed. The rest of the audio was blank. I am not sure whether I am doing something wrong in the sample rates or samples per block. The preformatted text would work if you replace target.arrFiles with an array of valid wav files.


#19

Can you step through in your debugger to check that all the readers are being created and added correctly?


#20

Yes, I checked. All the readers are being created and added correctly.