Thursday, October 29, 2015

Tracking workout data, using Zapier, MapMyFitness, and Google Sheets

A few months ago, I joined the Zapier team, and embarked on the mission of making computers perform more work (and thus, reduce the monotony inflicted on us humans).



I'm a huge fitness-data geek, and love all of the stats I can get from apps like MapMyFitness. When I finish a workout, I manually type in the info/stats from that workout over in my daily journal (currently, Evernote), which is time-consuming, and usually error-prone.


Seems like something that a computer could easily perform - and now that's entirely possible!




Using Zapier.com, it's pretty easy to connect up things so that your workout data from MapMyFitness can be fed into Everenote, Google Sheets, or hundreds of other possibilities.



Here's an example that connects things with Google Sheets, which makes it easy to see your workouts in a spreadsheet view.

If you don't already have an account over at Zapier, you can set one up (for free!). Once you're in, you can set up a Zap, which connects data from one source (like MapMyFitness) to a destination (like Google Sheets - which assumes you already have a Google/Gmail account. If not, it's probably time to give up the AOL.com e-mail address you're using).

Let's set up a Google Sheet (spreadsheet) which will receive the workout data from MapMyFitness.

Go to drive.google.com and create a brand new Google Sheet:

In that sheet, setup some column headings, which indicate the fields/data that you will eventually connect up:



At the Zapier web site, go ahead and create a new Zap!

For the Trigger (data source), select MapMyFitness:


For the Action (data destination), select Google Sheets (again - this assumes that you have a Google/Gmail account, and have setup a spreadsheet like the one described earlier in this article):

Step #1 of your Zap should look something like this:


Zap Step #2: Connect your MapMyFitness account:

It will redirect you to the web site for MapMyFitness, where you have to grant access for Zapier to read your Workout data. This does not store any passwords - instead, MapMyFitness returns an "access token" that is used to access your workout information.

Zap Step #3: Connect your Google Sheets account. Same thing as the previous step - we don't store any passwords, just an "access token" provided on your behalf.

Zap Step #4: Filtering.
If you'd like to filter out things (like certain types of workouts), you can select the criteria here. Otherwise, just ignore this and continue to the next step.

Zap Setup #5: Mapping fields

This is the most important step - this is where you select your Google Spreadsheet, and Worksheet within that spreadsheet (since a spreadsheet may contain many worksheets)




Then, you'll need to select the incoming fields from MapMyFitness, and deposit the data into the correct columns.  For each field, use the button "Insert Fields", and select the correct entry. Like this:


Step #6:  Test it out! Go ahead and "Test Zap with this Sample". This will confirm that Zapier is able to receive Workout data from MapMyFitness. If you already have workout data present, you'll see the three most recent workout entries appear like this:



Step #7: Name the Zap, use something that will help you remember what it's doing, like "Track my workouts"

Go ahead and turn on the Zap, and then go for a walk/run/ride/swim or whatever you do with MapMyFitness. As you complete new workouts, they will accumulate within that spread sheet. Like this:




From there, the possibilities are endless - such as functions to display your total mileage for this year!




Sunday, October 11, 2015

It's a bomb^H^H^H^H clock!

A month ago, there was a huge amount of discussion over a clock that a kid brought to school, which had him arrested and suspended. I have a clock obsession, but only for really weird/geeky clocks. My current favorite is the Tix Clock available at ThinkGeek.

Since I'm usually a month (or more) behind on my blogging, here's my story.

Back in 1998, my dad happened to have a bunch of dual-color LEDs, consisting of Green and Red (and thus, Yellow when both colors were illuminated) that he no longer needed. When I counted them out, there were just over 60 LEDs present - which made me wonder if I could build a clock?

I had spent too much time building all kinds of fun things using various forms of the 8051 microcontroller, and since I had the EEPROM burner and UV eraser in my home lab, it was the best fit for the project.  (These days, I'd much rather use an Arduino or something).

Since a clock would require 60 individually addressable LEDs, along with two individually addressable colors, that equates to a lot more pins than what's available on the 8051.


Multiplexing to the rescue!

I'm a huge fan of multiplexers (and serial shift registers). In this case, I took the 60 LEDs and divided them into four quadrants. I could use two pins to select a quadrant, and then use four pins to select 16 different possible LEDs within that quadrant, using the 74154 4x16 demultiplexor. Throw in the two pins needed for the color selection, and I could drive all of the necessary data signals with eight little bits!

Since I had a few extra data pins available, I hooked up a few push buttons. Two are to set the minutes forward/backward, and two are to set the hours forward/backward. There's a fifth button, but I don't remember what it does.  Maybe some day I'll find the old assembler code I wrote for this project, and see if there's any mention of its purpose.


Assembly

Any true hacker's project rarely comes in a "polished/presentable" case. So the next question was what kind of packaging to use for housing this project? I needed something round, and a large enough size to house a custom circuit board that I made. I thought of a frisbee, but they weren't deep enough for all of the wiring (done by hand - ugh!).  The next best candidate was an ol' fashioned pie tin!  After stuffing everything inside it, I used a piece of wire mesh to hold everything inside it.


Final result

Here's the "internal view".  The 8051 is the big 40-pin beast in the middle (with electrical tape covering the UV-EEPROM window). The push-buttons are just above the CPU. The 74154 is in the top-right corner. There's a bunch of 7407 inverters spread around the perimeter, which are used to drive the LEDs (common cathode/ground), along with a pair of transistors that supply the red/green anodes on the LEDs. Yes, I used to have a lot of spare time back then.




Action shot: The clock displaying the time 3:01:36 o'clock.



Semi-useless video clip:




I'm really thankful I never brought it into school for "show and tell". What's your favorite geeky clock? Leave a comment below!



Monday, July 13, 2015

Grandma's Marathon 2015

   After all of the grueling fun of running Twin Cities Marathon last fall, I decided to do another full marathon (uff-da!) 

  Here in Minnesota, everyone talks about Grandma's Marathon, so it seems to be the "premiere running event" of the upper Midwest. After having completed the run, it's nice and all, but it doesn't compare to the energy and entertainment of the Twin Cities Marathon. 

  Before the race started, the skies opened up and poured rain on top of the runners, as we waited in a car dealership parking lot. Some of the more "dry-minded" folks hit the pavement, and rolled beneath the various SUVs and "higher clearance" vehicles on the lot. Lots of folks (including me) pulled out the disposable trash bag, and threw it on as a temporary poncho.

  The rain lightened up during the first half of the run, and had pretty much stopped by mile 13. Unfortunately, everything was completely soaked inside and out. By mile 15, my right sock formed a soggy wrinkle beneath the knuckle of my big toe, and a blister ensued. I spent the later miles pushing harder with my left leg, in order to take the pressure off my right foot.


  All of that imbalanced running caught up to me around the 22 mile marker (and Lemon Drop hill), when my left quad started cramping up. I spent the last 4.2 miles alternating between a carefully balanced jogging stance, or just walking and massaging the cranky quad muscle, trying to coax a little more effort out of it.

  In the end, I managed to squeeze out a 4:10 finish, and a couple of free beers (whew!)

Wednesday, February 18, 2015

Running the Numbers

Over the last few years, I've participated in a few running events - which makes it difficult to mentally keep track them, and my completion times (fortunately, I have an Evernote Premium subscription to help with all of that). 

Some people consistently run the same event/distance every year, so they have no problem remembering their PRs. I like to try out new events/courses, and occasionally mix in a familiar event from a previous year - which further complicates the mental organization of stats.

Fortunately, most of these events have the finishers' information available via their websites, so with a bit of searching, I can eventually recall my stats from the past. 

But this brings up the question: Wouldn't it be great if there was a web interface to search all of those web interfaces simultaneously? 


Swiss Army Node

If you haven't already noticed from my previous blog posts, I love Node.js (and/or io.js -  hopefully they'll become the same thing again). Node.js is great at handling lots of input/output requests asynchronously, efficiently, without needing lots of code - especially if you use Promises to manage the application flow. 



