Click here to Skip to main content
11,495,758 members (65,812 online)
Click here to Skip to main content

C# Async Audio Waveform Generator

, 26 Aug 2014 Apache 7.8K 1.6K 19
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Apache License, Version 2.0

Share

About the Author

Project Sfx

Other Other
No Biography provided

Comments and Discussions

 
QuestionModify code for .NET 4.0 Pin
Stoyan Matev8-Dec-14 2:15
memberStoyan Matev8-Dec-14 2:15 
QuestionInitialisation exception Pin
Member 1116506216-Nov-14 11:19
memberMember 1116506216-Nov-14 11:19 
QuestionThanks you very much! Pin
Touth29-Sep-14 9:03
memberTouth29-Sep-14 9:03 
QuestionExplain your code well Pin
Akhil Mittal 27-Aug-14 21:44
mvp Akhil Mittal 27-Aug-14 21:44 
AnswerRe: Explain your code well Pin
Rage28-Aug-14 6:33
professionalRage28-Aug-14 6:33 
GeneralRe: Explain your code well Pin
Akhil Mittal 28-Aug-14 19:42
mvp Akhil Mittal 28-Aug-14 19:42 
AnswerRe: Explain your code well Pin
Project Sfx28-Aug-14 6:47
memberProject Sfx28-Aug-14 6:47 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 26 Aug 2014
Article Copyright 2014 by Project Sfx
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid