Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Async Audio Waveform Generator

0.00/5 (No votes)
26 Aug 2014 1  
A C# class for generating an audio waveform asynchronously.

Introduction

This project contains a C# class for generating an audio waveform asynchronously from an audio file, using the Task Parallel Library (TPL).

An audio waveform is the visual representation of an audio track, in the form of a plot of its power level against time. It is generated by continuously decoding and finding the power level of small samples of audio from the start to end of the audio track. These power levels are plotted like points on a graph, and then joined together to form a waveform. However, this decoding process is expensive and the decoding of an entire audio track can take some time.

This project aims to improve this issue by:

1) Running the decoding process asynchronously in the background, allowing the UI to remain responsive throughout.

2) Allowing the waveform to be partially rendered and displayed before decoding is completed. If the waveform is continuously rendered and displayed while decoding, an animation effect is produced, as the waveforms appears to "grow" from start to end.

Features

  • Adjustable waveform settings
    • Detail (Higher detail = sharper image)

    • Direction (Left to right / Top to bottom / etc)

    • Orientation (Left side on top or left / Left side on bottom or right)

    • Colors of left side, right side, and center line

  • Supports cancellation

  • Easily modifiable class

  • Supports multiple formats (MP3, MP2, MP1, OGG, WAV, AIFF)

Using the code

  • Add a reference to Bass.Net.dll.
  • Place bass.dll in the output folder. 
  • Add the WaveformGenerator class to the project.
  • Import the WaveformGenerator namespace.

Properties

Detail (Higher detail = sharper image)

There is [Detail] plot point(s) per pixel of the waveform image. (i.e. If detail = 2, for every pixel of the waveform image, there are 2 plot points.) 

Anti-aliasing is used to allow waveforms of Detail > 1 to be rendered nicely.

wg.Detail = 1.5f;

Direction (Left to right / Top to bottom / etc)

wg.Direction = WaveformGenerator.WaveformDirection.LeftToRight;

Orientation (Left side on top or left / Left side on bottom or right)

wg.Orientation = WaveformGenerator.WaveformSideOrientation.LeftSideOnTopOrLeft;

Colors of left side, right side, and center line.

Left and right sides are the left and right channels of a stereo audio. Center line is the line drawn between the left and right sides.

wg.LeftSideBrush = new SolidBrush(Color.Orange);
wg.RightSideBrush = new SolidBrush(Color.Gray);
wg.CenterLineBrush = new SolidBrush(Color.Black);

Methods

To start detection/decoding:

wg.DetectWaveformLevelsAsync();

To cancel detection/decoding:

wg.CancelDetection();

To generate waveform during or after detection/decoding:

wg.CreateWaveform(width, height);

Full code (as from demo project):

private WaveformGenerator wg;

public Form1() {
	InitializeComponent();

	Bass.BASS_Init(-1, 44100, BASSInit.BASS_DEVICE_DEFAULT, IntPtr.Zero);
}

private async void openBtn_Click(object sender, EventArgs e) {
	OpenFileDialog ofd = new OpenFileDialog();

	if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
		openBtn.Enabled = false;
		cancelBtn.Enabled = true;

		wg = new WaveformGenerator(ofd.FileName);

		// Change settings.
		wg.Direction = WaveformGenerator.WaveformDirection.LeftToRight;
		wg.Orientation = WaveformGenerator.WaveformSideOrientation.LeftSideOnTopOrLeft;
		wg.Detail = 1.5f;
		wg.LeftSideBrush = new SolidBrush(Color.Orange);
		wg.RightSideBrush = new SolidBrush(Color.Gray);

		wg.ProgressChanged += wg_ProgressChanged;
		wg.Completed += wg_Completed;
		wg.CreateStream();
		 
		await wg.DetectWaveformLevelsAsync();

		// Add code to execute after completion. (Alternatively, add it in the wg_Completed method)
		// ...
	}
}

private void cancelBtn_Click(object sender, EventArgs e) {
	wg.CancelDetection();
}

private void wg_Completed(object sender, EventArgs e) {
	Console.WriteLine("Completed");

	// Add code to execute after completion. (Alternatively, add it after "await wg.DetectWaveformLevelsAsync();")
	// ...

	wg.CloseStream();
	openBtn.Enabled = true;
	cancelBtn.Enabled = false;

	ReloadWaveform();

	//pictureBox1.Image.Save("Waveform.jpg");
}

private void wg_ProgressChanged(object sender, WaveformGenerator.ProgressChangedEventArgs e) {
	ReloadWaveform();
}

private void Form1_SizeChanged(object sender, EventArgs e) {
	if (pictureBox1.Image != null)
		ReloadWaveform();
}

private void ReloadWaveform() {
	if (pictureBox1.Width > 0 && pictureBox1.Height > 0) {
		if (pictureBox1.Image != null)
			pictureBox1.Image.Dispose();
		pictureBox1.Image = wg.CreateWaveform(pictureBox1.Width, pictureBox1.Height);
	}
}

How is the waveform image generated?

To retrieve the power level of a 20ms "frame" (sample) of an audio track.

Bass.BASS_ChannelGetLevel(stream, levels);

When creating the waveform image, these frames are split into equal groups and compressed into "render frames", where each render frame represents a plot point. The power level of these render frames = the average power level of all the frames in it. The plot points are connected end to end to form a polygon, and then the left and right sides of the waveform are drawn using the Graphics.FillPolygon method.

g.FillPolygon(leftSideBrush, leftPlotPointsArray);

Generating a waveform image of Detail = 2 and Width = 3 (pixels):

For simplicity, the width is set at 3 pixels but is "zoomed in".

Notice:
  • There are 2 plot points per pixel as Detail = 2.
  • For a partial render, the last plot point connects straight down to the base line and not to the end of it.
  • In reality, the waveform image generated will likely be much greater than 3 pixels.

Dependencies (third-party libraries)

BASS : Audio library for decoding audio file.

BASS.NET : BASS .NET wrapper.

The main use of the BASS / BASS.NET library is to decode and retrieve the power levels of an audio file. This library can be replaced with another with such a feature, e.g. NAudio.

Notes

This class is not compatible with C# versions below 5.0 (.NET versions below 4.5) as it uses TPL. It can be easily modified for previous versions by using other threading classes (e.g. BackgroundWorker) instead of Tasks.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here