Click here to Skip to main content
Click here to Skip to main content

Compose sounds from frequencies and visualize them

By , 17 Apr 2006
 

Introduction

Have you ever wondered why the same note sounds differently when played on different instruments? Do you want to know how sound can become visible? Welcome to the beginner's wave explorer!

With this little application you can compose sounds from scratch by adding many frequencies. You can listen to your sound and add sine waves until you get the desired result. In addition to a common wave display, you can watch the sound change in synaesthesia mode. This alternative visualization creates one bitmap from the whole wave, which you can scale and color to see the sound move and grow.

You might wonder why I wrote the WaveMixer. There are two good reasons: Fun and curiosity. For another sound application, I needed a disturbing noise including a specific frequency. When I realized that my little knowledge about PCM wave was too superficial to generate natural sounding noises, I sacrificed a weekend and made up this nice tool. It is a comfortable start into the wonderful world of pulse code modulated waves.

The sounds are played using the code from A full-duplex audio player in C# using the waveIn/waveOut APIs.

The features and how they work

Your first experience with waves

Before we start composing a sound, we need an empty wave. WaveMixer starts with a fresh and clean wave of five seconds. If you want a shorter or longer sound, enter the length in seconds and create it:

Nothing has happened until now; the new wave has only been initialized. These four lines did that:

//define sampling rate, sample size and number of channels
WaveFormat format = new WaveFormat(44100, 16, 2);
//initialize the samples
short[] samples = 
  new short[(int)(format.Channels * format.SamplesPerSec * numNewWave.Value)];
//load the empty wave into the display control
WaveSound waveSound = new WaveSound(format, samples);
waveControl.WaveSound = waveSound;

Let's create an A! a' is defined as 440Hz, so this will be the first frequency to add. Enter the frequency of a', a maximum amplitude for the sine wave between 1 and 32767, and the time at which the wave shall be added:

The result looks boring and sounds just the same. Left and right channels contain the same beeeeep:

The synaesthesia view is not much better at this point, but you might try a few different image sizes to see how the result changes:

PCM stands for Pulse Code Modulation and means that there are no wave functions or exact descriptions (as you probably know from the MIDI standard), instead samples are taken from the actual wave. A .wav file contains loads of snapshots, the WaveOut API sends them so the speakers so that you can hear something similar to the original wave. You have just created such a set of wave samples from scratch, and here is the code behind the buttons. It calculates the indexes of the first and last affected samples and then mixes the new sample values into the existing ones.

/// <summary>Adds a sine sound of a specific frequency.</summary>
/// <param name="waveSound">The sound that's being edited</param>
/// <param name="frequencyHz">Frequency of the new wave in Hertz.
/// </param>
/// <param name="offsetSeconds">Starting second of the new wave.
/// </param>
/// <param name="lengthSeconds">Length of the new wave in seconds.
/// </param>
/// <param name="amplitude">Maximum amplitude of the new wave.
/// </param>
public void AddWave(WaveSound waveSound,
    float frequencyHz, float offsetSeconds,
    float lengthSeconds, int amplitude)
{
    //get the existing wave samples
    short[] samples = waveSound.Samples;

    //interval for 1 Hz
    double xStep = (2 * Math.PI) / waveSound.Format.SamplesPerSec;

    //interval for the requested frequency = 1Hz * frequencyHz
    xStep = xStep * frequencyHz;

    long lastSample;
    double xValue = 0;
    short yValue;
    short channelSample;

    long offsetSamples = (long)(
        waveSound.Format.Channels
        * waveSound.Format.SamplesPerSec
        * offsetSeconds);

    //if the beginning sample exists
    if (offsetSamples < samples.Length)
    {
        lastSample = (long)(
            waveSound.Format.Channels
            * waveSound.Format.SamplesPerSec
            * (offsetSeconds + lengthSeconds) );

        if (lastSample > samples.Length)
        { //last sample does not exist - shorten the new wave
          lastSample = 
              samples.Length - waveSound.Format.Channels + 1;
        }

        //for all affected samples
        for (long n = offsetSamples; n < lastSample; 
                               n += waveSound.Format.Channels)
        {
            //calculated the next snapshot from the sine wave
            xValue += xStep;
            yValue = (short)(Math.Sin(xValue) * amplitude);

            //mix the value into every channel
            for (int channelIndex = 0; 
               channelIndex < waveSound.Format.Channels; channelIndex++)
            {
                channelSample = samples[n + channelIndex];
                channelSample = (short)((channelSample + yValue) / 2);
                samples[n + channelIndex] = channelSample;
            }
        }
    }
}

Before I explain the visualization, let us make the wave sound a bit more natural. Add 220 Hz (a), 880 Hz (a'') and other octaves to the wave; remember that an octave is frequency*2 or frequency/2:

The result looks more interesting and sounds a little better. Anyway, one note is not yet musical; we need something like an elementary melody. Add another wave of 440 Hz, but let it begin at second 1 and last for only half a second. Look and hear how the sound changes when you add short waves here and there:

Staring at a black and white graph all the time is boring, you can as well observe your wave's bitmap:

Now you'll find that the sound is quite harmonic, but still not natural. What do we need? Dust and dirt for our samples, random waves in varying frequencies! Instead of the exact frequencies, try adding a group of waves. The frequencies should be inside certain limits to keep them close to a' of 440 Hz.

After a few hundred random waves close to 440 Hz, I recommend you to add one exact wave from second 0 to the end, with a high amplitude of at least 30000. This will make the strange noise clearer again. The result sounds fascinating and looks amazing:

In the colorful view you can see how the last, exact wave dominates the sound and leaves only little space for the disturbances that we added before:

The synaesthesia mode

For a long time I've wondered about the colours noises really have. Everybody sees the same sound in different colours, most people even see nothing at all, and they say they can only hear it. The direct translation of pulse codes into pixels does not work, because there are three colours (red, green and blue), but usually only two channels (perception of left and right ears). Which colour belongs to which channel? I'm afraid there's no general answer...

I decided to leave the colour decision to the user. Instead of mixing both channels into one pixel (e.g. red for left, green for right) the WaveMixer paints them next to each other. That means, every sample gets represented by two dots: Left pixel and right pixel. Just as the samples are played one after another, WaveMixer paints the pixels pair by pair. Where will the next row begin is defined by the dimensions of the bitmap. I zoomed the wave picture close enough, so that you could focus every single pixel-pair and try to "hear with your eyes". ;-)

If red, green and/or blue is used for a channel, it can be configured in the user interface. You already know those checkboxes. The actual values of the chosen colour components result from the sample's value. Sadly, the Int16 wave samples had to be reduced to unsigned byte values:

//get the factor that reduces the highest sample to 127
float scale = 127f / maximumSampleValueInTheWholeWave;

//scale one sample
byte scaledSampleValue = (byte)(sampleValue * scale);

//mix the sample's colour
pixelColor = Color.FromArgb(
    channelColors[channelIndex].IsRed ? scaledSampleValue : 0,
    channelColors[channelIndex].IsGreen ? scaledSampleValue : 0,
    channelColors[channelIndex].IsBlue ? scaledSampleValue : 0);

Of course, the picture does not have to be as large as the sound. If you choose dimensions that contain less pixel-pairs than there are samples to visualize, the samples are packed into blocks, and the loudest sample of every block defines the colour:

int samplesPerPixel = 1;
if (countPixels < countSamples)
{
    samplesPerPixel = Math.Ceiling(countSamples / countPixels);
}

How this block size is applied to squeeze many samples into one pixel is explained in the method WaveUtility.PaintWave in the source file. But before you dive into the code, let me show you a cool example.

Fun with the Windows system sounds

Most Windows editions contain a lot of tiny wave files in [installDrive]\[installPath]\Media, usually the path is c:\windows\media and one of the files is notify.wav. It looks funny even in the wave view:

Now, switch over to synaesthesia view and enter a picture width/height of both 250. Alternatively, width=100 and height=200 are also not bad.

Isn't that amazing? Notify.wav looks really cool for such a well known, boring sound:

By the way...

Before you go on playing with the weird waves, have a close look at the frequencies of these notes:

c' d e f g a h c' d' e' f' g' a' h'
132 148,5 165 176 198 220 247,5 264 297 330 352 396 440 495

and remember that a C-Dur accord is C-E-G. You could mix this accord by adding the following waves:

  1. 264 Hz, Amplitude 30000
  2. 330 Hz, Amplitude 20000
  3. 396 Hz, Amplitude 10000

Wait and see ... or hear, it's all the same!

License

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

About the Author

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

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionNull Ref in waveOutOpenmember#teve20 Jan '12 - 12:21 
I have converted to VS2010 and built with no problems.
When I run the program I get an exception when I try to play a sound file.
I copied the file from windows media "Speech Off.wav" to a tmp dir because I did not have rights to open it in that folder. I pres play and get a null reference exception. Not sure why though.
 
