Click here to Skip to main content
11,490,973 members (67,781 online)
Click here to Skip to main content
Add your own
alternative version

Compose sounds from frequencies and visualize them

, 17 Apr 2006 CPOL 141.2K 2.3K 160
What you never wanted to know about PCM.
/* This class has been written by
 * Corinna John (Hannover, Germany)
 * cj@binary-universe.net
 * 
 * The GPL (GNU General public license)
 * is valid for this file.
 * 
 * Please send me a little feedback about what you're
 * using the code for and what changes you'd like to
 * see in later versions.
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Drawing;
using System.Drawing.Imaging;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace WaveMixer {
	public partial class WaveDisplayControl : UserControl {

		private String currentFile;
		private WaveSound waveSound;
		private WaveOutPlayer player;
		private Stream playerStream;
		private float zoomPercent;
		private short maxSampleValue;
		
		/// <summary>Is called after a wave file has been opened</summary>
		public event EventHandler Open;

		/// <summary>Returns path and name of the currently displayed file</summary>
		public String CurrentFile{
			get { return currentFile; }
		}

		/// <summary>Gets or sets the zoom factor</summary>
		public float ZoomPercent
		{
			get { return zoomPercent; }
			set {
				zoomPercent = value;
				tstZoom.Text = zoomPercent.ToString("n2", CultureInfo.CurrentCulture);
				if (waveSound != null) {
                    DrawWave();
				}
			}
		}

		/// <summary>Gets the duration of the displayed file in seconds</summary>
		public float Duration{
			get{
				return (float)waveSound.Count / ((float)waveSound.Format.Channels * (float)waveSound.Format.SamplesPerSec);
			}
		}

		/// <summary>Gets the value of the loudest sample</summary>
		public short MaxSampleValue
		{
			get
			{
				return maxSampleValue;
			}
		}

		/// <summary>Gets or sets the displayed wave sound</summary>
		/// <remarks>Setting the sound raises an "Open" event</remarks>
		public WaveSound WaveSound
		{
			get { return waveSound; }
			set {
				waveSound = value;
				
				if (waveSound != null) {
					DrawWave();
				}
				
				tsbPlay.Enabled = true;
				tsbStop.Enabled = true;

				if (Open != null) {
					Open(this, EventArgs.Empty);
				}
			}
		}

		/// <summary>Constructor</summary>
		public WaveDisplayControl()
		{
			InitializeComponent();
			zoomPercent = float.Parse(tstZoom.Text, NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture);
			currentFile = string.Empty;

			tsbPlay.Enabled = false;
			tsbStop.Enabled = false;
		}

        /// <summary>Draw the wave and display statistics</summary>
        public void DrawWave()
        {
            maxSampleValue = 0; 
            DrawWave(waveSound.LeftSamples, picLeftWave);
            DrawWave(waveSound.RightSamples, picRightWave);
            picRightWave.Top = picLeftWave.Bottom - 1;

            //show statistics
            tsslLength.Text = this.Duration.ToString("n2", CultureInfo.CurrentCulture);
            tsslVolume.Text = maxSampleValue.ToString(CultureInfo.CurrentCulture);
        }

		/// <summary>Draw a left or right channel wave</summary>
        private void DrawWave(short[] channelSamples, PictureBox picWave)
		{
			Control topLevelControl = this.FindForm();
			if (topLevelControl == null) { topLevelControl = this; };
			topLevelControl.Cursor = Cursors.WaitCursor;

			try {

				float zoom = zoomPercent / 100;
				
				//resize picturebox
				int scaledWidth = (int)((float)channelSamples.Length * zoom);
				if (scaledWidth > 9999) { scaledWidth = 9999; } //prevent ArgumentException creating Bitmap
				picWave.Width = scaledWidth;

				//calculate scaling
				picWave.Height = (panelContainer.Height - SystemInformation.HorizontalScrollBarHeight) / 2;
				int samplesPerPixel;
				int spacePerSample;
				if (picWave.Width > channelSamples.Length) {
					//multiple pixels per second
					samplesPerPixel = 1;
					spacePerSample = picWave.Width / channelSamples.Length;
				} else {
					//multiple samples per pixel
					spacePerSample = 1;
					samplesPerPixel = channelSamples.Length / picWave.Width;
				}

				//calculate width of one second
				int pixelsPerSecond = (int)(waveSound.Format.Channels * waveSound.Format.SamplesPerSec * spacePerSample / samplesPerPixel);

				Bitmap bitmap = new Bitmap(picWave.Width, picWave.Height, PixelFormat.Format24bppRgb);
				Graphics graphics = Graphics.FromImage(bitmap);
				Pen pen = new Pen(Color.Black);
				graphics.Clear(Color.White);

				int absValue;
				for (int channelSamplesIndex = 0; channelSamplesIndex < channelSamples.Length; channelSamplesIndex++) {
					absValue = Math.Abs((int)channelSamples[channelSamplesIndex]);
					if (absValue > maxSampleValue) {
						maxSampleValue = (absValue > short.MaxValue) ? (short)(absValue - 1) : (short)absValue;
					}
				}

				float yOffset = bitmap.Height / 2;

				if (maxSampleValue != 0) { //not trying to display silence (all pixels == 0)

					float yScale = yOffset / maxSampleValue;

					float xPosition = 0;
					int pixelMaximum = 0;
					int pixelMinimum = 0;
					short currentSample;

					PointF previousPoint = new PointF(0, yOffset);
					for (int n = 0; n < channelSamples.Length; n += samplesPerPixel) {
						currentSample = channelSamples[n];
						pixelMaximum = 0;
						pixelMinimum = 0;

						for (int sampleIndex = n; sampleIndex < (n + samplesPerPixel); sampleIndex++) {
							if (currentSample > pixelMaximum) { pixelMaximum = currentSample; }
							if (currentSample < pixelMinimum) { pixelMinimum = currentSample; }
						}

						pixelMaximum = (int)(pixelMaximum * yScale);
						pixelMinimum = (int)(pixelMinimum * yScale);

						graphics.DrawLine(pen, previousPoint.X, previousPoint.Y, xPosition, yOffset + pixelMinimum);
						graphics.DrawLine(pen, xPosition, yOffset + pixelMaximum, xPosition, yOffset + pixelMinimum);
						previousPoint.X = xPosition;
						previousPoint.Y = yOffset + pixelMaximum;

						xPosition += spacePerSample;
					}
				}

				//show seconds
				int second = 0;
				Brush brush = new SolidBrush(Color.Blue);
				Font boldFont = new Font(this.Font, FontStyle.Bold);
				for (int n = 0; n < picWave.Width; n += pixelsPerSecond) {
					graphics.DrawString(second.ToString(), boldFont, brush, n, picWave.Height - 15);
					second++;
                }

				pen.Color = Color.Red;
				graphics.DrawLine(pen, 0, yOffset, bitmap.Width, yOffset);

				graphics.Dispose();
				picWave.Image = bitmap;
                picWave.Refresh();
			} finally {
				topLevelControl.Cursor = Cursors.Default;
			}
		}

		/// <summary>Stop playback, before closing the application</summary>
		public void Close()
		{
			PlayerStop();
		}

		/// <summary>Change the zoom factor, when the user leaves the zoom box</summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tstZoom_Leave(object sender, EventArgs e)
		{
			OnZoomFactorChanged();
		}

		/// <summary>Change the zoom factor, when the user leaves the zoom box</summary>
		private void OnZoomFactorChanged()
		{
			float newZoomPercent;
			if (float.TryParse(tstZoom.Text, NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture, out newZoomPercent))
			{
				this.ZoomPercent = newZoomPercent;
			}
		}

		/// <summary>Play the wave sound</summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tsbPlay_Click(object sender, EventArgs e)
		{
			if (waveSound != null) {
				playerStream = waveSound.CreateStream();
				this.player = new WaveOutPlayer(this.WaveSound.Format, playerStream);
			}
		}

		/// <summary>Stop playback</summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tsbStop_Click(object sender, EventArgs e)
		{
			PlayerStop();
		}

		/// <summary>Stop the player and close its stream</summary>
		private void PlayerStop()
		{
			if (player != null) {
				try {
					player.Dispose();
				} finally {
					player = null;
				}
			}
			if (playerStream != null) {
				playerStream.Close();
			}
		}

		/// <summary>Open a sound from a wave file</summary>
		/// <param name="fileName"></param>
		public void OpenFromFile(String fileName)
		{
			currentFile = fileName;
			this.WaveSound = new WaveSound(fileName);
		}

		/// <summary>Let the user select a wave file, and then open it</summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tsbOpen_Click(object sender, EventArgs e)
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Filter = "*.wav|*.wav";
			if (dlg.ShowDialog() == DialogResult.OK) {
				OpenFromFile(dlg.FileName);
			}
		}

		/// <summary>Save the displayed sound to a wave file</summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void tsbSave_Click(object sender, EventArgs e)
		{
			if (waveSound != null) {
				SaveFileDialog dlg = new SaveFileDialog();
				dlg.Filter = "*.wav|*.wav";
				if (dlg.ShowDialog() == DialogResult.OK) {
					waveSound.SaveToFile(dlg.FileName);
				}
			}
		}

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Corinna John
Software Developer
Germany Germany
Corinna lives in Hannover/Germany (CeBIT City) and works as a Delphi developer, though her favorite language is C#.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150520.1 | Last Updated 17 Apr 2006
Article Copyright 2005 by Corinna John
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid