Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / C#

Compose sounds from frequencies and visualize them

Rate me:
Please Sign up or sign in to vote.
4.87/5 (53 votes)
17 Apr 2006CPOL6 min read 191.7K   3.5K   162  
What you never wanted to know about PCM.
/* This class has been written by
 * Corinna John (Hannover, Germany)
 * cj@binary-universe.net
 * 
 * You may do with this code whatever you like,
 * except selling it or claiming any rights/ownership.
 * 
 * Please send me a little feedback about what you're
 * using this code for and what changes you'd like to
 * see in later versions.
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace WaveMixer {

    /// <summary>Stores format and samples of a wave sound.</summary>
	public class WaveSound {

		private WaveFormat format;
		private short[] samples;

        /// <summary>Returns the format header information.</summary>
		public WaveFormat Format
		{
			get { return format; }
		}

        /// <summary>Returns the number of samples.</summary>
		public int Count
		{
			get { return samples.Length; }
		}

        /// <summary>Returns the wave samples.</summary>
        public short[] Samples
		{
			get { return samples; }
		}

        /// <summary>Returns the left channel samples.</summary>
        public short[] LeftSamples
		{
			get {
				return GetChannelSamples(0);
			}
		}

        /// <summary>Returns the right channel samples.</summary>
		public short[] RightSamples
		{
			get
			{
				return GetChannelSamples(1);
			}
		}

        /// <summary>Returns the sample at the given position.</summary>
		public short this[int indexer]{
			get { return samples[indexer]; }
			set { samples[indexer] = value; }
		}

        /// <summary>Constructor.</summary>
        /// <param name="format">Format header information.</param>
        /// <param name="samples">Wave samples.</param>
		public WaveSound(WaveFormat format, short[] samples)
		{
			this.format = format;
			this.samples = samples;
		}

        /// <summary>Constructor.</summary>
        /// <param name="format">Format header information.</param>
        /// <param name="samples">Wave samples.</param>
        public WaveSound(string fileName)
        {
            this.ReadFile(fileName);
        }

        /// <summary>Separates all samples of one channel.</summary>
        /// <param name="channelIndex">Index of the channel.</param>
        /// <returns>All samples that belong to the requested channel.</returns>
		private short[] GetChannelSamples(int channelIndex)
		{
			short[] channelSamples;
			if (format != null)
			{
				channelSamples = new short[samples.Length / format.Channels];

				int channelSamplesIndex = 0;
				for (int n = channelIndex; n < samples.Length; n += format.Channels)
				{
					channelSamples[channelSamplesIndex] = samples[n];
					channelSamplesIndex++;
				}
			}
			else
			{	//not yet initialized
				channelSamples = new short[0];
			}
			return channelSamples;
		}

        /// <summary>Creates a stream in RIFF Wave format from the wave information.</summary>
        /// <returns>Formatted stream.</returns>
		public Stream CreateStream()
		{
			MemoryStream stream = new MemoryStream();
			BinaryWriter writer = new BinaryWriter(stream);
			Int32 dataLength = (Int32)(samples.Length * format.BitsPerSample / 8);

			writer.Write(System.Text.Encoding.ASCII.GetBytes("RIFF".ToCharArray()));

			writer.Write((Int32)(dataLength + 36)); //File length minus first 8 bytes of RIFF description

			writer.Write(System.Text.Encoding.ASCII.GetBytes("WAVEfmt ".ToCharArray()));

			writer.Write((Int32)16); //length of following chunk: 16

			writer.Write(format.FormatTag);
			writer.Write(format.Channels);
			writer.Write(format.SamplesPerSec);
			writer.Write(format.AvgBytesPerSec);
			writer.Write(format.BlockAlign);
			writer.Write(format.BitsPerSample);

			writer.Write(System.Text.Encoding.ASCII.GetBytes("data".ToCharArray()));

			writer.Write(dataLength);

			for (long n = 0; n < samples.Length; n++) {
				if (format.BitsPerSample == 8)
				{
					writer.Write((byte)samples[n]);
				}
				else
				{
					writer.Write(samples[n]);
				}
			}

			writer.Seek(0, SeekOrigin.Begin);
			return stream;
		}

        /// <summary>Reads a wave file.</summary>
        /// <param name="fileName">Name and path of the file.</param>
        /// <returns>Format and samples.</returns>
        private void ReadFile(string fileName)
        {
            BinaryReader reader = new BinaryReader(new FileStream(fileName, FileMode.Open));

            this.format = ReadHeader(reader);

			if(format.BitsPerSample != 8 && format.BitsPerSample != 16){
				System.Windows.Forms.MessageBox.Show("The sound has " + format.BitsPerSample + " bits per sample. Please choose a sound with 8 or 16 bits per sanmple.");
				return;
			}


            int dataLength = reader.ReadInt32();

            int maxSampleValue = 0;
            int bytesPerSample = format.BitsPerSample / 8;
            int countSamples = dataLength / bytesPerSample;

            //sbyte[] channelSamples8 = new sbyte[countSamples];
			short[] channelSamples16 = new short[countSamples];
			sbyte channelSample8;

            int channelSamplesIndex = 0;
            int absValue;
            for (int sampleIndex = 0; sampleIndex < countSamples; sampleIndex++)
            {

                if (format.BitsPerSample == 8)
                {
					channelSample8 = reader.ReadSByte();
					channelSamples16[channelSamplesIndex] = (short)(channelSample8 + (channelSample8 << 8));
					if (Math.Abs((int)channelSample8) > maxSampleValue)
                    {
						maxSampleValue = Math.Abs((int)channelSample8);
                    }
                }
                else
                {
                    channelSamples16[channelSamplesIndex] = reader.ReadInt16();
                    absValue = Math.Abs((int)channelSamples16[channelSamplesIndex]);
                    if (absValue > maxSampleValue)
                    {
                        maxSampleValue = (absValue > short.MaxValue) ? absValue - 1 : absValue;
                    }
                }

                channelSamplesIndex++;
            }

            reader.Close();

			format.BitsPerSample = 16;
			//if (format.BitsPerSample == 8)
			//{
			//    for (int n = 0; n < channelSamples8.Length; n++)
			//    {
			//        channelSamples16[n] = (short)channelSamples8[n];
			//    }
			//}

            this.samples = channelSamples16;
        }

        /// <summary>Read a chunk of four bytes from a wave file</summary>
        /// <param name="reader">Reader for the wave file.</param>
        /// <returns>Four characters.</returns>
        private string ReadChunk(BinaryReader reader)
        {
            byte[] ch = new byte[4];
            reader.Read(ch, 0, ch.Length);
            return System.Text.Encoding.ASCII.GetString(ch);
        }

        /// <summary>
        /// Read the header from a wave file, and move the
        /// reader's position to the beginning of the data chunk
        /// </summary>
        /// <param name="reader">Reader for the wave file.</param>
        /// <returns>Format of the wave.</returns>
        private WaveFormat ReadHeader(BinaryReader reader)
        {
            if (ReadChunk(reader) != "RIFF")
                throw new Exception("Invalid file format");

            reader.ReadInt32(); // File length minus first 8 bytes of RIFF description, we don't use it

            if (ReadChunk(reader) != "WAVE")
                throw new Exception("Invalid file format");

            if (ReadChunk(reader) != "fmt ")
                throw new Exception("Invalid file format");

            int len = reader.ReadInt32();
            if (len < 16) // bad format chunk length
                throw new Exception("Invalid file format");

            WaveFormat carrierFormat = new WaveFormat();
            carrierFormat.FormatTag = reader.ReadInt16();
            carrierFormat.Channels = reader.ReadInt16();
            carrierFormat.SamplesPerSec = reader.ReadInt32();
            carrierFormat.AvgBytesPerSec = reader.ReadInt32();
            carrierFormat.BlockAlign = reader.ReadInt16();
            carrierFormat.BitsPerSample = reader.ReadInt16();

            // advance in the stream to skip the wave format block 
            len -= 16; // minimum format size
            while (len > 0)
            {
                reader.ReadByte();
                len--;
            }

            // assume the data chunk is aligned
            string chunk;
            do
            {
                chunk = ReadChunk(reader);
            } while (reader.BaseStream.Position < reader.BaseStream.Length && chunk != "data");

            return carrierFormat;
        }

        /// <summary>Saves the sound to a wave file.</summary>
        /// <param name="fileName">Name and path of the destination file.</param>
		public void SaveToFile(string fileName)
		{
			Stream stream = CreateStream();
			FileStream file = new FileStream(fileName, FileMode.Create);

			int blockLength;
			long remainingLength;
			while (stream.Position < stream.Length)
			{
				remainingLength = stream.Length - stream.Position;

				if (remainingLength > int.MaxValue)
				{
					blockLength = int.MaxValue;
				}
				else
				{
					blockLength = (int)remainingLength;
				}

				byte[] buffer = new byte[blockLength];
				stream.Read(buffer, 0, blockLength);
				file.Write(buffer, 0, blockLength);
			}

			file.Close();
			stream.Close();
		}

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions