Join WAV /AIFF files

Hi, I was wondering whether there was a plugin or if anyone has any example code of how to join two or more wav files together?

I have found a couple of links but am not sure how best to use the info here to achieve it:

https://forum.xojo.com/9660-noteplayer-problem/0

http://www.vbforums.com/showthread.php?747553-WAV-joiner

Thanks.

Hi Denise

Using memory blocks it’s relatively easy to join two wave files.

[code]Sub JoinWaveFiles(InFile1 As FolderItem, InFile2 As FolderItem, OutFile As FolderItem)
Dim InData1 As MemoryBlock
Dim InData2 As MemoryBlock
Dim bsIn As BinaryStream

// Read the files into memory
bsIn = BinaryStream.Open(InFile1)
InData1 = bsIn.Read(bsIn.Length)
bsIn.Close
bsIn = BinaryStream.Open(InFile2)
InData2 = bsIn.Read(bsIn.Length)
bsIn.Close

// Create the joined Data
Dim OutData As New MemoryBlock(InData1.Size + InData2.Size - 44)
OutData = InData1 + InData2.MidB(44)

// Update the header
Dim chunksize As Integer = OutData.Size - 8
Dim subchunksize As Integer = OutData.Size - 44

OutData.Long(4) = chunksize
OutData.Long(40) = subchunksize

// Save the joined files to disk
Dim bsOut As BinaryStream
bsOut = BinaryStream.Create(OutFile, True)
bsOut.Write(OutData)
bsOut.Close

End Sub[/code]

Cheers
Wayne

Thanks Wayne that works great! I’m not used to working with memory blocks so this is all new to me.

I am trying to adapt this to work with multiple files in a loop but can’t seem to get it right and the merged wav file created does not play.

       
        Dim wavData As MemoryBlock
        Dim totalWavData As MemoryBlock
        Dim bsIn As BinaryStream
        Dim bsOut As BinaryStream
        Dim i, fileCount As Integer
        
        // Files() is an array of FolderItems
        fileCount = UBound(Files)
        
        For i = 0 to fileCount
          
          temp = Files(i)
          
          If temp <> Nil and temp.Exists then
            
            // Read the files into memory
            bsIn = BinaryStream.Open(temp)
            wavData = bsIn.Read(bsIn.Length)
            bsIn.Close
            
            If i = 0 then
              totalWavData = New MemoryBlock(wavData.Size)
              totalWavData = wavData
            Else
              totalWavData.Size = totalWavData.Size + (wavData.Size - 44)
              totalWavData = totalWavData + wavData.MidB(44)
            End If
            
          End If
          
        Next
        
        // Update the header
        Dim chunksize As Integer = totalWavData.Size - 8
        Dim subchunksize As Integer = totalWavData.Size - 44
        
        totalWavData.Long(4) = chunksize
        totalWavData.Long(40) = subchunksize
        
        // Save the joined files to disk
        bsOut = BinaryStream.Create(OutFile, True)
        bsOut.Write(totalWavData)
        bsOut.Close

Any ideas on what I’m doing wrong?

Wayne’s code has some problems in that it’s assuming the data starts at 44; it doesn’t always. Plus it makes a lot of assumptions, like the two WAVE files must match channel, sample rate, and bitrate. But if you know they are the same, and you know the data starts at offset 44, it’s fine.

The code could be made better if some sections were added to accommodate mono-stereo joins (by simply making the mono one stereo by duplicating the data and interleaving it) and bitrate (multiplying each sample in the lower bit rate up to the higher bitrate); sample rate is way more complicated - perhaps just reject it if the sample rates are different.

Also, the code strips out any additional chunks like ‘smpl’ or ‘LISTINFO’ etc. Bummer, but you may not care. ‘smpl’ is most important but that’s mainly for loop points and if you are joining, that won’t matter anymore anyway.

Thanks Garth. I think the channel, sample and bit rates are the same but I’m not sure about how to determine where the data starts and how large the data chunk is. The code seems to work for two small sample windows wav sound effect files but not for other more complex files so I assume this is due to the data start position.

I’ve debugged to see what each individual wav field’s value is (inside the memory block) and the wavData.Long(40) is inconsistent…that is, sometimes it is the wavdData.size-44, other times it is massively larger and sometimes it is a large negative number. This leads me to believe that the byte point of (40) does not always relate to the same binary data that defines the actual data chunk size.

Is there a way to examine the header of a wav file and determine what byte range relates to the header size and the chunk size? If so then I maybe could grab this for each wav file as I go along and work out the appropriate offsets etc.

For OS X, I think you can use the terminal concat method for wav files too

After reading a post suggesting to do that on PC with the copy command, I tried with two sounds and the OS X cat command from the system (Purr.Wav and Pop.Wav), did not work :frowning:

It probably works with some files, but there seems to be no guarantee (cf. Garth post).

My code was a simple translation of the vb link in the OP.

I would highly recommend to lookup the WAV file format.
Or to use an API like QuickTime.

[quote=121402:@Michel Bujardet]After reading a post suggesting to do that on PC with the copy command, I tried with two sounds and the OS X cat command from the system (Purr.Wav and Pop.Wav), did not work :frowning:

It probably works with some files, but there seems to be no guarantee (cf. Garth post).[/quote]

Both samplerate and channels has to be same. Otherwise it will fail. This is the same for video files.

Hi all. Thanks for all your help. I’ve now managed to get it working. I basically just needed to identify where the data chunk marker was to determine the header size in case it varied from the standard 44 bytes etc. So I put the memoryblock file data into a string and used MidB to find out where the “data” label was and once you have that (remembering to subtract 1 of course since the memory block is a zero-based index) it’s easy to work out where the the data chunk size marker is and where the data chunk starts.

Denise,

Congrats on figuring that out.

I wrote a bmap editor for the Amiga back in the day to deal with AIFF and other bmap types. It was a huge crawler with a massive if {} else if {} tree.

I learned an important lesson then - the ONLY consistent data type is the bit! :slight_smile:

I hate getting late to one of the few threads I can help with.

WAV files are not “wave files”. WAV is a format, “wave” is what raw data is often referred to.

WAV files can’t (or at least) shouldn’t be concatenated in the terminal. They are an actual format with a proper termination (so much so, that cutting up a WAV halfway renders it unreadable for all “strict” WAV readers).

Any WAV joining that assumes concatenation risks creating a mess if both WAV files are of different bitrate or bit depth.

I had made a WAV recorder that sometimes failed and ended up with enough broken files to understand that the best way to deal with WAVs is storing the raw data and only building the wav at the end. Joining is similar: The format dictates how they can be joined or even if they even can).

Exactly. The advice to use copy to concatenate is like what you see out there of concatenating AVI or MPEG files, taking advantage of how players ignore garbage in the middle of files. Or like saving an HTML table with an XLS extension to have it Excel read it. It may work in several or most cases, but it’s very bad practice, and introduces a huge variable that will rear its ugly head at the worst possible time.

I think the only way to join wav files is using sox in a shell

Hi. A while ago I managed (with your help) to successfully create a method to merge multiple wav files into a single file and this works on Windows great. The problem is I have now moved to OS X and am using NSSpeechSythesizer to perform a similar function but Cocoa saves the audio to AIFF instead and as such I can’t use the same code as it is.

I tried specify a wav file in the NS speaktostring() call but it still saves in AIFF.

Thankfully AIFF is also an open format so it can be traversed but I need a little help:
https://en.wikipedia.org/wiki/Audio_Interchange_File_Format

I know there is FFmpeg out there under LGPL but am not sure I can distribute this in software I am selling so thought maybe it was best to just modify my own code using their source as reference in how to understand the AIFF format.

https://github.com/FFmpeg/FFmpeg/blob/415f907ce8dcca87c9e7cfdc954b92df399d3d80/libavformat/aiff.h
https://github.com/FFmpeg/FFmpeg/blob/adc489adc97faf959f955933d198659f6227adb5/libavcodec/shorten.c
https://github.com/FFmpeg/FFmpeg/blob/64545dd6001355491084902008431e45b3f03c50/libavcodec/g726.c
https://github.com/FFmpeg/FFmpeg/blob/fc9f14c7de5bff05bab6f7b258ca70b777ce04ed/libavformat/aiffenc.c
https://github.com/FFmpeg/FFmpeg/blob/a51867ee6bc717a54ea2436e31ee626f02fdf25d/libavformat/aiffdec.c

The code below is the wav merge routine. Can anyone help me convert this to merge AIFF files instead?

