Musings of a Fondue

Tetris Theme Song Using Processing

Original Date: November 23, 2013

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.)

Here it is live

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

Sheet music (for notes and durations)
Frequency for each note
Reference for how it should sound

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.

Comments