Introduction: Arduino Piano in C#

In this project, we will make a music keyboard controllable from any Windows computer from an application written in the C# programming language. The breadboard setup is very minimal. Where we will concentrate our efforts is in the code, which will allow notes to be played at the press of a button and songs to be played when a hotkey is pressed.

One added bonus I included is to create a simple light show to play along with our music notes for added effect.

What you will need:

1 Arduino Uno (or similar model)

1 breadboard (medium)

1 Piezo buzzer

1 LED (optional)

2 resistors (220 Ohms, 1 is optional)

Visual Studio Community (Go to the Download Page)

Step 1: Connect the Buzzer

Parts needed:

1 Piezo buzzer

1 220 Ohm resistor


We will first create a simple connection to a Piezo buzzer, included in many basic Arduino starter kits. This will serve as our "speaker" for the keyboard we are building. A buzzer is not as loud as a speaker might be, but for this project, it will do fine.The 220 ohm resistor protects the buzzer from being overloaded with current. You can use a lower ohm resistor to make the buzzer louder.

Simply connect the buzzer to one side of the breadboard. Bridge a resistor across to the other side and connect it to pin 3 on the Arduino.

Next, connect the ground to the Arduino.

That's it for this step!

Step 2: (optional) Connect an LED

Parts needed:

1 LED (any color)

1 220 Ohm resistor


For a super-low-budget light show, connect an LED! You could really take this step to another level, but I decided to hook up a simple LED that flashes as the notes in our songs are played.

First, hook up the LED on the breadboard, bridging the resistor from the long probe on the LED to the other side of the breadboard, like in the step where we connected the buzzer. Connect the resistor to pin 2 on the Arduino.

Then, connect the short side of the LED to ground, and we now have a spectacular light show to go along with our sound system.

Now we're ready to start coding!

Step 3: Coding the Arduino

First, we will create the code that actually takes keystrokes and outputs a corresponding sound, allowing you to play the Arduino like a piano, and also lighting up the LED if you decided to connect one.

The code is available for download, so you should be able to download the .ino file and run it directly.

A quick note about using the serial port to communicate between the computer and the Arduino: Run the code on the Arduino first, and then the C# code. Also, when running the code on the Arduino, don't open the Serial Monitor. This is because once the C# program opens, it essentially acts as the Serial Monitor, establishing a one-to-one connection with it. That means if you try to print your output, it will simply send it to the C# program and output it there. This is still ok to do, but when trying to use it like a keyboard, all of the output can get distracting.

Now, let's take a look at the Arduino code:

int speakerPin = 3;
int ledPin = 2;
String inData;

void setup()
{
    pinMode( speakerPin, OUTPUT );
    pinMode( ledPin, OUTPUT );

    Serial.begin( 9600 ); 
}

void loop()
{
    if( Serial.available() )
    {
        while( Serial.available() > 0 )
        {
            char received = Serial.read();
            inData += received;
            if( received == '1' )
                Mario();
            else if( received == '2' )
                StarWars();
            else if( received == '3' )
                FurElise();
            else
            {
                playNote( received, 200 );
                // Play the note, and then turn off the LED
                digitalWrite( ledPin, LOW );
            }
        
        } // while

        inData = ""; // Clear recieved buffer
    
    } // if available
    
} // loop



void playSong( char *notes, int *beats, int tempo, int numNotes )
{
    int beatCount = 0;
    for( int noteCount = 0; noteCount < numNotes - 1; noteCount++ )
    {
        if ( notes[noteCount] == ' ' )
        {
            // REST BETWEEN NOTES
            // Note: the rest between each note allows the note to play in its entirety.
            //    To add an actual rest, do: playNote( 'R', time );
            delay( beats[beatCount++] * tempo ); // rest
            digitalWrite( ledPin, LOW );
        }
        else
        {
            int duration = beats[beatCount] * tempo;
            
            // PLAY NOTE
            int freq = playNote( notes[noteCount], duration );

            /*Serial.println( "Note: " + (String) notes[noteCount] 
                + " (Frequency: " + (String) freq + "; Delay:" + beats[beatCount] 
                + "; noteCount: " + noteCount + "; beatCount: " + beatCount + ")" );*/
        }
        
        // pause between notes
        delay(tempo / 2);
        
    } // for
    
} // playSong



