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.
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 "
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.
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);
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()
public void StartPlay()
public void StopPlay()
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
Variables represent the well-tempered waves, and variables with
_p suffix represent the Pythagorean sounds.
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
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.
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_Keyup with the help of
case structure, the program chooses the good sound (of course, we can choose well tempered or Pythagorean sound like in
mouseclick event) and call
StopPlay methods of the
button1.BackColor = Color.Silver;
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
This is the very first version of this example.