Sunday, April 14, 2013

Play the piano on your computer with Python

By Vasudev Ram

This program is meant for kids of all ages ...

The program, (shown below), lets you play music on your computer, something like a piano, by pressing the keys C D E F G A B, as in Western classical music.

I've written various versions of such a program in the past, starting from my first home computer, in different languages (BASIC, Turbo Pascal, Turbo C), for fun.

The program works on Windows and uses the Python winsound module. I got to know about the winsound module a while ago, and tried it then, and wrote a program like this.

To run this program, you need Windows and Python, and a working sound system; built-in speakers on a PC or laptop will work, but you will get better sound quality from external speakers.

The program is rudimentary and is not meant to simulate a full piano; that would be much more complex.

Here is the piano program in Python:


# Description: A simple program to make the computer act like 
# a piano. Plays a note for each of the keys C, D, E, F, G, A, B,
# according to the Western music middle octave.

# References:

# Author: Vasudev Ram -

import string
from time import sleep
from winsound import Beep
from msvcrt import getch

def play_note(frequency, duration):
    Beep(frequency, duration)

Compute all the frequencies for the notes C D E F G A B,
for the middle C octave, where note A = 400 Hz.

Middle octave note frequencies computed from A = 440 Hz,
using the info at
(Multiply a note frequency by the square of the twelfth root
of 2 to get the next higher note, and divide it by the same
to get the next lower note).

a = 440
# twelfth_root_2 is the 12th root of 2
twelfth_root_2 = 2 ** (1.0/12.0)
# tr2 is a shorter name for twelfth_root_2
tr2 = twelfth_root_2
# tr2sqrd is tr2 squared
# tr2sqrd is the factor by which to multiply (or divide) the 
# frequency of any note, to get the next higher (or lower) note
# in the same octave.
# E.g. multiplying the frequency for note A in the middle C octave,
# i.e. 440 Hz, by tr2sqrd, gives the frequency for the next higher
# note in the same octave, i.e. note B.
tr2sqrd = tr2 * tr2

b = int(round(a * tr2sqrd))

# C D E F G A B

g = int(round(a / tr2sqrd))
f = int(round(g / tr2sqrd))
e = int(round(f / tr2sqrd))
d = int(round(e / tr2sqrd))
c = int(round(d / tr2sqrd))

print "c =", c, "d =", d, "e =", e, "f =", f
print "g =", g, "a =", a, "b =", b

# Set up the notes dict mapping letter keys to notes to play
notes = {}
notes['c'] = c
notes['d'] = d
notes['e'] = e
notes['f'] = f
notes['g'] = g
notes['a'] = a
notes['b'] = b

print "Simple piano program in Python, by Vasudev Ram"
print "To play, keep on pressing any of these keys: c d e f g a b"
print "Make sure the Caps Lock light on your keyboard is off"
print "Press q to stop"
print "             The floor is yours, Maestro!"
print chr(7) # bell

duration = 300 # milliseconds
note = string.lower(getch())

while True:
    print note,
    if note in notes:
        play_note(notes[note], duration)
    elif note == 'q':
    note = string.lower(getch())


# And farewell tune ...

for note in ('c', 'c', 'd', 'd', 'g', 'a', 'b'):
    play_note(notes[note], duration)


for note in ('d', 'd', 'c', 'c', 'b', 'a', 'g'):
    play_note(notes[note], duration)
print "Encore!"
#--------------- EOF: ----------

You can run the piano program with the command:


It gives instructions on how to use it.

It's easy - just keep pressing any of the keys c d e f g a b, or q to quit.


Note 1: I computed the frequencies for the notes (C D E F G A B) from the Wikipedia link (in the code above), but there are multiple notes with almost the same name (even in the same octave), and I don't have much knowledge of music, so sorry if some of the notes are off key. Feel free to give suggestions in the comments, and I'll try to implement them.

Note 2: I just whipped up the code quickly, it can be improved, I may do that later.

Also, if you are into Python and music, check this post:

Pyknon music library for Python (MIDI)

I also came across a couple of other music-related Python modules, but that is a subject for another post.

- Vasudev Ram - Dancing Bison Enterprises


Unknown said...

In the C Major scale shouldn't there be a half step between E and F? In other words: F = tr2 * E. The same goes for the step between B and C, although you don't provide that interval. As a way of checking, see if C above middle C (C') is one octave above middle C (i.e. double the frequency) in your scale.

Vasudev Ram said...

Thanks for your comment.

As I said in the post, I don't have any knowledge of music theory, so I really didn't know which exact notes (or their frequencies) should be used, in order to be correct. I guessed.

So, about your comment, is my F value wrong or is E wrong? And same question for B and C?

Not sure which you mean, because I calculated some of the values "downwards", from higher to lower notes, as you can see from the code - from A, I calculated B (upwards, by multiplying by tr2sqrd), but for all the other notes, I calculated from A downwards (by dividing).

If you can specify which are the right frequencies for each note (by referring to the table, that'll make it easy for me to make the corrections).

Vasudev Ram said...

To make it more clear:

My calculations gave:

e = 311 f = 349

Which of the two is wrong, and what is the right value?

Actually I'm not sure of the meaning of your point about B and C, since they are at opposite ends of the scale. Please clarify.

Phil said...

I agree about half-tones. Try:

a = 440
b = int(round(a * tr2 ** 2))
c = int(round(a * tr2 ** 3))
d = int(round(a * tr2 ** 5))
e = int(round(a * tr2 ** 7))
f = int(round(a * tr2 ** 8))
g = int(round(a * tr2 ** 10))

Vasudev Ram said...

Not sure what you mean by "I agree about half-tones".

And, as I said, I don't know music theory, but doesn't the Western music scale go like this:

C D E F G A B ?

(from vaguely remembered lessons some time ago.)

If that is so, your formula above seems to start from somewhere in one octave and go into the next?

I wanted only one full octave, not part of one and part of another higher one.

phil said...

Sorry, I wasn't very clear. It's best to think in terms of semi-tones, of which there are 12 in an octave, so each semi-tone is 2**(1/12) higher than the next. The 12 semitones are:
C C# D D# E F F# G G# A A# B.
They are also known as
C Db D Eb E F Gb G Ab A Bb B, where "b" is the "flat" symbol.

So stepping from A to B requires TWO semitones, hence:
b = int(round(a * tr2 ** 2))
I avoided any rounding error by performing all calculations relative to the base of A, eg:
c = int(round(a * tr2 ** 3)) # 3 semitones above A.

Incidentally, the scale you constructed is a minor scale, "A minor", because it starts on A, not C, and uses all white notes. The intervals for a minor scale are:
2 1 2 2 1 2 2
and for a major scale:
2 2 1 2 2 2 1

The steps in my code: 2,3,5,7,8,10 correspond to the minor intervals 2 1 2 2 1 2 2.

An A Major scale would be:
a = 440
b = int(round(a * tr2 ** 2))
c = int(round(a * tr2 ** 4))
d = int(round(a * tr2 ** 5))
e = int(round(a * tr2 ** 7))
f = int(round(a * tr2 ** 9))
g = int(round(a * tr2 ** 11))

but the notes labeled c, f, g would then really be C#, F# and G#.

Have I confused you sufficiently yet :-)

Feld said...

Okay, music theory 101:

Look at a piano keyboard. Every octave has 12 keys, five black and seven white. The interval between two adjacent keys is the mentioned factor of 2^(1/12). That means if you skip over one then it is 2^(2/12), and a full octave, or twelve steps makes it a full doubling of frequency.

You only wanted the white keys, so often you have to use a step of two, but not always! The counter-intuitive thing is that a whole tone is not always the same as two half-tones!

But look at a picture of a piano keyboard and it is clear what the steps are supposed to be: 2^(1/12) per key.

By the way, does anyone know how to get sounds like this on a Linux platform? I can't find anything similar to winsound. I am trying to make an electronic piano with a Raspberry Pi computer and found this blog while googling for help.

Vasudev Ram said...

@Feld: Thanks for your comment. It does make things somewhat more clear to me.

And for Linux, you could try PyAudio and Pyglet:

Not sure if they can do what winsound does, but you could check. Then there are also other Linux sound libraries / tools like ALSA, libsndfile, etc.

Also, a belated thanks to Phil and others who replied earlier.

小馬哥 said...

"for the middle C octave, where note A = 400 Hz."

Should this be 440Hz instead?

Vasudev Ram said...

Probably. Thanks for the catch.