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

Simple Keyboard Electric Organ

0.00/5 (No votes)
15 Oct 2014 1  
This is a simple electric organ (piano) to teach how to synthesize sound with directsound.

Introduction

This article will show how to synthesize simple sound with mathematical formulas, and play it without using any sound files, i.e., wav or mp3. All sound waves are synthesized with the help of Math.Sine function. This is the very first step, if somebody wants to create his/her own synthesizer. I use two sound systems: the everywhere-used "well-tempered" or called "equal-temperated" sounds, and early used "pythagorean-diatonic" sound systems. With this program, everybody can hear the difference between two sound systems. This electric piano can play multiple sounds in the same time. The sounds can be played with mouse, or computer keyboard. I wanted to make this program as simple as possible, so this program is a fun, please nobody think of any complex thing.

Background

To understand the basics of this, it's very useful to study some basics of working of Directx-directsound. I found a very useful article here. This is a working example, and I could start with the help of it. Some other basics of working directsound systems can be found in the official Microsoft's pages here. There are many articles about of directsound, but I needed specially directsound with .NET. Many articles use C++, so there aren't good for me at this time.

The other thing is everybody can ask how could I calculate the frequencies of the sounds. There are also many-many articles in the net, or lexicons. I think, everybody knows the basic 'A' sound's frequency is 440Hz (cycle per second) (In medieval ages there were 415 and others...). One octave higher sound's frequency is multiplicated with 2, one octave below is divided with 2. To calculate the "well tempered" sounds frequency is relative easy: the well tempered frequencies make a geometric progression with quotient 12-th root of 2. This is a constant approximately 1.05946. So, for calculating, we have to multiply the frequency with the first, second, third, etc. power of this constant. All this is well written in this article. The other, the Pythagorean sound system is more difficult. Some musical qualification is needed, i.e., to understand what does a quint, or an octave mean... And what is quint-round.

So, I don't want to make a singing or music lesson, (I'm not a theoretical musician) but I offer to read these articles about Pythagorean tuning or Chromatic scale can be also interesting. Both articles are understable, I think not only for musicians.

Using the Code

Lets' look into the machine. First, I made class: the name is Sound. Each sound is a representative of this class. The class has two constants, the normal A sounds frequency, and the quotient of the equal tempered frequency's geometric progression. There are static, because this constant is used in the class not only in representatives. The class has a "SecondaryBuffer" typed private property this is the "array" that will contain the synthetized sound samples. The class's constructor can calculate the sound samples, and puts into the wave-samples into the SecondaryBuffer. (For understanding what is secondary buffer, you have to understand the working of directsound, so please read articles that I offered before). For calculating, I use Sine mathematical function in a cycle.

Some preparing:

//How many samples we need
int samples = (int)(format.SamplesPerSecond * format.Channels) ;
//Make a short-array with length of number calculated before.
short[] buffer = new short[samples];
//calculate how many bytes we need into buffer
description.BufferBytes = buffer.Length * format.BlockAlign;

The sample calculating cycle:

//cycle to calculate sine wave sound from 0 to number of samples
  for (int i = 0; i < buffer.Length; i++)
            {
             //calculate the i-th member of array with Sin function. multiplicate with 2 will make higher amplitude of voice.
                buffer[i] = (short)(AMPL * Math.Sin(2 * Math.PI * i * freq / format.SamplesPerSecond));
            }

Buffer is an array of short, then I write buffer into secondary buffer:

sec_buffer.Write(0, buffer, LockFlag.None);

Last, sound class has three methods, to start and to stop the sound, and play a short sound that I use at mouse-click event.

public void ClickPlay()
{
    sec_buffer.Play(0, BufferPlayFlags.Default);
}

//Let's start to play sound by button pressing
public void StartPlay()
{
   sec_buffer.Play(0, BufferPlayFlags.Looping);        
}
//Let's stop the sound by button releasing
public void StopPlay()
{
    sec_buffer.Stop();
}

If somebody wants to make a medieval, or any kind of "exotic" sounded piano: set the NORMALA constant to 415 (or any other value that you want), and run the program. The sounds will be consistent - but not "normal".

Then, let's look at the Form. Each sound is a global representative of Sound class:

Sound c;
Sound cis;
Sound d;
Sound dis;
...

Variables represent the well-tempered waves, and variables with _p suffix represent the Pythagorean sounds.
The Form_Load event initializes all of sounds. When the form is ready, then all voices have calculated, when I used the new keyword to create (construct) the class. Now, the program is ready to catch the keyboard or mouseclick event on piano-keyboard. I formatted the keyboard from simple buttons, but I had to pay attention to the order of placing buttons. The second button will cover the first. So that is the reason why the order of button numbers (button 1..2..15 etc.) are a bit confusing. First, I made some preparation to initialize directsound like below. For understanding details, please read articles about directsound that I offered in the "Background" section.

WaveFormat format = new WaveFormat();
//format tag=pcm
format.FormatTag = WaveFormatTag.Pcm;
//one channel
format.Channels = 1;
//How many bits per sample
format.BitsPerSample = 16;
//how many samples per second of the voice. 44100 like CD quality
format.SamplesPerSecond = 44100;
//Block align calculating
format.BlockAlign = (short)(format.Channels * format.BitsPerSample / 8);
//Bytes per sec calculating
format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;

//Create a buffer description with the waveformat
BufferDescription description = new BufferDescription(format);
//Can control the volume
description.ControlVolume = true;

//Defines a Directsound device
Device device = new Device();
//Assign as a new windows control
device.SetCooperativeLevel(new System.Windows.Forms.Control(), CooperativeLevel.Priority);

//Now create the sound-class members. Each sound is a member of Sound class. 
//In Sound's constructor  calculates the exact numbers of sound-samples.
//First creating the well temperated sounds, then Pythagorean.

//********************* Well temperated sounds *************************

//The sounds frequencies are members of a geometric progression with quotient is TEMPER (see below). And I calculate each frequency with multiplicate the  powers of TEMPER.
c = new Sound(Sound.NORMALA / Math.Pow(Sound.TEMPER, 9), format, description, device);

The last row means: I create the (well-temperated) C sound which frequency is equal the A sounds frequency (440Hz) divided by the 9-th power of quotient, where the quotient is 12-th root of 2. It is simple, isn't it?

Every button has a mouseclick event. In mouseclick event with help of if structure, I choose the appropriate sound, and call the sound (class-member's) ClickPlay method. With checkbox1 and 2, we can choose which sound system we want to use. We can choose both of them: in fact the difference can be heard best in this situation.

if (checkBox1.Checked)
{
    cis.ClickPlay();
}
if (checkBox2.Checked)
{
    cis_p.ClickPlay();
}

Last, I have to catch the keyboard event. At key_down event, the sound will start, and key_up will stop. In normal case, we can't catch the Forms' key events, just the key-events of components, i.e., textbox, etc. which can have focus. But the Form has no focus. The problem is solved in this line in Form-Load event:

this.KeyPreview = true;

After this line, we are able to catch the key events. In Form_Keydown, and Form_Keyup with the help of switch-case structure, the program chooses the good sound (of course, we can choose well tempered or Pythagorean sound like in mouseclick event) and call StartPlay or StopPlay methods of the Sound-class.

//Check whether temperated or diatonic is selected
if (checkBox1.Checked)
{
    //Select the key-events what to do
    switch (ee.KeyCode)
    {
         //In case of A button pressed down, then C sound will start...
        case Keys.A:
            //and the button's colour let's be grey
            button1.BackColor = Color.Silver;
            c.StartPlay();
            break;

The Key_Down and Key_Up eventhandlers are really similar: first start the sound, second stops. And of course, both of them recolorize the button. The Key_down will make silver color, the Key_up makes back the original black or white color.

Points of Interest

With this article, you can learn:

  • How to use Directx/Directsound
  • How to synthesize sounds without using wav or mp3 files, just with the help of sine function
  • Last, but not least, some theory of music

History

This is the very first version of this example.

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