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:
int samples = (int)(format.SamplesPerSecond * format.Channels) ;
short[] buffer = new short[samples];
description.BufferBytes = buffer.Length * format.BlockAlign;
The sample calculating cycle:
for (int i = 0; i < buffer.Length; i++)
{
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);
}
public void StartPlay()
{
sec_buffer.Play(0, BufferPlayFlags.Looping);
}
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.FormatTag = WaveFormatTag.Pcm;
format.Channels = 1;
format.BitsPerSample = 16;
format.SamplesPerSecond = 44100;
format.BlockAlign = (short)(format.Channels * format.BitsPerSample / 8);
format.AverageBytesPerSecond = format.BlockAlign * format.SamplesPerSecond;
BufferDescription description = new BufferDescription(format);
description.ControlVolume = true;
Device device = new Device();
device.SetCooperativeLevel(new System.Windows.Forms.Control(), CooperativeLevel.Priority);
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.
if (checkBox1.Checked)
{
switch (ee.KeyCode)
{
case Keys.A:
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.