[code]
Dim wavData As MemoryBlock
Dim totalWavData As MemoryBlock
Dim wavStringData As String
Dim bsIn As BinaryStream
Dim bsOut As BinaryStream
Dim i, fileCount As Integer
Dim temp, OutFile As FolderItem

    // Files() is an array of FolderItems
    fileCount = UBound(Files)

For i = 0 to fileCount

      temp = Files(i)
      
      If temp <> Nil and temp.Exists then
        
        // Read the files into memory
        bsIn = BinaryStream.Open(temp)
        wavData = bsIn.Read(bsIn.Length)
        bsIn.Close
        
        // Grab byte point of data chunk label marker
        wavStringData = WavData.StringValue(0, WavData.Size)
        
        // Set marker values according to wav header
        dataChunkMarker = wavStringData.InstrB("data") - 1 // Subtract 1 for string offset since memory block bytes start at zero index
        dataChunkSizeMarker = dataChunkMarker + 4 // Add 4 to acount for starting after "data" label
        dataStart = dataChunkMarker + 8 // Add 8 to acount for starting after four byte "data" label and four byte "data hunk size" label
        
        If dataChunkMarker >= 36 then
          
          // Grab actual size of data chunk
          dataChunkSize = wavData.UInt32Value(dataChunkSizeMarker)
          
          // Create a new memory block using and modifying the original wav header data
          If i = 0 then

// Header should be 44 bytes so use exisiting data to recreate it in case it is not standard
Dim m As new MemoryBlock(44)
Dim Channels, SampleRate, BitsPerSample As Integer

// Grab exisiting data that we can use in new header
Channels = wavData.UInt16Value(22)
SampleRate = wavData.UInt32Value(24)
BitsPerSample = wavData.UInt16Value(34)

// Note: UInt16 = 2 bytes and UInt32 = 4 bytes

m.LittleEndian = True

m.UInt32Value(0) = &h46464952 // “RIFF”
m.Int32Value(4) = wavData.Size - 8 // This will be set again later to correct concatenated size
m.UInt32Value(8) = &h45564157 // “WAVE”
m.UInt32Value(12) = &h20746d66 // "fmt " chunk ID
m.UInt32Value(16) = 16 // fmt chunck size
m.UInt16Value(20) = 1 // PCM
m.UInt16Value(22) = Channels // number of Channels (mono or stereo)
m.UInt32Value(24) = SampleRate // Sample rate, 44100 etc
m.UInt32Value(28) = SampleRate * ((BitsPerSample * Channels) / 8) // 44100 * 1 * 8 / 8 //SampleRate * NumChannels * BitsPerSample/8 //ByteRate
m.UInt16Value(32) = (BitsPerSample * Channels) / 8 // 1 * 8 / 8 //NumChannels * BitsPerSample/8 // Block align
m.UInt16Value(34) = BitsPerSample // Bits per sample (8 bits = 8, 16 bits = 16, etc.)
m.UInt32Value(36) = &h61746164 // “data” chunk ID
m.UInt32Value(40) = wavData.Size // NumSamples * NumChannels * BitsPerSample/8 // data chunk size which will be set again later to correct concatenated size
totalWavData = m
End If

          // Add the actual data to the memory block (which will resize itself according to amount of data added)
          totalWavData = totalWavData + wavData.MidB(dataStart, dataChunkSize)
          
        End If
        
      End If
      
    Next
    
    // Update the header
    fmtChunkSize = totalWavData.Size - 8
    dataChunkSize = totalWavData.Size - 44
    
    totalWavData.Int32Value(4) = fmtChunkSize
    totalWavData.UInt32Value(40) = dataChunkSize
    
    // Save the joined files to disk
      bsOut = BinaryStream.Create(OutFile, True)
      bsOut.Write(totalWavData)
      bsOut.Close[/code]

Just found these links that have a bit of a better breakdown of the file format:

https://hg.python.org/cpython/file/2.7/Lib/aifc.py
http://paulbourke.net/dataformats/audio/
http://www.lim.di.unimi.it/IEEE/TRAVIS/AIFFC.HTM
http://stackoverflow.com/questions/14430718/how-do-i-create-an-aiff-file
http://aeroquartet.com/movierepair/aiff
http://web.mit.edu/audio/src/sox/aiff.c