Friday 31 August 2007

A little toy for you


If you want to play about with a little toy I've been messing around with then you can find it here (but probably not for long, since that's a temporary storage area).

It's a little Python app (requiring pygame to be installed, on Debian systems like Ubuntu this is provided by the python-pygame package) which, when given a band name, connects to last.fm and gets related artists, and their related artists, etc. putting them all in a graph weighted to their relative strengths to make a map of musicians (for an explanation of the algorith see below).

This is very buggy, unbelievably inefficient, and could likely cause a lot of stress for last.fm, so use sparingly (remember, if someone wants to DOS attack last.fm this is not the most efficient way of doing so, so don't think of it at warez or anything because it's not meant to be).

To use:
Extract the archive, then run the file Visualiser.py. I would recommend running it in a way that can be killed easily (for example from within the Geany IDE, which has an Execute and Stop button), since it is an experiment with threading for me and killing those threads can be annoying if (when) the (increasingly complex) graphics thread dies (although if you do run it from a commandline then you can give it a band name directly, like "./Visualiser.py Children of Bodom")

When the window comes up click on the Artist Entry field to select it, use backspace to get rid of the default text, then enter the artist you want to start with and press return. The program should then start downloading images and relationships between artists, adding them to the graph as it does. The deeper the relationship matching the more accurate the map, at the expense of coverage, the more artists modelled the more accurate the map, at the expense of speed (there's a stupid-ass bug in the total artists down button at the moment because I'm an idiot). You can choose to see images (which, if disabled, will also disable image downloading), text labels and the underlying web of connections (the lighter the strand the stronger the connection). Note that enabling and disabling images shifts the nodes in the rendering of the web only, it doesn't affect the modelling in any way (the artists are modelled with their position as the top-left corner of the image/text, which doesn't change).

What it's doing:
The artists exist in a python dictionary, with various different lists of the keys existing for different purposes (the main one being usableArtists). Connections between artists are stored in a separate dictionary, and pairs of artists with no connection between each other are stored in another dictionary.

The Handling thread goes through a list of artists which are known to exist but who are not yet modelled. It then puts each artist in turn into the dictionary and appropriate lists, creating connections as far as the link depth goes (a link depth of zero only connects the most related artist to each artist, which makes forming closed systems easier (eg. Edguy is most related to Avantasia, Avantasia is most related to Edguy, resulting in only those two being modelled for a link depth of zero)). After the connections have been made it sets up a repulsion between the new artist and every artist which it is not connected to. It continues like this until there are either no more artists left to process (a closed system has been made) or until the total artists limit is reached (cutting short a complete modelling of the given variables).

The Image Fetching thread, as long as images are enabled, fetches images for those artists which haven't been assigned one yet. The Relation Fetching thread does the same thing for artists without relationship information.

The Drawing thread draws the graphics and handles user interaction.

The Force thread goes through the list of modelled artists, setting the force on each to zero and halving their velocities (which gives a little bounce to the system). It then goes through the list of connections, assigning an optimum distance apart for the artists (100% similar artists should be close, 20% similar artists should be further away. The distance is direction-independent), then adds a force to each based on how far away they are from this optimum distance, further away means greater force, and the stronger the connection the stronger the force. This means that weakly related artists will tend to move a good distance apart over time, but if other factors can easily overpower this tendency. The list of repulsions is then gone through, forcing apart any not connected artists which are closer than a certain distance.

The Linear thread constantly updates the movement of the artists, calculating their acceleration based on their experienced force and a set mass, from that calculating their velocity and from that calculating their position (which in turn feeds back to the Force thread)

Well, that's basically it for the time being. There are many bugs with it (due to the repulsion's cut-off point some arms of the map can arc around and become close to other areas to which they have no relation, non-English alphanumeric characters cause problems, the total artists down button is broken, there is a rare tendency for the artists to shoot off and crash PyGame by having extremely massive coordinates and probably a lot more needs seeing to as well) but it is purely experimental at the moment.

And the point of this? Well, firstly I think it is pretty cool. Aside from that I think it would be a nice way of browsing one's music collection. By hooking into the multiple genre tags that last.fm gives to artists then this information could be used to map out genres, thus letting users select an area of music they want to listen to, rather than having to specify Speed Metal OR Thrash, and playlists could be defined by drawing a path through the map, moving gradually from one genre to another without obvious jumps. Anyway, that is work for the future. At the moment have fun watching the pictures bounce aroud on strings :)

No comments: