BLOG

C# WAV2GIF

30 April 2009 by Stuart Cam

Introduction

Almost every device which can record or playback digital audio files supports the wave file format. WAV files are commonly used to store CD-quality recordings, often without any compression and typically at 16 bit stereo 44.1 kHz. As a result of their ubiquity and usefulness, WAV files are the lingua franca for professional musicians.

As an aspiring musician myself I have amassed quite a collection of recordings in this format. I wanted the ability to visualise each track as a wave form, preferably without having to load up my sound editing program every time. To the trained eye a wave form contains quite a lot of visible information such as loudness, stereo characteristics, movement and duration. Take a look at an example below:

Wave File visualisation

I see a rhythmic form, which suggests to me a beat or a percussive instrument, such as a piano. I can also see a slight amount of clipping on the first set of peaks which suggests that this sound will be quite loud and that it either underwent some post-production treatment like limiting or compression or it has been recorded too loudly. This wave form was taken from the beginning of March Of The Troops, have a listen for yourself.

I took a look on the internet for a decent WAV2GIF conversion tool and was a little disappointed with the results, so I decided to write my own.

Specification

A little research on the WAV file format threw up two really good resources:

From these I realised that I could get away with a simple implementation which just handled 16 bit stereo 44.1 kHz files. I also decided to flatten the representation of stereo files down to just one wave form.

C# Solution

I've written a C# console application which accepts the following arguments:

WaveWriter.exe [in.wav] [out.gif] [resolution]

  • [in.wav] - Filepath to the 44.1Khz Stereo WAV file on your hard disk (e.g. c:\mixdown.wav)
  • [out.gif] - Filepath to the output GIF file. Note, it will overwrite (e.g. c:\out.gif)
  • [resolution] - Horizontal pixels per second. A value of 100 for example will create an image 100 pixels wide for a 1 second duration WAV file.

I'll skip over the WaveFile class as it's essentially just a data structure, but the following two functions are where the main work is done.

Firstly a function to read in a file and convert to a WaveFile data structure:

public static WaveFile FromFile(string fileName)
{
    var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
    var reader = new BinaryReader(stream);
    var wavFile = new WaveFile();

    reader.ReadChars(4); // Skip the ChunkID
    wavFile.RiffChunk.ChunkSize = reader.ReadUInt32();
    reader.ReadChars(4); // Skip the Format

    wavFile.FileName = fileName;
    wavFile.FileSize = wavFile.RiffChunk.ChunkSize + 8;

    while (reader.BaseStream.Position < wavFile.FileSize)
    {
	var chunkId = new string(reader.ReadChars(4));
	switch (chunkId)
	{
	    case FMTChunk.SUB_CHUNK1_ID:
		wavFile.FMTChunk.Subchunk1Size = reader.ReadUInt32();
		wavFile.FMTChunk.AudioFormat = reader.ReadUInt16();
		wavFile.FMTChunk.NumChannels = reader.ReadUInt16();
		wavFile.FMTChunk.SampleRate = reader.ReadUInt32();
		wavFile.FMTChunk.ByteRate = reader.ReadUInt32();
		wavFile.FMTChunk.BlockAlign = reader.ReadUInt16();
		wavFile.FMTChunk.BitsPerSample = reader.ReadUInt16();
		if (wavFile.FMTChunk.Subchunk1Size > 16)
		{
		    // potentially contains ExtraParamSize & ExtraParams
		    wavFile.FMTChunk.ExtraParamSize = reader.ReadUInt16();
		    if (wavFile.FMTChunk.ExtraParamSize > 0)
		    {
			var readBytes = reader.ReadBytes(wavFile.FMTChunk.ExtraParamSize);
			wavFile.FMTChunk.ExtraParams = readBytes;
		    }
		}
		break;
	    case DataChunk.SUBCHUNK2_ID:
		var subchunk2Size = reader.ReadUInt32();
		// check that the chunk size contains data
		// in case we have an invalid wave file
		if (subchunk2Size > 0)
		{
		    wavFile.DataChunk.Subchunk2Size = subchunk2Size;
		    var readBytes = reader.ReadBytes((int)wavFile.DataChunk.Subchunk2Size);
		    wavFile.DataChunk.Data = readBytes;
		}
		break;
	}
    }
    return wavFile;
}

And its sibling the WaveFormWriter which will accept an input WaveFile and write out a GIF:

public class WaveformWriter
{
    private readonly WaveFile _wavFile;

    public WaveformWriter(WaveFile wave)
    {
        _wavFile = wave;
    }

    public void ToFile(string filename, int pixelspersecond)
    {
        var totalseconds = (int)(_wavFile.Duration.Duration().TotalSeconds + 1);
        ToFile(filename, totalseconds * pixelspersecond, 256);
    }

    public void ToFile(string filename, int width, int height)
    {
        var bitmap = new Bitmap(width, height);
        var graphics = Graphics.FromImage(bitmap);
        graphics.Clear(Color.White);

        int zeroline = height / 2;
        var previous = new PointF(0, zeroline);

        for (int i = 0; i < _wavFile.DataChunk.Data.Length; i += 32)
        {
            int value = BitConverter.ToInt16(_wavFile.DataChunk.Data, i);
            float ypoint = zeroline + (height * value / short.MaxValue);
            float xpoint = (float)((i / Convert.ToDouble(_wavFile.DataChunk.Data.Length)) * width);
            var current = new PointF(xpoint, ypoint);
            graphics.DrawLine(Pens.Black, previous, current);
            previous = current;
        }

        if (File.Exists(filename)) File.Delete(filename);
        bitmap.Save(filename, ImageFormat.Gif);
        bitmap.Dispose();
    }
}

Running the application for March Of The Troops at a resolution of 10 pixels/sec produces the following waveform:

March Of The Troops Waveform

It looks like I might have gone a little overboard on the compression and limiting in post-production!

Further Information

There is no error-checking.

I have not attempted to speed up the bitmap drawing. For large (>50mb) WAV files the rendering can take a little while, a simple improvement could be made by altering the number of bytes sampled (line 25 second code snippet, increase the value of 32).

It would also be quite useful to be able to support different bit rates, channel counts and sampling frequencies.

Download

Download the source + compiled application (10.24 kb)

Tags: , ,

Categories: .NET | C Sharp | Music


© Codebrain 2017. All Rights Reserved. Registered in England: 07744920. VAT: GB 119 4078 13