Object reference not set to an instance of an object.
 
at WaveMixer.WaveNative.waveOutOpen(IntPtr& hWaveOut, Int32 uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, Int32 dwInstance, Int32 dwFlags)
at WaveMixer.WaveOutPlayer..ctor(WaveFormat format, Stream audioStream) in C:\Users\steve\Documents\Visual Studio 2010\Projects\WaveMixer_src\WaveOut.cs:line 159
at WaveMixer.WaveDisplayControl.tsbPlay_Click(Object sender, EventArgs e) in C:\Users\steve\Documents\Visual Studio 2010\Projects\WaveMixer_src\WaveDisplayControl.cs:line 248
at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ToolStrip.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
at WaveMixer.Program.Main() in C:\Users\brailsford.steve\Documents\Visual Studio 2010\Projects\WaveMixer_src\Program.cs:line 17
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
GeneralExcellent example. Thanksmemberyapple29 Nov '10 - 11:06 
This is an excellent example to start.
Generalimportantmemberseka20810 Dec '09 - 6:39 
after greeting
iwant to catch on .dll or source code
to generate sound map from image
for using it in project for blind person
my email:seka208@yahoo.com
thanks for you
GeneralProblem when running.memberweedweaver17 Nov '08 - 2:58 
I am having problems running the code. it bombs out on:
 
"zoomPercent = float.Parse(tstZoom.Text, NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture);"
 
I don't know if it is anything to do with my culture settings ("en-GB")?
 
The error thrown is: Input string was not in a correct format.
 
Cheers
GeneralRe: Problem when running.memberweedweaver17 Nov '08 - 3:08 
dont worry, it was the value of the text box (tstZoom.Text) it was set to 0,5 rather than 0.5
 
Thanks
GeneralRe: Problem when running.memberCorinna John17 Nov '08 - 6:48 
Hi,
this is a known problem. I cannot start the project with "0.5" in the box, because I get the same error that you get with "0,5". It's a stupid side effect of the system language settings... Frown | :(
 
This statement is false.

Generaldrawing sound wavememberhemity14 May '08 - 8:29 
please i need help in record and draw the sound wave in the same time i entered my voice
Generalplease help mememberashenayeemruz10 May '08 - 21:24 
hello ,
I want to determine freqency of sound that record from sound cart.
how can i do it?
GeneralRe: please help mememberCorinna John11 May '08 - 10:47 
Hello,
I guess this site can help you:
http://groovit.disjunkt.com/analog/time-domain/audio.html[^]
 
This statement is false.

GeneralGreatmembererfi12 Feb '08 - 6:59 
thank you. this is a great article
QuestionCompiled versionmemberToneyE30 Jan '08 - 3:20 
Looks interesting, could anyone please compile this tool and offer the link to the zip here?
Questionquestion about wav usingmemberheadburster21 Aug '07 - 23:33 
Hello,
 
I have a question about wav using. I am beginner in C#, I never used that language before...
Your code is very interesting, but unfortunally for me, he doesn't give me keys for the problem I have. In fact, I want to create an FFT spectral diagram from a wav file. I find the Exocortex.DSP dll which work fine, but I just need to "transform a wave file in a float tab". I am sure it exists a very simple C# functionnaly to do that, but I can't find it...
 
Can you help me?
 
Thanks in advance
 
Matthieu
GeneralMP3 or other audio formatmemberMichael Sync5 Jun '06 - 23:40 
Good works..
Just one suggestion..
It'd be great if MP3 and other audio formats can be played with your player.
GeneralOne ErrormemberMichael Sync5 Jun '06 - 23:15 
I got this error.. "Input string was not in a correct format."
GeneralRe: One ErrormemberMichael Sync5 Jun '06 - 23:20 
Other thing is that Im not able to place the wave ctl on the form..
-Here is the error msg -------------
---------------------------
Microsoft Visual C# 2005 Express Edition
---------------------------
Failed to create component 'WaveDisplayControl'. The error message follows:
 
'System.FormatException: Input string was not in a correct format.
 
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
 
at System.Number.ParseSingle(String value, NumberStyles options, NumberFormatInfo numfmt)
 
at System.Single.Parse(String s, NumberStyles style, NumberFormatInfo info)
 
