|
|
Comments and Discussions
|
|
 |

|
The code has 2 problems
(1) The wav files to be merged need to have exactly the same format, e.g. number of channels (mono/stereo), bits per sample (8 or 16) and sampling rate (in kHz). If the formats are not the same, the combined wav files will have distortion as indicated by some earlier comments.
(2) Value for ChunkSize in the merged wave file header is incorrect. The beginning of function Merge should read
public void Merge(string[] files, string outfile)
{
WaveIO wa_IN = new WaveIO();
WaveIO wa_out = new WaveIO();
wa_out.DataLength = 0;
wa_out.length = 0;
foreach (string path in files)
{
wa_IN.WaveHeaderIN(@path);
wa_out.DataLength += wa_IN.DataLength;
}
wa_out.length = wa_out.DataLength + 36;
....
}
ChunkSize (wa_out.length) is always 36 bytes more than Subchunk2Size (wa_out.length) so you can't sum up all the ChunkSize values of the individual wave files to get the final value.
This error will be ignored and inaudible by many players (including Windows Media Player), but some advanced audio editors (GoldWave for example) may detect the error and prompt the user, or refuse to open the file.
|
|
|
|

|
It says this article is licensed under the CPOL, but the code says it is copyrighted by you. Which is it? Great code by the way. Thanks.
|
|
|
|

|
Hi friends, I too got the problem of noise in the 2nd sound when combining two wav sounds. Based on my observation on several sounds, the problem is due to byte misalignment of the 2nd sound's data after the 1st sound in the merged file, i.e., the noise comes for 2nd sound only when its data (after its Subchunk2) starts at odd offset within the merged file.
I've figured out that if the second file's sound data starts at even offset within the merged file, then there will be no noise problem. So, first write newly created header to the new file. Then write the first file's data. After writing the 1st file's data to the new file, just check the position value of offset to be written. If it is odd, then increment it by 1. If it's already even, continue writing 2nd file's data in the merged file. Hope this solves your problem too...
|
|
|
|

|
Whether I'm processing one or many, the output file contains heavy distortion and background noise. I see several others have experienced the same, but no solution is posted. I'm almost there with this solution, but for the noise issue. Can anyone lend a hand?
NOTE: that what I use the normal windows files originally referenced, the output is clear, not distortion. When I use two .wav files generated by our corporate voice mail system (the real target files), I get the distorion. This is obvoiusly the characteristics of the files I'M using, but I don't know how to adust for their differences. (They sound fine individually, are distored when concat'd)
Thanks in advance
modified on Tuesday, November 17, 2009 2:46 PM
|
|
|
|

|
I reworked this example into a class which allows appending wav files and outputting it to a file or to a memory stream which can then be played like this:
Dim objPlayer As New Media.SoundPlayer
Dim wavFile As New wavFile
With wavFile
.loadFile("C:\wav\file1.wav")
.append("C:\wav\file2.wav")
.append("C:\wav\file3.wav")
objPlayer.Stream = .toStream
objPlayer.Play()
End With
Please note you can only append/concat files of plain PCM format without any additional chucks like 'fact'. This is working for me. Have fun.
This is the class:
Imports System.IO
Public Class wavFile
#Region "Properties"
Dim _length As Integer = 0
Dim _channels As Int16 = 0
Dim _sampleRate As Int32 = 0
Dim _dataLength As Int32 = 0
Dim _bitsPerSample As Int16 = 0
Dim _data(32768) As Byte
Dim _wavFile As wavFile = Nothing
Public Property length() As Integer
Get
Return _length
End Get
Set(ByVal value As Integer)
_length = value
End Set
End Property
Public Property channels() As Int16
Get
Return _channels
End Get
Set(ByVal value As Int16)
_channels = value
End Set
End Property
Public Property sampleRate() As Int32
Get
Return _sampleRate
End Get
Set(ByVal value As Int32)
_sampleRate = value
End Set
End Property
Public Property dataLength() As Int32
Get
Return _dataLength
End Get
Set(ByVal value As Int32)
_dataLength = value
End Set
End Property
Public Property bitsPerSample() As Int16
Get
Return _bitsPerSample
End Get
Set(ByVal value As Int16)
_bitsPerSample = value
End Set
End Property
Public Property data() As Byte()
Get
Return _data
End Get
Set(ByVal value As Byte())
_data = value
End Set
End Property
#End Region
Public Sub loadFile(ByVal path As String)
Dim FS As FileStream = New FileStream(path, FileMode.Open, FileAccess.Read)
Dim BR As BinaryReader = New BinaryReader(FS)
_length = CInt(FS.Length - 8)
FS.Position = 22
_channels = BR.ReadInt16()
FS.Position = 24
_sampleRate = BR.ReadInt32()
FS.Position = 34
_bitsPerSample = BR.ReadInt16()
_dataLength = CInt(FS.Length - 44)
FS.Position = 44
ReDim _data(_dataLength - 1)
FS.Read(_data, 0, _dataLength)
BR.Close()
FS.Close()
End Sub
Private Function createHeader() As MemoryStream
Dim MS As New MemoryStream
Dim BW As New BinaryWriter(MS)
BW.Write(Convert.ToChar("R"))
BW.Write(Convert.ToChar("I"))
BW.Write(Convert.ToChar("F"))
BW.Write(Convert.ToChar("F"))
BW.Write(_length)
BW.Write(Convert.ToChar("W"))
BW.Write(Convert.ToChar("A"))
BW.Write(Convert.ToChar("V"))
BW.Write(Convert.ToChar("E"))
BW.Write(Convert.ToChar("f"))
BW.Write(Convert.ToChar("m"))
BW.Write(Convert.ToChar("t"))
BW.Write(Convert.ToChar(" "))
BW.Write(CInt(16))
BW.Write(CShort(1))
BW.Write(_channels)
BW.Write(_sampleRate)
BW.Write(CInt(_sampleRate * ((_bitsPerSample * _channels) / 8)))
BW.Write(CShort((_bitsPerSample * _channels) / 8))
BW.Write(_bitsPerSample)
BW.Write(Convert.ToChar("d"))
BW.Write(Convert.ToChar("a"))
BW.Write(Convert.ToChar("t"))
BW.Write(Convert.ToChar("a"))
BW.Write(_dataLength)
MS.Position = 0
Return MS
End Function
Private Sub appendToBase(ByVal path As String)
Dim newFile As New wavFile
Dim baseFile As New wavFile
Dim MSMergedFiles As New MemoryStream
Dim BW As New BinaryWriter(MSMergedFiles)
With newFile
.loadFile(path)
_bitsPerSample = .bitsPerSample
_channels = .channels
_sampleRate = .sampleRate
End With
With baseFile
.dataLength = _dataLength
.length = _length
.data = _data
End With
_dataLength = newFile.dataLength + baseFile.dataLength
_length = newFile.length + baseFile.length
BW.Write(baseFile.data)
BW.Write(newFile.data)
MSMergedFiles.Position = 0
_data = ToByte(MSMergedFiles)
BW.Close()
End Sub
#Region "Members"
Public Sub append(ByVal path As String)
If (String.IsNullOrEmpty(path)) Then
Throw New ArgumentNullException("path")
End If
If File.Exists(path) = False Then
path = "C:\voice\ding.wav"
End If
If _dataLength = 0 Then
loadFile(path)
Else
appendToBase(path)
End If
End Sub
Private Function ToByte(ByVal MS As Stream) As Byte()
Dim streamLength As Integer = Convert.ToInt32(MS.Length)
Dim buffer As Byte() = New Byte(streamLength - 1) {}
MS.Read(buffer, 0, streamLength)
MS.Close()
Return buffer
End Function
Public Sub ToFile(ByVal path As String)
Dim FS As FileStream = New FileStream(path, FileMode.Create, FileAccess.Write)
Dim BW As BinaryWriter = New BinaryWriter(FS)
BW.Write(ToByte(createHeader()))
BW.Write(_data)
BW.Close()
FS.Close()
End Sub
Public Function toStream() As Stream
Dim MS As New MemoryStream
Dim BW As New BinaryWriter(MS)
BW.Write(ToByte(createHeader()))
BW.Write(_data)
MS.Position = 0
Return MS
End Function
#End Region
End Class
|
|
|
|

|
Hi Seen the sample and it works great but was wondering if it is possible to open 2 files and combine the results in memory rather than writing to a new file so that they can be pushed out as a byte array, i.e.
FileStream fs1 = new FileStream(file1, FileMode.Open, FileAccess.Read);
FileStream fs2 = new FileStream(file2, FileMode.Open, FileAccess.Read);
// Create a byte array of file stream length
byte[] data1 = new byte[fs1.Length];
byte[] data2 = new byte[fs2.Length];
//Read block of bytes from stream into the byte array
fs1.Read(AudioData1, 0, System.Convert.ToInt32(fs1.Length));
fs2.Read(AudioData2, 0, System.Convert.ToInt32(fs2.Length));
//do something here to extract and combine two audio paths to a single byte array
|
|
|
|

|
Daer Ehab
السلام عليكم ورحمة الله
thans for your bost it help me for my project.
but stil i have problem in the output file some noice so can you tell me how i can clear it.
Thanks & Regards
|
|
|
|
|

|
Do you have any code that makes sure you're not introducing a large change in sample value at the border between two files? That's a pretty common oversight for people starting audio signal processing.
To understand what's happening, think about what it would sound like if you concatenated two sine waves, and at the border between them, the first one was at its peak, and the second one was at its valley. You get a click or a pop noise because your speakers can't play a sound with such a high frequency.
|
|
|
|

|
You can probably avoid that problem by reading the length of the wav-file directly from the header. I had one file which had a copyright notice at the end - when I tried to set the data length by looking at the size of the FileStream (as in the code above), it gave me those strange clicks in my new files, because the audio player would try to play that copyright text, thinking it was part of the audio data.
|
|
|
|

|
I'm trying to merge a couple of these files for a project and even if i run the file without merging through your code, i get a lot of distortion in the background. I tried playing around with the samplerate, channel and bitspersample, but no avail. It does work perfectly with other media files (ie the ones found in the windows/media folder).
Changing the file's quality isn't an option since it's produced by an automated system. Please help!
|
|
|
|

|
Did you find a solution for the distortion that's been introduced? I'm experiencing the same issue, but cannot figure out the fix for it!
Thanks in advance...
|
|
|
|

|
Hi i have some problem with .wav files i have to find the length of the file in mm:ss but where as i am taking fs.length it is giving the size of the file can any one help on this
RAJ
|
|
|
|

|
I appreciate it, helped a lot!
|
|
|
|

|
Hi,
This is the best explained article i have personally seen....!
I spent a whole day trying to merge to wave files with just using bufferstreams and other ways but this was just simply awesome
This article has made life from a nightmare to a living dream in just some time needed to read the article..........!
Thanks a lot........!
Amit Patel
apatel@brandinstitute.com
|
|
|
|

|
Hi, great code...helped me figure out the wav format so i could handle merging them.
So i wrote a new class using some of your code to make it more objectly looking thing :P
Also i just copied in a function called "ReadFully()" from another solution and project, and didnt test build, but it should build ok just eyeballing it :P
Adds some stuff to convert to streams which i needed... hope i didn't leak memory somewhere lol
Also adds equality operators and WavFile.Empty so i hope i did all that stuff ok to lol
public class WavFile
{
#region Properties
private int length;
public int Length
{
get { return length; }
}
private short channels;
public short Channels
{
get { return channels; }
}
private int sampleRate;
public int SampleRate
{
get { return sampleRate; }
}
private int dataLength;
public int DataLength
{
get { return dataLength; }
}
private short bitsPerSample;
public short BitsPerSample
{
get { return bitsPerSample; }
}
private byte[] data;
public byte[] Data
{
get { return data; }
}
public static WavFile Empty
{
//WARNING: tried it this way, "public static readonly WavFile Empty = new WavFile();"
//problem was even though it was readonly, it was being modified and used for storage...
//which lead to weird bugs with sounds undesirably being added on to each other when it
//was thought the file was a blank when set to WavFile.Empty :P
get { return new WavFile(); }
}
#endregion
#region Constructor
public WavFile()
{
length = 0;
channels = 0;
sampleRate = 0;
dataLength = 0;
bitsPerSample = 0;
data = new byte[0];
}
public WavFile(Stream wavStream)
{
using (BinaryReader reader = new BinaryReader(wavStream))
{
length = (int)wavStream.Length - 8;
wavStream.Position = 22;
channels = reader.ReadInt16();
wavStream.Position = 24;
sampleRate = reader.ReadInt32();
wavStream.Position = 34;
bitsPerSample = reader.ReadInt16();
dataLength = (int)wavStream.Length - 44;
wavStream.Position = 44;
data = new byte[dataLength];
wavStream.Read(data, 0, data.Length);
}
}
//WARNING: i just added this constructor in while writing this post and didnt test it lol
public WavFile(string path)
{
if (String.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
if (!File.Exists(path))
throw new FileNotFoundException(String.Format("File: {0} was not found", path));
using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
using (BinaryReader reader = new BinaryReader(fs))
{
length = (int)fs.Length - 8;
fs.Position = 22;
channels = reader.ReadInt16();
fs.Position = 24;
sampleRate = reader.ReadInt32();
fs.Position = 34;
bitsPerSample = reader.ReadInt16();
dataLength = (int)fs.Length - 44;
fs.Position = 44;
data = new byte[dataLength];
fs.Read(data, 0, data.Length);
}
}
public WavFile(WavFile copy)
{
length = copy.Length;
channels = copy.Channels;
sampleRate = copy.SampleRate;
dataLength = copy.DataLength;
bitsPerSample = copy.BitsPerSample;
data = (copy.Data == null) ? new byte[0] : copy.Data;
}
#endregion
#region Members
public void Merge(WavFile addOn)
{
if (addOn == null ||
addOn == WavFile.Empty)
throw new ArgumentNullException("addOn");
if (this == WavFile.Empty)
{
this.channels = addOn.Channels;
this.sampleRate = addOn.SampleRate;
this.bitsPerSample = addOn.BitsPerSample;
}
this.dataLength += addOn.DataLength;
this.length += addOn.Length;
this.data = ArrayUtilities.MergeArrays(this.data, addOn.Data);
}
public Stream ToStream()
{
//NOTE: we cannot use the "using (...) {}" tags here, or ".Close()".
//Closing bw will close the stream.
//Closing ms and the stream gets returned as closed.
MemoryStream ms = new MemoryStream();
BinaryWriter bw = new BinaryWriter(ms);
//ms.Position = 0;
bw.Write(new char[] { 'R', 'I', 'F', 'F' });
bw.Write(length);
bw.Write(new char[] { 'W', 'A', 'V', 'E', 'f', 'm', 't', ' ' });
bw.Write((int)16);
bw.Write((short)1);
bw.Write(channels);
bw.Write(sampleRate);
bw.Write((int)(sampleRate * ((bitsPerSample * channels) / 8)));
bw.Write((short)((bitsPerSample * channels) / 8));
bw.Write(bitsPerSample);
bw.Write(new char[] { 'd', 'a', 't', 'a' });
bw.Write(dataLength);
bw.Write(data);
ms.Position = 0; //very important, else ToFile() doesn't work because Stream.Read() calls get 0 without this
return ms;
}
///
/// Reads data from a stream until the end is reached. The
/// data is returned as a byte array. An IOException is
/// thrown if any of the underlying IO calls fail.
///
/// The stream to read data from
/// http://www.yoda.arachsys.com/csharp/readbinary.html
private static byte[] ReadFully(Stream stream)
{
byte[] buffer = new byte[32768];
using (MemoryStream ms = new MemoryStream())
{
while (true)
{
int read = stream.Read(buffer, 0, buffer.Length); //if read = 0, your stream is either closed, or its position isn't in a good place, try setting its position = 0; before sending it to this function
if (read <= 0)
return ms.ToArray();
ms.Write(buffer, 0, read);
}
}
}
public byte[] ToByte()
{
using (Stream s = ToStream())
{
return ReadFully(s);
}
}
public void ToFile(string path)
{
if (String.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
if (!Directory.Exists(Path.GetDirectoryName(path)))
throw new DirectoryNotFoundException(String.Format("Directory: {0} was not found.", Path.GetDirectoryName(path)));
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
using (BinaryWriter bw = new BinaryWriter(fs))
{
bw.Write(ToByte());
}
}
#endregion
#region Operators
public override int GetHashCode()
{
return
length ^
channels ^
sampleRate ^
dataLength ^
bitsPerSample ^
data.GetHashCode();
}
public override bool Equals(object obj)
{
if (!(obj is WavFile))
return false;
WavFile b = (WavFile)obj;
if (length != b.Length ||
channels != b.Channels ||
sampleRate != b.SampleRate ||
dataLength != b.DataLength ||
bitsPerSample != b.BitsPerSample ||
data.Length != b.Data.Length)
{
return false;
}
//their lengths are same, but comparing data like "data == b.Data" will fail so do it manually...
for (int i = 0; i < data.Length; i++)
{
if (data[i] != b.Data[i])
return false;
}
return true;
}
public static bool operator ==(WavFile a, WavFile b)
{
return a.Equals(b);
}
public static bool operator !=(WavFile a, WavFile b)
{
return !(a.Equals(b));
}
#endregion
}
-- modified at 15:52 Tuesday 17th July, 2007
|
|
|
|

|
I've learnt a lot from this article...it deserves 5... Thanks
For my experiments, I needed some more functionalities like WaveMix(), StripSilence, ChangeVolume(), etc. So, I created those and took the liberty to publish at http://www.codeproject.com/useritems/WAVE_Processor_In_C_.asp. Hope you'll find it interesting.
Love to learn and share
|
|
|
|

|
i want to use two wav file reduced. how can i do ?
I am a student form Taiwan ROC.
|
|
|
|

|
However when merging small waves that i made with the windows sound recorder i get click sounds at the beginning of each merged wave in the resulting file. Has this to do with the 'Sound Recorder' or is it maybe a padding, blocksize issue ?
Greetings
A.J.
Don't Panic, debug it!
|
|
|
|

|
Ok seems like AudioRecorder was the "problem" here. The files exported with Audacity are slightly smaller and if i merge those together there is no problem, the click sound was experienced at the end of each wave sample by the way. Guess AudioRecorder makes some sort of alignment here..
Thanks again, it's nice.
A.J.
Don't Panic, debug it!
|
|
|
|

|
i am working on vb.net 2003 with 1.1 framework.
i applied this logic for concatenating multiple wav files to create one.however my problem is while concatenating its just plays alternative files properly and for others,ie for 2nd , 4th file and so on... it just plays distracting noise sound. Any body can help me with this??? thnks in advance
|
|
|
|

|
Without knowing exactly what you did (you didn't provide any code) it sounds as if you don't honor the correct sample alignment.
You have to be careful to align each sample on (numBits/8)*numChannels boundaries. So for concatenation of 16bit stereo files each sample's starting position must be dividable by ((16/8)*2) = 4.
Regards,
mav
--
Black holes are the places where God divided by 0...
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
|
How to concatenate wave files in a single file
| Type | Article |
| Licence | CPOL |
| First Posted | 15 Aug 2006 |
| Views | 93,835 |
| Downloads | 2,154 |
| Bookmarked | 77 times |
|
|