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

A Little Gaming Nostalgia

29 April 2009 by Stuart Cam

Like most alpha geeks I became interested in computers from quite a young age. How young I hear you ask? Well, I can remember a time when I'd borrow computer books filled with BASIC from the local library, then proceed to copy the code line-for-line into my beige brick-like Acorn Electron. It was an incredibly tedious task where I'd wind up spending hours trying to find the typo. Even if I managed to transcribe the code exactly, most of the time the games were rather disappointing.

Fast forward to around 1995-96 and I started playing Doom II, and then its predecessor Quake. I was obsessed with Quake. Real 3D in a computer game - simply amazing. It was around this time that the first dial-up internet providers were making headway. Armed with my lowly 28.8 modem and a moderate amount of teen aggression I wrecked havoc in online clan matches and duels! CopyCat was the player to fear!

My interest piqued when I discovered that you could create your own game levels. Modding was a new phenomenon that was starting to gain traction. A friend of mine introduced me to Quest, a level editor for Quake, after which point I was totally hooked. I ran a web site for a couple of years called The Levelab which provided hints and tips on level design and hosted maps for the UK One-on-One League, or UKOOL for short.

So, where is this ramble going? Well, I was digging through some old CDs last night and stumbled across my old Quake and Quake II levels! I thought it'd be fun to fire them up once again and take some screenshots for this blog. I have also decided that after nearly a decade gathering dust I should release the uncompiled source code for each map into the public domain, because frankly, my level designing days are over. If John Romero can release his source code, so can I!

UKOOLDM2 - The Egyptian Cistern

Released: 21st June 1997

One of my first ever Quake 1-on-1 deathmatch levels. It's my take on the classic deathmatch level DM4 and often results in frantic gameplay.

UKOOLDM2 - The Egyptian Cistern screenshots

Punishment

Released: 29th July 1997

Winner of the Random Acts of Senseless Violence (RASV) Quake level design competition, bagging me a brand new Voodoo 3DFX card in the process. Back in the day these graphics cards sold for £139.99 so I was over the moon to win!

Punishment screenshots

Analysis

Released: 9th December 1997

A bespoke Quake deathmatch level commissioned by PC Gamer to be included on the front of their Gold Edition magazine.

Analysis screenshots

Fusion '98

Released: 12th April 1998

A custom Quake II level commissioned by Reality-X for their Fusion LAN meeting in Leeds, UK. The last level I ever released.

Fusion 98 screenshots

A little bit of Googling unearthed a rather old interview with me from 1998. Poor grammar, odd phrases and spelling completely probably intentional!

Tags: , , , ,

Categories: Games

Scrum Meeting Questions

28 April 2009 by Stuart Cam

Part of the attraction of using the Scrum framework for developing software is its ability to generate rapid feedback (not all news is good, but we won't go there today!).

On a recent project we held daily stand-up meetings every morning at 9:15am where each member of the team would answer the following questions:

1. What have you done since yesterday?
First of all the Product Owner needs to have defined what done really means. For this project it meant coded, unit tested, peer reviewed and integrated into the main development line without breaking the build. It is of course possible to extend coverage way beyond writing code and incorporate such things as documentation and regression testing. Adding items into the definition of done will have a negative impact on the team's velocity, which for a disruptive product with first-to-market desires was of paramount importance. Ken Schwaber and Scott Hanselman talk about done in this podcast. Ken also deliberates on the subject of done in his Google Tech Talk Video.

2. What are you planning to do by today?
This is a firm commitment to the team about your intentions for the day. In our team we knew exactly who was working on what and when they'd anticipate finishing. This was extremely useful for coordinating work and avoiding lengthy sessions with our source control system trying to merge files because two people made parallel modifications to shared resources.

3. Do you have any problems preventing you from accomplishing your goal?
Sometimes the problems are technical, at which point another team member often indicates willingness to help. The important thing to note is that all sideline discussions must take place after the stand-up. I've seen several meetings disintegrate into design discussions because the Scrum Master didn't enforce enough control over the conversation. More often than not the problems are non-technical in nature; waiting on external parties, interrupted work streams, unforeseen events and a whole bunch of other stuff just makes things difficult! This is a cue for the Scrum Master to take note and remove said obstacles!

Although strictly not part of the 'official' stand-up meeting questions we also felt it useful to add:

4. Do you want to share anything useful with the team?
A short (1-2 minutes) opportunity for the developer to talk about some insight they may have gained or some exciting code they have written. These were often rather animated mini-presentations which gave the developer an opportunity to vent some ego and share valuable knowledge with other developers. They had an overall positive effect on the mood of the whole team which lasted throughout the day. Just be careful not to start design discussions during the meeting with this one!

Tags: ,

Categories: Agile | Scrum | Management

High Performance Websites

25 April 2009 by Stuart Cam

Steve Souders of Yahoo! gives a Google talk about his experiences with high performance websites.

Useful Links

Tags: , ,

Categories: Web


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