In the last post the tune to ‘Brother
Jacob’ was generated using GoAudio. We started of by actually generating all the notes and their
corresponding frequencies, and mapping them to a corresponding string, such that when we played A4
we would get a frequency of 440
.
There were two problems with this from what I can tell, the first is that this approach is potentially repetitive. Each time we want to generate some music, we’re likely to generate a mapping of notes to frequencies. Having this as an external part of the code rather than the GoAudio library is the first problem.
The second problem is that this approach was not flexible nor easy to read. Yes, the mapping did work, but can you easily see which octave the notes are being generated in?
As a reminder, here is the original code:
|
|
Which octaves are generated here? Well, given that we find the frequency of middle C, which we calculated from
A440, we can tell that we are starting at C4
and moving up 24 semitones in the for-loop means we are
moving across two octaves. Each octave being 12 semitones, thus we are covering both the fourth and
fifth octave in this segment of code.
Improving generateNotes()
The first problem is easily solved, the code that we are examining in the following section is now covered by GoAudio. If you want to skip the rest of this post, you can just view this file.
The second problem, which was to deal with the readability and flexibility of the code is tackled next. First, rather than keeping a map of all possible frequencies for all octaves in all notes, we will calculate it on the spot. This is more wasteful of our CPU resources, but if a user wanted to keep the full spectrum available they could keep the map locally, no need to pollute the library with this.
Our botched together code from the last post does get us pretty close to a working solution here, To generate a frequency of a note in any octave, we have to change this line:
|
|
Here we are stuck in octave 4 by default (due to FR) and we stay in octave 4 for as long as i
is
below 12. There are 12 semitones in an octave, so when we try to generate the frequency for i = 13
we are generated it one octave higher, or 1 + 1/12
. This is the key to extending the formula to
work for any octave. As our reference will still be A440, we have to calculate it with an offset of
4 in mind. This gives us the following piece:
|
|
Now that we have this in place, we still need to figure out how to turn a note (A,B,..G) into something that we can plug into the formula. That’s easier than it sounds, a note here correlates to a certain semitone, which in the equation above is represented by (i). Remember that in the previous post we were literally iterating through all semitones, thus notes, to generate all their frequencies.
Thus, we add a mapping from note -> integer:
|
|
The sharps are denoted by #
, and the flats are denoted by b
. Thus c# = db = 4
.
Now that we have this system in place for the twelve semitones, we can plug it into the above
formula. This we might assume the code becomes:
|
|
Unfortunately, this will not be correct yet. The problem lies in how this musical scale works, a new
octave does not start on A, instead an octave starts on C and ends on B, hence the common spectrum is covered by C0..B8
.
I think this is just a historical curiosity of the musical scale commonly used in western music, but
I don’t know enough about the history to really know.
What this means for us is that we have to adapt the octave slightly depending on which note we pass
to the function. Concretely, for anything below C we will drop the octave by 1. Such that A,4
effectively calculates A,3
.
|
|
Rewriting Brother Jacob
Now we’re essentially ready to rewrite our tune from the previous
post, but I’ve added one more
convenience function to GoAudio. I thought it’d be handy if
we could just pass the note+octave as a string, so that we can say NoteFrequency("B4")
rather than
NoteFrequency("B",4)
. It’s just a small “quality of life” addition. This convenience function can
be seen in the ParseNoteToFrequency function.
Check this gist for an example of the updated code.
If you liked this and want to know when I write new posts, the best way to keep up to date is by following me on twitter.