Monday 16 March 2015

A geek way to wish Happy Birthday!

There is nothing more awesome than doing regular things in a geeky way. Few days back I was wondering what would be the geekiest way to wish someone happy birthday. It should be geeky alright, but should also have regular things like cake + candles + birthday song + wishing happy birthday.

Now mixing all this with my very little knowledge in signal processing, I wrote a python code which does this:

Fork me on github

How it works?

So, here we have a virtual cake who's shade has an equalizer effect corresponding to the "happy birthday" song that is played in the background. Here, the cake has candles with flames fluttering randomly. Also, we have a fancy display of happy birthday message.

Lets see how each of it is done one by one.

Equalizer effect

The key here is to consider a sample size of frames in the audio corresponding to the part which is playing currently and display the normalized amplitudes inside the cake. First let's take a look at the amplitudes in the sound wave (stereo):

Channel L:

Channel R:

As we can see even though the audio has two channels, both are nearly the same. Hence for further calculations we will discard one of the channels. If this was not the case, we should have taken the average of both the channels. If the audio file you have taken is already mono, you don't have to worry about anything.

As there is no negative amplitude in the equalizer effect, we will make all values positive:

Now that we have the required data, we can iterate over the frames and display the amplitudes in the cake to give equalizer effect. In the current example, I am considering 1500 frames per iteration. No matter how many frames you consider in a given iteration, the total time taken to complete the iteration should be same as the time required to play the song. Hence we add a sleep after every iteration. The net sleep time+processing time should be equal to song's play time.

We can't display all the 1500 frames at a given time. Hence we take samples in that 1500 frames and averages of each sample is found. The averages are then normalized between the maximum and minimum amplitudes that can occur in the entire song. These normalized averages are then represented as a sequence of 8s (longer the sequence implies higher the amplitude). 

Walah! you have the equalizer effect.

There is a serious problem here. The time required for processing, printing on terminal and waking up from sleep are not determinable. For instance printing on xterm happens very fast whereas the mac terminal or gnome-terminal can be very slow. Hence we need to add a manual correction to the sleep time in order to stay in phase with the song which is being played.

Fluttering candle flames

If you observe the video, the candle flames can have three states:

<space> <dot> <space>
<space> <space> <dot>
<dot> <space> <space>

So basically we just have to randomly switch between these three states to get the fluttering candle flame effect.

Happy birthday text

I have used figlet to print the message. You can also display with different fonts and sizes.

Python Code




  1. It says TypeError: string indices must be integers, not tuple on line 14

    1. For your wav file you have to change "data[:,0]" to "data"

    2. I'm sorry I am a newbie at python. So after changing it to data, instead of data[:,0] on line 14, 19 and 20, it says:
      _min = min([abs(x) for x in data]) #max amplitude in the wav
      TypeError: bad operand type for abs(): 'str'

    3. Can you upload the wav file(to google drive or something) and share it?

    4. I'm using your wav file. The first error I get is:
      /System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/scipy/io/ WavFileWarning: chunk not understood
      warnings.warn("chunk not understood", WavFileWarning)

    5. It looks like you have an older version of scipy. Hence It is not able to read the metadata in the wav file.
      Now I have removed the metadata. Download the latest wav file and try again.
      Also, undo the changes you have made to the code.

    6. Works now. Thanks :) I'll not try and understand the code behind.

    7. Thanks for pointing out the error :)