Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / Windows Forms

C# Async Audio Waveform Generator

Rate me:
Please Sign up or sign in to vote.
4.45/5 (10 votes)
26 Aug 2014Apache3 min read 40.6K   7K   23   11
A C# class for generating an audio waveform asynchronously.

Image 1

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.

C#
wg.Detail = 1.5f;

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

C#
wg.Direction = WaveformGenerator.WaveformDirection.LeftToRight;

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

C#
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.

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

Methods

To start detection/decoding:

C#
wg.DetectWaveformLevelsAsync();

To cancel detection/decoding:

C#
wg.CancelDetection();

To generate waveform during or after detection/decoding:

C#
wg.CreateWaveform(width, height);

Full code (as from demo project):

C#
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.

C#
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.

C#
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".

Image 2

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, along with any associated source code and files, is licensed under The Apache License, Version 2.0


Written By
Other Other
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questionsave flot array in to file? Pin
smd_yasin31-Jul-17 3:14
smd_yasin31-Jul-17 3:14 
QuestionWav Bar Width Change Pin
smd_yasin18-Jul-17 7:14
smd_yasin18-Jul-17 7:14 
Questionhow to use in 4.5 version Pin
smd_yasin18-Jul-17 4:15
smd_yasin18-Jul-17 4:15 
SuggestionCODE UPDATE Pin
Darkensses16-Jul-16 13:12
Darkensses16-Jul-16 13:12 
QuestionModify code for .NET 4.0 Pin
Stoyan Matev8-Dec-14 1:15
Stoyan Matev8-Dec-14 1:15 
QuestionInitialisation exception Pin
Yasskier16-Nov-14 10:19
Yasskier16-Nov-14 10:19 
QuestionThanks you very much! Pin
Touth29-Sep-14 8:03
Touth29-Sep-14 8:03 
QuestionExplain your code well Pin
Akhil Mittal27-Aug-14 20:44
professionalAkhil Mittal27-Aug-14 20:44 
AnswerRe: Explain your code well Pin
Rage28-Aug-14 5:33
professionalRage28-Aug-14 5:33 
GeneralRe: Explain your code well Pin
Akhil Mittal28-Aug-14 18:42
professionalAkhil Mittal28-Aug-14 18:42 
AnswerRe: Explain your code well Pin
Project Sfx28-Aug-14 5:47
Project Sfx28-Aug-14 5:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.