My piano lessons have had me doing a bunch of ear training to learn absolute pitch. I wanted a way to practice with audio only, so I could continue to do other things while the training program played in the background.
We’ll assume the existence of a function that can take a frequency in Hertz and play the corresponding tone for a second or two. (It’s relatively easy to output pure sine waves, but you could also do something fancy like connect this to a VST plugin to output notes on whatever instrument you want).
=440.0) play_frequency(hz
An octave spans between \(x\) and \(2x\) Hz. Because there are 12 notes per octave, the ratio between successive notes is \(\sqrt[12]{2}\), or about \(1.0595\).
= 2 ** (1 / 12) ratio
The “reference pitch” I had been learning was bass C. Because I knew that 440 Hz was an A, and could tell which octave was playing by ear, I found that the closest A was at 110 Hz (divide by two a couple times). Then, we can step up from A to A♯ to B to C by successive multiplications of our ratio.
= 110.0 * ratio * ratio * ratio * ratio bassC
I also wanted the program to be able to say note names out loud. The
say
command makes this relatively easy, although sometimes
it pronounces things in strange ways. This is easy enough to correct for
after some trial and error (using the spelling ayy
instead
of a
which say
pronounced more like
uh
).
= [
names 'c',
'c sharp d flat',
'd',
'd sharp e flat',
'e',
'f',
'f sharp g flat',
'g',
'g sharp, ayy flat',
'ayy',
'ayy sharp b flat',
'b'
]
We can then quite easily get a list of all frequencies with their corresponding names
= bassC
freq = []
notes for name in names:
notes.append((name, freq))*= ratio freq
The last step was to set up some timings and useful features (such as playing the reference note once every five notes) and play random notes to test our knowledge. There we have it, an endless ear training program in ~20 lines of code.
= 0
i while True:
if i % 5 == 0:
f'say "reference bass c"')
os.system(
play_frequency(bassC)1)
time.sleep(= random.choice(notes)
name, freq
play_frequency(freq)0.5)
time.sleep(f'say "that was {name}"')
os.system(2)
time.sleep(+= 1 i