int playNote( char note, int duration )
{
    int frequency = 262;

    //Serial.print( "Now playing: " + (String) note + ";" );
    
    switch( note )
    {
        case 'A':       // c
            frequency = 131;
            break;
        case 'S':       // d
            frequency = 147;
            break;
        case 'D':       // e
            frequency = 165;
            break;
        case 'F':       // f
            frequency = 175;
            break;
        case 'G':       // g
            frequency = 196;
            break;
        case 'H':       // a
            frequency = 220;
            break;
        case 'U':       // b-flat
            frequency = 233;
            break;
        case 'J':       // b
            frequency = 247;
            break;
        case 'K':       // C
            frequency = 262;
            break;
        case 'L':       // D
            frequency = 294;
            break;
        case 'Z':       // E
            frequency = 330;
            break;
        case 'X':       // F
            frequency = 349;
            break;
        case 'C':       // G
            frequency = 392;
            break;
        case 'V':       // A
            frequency = 440;
            break;
        case 'B':       // B
            frequency = 494;
            break;
        case 'N':       // C5
            frequency = 523;
            break;
        case 'Q':       // Percussion sound
            frequency = 20;
            break;
    }

    if( note == 'r' || note == 'R' || note == ' ' )
    {
        delay( duration );
        digitalWrite( ledPin, LOW );
    }
    else
    {
        digitalWrite( ledPin, HIGH );
        tone( speakerPin, frequency, duration );
    }

    //noTone( speakerPin );

    return frequency;
}


// FUR ELISE
void FurElise()
{
    char notes[] = "Z L Z L Z J L K H A D H J D G J K D Z L Z L Z J L K H A D H J D K J H Z L Z L Z J L K H A D H J D G J K D Z L Z L Z J L K H A D H J D K J H J K L Z G X Z L F Z L K D L K J D Z D Z L Z L Z L Z L Z L Z L Z J L K H A D H J D G J K D Z L Z L Z J L K H A D H J D K J H ";
    //              Z L Z L Z J L C H V L H J L C J C L Z L|Z L Z J L C H V L H J L C J H Z L Z L Z J L C|H V L H J L C J C L Z L Z L Z J L C H V L H|J L C J H J C L Z C X Z L X Z L C L L C|J L Z L Z L Z L Z L Z L Z L Z L Z J L C H V L H J L C J C L Z L Z L Z J L C H V L H J L C J H
    int beats[] = { 1,1,1,1,1,1,1,1,3,1,1,1,3,1,1,1,3,1,1,1,1,1,1,1,1,1,3,1,1,1,3,1,1,1,3,1,1,1,1,1,1,1,1,3,1,1,1,3,1,1,1,3,1,1,1,1,1,1,1,1,1,3,1,1,1,3,1,1,1,3,1,1,1,2,1,1,1,2,1,1,1,2,1,1,1,3,1,5,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,1,1,3,1,1,1,3,1,1,1,1,1,1,1,1,1,3,1,1,1,3,1,1,1,9,1};
    int tempo = 100;
    int numNotes = sizeof( notes );

    playSong( notes, beats, tempo, numNotes );
}


// SUPER MARIO BROS.
void Mario()
{
                  //                        1             2               3
                  //1 3 5 6 8 1 2 3 1 3 5 7 1 2 3 4 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 7 7 8
    char notes[] = "Z Z r Z r K Z r C r G r K Q Q G Q D Q Q H r J r U H Q G Z C V r X C r Z K L J r";
    int beats[] = { 2,2,1,2,1,1,1,1,2,2,2,2,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1 };
    int tempo = 68;
    int numNotes = sizeof( notes );

    playSong( notes, beats, tempo, numNotes );
}


// STAR WARS
void StarWars()
{
    char notes[] = "A G F D S K G F D S K G F D F S A G F D S K G F D S K G F D F S R ";
    int beats[] = { 6,3,1,1,1,6,3,1,1,1,6,3,1,1,1,9,6,3,1,1,1,6,3,1,1,1,6,3,1,1,1,9,2 };
    int tempo = 100;
    int numNotes = sizeof( notes );

    playSong( notes, beats, tempo, numNotes );
}