at System.Single.Parse(String s, NumberStyles style, IFormatProvider provider)
 
at WaveMixer.WaveDisplayControl..ctor() in C:\Excel\WaveMixer_src\WaveDisplayControl.cs:line 97'
---------------------------
OK
---------------------------
 
The steps that I performed..
1) Download the project from code project.
2) Extract the file
3) Open it Visual Studio 2005 C# Express Edition.
4) Run the application.
Observe: I got the error..
5) Add another form.
6) Place the user control on the form.
Ob: the error comes out.
GeneralRe: One ErrormemberMichael Sync5 Jun '06 - 23:30 
Oh.. I got the solution..
 
I got the error in that line.
>>zoomPercent = float.Parse(tstZoom.Text, NumberStyles.AllowDecimalPoint, CultureInfo.CurrentCulture);
 
The value of tstZoom.Text is "0,5".
tstZoom.Text = "0,5"
 
I think that this value can't be parse to float in my machine.. So, I have to change this text("0,5") to "5" .. After that. it's working fine..
 
Thanks..
 

GeneralRe: One ErrormemberCorinna John6 Jun '06 - 3:17 
Sorry Wink | ;)
That's one of .NET's internal localisation jokes.
On my machine the "." cannot be parsed, so I had to use ",".
 
_________________________________
Please inform me about my English mistakes, as I'm still trying to learn your language!

GeneralRe: One ErrormemberMichael Sync15 Jun '06 - 7:44 
Oki.. Smile | :)
Which OS are you using? Which language?
 
Do you hav any plan to support MP3 or other formats?

GeneralRe: One ErrormemberCorinna John15 Jun '06 - 9:29 
MP3? Well, not really. Not at the moment.
When I'll again suffer from bad activity attacks and code addiction, maybe I'll try and add it. Wink | ;)
 
The issue with the the "." comes from my Windows language. I use a German XP Home with an English C# Express. The system language is German, so the decimal separator is "," and .NET refuses to recognize other separators.
 
_____________________________________________________________________________
I don't expect too much, all I want is your vote for Halbsichtigkeit.

GeneralRe: One Errormember521quang30 Dec '07 - 21:43 
I want to know why the value can't be parse to float in my machine.
Is there a common solution to solute this question?
thanks Big Grin | :-D
GeneralRe: One Errormemberrobajob22 Mar '08 - 0:55 
In case anyone's still looking for an answer to this, the solution for me was to open WaveDisplayControl.cs in the Designer and change tstZoom.Text from "0,5" to "0.5".
GeneralGreat articlememberPhil J Pearson9 May '06 - 4:23 
Thanks Corinna! Rose | [Rose] It's a fascinating article, even though I had no prior interest in the subject.
 
This is not criticism but a little helpful information. Your English is extremely good and I think you deserve a little extra advice on terminology:
 
1. C-Dur in English is C-Major.
2. By 'accord' you mean 'chord'.
3. The note you call 'h' is 'b' in English.
 
You probably knew that anyway, but I just thought I'd mention it in case it helps someone.
 
Regards,
 
Phil
 
-- modified at 11:02 Tuesday 9th May, 2006
GeneralRe: Great articlememberCorinna John9 May '06 - 5:40 
Hi Phil!
 
me.Memory.Activate();
 
Thanks a lot for the remedial teaching on musical vocabulary.
Now I remember that the english terms were different! D'Oh! | :doh:
 
Do you think I should submit a fixed article?
Well, you were able to decipher the terms ... and all others didn't even notice it. Wink | ;)
 
Regards,
 
coco
 
_________________________________
Please inform me about my English mistakes, as I'm still trying to learn your language!

GeneralRe: Great articlememberPhil J Pearson9 May '06 - 10:55 
I certainly wasn't suggesting that you should update the article.
I find that the minor mistakes made by non-native English speakers add to the interest (even charm) of their writing, provided they don't detract from understandability. Your "mistakes" are certainly minor and, as you can tell, don't impede understanding.
 
Regards,
Phil

GeneralSpectrogramsmemberJonas Beckeman25 Apr '06 - 6:05 
Funny, stumbled on this article just as I've gotten into the "sound as bitmap" stuff (since that's how you have to store them if you want to do GPU processing on them).
 
Just a tip: for visualization of sounds, a spectrogram is very nice. I haven't seen any open C# projects doing that, so there's a challenge for all you codeprojecteers! (You can have a dirty old C++ project of mine if you want.)

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

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