In the previous chapters you learned everything you needed to know about different periodic waveforms.
Now that you know how it sounds, how does it look like? In musical notation it appears like this:
In scientific pitch notation it’s referred to as A4 because of it’s occurence in the 4th octave. In music an octave is the interval between a pitch and another with half or double it’s frequency.
A4 has a frequency of 440 Hz. If we double the frequency (880), we still hear the note A but one octave up thus called A5. An interval always starts with the note C. According to this, the first note in the 4th octave is C4. In Western countries such an interval is divided into 12 equal parts.
Please note, it’s equal on a logarithmic scale, which means it’s nonlinear. In other words, we can’t simply divide the difference in frequency between 880 and 440 by 12 to get the smallest interval which makes up the semitones.
Instead we use the Twelfth root of two. To get the next semitone after A4, which is A♯4|B♭4, we multiply 440 by 21⁄12 and get 466.1637615180899.
In AS3 we can calculate it using the Math.pow() function.
1 |
trace(440 * Math.pow(2, 1 / 12)); |
Such a system of tuning is called equal temperament because of the same intervals between adjacent pitches.
Cool! We know that in this system an octave is made up of 12 semitones, what are the notes?
On the 12-tone chromatic scale, it looks like this:
# is pronounced ‘sharp’. The 4 refers to the 4th octave in scientific pitch notation.
But wait! The exact same scale could also be written like this:
I can’t believe it! Let’s have a closer look by comparing the second semitone in both notations, pretending to be the same tone!
Obviously it isn’t the same note, it’s even on another line! What we see here is a problem caused by the equal temperament. On an equally tempered instrument like a piano or the guitar C♯ and D♭ share the same key/fret! If it’s tuned to another system, these two notes indeed sound slightly different because the pitch of D♭ is a little bit higher.
No problem though since the equal temperament is the base for our tutorial.
We can safely say: C♯=D♭, D♯=E♭, F♯=G♭, G♯=A♭, A♯=B♭.
For simplicity I’ll refer to those particular notes by it’s ‘sharp’ name only, e.g. C♯4.
As you just heard again, I was talking about C4 being the first note on the 12-tone chromatic scale in the 4th octave in scientific pitch notation. In this notation the octaves range from -1 to 10, thus C-1 is the lowest while B10 is the highest note.
We can calculate the frequency of both tones using the Twelfth root of two.
A4, which frequency is 440 Hz serves as our base once more. It’s the tenth note on the chromatic scale, which means we’re 3 semitones away from the first note in the fifth octave. Five octaves are missing to reach the tenth octave and a single octave is made up of 12 semitones. 11 semitones are missing to go from C10 to B10.
We’ve enough information to calculate the frequency for B10!
1 |
trace(440 * Math.pow(2, (3 + (5 * 12) + 11) / 12)); |
Let’s go into the opposite direction and find out the frequency of C-1.
9 semitones are required to go from A4 to C4 and again 5 octaves to go from C4 to C-1.
1 |
trace(440 * Math.pow(2, (-9 - (5 * 12)) / 12)); |
If you end up with the following values, everything went well:
C-1=8.175798915643707 Hz
B10=31608.53128039195 Hz
Do you notice anything? Exactly, you’re just remembering something you read in part 1 of this series. The human ear is limited to frequencies up to 20000 Hz, so we won’t ever need a 31609 Hz tone. Likewise 8.18 Hz. It’s actually that deep, that you can hear the individual cycles. I won’t get into detail how this sounds like and let it up to your imagination.
In conclusion, for music we don’t need the full range of notes going from C-1 to B10.
I’d say we settle with all notes from C1 up to B7!
With the frequencies rounded to 3 decimal places and everything arranged in a nice looking table, we end up with this:
Oct1 | Oct2 | Oct3 | Oct4 | Oct5 | Oct6 | Oct7 | |
---|---|---|---|---|---|---|---|
C | 32.703 | 65.406 | 130.813 | 261.626 | 523.251 | 1046.502 | 2093.005 |
C♯ | 34.648 | 69.295 | 138.591 | 277.183 | 554.365 | 1108.731 | 2217.461 |
D | 36.708 | 73.416 | 146.832 | 293.665 | 587.33 | 1174.659 | 2349.318 |
D♯ | 38.891 | 77.781 | 155.563 | 311.127 | 622.254 | 1244.508 | 2489.016 |
E | 41.203 | 82.406 | 164.814 | 329.628 | 659.255 | 1318.51 | 2637.02 |
F | 43.653 | 87.307 | 174.614 | 349.228 | 698.456 | 1396.913 | 2793.826 |
F♯ | 46.249 | 92.498 | 184.997 | 369.994 | 739.989 | 1479.978 | 2959.955 |
G | 48.999 | 97.998 | 195.998 | 391.995 | 783.991 | 1567.982 | 3135.963 |
G♯ | 51.913 | 103.826 | 207.652 | 415.305 | 830.609 | 1661.219 | 3322.438 |
A | 55.000 | 110.000 | 220.000 | 440.000 | 880.000 | 1760.000 | 3520.000 |
A♯ | 58.270 | 116.541 | 233.082 | 466.164 | 932.328 | 1864.655 | 3729.31 |
B | 61.735 | 123.471 | 246.942 | 493.883 | 987.767 | 1975.533 | 3951.066 |
Now we have a perfect reference to quickly find a particular frequency, e.g. F♯7 equals 2959.955 Hz. Beautiful! Time to coax some different notes from our tonegenerator!
To do this we could simply copy & paste some frequencies out of the above table but it won’t be very convenient. Actually it would be way more comfortable to refer each indivudual note by it’s name, e.g. A4.
One way to do this would involve manually creating an array, which is merely a table too, and add each individual note and it’s corresponding frequency as an element.
1 |
var notes:Array=new Array(["c-0",16.35],["c#0",17.32],...); |
Needless to say that this is a waste of time and a lot of work. Programmers are lazy bums by nature and always seek for better ways to do things. Beside this, if we later decide to expand our range or change the tuning, we would have to redo everything.
Luckily there’s a better way! Let the code do all the dirty work for us!
1 2 3 4 5 6 7 8 9 10 |
var numOctaves:int = 7; var frequencies:Array = new Array(); var notes:Array = new Array("c-", "c#", "d-", "d#", "e-", "f-", "f#", "g-", "g#", "a-", "a#", "b-"); for (var a:int = 1; a < numOctaves; a++) { for (var b:int = 0; b < notes.length; b++) { frequencies.push(new Array(notes[b % notes.length] + a, 32.70319566257483 * Math.pow(2, frequencies.length / 12))); } } |
I’m not kidding, that’s it!
One thing is still missing though – we need a function which will let us search the just generated frequencies table for a particular note and in return gets us the appropriate frequency.
This ain’t magic either
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private function findFrequency(inpNote:String):Number { var retVal:Number; for (var a:int = 0; a < frequencies.length; a++) { if (frequencies[a][0] == inpNote) { retVal = frequencies[a][1]; break; } } return retVal; } |
Let’s test if our function works properly.
Try the following code:
1 |
trace(findFrequency("c-5")); |
and you should see 523.2511306011972 in your debug panel. Bulls-eye!
We’re ready to enhance our tonegenerator.
As usual here’s the complete code first:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
package { import flash.display.Sprite; import flash.events.Event; import flash.media.Sound; import flash.events.SampleDataEvent; public class Main extends Sprite { private const buffer:int = 8192; private const sampleRate:int = 44100; private var phase:Number = 0; private var volume:Number = 0.1; private var oscillator:String = new String("sawtooth"); private var numOctaves:int = 7; private var frequencies:Array = new Array(); private var notes:Array = new Array("c-", "c#", "d-", "d#", "e-", "f-", "f#", "g-", "g#", "a-", "a#", "b-"); private var noteToPlay:String = new String(); private var frequencyToPlay:Number; private var noteLength:Number = 11025; private var counter:int = 0; private var currentPos:int = 0; private var notesToPlay:Array = new Array(); private var majorPentatonic:Array = new Array(3, 2, 2, 3, 2, 3, 2, 2, 3, 2, 3, -3, -2, -3, -2, -2, -3, -2, -3, -2, -2, -3, -3); private var harmonicMinor:Array = new Array(2, 1, 2, 2, 1, 3, 1, 2, 1, 2, 2, 1, 3, 1, -1, -3, -1, -2, -2, -1, -2, -1, -3, -1, -2, -2, -1, -2, -2); private var major:Array = new Array(2, 2, 1, 2, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, -1, -2, -2, -2, -1, -2, -2, -1, -2, -2, -2, -1, -2, -2, -2); private var selectedScale:String; private var scales:Array = new Array("majorPentatonic", "harmonicMinor", "major"); public function Main():void { if (stage) init(); else addEventListener(Event.ADDED_TO_STAGE, init); } private function init(e:Event = null):void { removeEventListener(Event.ADDED_TO_STAGE, init); for (var a:int = 1; a < numOctaves; a++) { for (var b:int = 0; b < notes.length; b++) { frequencies.push(new Array(notes[b % notes.length] + a, 32.70319566257483 * Math.pow(2, frequencies.length / 12))); } } getMeSomeNotes(); var mySound:Sound = new Sound(); mySound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData); mySound.play(); } private function onSampleData(event:SampleDataEvent):void { var sample:Number; for (var a:int = 0; a < buffer; a++) { switch (oscillator) { case "sine": sample = Math.sin(phase) * volume; break; case "square": sample = phase < Math.PI ? volume : -volume; break; case "sawtooth": sample = volume - (volume / Math.PI * phase); break; case "triangle": sample = phase < Math.PI ? (-volume + (2 * volume / Math.PI) * phase) : (3 * volume - (2 * volume / Math.PI) * phase); break; } phase += Math.PI * 2 * frequencyToPlay / sampleRate; if (phase > Math.PI * 2) { phase -= Math.PI * 2; } if (++counter == noteLength) { counter = 0; getMeSomeNotes(); } event.data.writeFloat(sample); event.data.writeFloat(sample); } } private function findFrequency(inpNote:String):Number { var retVal:Number; for (var a:int = 0; a < frequencies.length; a++) { if (frequencies[a][0] == inpNote) { retVal = frequencies[a][1]; break; } } return retVal; } private function getMeSomeNotes():void { if (currentPos == 0) { selectedScale = scales[int(randomRange(0, scales.length))]; var rootNote:String = String(notes[randomRange(1, notes.length - 1)] + int(randomRange(2, numOctaves - 3))); var tempOctave:int = int(rootNote.charAt(2)); var tempNote:String = rootNote.substring(0, 2); var tempPos:int = notes.indexOf(tempNote); notesToPlay = new Array(); for (var a:int = 0; a < this[selectedScale].length; a++) { notesToPlay.push(tempNote + tempOctave); tempPos += this[selectedScale][a]; if (tempPos > notes.length - 1) { tempPos -= notes.length; tempOctave++; } if (tempPos < 0) { tempPos += notes.length; tempOctave--; } tempNote = (notes[tempPos]); } trace(notesToPlay); } noteToPlay = notesToPlay[currentPos] frequencyToPlay = findFrequency(noteToPlay); if (++currentPos == this[selectedScale].length) { currentPos = 0; } } private function randomRange(minNum:Number, maxNum:Number):Number { return (Math.floor(Math.random() * (maxNum - minNum)) + minNum); } } } |
If you successfully copy & pasted the code you should hear a melody like this:
It ain’t a random melody though, it’s actually the major pentatonic scale.
In music, a scale is a collection of notes that have been grouped together.
The most well-known among them all is surely the C-major scale.
Written on a staff, it looks like this:
As you can see, it’s made up of the notes C – D – E – F – G – A – B. The structure of a scale is always the same and is made up of specific steps between notes. If we were to write down this sequence of steps for the C-major scale, it’s 2 – 2 – 1 – 2 – 2 – 2 – 1.
2 is equal to a Whole, 1 to Half. If we look a the numbers, we can see that it takes 2 steps to go from C to D, so there must be something in-between. Indeed, it’s the note C#!
C – 1 step – C# – 1 step – D | thus 1 step + 1 step = 2 steps
Knowing that a specific scale is always made up of the same steps is important. With this in mind we can easily write down a major scale in another key. Let it be G-major.
Let’s write down all notes ranging from G to G one octave up:
G – 1 step – G# – 1 step – A – 1 step – A# – 1 step – B – 1 step – C – 1 step – C# – 1 step – D – 1 step – D# – 1 step – E – 1 step – F – 1 step – F# – 1 step – G
Looks like the G-major scale is made up of the notes G – A – B – C – D – E – F#. Awesome!
Reason I’m telling you this, we’re taking advantage of the fact in our code.
Take a closer look at line 26:
1 |
private var major:Array = new Array(2, 2, 1, 2, 2, 2, 1, 2, 2, 1, 2, 2, 2, 1, -1, -2, -2, -2, -1, -2, -2, -1, -2, -2, -2, -1, -2, -2, -2); |
Looking familar, huh? There’s just a little difference, it looks like the sequence is written twice. That’s true, we’re playing the major scale over two octaves.
This array is used by the getMeSomeNotes() function. This function initially picks a random root note. If we want the C-major scale, it should pick C-3 for example. According to the sequence in the major array, it then creates a new array containing real note names.
c-3,d-3,e-3,f-3,g-3,a-3,b-3,c-4,d-4,e-4,f-4,g-4,a-4,b-4,c-5,b-4,a-4,g-4,f-4,e-4,d-4,c-4,b-3,a-3,g-3,f-3,e-3,d-3,c-3
The elements in this array can then be used by the findFrequency() function to tell our tonegenerator which frequency it should generate.
To make it sound more interesting, I’ve included sequences for the major, major pentatonic and the harmonic minor scales.
There’s also a new variable called noteLength which, you possibly won’t believe it, determines how long it should play the individual notes. The number is chained to the samplerate.
If you remember, we’re using 44100 samples a second. If we divide noteLength by the samplerate, 11025/44100, we get 0.25, which means a tone lasts for 250ms.