In the loop() method, we can see that the Arduino listens for a signal on the serial port. Once it receives a byte, it interprets it as a character and based on the character, does something with it.

If the character is either a '1', '2', or a '3', it will call a method. Each of the methods implements the playSong() method, passing an array of notes, an array of beats, the tempo, and the number of notes to play to it. In C++, arrays are treated differently than in other languages, not storing its length or other information to it, so that is why we pass the number of notes to the method.

When the character received from the C# program over the serial connection is not one of the characters described above, the program tries to play a note corresponding to the character.

There is actually a "pitches.h" library that can be used to accomplish the same thing as the playNote() method, but I found that it is easier simply to assign a single character [corresponding to a character on the keyboard] to a note rather than assigning something that looks like "NOTE_AS3". This is simply my preference so that when figuring out songs, I could use the C# window to figure out the notes in a song, and just copy and paste those characters into the Arduino program.

Anyway, you can tweak the two programs to do anything you want, so have fun building something cool!

Step 4: Creating the C# Console App

Lastly, we will need a way to communicate with the Arduino via the Serial port. Open up Visual Studio and go to:

1. File > New Project...

On the left, you will see a column of languages you can create your project in.

2. Expand "Other Languages" and click on "Visual C#".

This will show a list of available types of C# programs. We need to build a Windows-only app, so select .NET Framework. This is so that we can use the serial connection I/O library.

3. Select "Console App (.NET Framework)" and name the app.

4. Click [Ok] to create the project.

Nex, we'll insert some code. In your Program.cs file, copy and paste the following code or download the zip file:

using System;
using System.IO.Ports;

namespace SimpleKeyboard
{
	class Program
	{
		private static SerialPort SerialPort; 
		static void Main( string[] args )
		{
			// Note: you will probably need to change "COM8" to your serial port:
						//portName, baud, parity, data bits, stop bit
			SerialPort = new SerialPort( "COM8", 9600, Parity.None, 8, StopBits.One );
			
			// Attach a method to be called when there
			//		is data waiting in the port's buffer
			if ( SerialPort != null )
			{
				SerialPort.DataReceived += new SerialDataReceivedEventHandler( DataReceived );
				// Begin communications
				SerialPort.Open();
			}
			
			// Now that the serial port is open, listen for keystrokes
			//		and send them to the Arduino
			Console.WriteLine( "Press ESC to stop" );
			ConsoleKey Key = ConsoleKey.Enter;

			while ( !Console.KeyAvailable && Key != ConsoleKey.Escape )	
			{
				try
				{
					Key = Console.ReadKey().Key;
					SerialPort.Write( Key.ToString() );
				}
				catch ( Exception )
				{
					Console.WriteLine( "Error..." );
				}
			}

		} // Main

		/// 
		/// Set this to your listener in case you want to receive data from
		///		the Arduino.
		///
		private static void DataReceived( object sender, SerialDataReceivedEventArgs e )
		{
			// Show all the incoming data in the port's buffer
			Console.WriteLine( SerialPort.ReadExisting() );

		} // DataReceived

	} // class

} // namespace

Step 5: Play the Piano!

Once all the code is saved to your Windows computer and your Arduino, you are ready to practice to become the next Mozart.

To start the communication:

  1. Make sure the code is started on the Arduino and the Serial Monitor window is not open before going to the next step.
  2. Once the code is running on the Arduino, start the C# application.
  3. Use the middle and bottom rows to play notes, or use the number keys to tell the Arduino to play a song.

Note:

If the Serial Monitor is open, this can interfere with the communication between the Arduino and the computer. Just be sure it is closed out before starting the C# program. I didn't write any code to send any feedback to the C# program, but this is possible by using Serial.println() from the Arduino. Using this, it would be possible to write a GUI in C# to show the notes being played, or whatever you wanted to do. Here, I kept it simple to be able to learn the basics of Serial communication between the computer and Arduino.

Now that you're done, go write the next big hit!