If you have Processing installed, I highly recommend going through the examples included. There’s a lot of interesting stuff hiding there.
If not, download it. I promise you won’t regret it!
This project was based on one of these examples* which plays the Happy Birthday song. I managed to tweak it to play the Tetris theme song. (And felt like a million bucks haha.)
Update
Unsigned Java applications no longer work on web browsers (at least not without much effort). So here's a recording of it I made using Audacity,
*See examples/Books/Processing Handbook/Extensions/Sound/Ex_02
The Code
I got the sheet music from here and combining it with this answer, was able to get the values and durations of the notes in the song. I then converted the note values to their respective frequencies (for example E6 became 659.25511).
Calculating the note durations was a bit of a hack. Looking at the sheet music, I could see the notes lasted ⅛, ¼, ½, or ¾ of a beat. Listening to the song here, I guesstimated that it takes about 13 seconds to finish 8 bars (the melody). So,
Given,
13 seconds = duration of 8 bars;
duration of 1 bar / 8 = duration of ⅛ note
Then,
duration of ⅛ note = 13 seconds / 8 / 8
= 0.203125 seconds
= 203.125 ms
Reading from sheet,
var notes = [
E6, B5, C6, D6, C6, B5, A5, A5, C6, E6, D6, C6, B5, C6, D6, E6, C6, A5,
A5, A5, B5, C6, D6, F6, A6, G6, F6, E6, C6, E6, D6, C6, B5, B5, C6, D6,
E6, C6, A5, A5
];
var duration = [
2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 3, 1, 2, 2, 2, 2, 1, 1, 1, 1,
3, 1, 2, 1, 1, 3, 1, 2, 1, 1, 2, 1, 1, 2, 2, 2, 2, 2
];
Converting accordingly,
var frequency = [
659.25511, 493.8833, 523.25113, 587.32954, 523.25113, 493.8833, 440.0, 440.0,
523.25113, 659.25511, 587.32954, 523.25113, 493.8833, 523.25113, 587.32954,
659.25511, 523.25113, 440.0, 440.0, 440.0, 493.8833, 523.25113, 587.32954,
698.45646, 880.0, 783.99087, 698.45646, 659.25511, 523.25113, 659.25511,
587.32954, 523.25113, 493.8833, 493.8833, 523.25113, 587.32954, 659.25511,
523.25113, 440.0, 440.0
];
var duration = [
406.250, 203.125, 203.125, 406.250, 203.125, 203.125, 406.250, 203.125,
203.125, 406.250, 203.125, 203.125, 609.375, 203.125, 406.250, 406.250,
406.250, 406.250, 203.125, 203.125, 203.125, 203.125, 609.375, 203.125,
406.250, 203.125, 203.125, 609.375, 203.125, 406.250, 203.125, 203.125,
406.250, 203.125, 203.125, 406.250, 406.250, 406.250, 406.250, 406.250
];
In the original example, noteDuration
is an integer since every note in the Happy Birthday song is the same length. In the Tetris song however, each note has a different duration so I tweaked the code to work with noteDuration
as an array.
This (original),
int noteDuration = 300; // Duration of each note in milliseconds
float[] rawSequence = { 293.6648, 293.6648, 329.62756, 329.62756, 391.995, 369.99445 , 293.6648, 293.6648,
329.62756, 293.6648, 439.997, 391.995, 293.6648, 293.6648, 587.3294, 493.8834,
391.995, 369.9945, 329.62756, 523.2516, 523.2516, 493.8834, 391.995,
439.997, 391.995 }; // Happy birthday
void setup() {
...
myChannel.initChannel(myChannel.frames(rawSequence.length * noteDuration));
...
for (int i = 0; i < rawSequence.length; i++) {
...
int e = int(noteDuration * 0.8);
...
time += noteDuration; // Increment the Channel output point
}
...
}
Became this,
float[] noteDuration = {
406.250, 203.125, 203.125, 406.250, 203.125, 203.125, 406.250, 203.125, 203.125, 406.250,
203.125, 203.125, 609.375, 203.125, 406.250, 406.250, 406.250, 406.250, 203.125, 203.125,
203.125, 203.125, 609.375, 203.125, 406.250, 203.125, 203.125, 609.375, 203.125, 406.250,
203.125, 203.125, 406.250, 203.125, 203.125, 406.250, 406.250, 406.250, 406.250, 406.250, 406.250
};
float[] rawSequence = {
659.25511, 493.8833, 523.25113, 587.32954, 523.25113, 493.8833, 440.0, 440.0, 523.25113,
659.25511, 587.32954, 523.25113, 493.8833, 523.25113, 587.32954, 659.25511, 523.25113,
440.0, 440.0, 440.0, 493.8833, 523.25113, 587.32954, 698.45646, 880.0, 783.99087,
698.45646, 659.25511, 523.25113, 659.25511, 587.32954, 523.25113, 493.8833, 493.8833,
523.25113, 587.32954, 659.25511, 523.25113, 440.0, 440.0, 0.0
};
void setup() {
...
//calculate total time
int totalTime = 0;
for(int i = 0; i < noteDuration.length; i++){
totalTime += noteDuration[i];
}
myChannel.initChannel(myChannel.frames(totalTime));
...
for (int i = 0; i < rawSequence.length; i++) {
...
int e = int(noteDuration[i] * 0.8);
...
time += noteDuration[i];
}
...
}
I was surprised when it all came together and actually worked!
Sources used
Update (November 2015):
My conversions were off by an octave, everything plays an octave lower than the original. For example, I mapped E6 to 659.255Hz. However E6 is actually 1318.51Hz and 659.255Hz corresponds to E5.