It's an interesting exercise, and you can learn a lot about programming, math and music theory.
I wrote some small scripts for harmonica/trumpet/guitar in Flash, Ruby and Python.
- Be sure to understand the circle of fifths.
- Be sure to understand modular arithmetic. +5 semitones is the same as -7 (modulo 12), and applying +5 twice is the same as -2.
- The most common keys are on top of the circle of fifths. C is the most common, then come F and G, then come B♭ and D, and so on.
- C# isn't common at all (at the bottom of the circle of fifths), so you'll more often transpose by +5 or -5 semitones than by +1 or -1.
- Each key is associated with 7 pitches. C major is simply "C, D, E, F, G, A, B". F is "F, G, A, B♭, C, D, E", G is "G, A, B, C, D, E, and F♯" and C# is "C♯, D♯, E♯, F♯, G♯, A♯, B♯".
- Knowing the key of a piece will tell you which notation you should use: e.g. B♭ instead of A# for the key of F.
- You can save your notes as a list of integers, representing the semitones. C can be represented as
24...), D as
2 and so on.
- You can try to determine the key of your melody by applying every offset between 0 and 11, and see if the notes fit inside the corresponding major scale:
- If all your notes (modulo 12) are included in
[0, 2, 4, 5, 7, 9, 11], then your melody can be written in C, without any # or b.
- Transpose all your notes (modulo 12) by -5, and see if they are included in
[0, 2, 4, 5, 7, 9, 11]. It would mean that the original key was F.
- Transpose by 5, and check again. The key would be G.
- Transpose by -10. The key would be B♭...
- It's possible that no key fits exactly. In this case, you'd simply use the key with the least amount of extra # or ♭.
- You could simply try to be lucky and use the notations for the most common keys :
['C', 'C#', 'D', 'Eb', 'E', 'F', 'F#', 'G', 'Ab', 'A', 'Bb', 'B'].
- Be sure to keep the original list of notes and the desired transposition in separate variables. That way, +1 followed by -1 is simply a transposition of 0, and you don't need to change anything from the original notation.
Let's say you have a list of notes, saved as
notes = [26,30,33,30,33,31,33,35,37,38,33,30,26,30,33,30,33,31,30,28,26]
without any other information.
You can remove duplicates :
[26, 28, 30, 31, 33, 35, 37, 38]
Since we're working modulo 12, 26=38=2 and 28=4, and the unique, sorted notes are:
[1, 2, 4, 6, 7, 9, 11]
- We already know that this melody isn't in C. (1 and 6 aren't in C major
[0, 2, 4, 5, 7, 9, 11]).
- If we transpose by -5 (or +7, which is the same), we get
[8, 9, 11, 1, 2, 4, 6] or, when sorted,
[1, 2, 4, 6, 8, 9, 11]. So this melody isn't in F.
- If we transpose by +5, we get
[0, 2, 4, 6, 7, 9, 11]. This melody isn't in G.
- If we transpose by -10, we get
[1, 3, 4, 6, 8, 9, 11]. This melody isn't in Bb.
- If we transpose by +10, we get
[0, 2, 4, 5, 7, 9, 11], the major scale! It means this melody can be written in D, without any accidental.
Writing notes in the original key
D major is written with those notes : D, E, F♯, G, A, B, and C♯.
It means we can write the melody as:
D F# A F# A G A B C# D A F# D F# A F# A G F# E D
or something like
D2 F#2 A2 F#2 A2 G2 A2 B2 C#3 D3 A2 F#2 D2 F#2 A2 F#2 A2 G2 F#2 E2 D2
if you want to show the corresponding octaves.
Transposing to another key
Let's say you want to add 3 semitones. You'd go from D to F.
If you simply add ### to every note, you'd get:
F A C A C A# C D E F C A F A C A C A# A G F
but it's not convenient to have both A and A#.
F major is written with F, G, A, B♭, C, D, and E. So your transposed melody would become:
F A C A C Bb C D E F C A F A C A C Bb A G F
Similarly, transposing by -2 semitones would transpose to C:
C E G E G F G A B C G E C E G E G F E D C
and +1 semitone would transpose to E♭ (note that the notation goes from two sharps to 3 flats):
Eb G Bb G Bb Ab Bb C D Eb Bb G Eb G Bb G Bb Ab G F Eb
var notes = [26,30,33,30,33,31,33,35,37,38,33,30,26,30,33,30,33,31,30,28,26];
To transpose, you can simply use map:
notes.map(x => x + 3);
# [29, 33, 36, ...]
modulo works too:
notes.map(x => x % 12);
# [ 2, 6, 9, 6, 9, 7, ...
And you can use a set in order to get unique notes:
new Set(notes.map(x => (x + 10) % 12));
# [ 0, 4, 7, 5, 9, 11, 2]