Turning frequencies into notes in Python

Viewed 2516

I am trying to convert a frequency value into a note like an input of 400 hz printing "A4", but I don't want to write a complete frequency table in my code. Is there any way to accomplish this?

3 Answers

Based on a conversion function found on Wikipedia:

import math

def freq_to_note(freq):
    notes = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']

    note_number = 12 * math.log2(freq / 440) + 49  
    note_number = round(note_number)
        
    note = (note_number - 1 ) % len(notes)
    note = notes[note]
    
    octave = (note_number + 8 ) // len(notes)
    
    return note, octave

Example: freq_to_note(440) returns ('A', 4)

Another approach will be using available packages.
You can use librosa package:

import librosa

librosa.hz_to_note(440.0)
# will return ['A5']

Or a small package that I wrote, called freq_note_converter:

import freq_note_converter

freq_note_converter.from_freq(440).note
# will return 'A'

BTW, They both supports rounding, for example, 430 or 450 will still return 'A'.

This is all the musical information you need in order to be able to derive a formula that tells you how many halftones away from A4 a given frequency is:

  • A4 is 440 Hz (not 400).
  • The ratio between the frequencies of any two adjacent halftones is constant (e.g. A/Ab and Bb/A give the same number).
  • The ratio between the frequencies of two tones that are an octave apart is 2.
  • There are twelve halftones in an octave.

(The last two points will let you figure out what the constant ratio in the second point is.)

Alternatively, you could use this to write a program that simply "steps" up or down from A4 until it reaches (or passes) the given frequency.

In music theory, the usual definition is 12 notes in each octave, going up an octave doubles the frequency, and A4 is defined as 440 Hz. It is also important to note that the notes are spread evenly in an octave.

Using this definition we can write a function that when given a frequency returns the note and the octave.

Since going from an A4 to an A5 multiplies by 2, and we need the notes to be spread evenly, this means that moving up a note must go exactly a 12th of the way to doubling, so going from A4 to B4 must multiply the frequency by the 12th root of 2 (2**(1/12)).

Writing such a function isn't trivial, but it also isn't hard. I figured that while not giving the solution itself is sometimes better for learning, in this case it's best for me to show the solution and explain every part.

import math

def frequency_to_note(frequency):
    # define constants that control the algorithm
    NOTES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] # these are the 12 notes in each octave
    OCTAVE_MULTIPLIER = 2 # going up an octave multiplies by 2
    KNOWN_NOTE_NAME, KNOWN_NOTE_OCTAVE, KNOWN_NOTE_FREQUENCY = ('A', 4, 440) # A4 = 440 Hz

    # calculate the distance to the known note
    # since notes are spread evenly, going up a note will multiply by a constant
    # so we can use log to know how many times a frequency was multiplied to get from the known note to our note
    # this will give a positive integer value for notes higher than the known note, and a negative value for notes lower than it (and zero for the same note)
    note_multiplier = OCTAVE_MULTIPLIER**(1/len(NOTES))
    frequency_relative_to_known_note = frequency / KNOWN_NOTE_FREQUENCY
    distance_from_known_note = math.log(frequency_relative_to_known_note, note_multiplier)

    # round to make up for floating point inaccuracies
    distance_from_known_note = round(distance_from_known_note)

    # using the distance in notes and the octave and name of the known note,
    # we can calculate the octave and name of our note
    # NOTE: the "absolute index" doesn't have any actual meaning, since it doesn't care what its zero point is. it is just useful for calculation
    known_note_index_in_octave = NOTES.index(KNOWN_NOTE_NAME)
    known_note_absolute_index = KNOWN_NOTE_OCTAVE * len(NOTES) + known_note_index_in_octave
    note_absolute_index = known_note_absolute_index + distance_from_known_note
    note_octave, note_index_in_octave = note_absolute_index // len(NOTES), note_absolute_index % len(NOTES)
    note_name = NOTES[note_index_in_octave]
    return (note_name, note_octave)

So now frequency_to_note(440) returns ('A', 4), and frequency_to_note(740) returns ('F#', 5), which appears to be correct.

It's important to note though that this function doesn't care about which octaves make sense, so something like frequency_to_note(1) returns ('C', -4), because if we indeed had a piano, which usually uses octaves 1-7, and added 5 more octaves to the left, the C note in that leftmost octave would indeed be 1 Hz. So depending on your use case you might want to raise an exception at the end if the octave is not between 1 and 7.

Related