Working from the back to the front

I prefer to start applications "from the back" - making sure that it's feasible to gather the necessary data, finding out what the limits are, and building an API that the front-end can use. 

I started with a list of popular running events in the area, and discovered that three of them used the mtec service to host the results for the past few years, which makes it super easy to get things started: 


Using Chrome Dev tools, I looked at the request being sent via their search form, and the markup that's returned (yeah, markup) - which can be a bit dangerous if it's not escaped correctly. For example, if a person has an apostrophe in their last name, this returned from their server:
<tr ...>  <td href="#">Firstname O'Lastname</td>  ... </tr>

Ideally, the apostrophe would be &#39; (but not mandatory). But this makes me wonder what would happen if I signed up for a race and entered my name as  <img src="..."/> (which reminds me of Bobby Tables).


Putting the pieces together

In the Node app, here's how I chose to implement the searching:
  • request and bluebird: From the back-end, I used the request package to create the HTTP/client connection to the race-results provider. Instead of using a callback function, I prefer to use Promises, so I wrapped the library using bluebird.promisify
  • cheerio: The race-results provider returns markup, but I'd prefer to use JSON for passing data. The cheerio package makes it super simple to use jQuery style selectors for parsing the text from the markup. 
  • bluebird again: In order to query lots of races simultaneously, I make a bunch of HTTP search/requests (which are now promises), and then use bluebird.all to wait until all of the searches have completed. Ideally, there should be a timeout promise in here so that if any of the individual search requests stalls, it won't delay the other searches.

Back to the Front

I've enjoyed using Angular UI Bootstrap on previous projects, so I wanted to branch out and try using Angular Material library. They share a lot of common concepts, such as the Bootstrap Grid versus the Material Design Layout Grid. It's nice to have resources that get a web interface up and working quickly and easily.

The UI is extremely simple, consisting of a single view that contains a few input fields, which are forwarded onto the back-end race-results providers.

To the Cloud

Given the straightforward/simple implementation of this app, it was easy to deploy into a Heroku Dyno. The app spends most of its time sleeping, so the initial request is always very slow (takes about 5 seconds for the Dyno to awaken).
Here's the deployed/running app: http://runcreeper.herokuapp.com

Show me the Code!

The code is available on GitHub at:  https://github.com/panurgy/run-creeper

Saturday, December 13, 2014

Handbell Hero!

Overview


It's like Guitar Hero, with 4 to 8 players, using a set of handbells from Groth Music. No musical background is necessary to play!

Background


Every year, HelpSystems has an annual employee luncheon, which also involves some kind of game/entertainment thing that's led by a department.


Our department manager, Matt Bresnan, came up with the idea of putting together a Guitar Hero type of game, but using a set of handbells. I was asked to look into the feasibility of writing this app, and was given the MythBusters directive that If it's worth doing, it's worth overdoing!



Given the tools that I already know, Java was the easy choice because it has MIDI capabilities built into the language (which seems excessive, but extremely helpful). On the front-end, JavaFx was the right fit for this project.




Building and Running the game


The instructions for downloading, building, and running the game can be found at the GitHub repo:  https://github.com/panurgy/handbellhero

The game works best on a Windows PC (using a 720p display or projector), and you'll need the Java 8 SDK (free download) installed (and in your PATH).

After a song completes, you'll need to press Alt-F4 to return to the menu screen (yeah, it's lame - I had it coded up to auto-close the window when the MIDI player/sequencer finished, but occasionally would receive an erroneous/premature "player stopped" event, that would close the game screen)


Limitations


The most technically challenging part of this game is getting the "falling notes" to sync up with the music as it's played. This involves a bit of "lead-in" time ahead of the player, which means the UI has to understand MIDI tempo information.  For some reason, I couldn't get the math to work out, given the Sequence's Division Type, the Sequencer Tempo in MPQ, and the Sequencer Tempo in BPM.

I found numerous posts and Stack Overflow questions related to this topic, but the math never seemed to work out correctly. 

In the end, I just used a lame/dirty hack involving a properties file which specifies the number of "ticks per second" used by each of the MIDI files. Tip - the app defaults to 1700 ticks per second, so if your file happens to be close to that, there's no adjustment needed.


An even bigger limitation


This game currently ignores the MIDI Set Tempo events that occur within the MIDI Sequence.  There is code present to find/read those events, but since the math/calculations never worked out, I just stuck to fixed-tempo songs.

Adding your own MIDI files


If you'd like to use your own MIDI files (using just the key of C Major), you'll need to save them into the "midi" directory.  If you find that the UI starts at the wrong tempo (which is very likely), run the app using the command hh.bat log

Select your song, and let it play for a few seconds, then close the game (press Alt-F4 a few times).  In your Command Prompt window, you'll see stuff like this:

PLAYER TICK = 2641
Ticks 4259.0 to 4341.0
Track entries 8 to 8


PLAYER TICK = 2730
Ticks 4341.0 to 4430.0
Track entries 8 to 8

You'll need to determine how many MIDI Ticks there are in one second. Subtract two of the non-zero "PLAYER TICK" values, such as 2730 - 2641 = 89.  Then multiply that times 20, because the game refreshes the UI 20 times a second.  Thus, this song runs at 1780 MIDI ticks per second.

Edit the file resources\stupidHackTempo.props and add an entry for your song. The song's filenames are case sensitive, and you'll need to use the Unicode sequence "\u0020" for any spaces in the song's filename.

Rerun the game, select your song, and hopefully the UI will line up with your music.


Huge thanks to these people!


This was a team effort, and I'd like to give a shout out to these people:
  • Allen Fisher, for doing a superb job putting together the MIDI files.
  • Marshall Baltzell, for doing an amazing job with the two image files, especially on the Handbell Hero logo - he was dead-on with the appearance.
  • Matt Bresnan, for coming up with the initial game design, and giving me the opportunity to take some time to code up game.
  • The creator of the Nightmare Hero font, which adds to the visual effect of the game.
  • The audio effect on the selection menu, which adds to the audible effect of the game.
  • Apache FilenameUtils - they have libraries that make coding easier (and less cumbersome).

Got feedback?


If you love it, hate it, or have any questions, let me know! Either leave a comment below, or share with the entire world via Twitter @Panurgy


Tuesday, October 21, 2014

Training for a marathon - without running ragged

For the past few months, my "big project" was training for the Twin Cities Marathon (Oct 5th, 2014). I chose this "project" back in May, because I've completed several half-marathons over the last few years, and I wasn't getting faster - so I figured I may as well go farther.

Conventional wisdom assumes that this would involve a lot of running - most of the plans that you find on the Internet require four (or more) days of running every week, but now that I qualify as a "master runner", I knew that my knees wouldn't enjoy all of the pavement pounding required for a "proper training". 



Time to train smarter


From a geek's perspective, here's what it takes to be successful in a marathon:
  • Efficiency - get the most "mpg" out of your muscles
  • Endurance - the size of your "gas tank"
  • Determination - the mental fortitude to finish the job 


Essential equipment


  • Good pair of shoes, purchased from a running store, not a big-box sporting goods store. I personally have a slight pronation.
  • Lightweight moisture-wicking shirt and shorts (hopefully, your climate isn't like mine).
  • An HRM - I prefer the older Polar models - this is your "tachometer", and helps identify when you're hitting the "red line" and need to ease up.

A helpful item is a GPS enabled smartphone, and the means to take it on the run (arm band, fanny pack, whatever). I'm a huge addict of the site MapMyRun, with the ability to plan a safe/sane route (due to the dearth of running trails in my town), and then mapping it as I run it (I'm a huge stats/data geek). 


Getting started - finding the baseline


Since I was becoming "comfortable" running a half marathon, the first question was finding the max distance until I hit the wall. In late May, the Sunday morning forecast was a temperature in the low 50's and foggy - which is pretty much the perfect conditions, because fog means no wind. I managed to plod along at my usual pace for 18 miles, struggled for the next 2 miles, and then ended up "doing intervals" for the last 3 miles (yeah, that's a fancy way of saying "walk and run"). 

After that long run, my quads were complaining the most, so I needed a training plan that would build up that muscle group. The solution: hills and stairs!


Figuring out a training plan


The Twin Cities Marathon is a fairly flat course. I read a bunch of blogs and reviews from people who had run it, and most people agreed that the most difficult part was miles 20-23, which are uphill, coming up 200 feet from the river valley. I figured that hill training would probably be a good component of my training.

During the summer, my weeks pretty much went like this:
Sunday: Long run - either 10 miles (or more) of hills, or 13-16 miles of flatter terrain.
Monday: Relax
Tuesday: Intervals on a Stair machine
Wednesday: Swim laps (half mile or so)
Thursday: Variety day - outdoor run (4.5 miles) at a faster pace, or cardio exercise class, or more stairs/intervals.
Friday: Occasional indoor rock climbing (my favorite addiction)
Saturday: Relax


Peaking and Tapering


In the middle of September, I was three weeks away from the big event, and apparently that's the time to peak and start tapering. My Sunday morning run was 20 miles (and the first few miles were in darkness, sigh). At the end, the biggest problem was fueling and hydration - I had only packed two energy gels and a 20oz water bottle (with one refill at a park along the way).

The next weekend (two weeks out), I did a 13 mile run, and tried the Cliff Shot Blocks (chewy things), and discovered that they're really hard to chew while running. On the plus side, they have caffeine, which helps keep things moving.

Finally, one week away, ran a "big" run of 6 miles. Seemed almost "too short" compared to the weekly runs I had been doing.  A few days later, I was itching for a short run, so I did a 4.5 mile loop on mostly flat terrain, just to keep the familiar stride.


The big day


I always arrive at these events obscenely early, so that I can figure out where the starting corrals are, the gear/bag drop-off, and the most essential equipment: porta-potties! The temperature was in the upper 30's, which isn't quite warm enough for the basic "t-shirt and shorts" weather, so I threw on a wicking loose-fitting long-sleeved shirt.

The race was an amazing experience - many of the neighborhoods along the course make a huge production of cheering on the runners. With all of that energy being thrown at you, it's hard not to enjoy the event. One of the spectators teased me for smiling too much, claiming that I was "wasting precious endorphins" and that I should save them for the 20+ mile mark. I just smiled even more.

Just past the 15-mile mark, I had some amazing friends helping me out, and I peeled off the sweat-saturated shirt, and threw on a fresh (and dry!) wicking t-shirt, which was big improvement.

In the end, I completed the event in 4 hours and 5 minutes, which seemed like a pretty good finish for my first time. Huge thanks to all of volunteers that help make this event "run smoothly"!



Monday, December 30, 2013

Which radio station really has the best variety of songs?


Listening to the radio


Years ago, I listened to the good ol' FM radio, and I pretty much had something playing 24x7 (as long as nobody complained about the music). Although, for some reason my "preferred" stations would always end up becoming acquired by another entity/competitor/whatever - like WLOL and The Edge.


Eventually, I grew tired of the seemingly "limited music variety" that was available by the usual radio stations, so I usually prefer to stream something (still filled with commercials) from Spotify, Pandora, and other sources, where I have a little more control over the variety (although even these providers seem limited in their breadth of variety)

Radio Tower
Since it's completely unfair to blatantly claim that the local terrestrial FM radio stations have a limited/repetitive song catalog, I figured I'd "be scientific" and actually collect data on what the stations are playing, and then visualize/graph the information to see what would show up. 

There are probably better ways to spot trends within data, but I prefer to view (and explain) things using visual means, so my approach is to simply present things, and "just see what happens".




Accumulating the data


I figured it'd be easy to gather the data, since most of the local station's web sites provide the "currently playing" song data, and if sites like tunein.com and radiosearchengine.com can provide the information, I should be able to figure it out too.

I started with 93X (KXXR), which seems to be the only "current rock-music station" in the area (yeah, pretty sad). I went to their website, and then fired up the developer tools to identify the means used to retrieve the "currently playing" song information. At the time (Dec 2013), it uses a jQuery JSONP call to retrieve the information, which is a decent workaround if the data provider doesn't support CORS

All I had to do was visit the station's website, clear the "Network" tab after everything had finished loading, and then wait for the next song to start. Eventually, entries would appear as the web page would poll the currently playing song, and then update things when needed.

I coded up a simple bit of JavaScript code using Node's http get routine and configured it to poll the website every so often. After a little while, my data collector stopped working. I pointed a real web browser at the station's website to see what the problem was, and I was presented with a "You've been blacklisted" type of message from the station's hosting provider!  I thought it was a tad ironic that a station that presents itself with a "bad-assed barely legal" veneer image had declared that I was too deviant to be allowed access to their website's information. 

My first thought was that they're trying to pretend that they can restrict access to that data in the same way that Major League Baseball tried to "copyright" facts and stats in order to deter the Fantasy Baseball leagues. Eventually, I figured out that it was a simple DDOS type of filter, and that all I had to do was make my data requests look "more like a real web browser".

One of the (many) great features in Chrome's Dev Tools is the option "Copy as cURL". After a few days, the blacklisting automatically cleared (or maybe my outgoing IP address changed), and I was able to access the station's site again. I opened Chrome's Dev Tools once again, and this time I copied everything that the original request used.

As I expected, the request was full of header settings, referer (yeah, it's misspelled), and cookies. I slam-dunked most of the pieces within that request into my "defined stations" database/collection, then hooked up the wonderful node-curl npm module, and unleashed my data-collection code upon the information. After a few hours, the data collection seemed stable, and hadn't been blacklisted. Mission accomplished!


Deploying the app


As difficult as all of that coding seems, deploying an app is actually the most difficult part (I keep planning on writing up my thoughts, explanations, and experiences about that topic, but it hasn't happened yet). 

When designing/developing an app, you need to have the end-goal in mind from the beginning, or it's going to be a complete train wreck. As a result, when I started imagining up this experiment, I specifically had NodeJS and MongoDB in mind so that I could deploy/use OpenShift (RedHat) and MongoLab.

The team over at OpenShift have a guide to help get started with a new NodeJS app, although I chose not to use their MongoDB cartridge, and preferred to manage the database myself. I eventually discovered that there's a quickstart guide for using OpenShift and MongoLab together, but I had already had things working - figures.

I personally love the PaaS, which is basically a "more evolved" form of a web-app container (I've been doing Java Apps since 1997). When I started deploying Java apps "to the cloud" years ago, I was disappointed in the amount of "wakeup time" that was required after hitting an app that had "gone to sleep"  (If you're wondering, Google's App Engine seems to have the fastest/best "wakeup time" for Java-based apps, but maybe that will be another "research project" someday).  Anyway, from my experience, NodeJS apps seem to have an extremely quick wakeup/response time (because humans should never have to wait for a computer).

Getting the app deployed was a breeze (due to the straightforward git-based interface used by OpenShift). I eventually coded up a simple REST-based interface so that I could peek on the collected data without having to dive into the MongoDB shell/console.

After a few days, I discovered that there were times where the data stream would contain entries like "Song information not available", which I obviously didn't want to include within my collected data (along with other "glitches" in the data stream). So, it took a few iterations of refinement and data scrubbing/cleansing in order to get a "clean" set of song information. 


Looking at the data from a different view


After a few days of collecting data, and improving the process (which is still ongoing), I wanted to get a better look at what I had accumulated.  I've been wanting to do "more sophisticated" graphs with the D3 library, because everything I've done with D3 thus far was akin to printing simple drinking straws with a Stratasys printer

I had an idea of how I wanted to "see the data", and after lots of searching, I discovered a graph called a "Cluster Dendrogram" with an example, which appears to use a data-protocol similar to Flare.  I updated my NodeJS code to provide a data-feed similar to what the graph expected/used, and had things working pretty easily (which is freakin' amazing, because that almost never happens in real life). 


After a few more iterations (and deployments) to my OpenShift instance, I was able to generate a graph of the songs played in the last 48 hours by the radio station - and as I had guessed, there is a small number of artists/bands that compose a "significant portion" of their song catalog.


The graph seemed interesting enough, but data isn't very meaningful unless you have a baseline or some other means of comparing things. So I used my existing JSON/cURL based collection engine on the websites for KQRS (which is a "classic rock" sister station of KXXR, and thus was pretty easy to set up), and the station KDWB (yeah, I know), which was a bit more challenging due to the way their JSON data feed/response is structured.

After a few more late-evening sessions (after the kids were in bed) of coding, tweaking, and deploying, I was able to obtain more graphs of the song selections for these stations.  

The station KQRS shows a "wide variety" of songs, because each song in the graph is pretty much played just once. 



The station KDWB is on the opposite end of the spectrum (being a more Top 40 type of format), and thus shows a very small number of artists/songs each with a numerous amount of broadcasts within the time period.



If you want to see the entire graphs in real-time, you can find them here:
http://noderadio-panurgy.rhcloud.com/graph.html

March 4, 2014 - deployment update - I've noticed that the RedHat/OpenShift instance tends to go to "sleep" after an unspecified period, which kills the radio station polling. I've also had ongoing issues with the radio station's ISP blocking my app due to suspected "DDoS Activity". So, I tweaked the code and made it work on OpenShift and CloudFoundry, and then deployed the app to a few other PaaS providers:

AppFog (CloudFoundry): AppFog (using AWS): http://noderadio.aws.af.cm
Updated July 1, 2014 - AppFog no longer supports instances on the HP Cloud, moved to AppFog on AWS/East.

IBM BlueMix (CloudFoundry @ SoftLayer): http://noderadio.mybluemix.net/
Updated July 1, 2014 - IBM BlueMix domain names have changed, now that they've officially launched the service.

Yet another update - Nov 27, 2014: Tried out Heroku's integration with DropBox and deployed an instance to their service, which increases the resiliency of the application now that it's running on yet another (free) PaaS:  http://noderadio.herokuapp.com

A nice bonus about this is that it's super easy to "scale wide" and provide capacity (and redundancy) by deploying the same app/code to multiple services.

Conclusion or consensus ?


After watching the graphs evolve, it definitely appears that stations whose format is biased towards the newer music tend to repeat songs more frequently, and thus "have less variety". On the other end of that spectrum are the stations whose format is biased towards classic (or older) songs, and thus can draw upon a wider variety of songs (and hence, less repetition).

So yeah, I basically spent two weeks of evenings and weekends to "prove" something that was pretty much "commonly known". Fortunately, my actual goal was to become more familiar with NodeJS, OpenShift, MongoDB (and its "schemaless nature" - which does not mean that the data isn't  organized), MongoLab, and D3 - and in that regard, the experiment was a huge success.

I guess I'll stick with Spotify, and songs like BT - Skylarking, which is great background music for coding (and I love night-time long-exposure photography). 

Add a comment below if you happen to have a favorite song/artist/station for coding music!

P.S. I finally committed the code out in a GitHub repo: panurgy/noderadio. Check back for updates, or follow me on Twitter