Music in the western classical tradition uses a twelve-tone chromatic scale. Any of the tones in this scale can be the basis of a major scale. Most musicians (especially pianists) learn the C-major scale in the early days of study, owing to the ability to play this scale entirely with the ivory (white) keys.
The following declaration shows how to initialize an array consisting of the twelve tones of the chromatic scale, starting from the C note.
1 2 | static string[] tones = { "C", "C#", "D", "D#", "E", "F",
"F#", "G", "G#", "A", "A#", "B" };
|
Even if you’re not a musician, learning the basic principles is fairly straightforward.
The well-known C-major scale, which is often sung as:
Do Re Mi Fa So La Ti Do
has the following progression:
C D E F G A B C
This progression is known as the diatonic major scale. If you look at
the tones
array, you can actually figure out the intervals associated
with this array:
C + 2 = D
D + 2 = E
E + 1 = F
F + 2 = G
G + 2 = A
A + 2 = B
B + 1 = C
So given any starting note, the major scale can be generated from the intervals (represented as an array).
So, for example, if you want the F-major scale, you can get it by starting at F and applying the steps of 2, 2, 1, 2, 2, 2, 1:
F + 2 = G
G + 2 = A
A + 1 = B' (flat) a.k.a. A#)
B'+ 2 = C
C + 2 = D
D + 2 = E
E + 1 = F
So this is the F-major scale:
F G A B' C D E F
We begin by creating a helper function, FindTone()
, which does a
linear search to find the key of the scale we want to compute. The
aim is to make it easy for the user to just specify the key of interest.
Then we can use this position to compute the scale given the major
(or minor, covered shortly) interval array.
1 2 3 4 5 6 7 | static int FindTone(string key) {
for (int i=0; i < tones.GetLength(0); i++) {
if (key == tones[i])
return i;
}
return -1;
}
|
To see what this function does, pick your favorite key (C and G are very common for beginners).
FindTone("C")
gives 0, the first position in the tones
array.FindTone("G")
gives 8.For example,
C is the first note in the array of tones, so FindTone("C")
would give
us 0. FindTone("F")
would give us 6.
So let’s take a look at ComputeScale()
which does the work of
computing a scale, given a key and an array of steps. The scale array
is allocated by the Main()
method, primarily to allow the same
array to be used repeatedly for calculating other scales.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | static void ComputeScale(string key, int[] steps, int[] scale) {
int tonePosition = 0;
int startTone;
startTone = FindTone(key);
if (startTone < 0)
return;
if (steps.GetLength(0)+1 != scale.GetLength(0))
return;
tonePosition = startTone;
for (int i=0; i < steps.GetLength(0); i++) {
scale[i] = tonePosition % tones.GetLength(0);
tonePosition += steps[i];
}
}
|
startTone
(obtained by calling FindTone()
) and
tonePosition
, which is the note we are presently visiting in
the tones
array.tones
and using the appropriate
intervals (the steps
parameter) to compute the next note, given
a current note.steps
array to decide what the next note is.scale[i]
is computed by taking
tonePosition % tones.GetLength(0)
. We need to do this, because
in most scales, you will eventually end up “falling off the end”
of the tones
array, which mens that you need to continue
computing notes from the beginning of the array. You can inspect
this for yourself by picking a scale (say, B) that is starting at
the end of the tones
array. This means you will need to go
to the beginning of the array to get C# (which is 2 tones away
from B).steps[i]
to tonePosition
.The following function writes the scale out (rather naively) by just
printing the notes from our existing tones
array.
1 2 3 4 5 6 | static void WriteScale(int[] scale) {
foreach (int i in scale) {
Console.Write ("{0} ", tones[i]);
}
Console.WriteLine ();
}
|
We say that the output is naive because any musician will tell you that a scale should be printed in a normalized way. For example, the F-major scale (shown above in our earlier explanation) is never written with A# as one of its notes. It is written as B-flat. It’s easy to manage the various cases by consulting the circle of fifths, which gives us guidance on the number of flats/sharps each scale has.
Lastly, we put this all together.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void Main (string[] args)
{
int[] scale = new int[8];
int[] major = { 2, 2, 1, 2, 2, 2, 1 };
int[] minor = { 2, 1, 2, 2, 1, 2, 2 };
string name = args[0]; // need command line tone name
Console.WriteLine("{0} major scale", name);
ComputeScale(name, major, scale);
WriteScale(scale);
Console.WriteLine("{0} minor scale", name);
ComputeScale(name, minor, scale);
WriteScale(scale);
}
|
This Main()
method shows how to set up the steps for both major
and minor scales. We’ve already explained how to express the steps of
a major scale. The minor scale basically drops the 3rd and 7th by a
semitone (a single step), which gives us a different pattern.
You can run this program to see the major and minor scales.