I’m building a string instrument that makes music with the strings’ harmonic tones as well as their fundamentals.  And, for hard-to-explain reasons, it’s going to have more than 12 fundamental tones per octave.  So I needed to figure out which tuning system has the best overlap between its equally-tempered fundamentals and their respective overtone sequences.  This is exactly the kind of massive combinatorics problem I don’t know how to solve in my head.  So I wrote some software to help me visualize it.

Temperaments v.s Overtones

It’s written in SVG and Javascript.  Firefox: definitely.  IE: definitely not.  I haven’t yet checked if Safari and Chrome support these features.

http://web.media.mit.edu/~hellyeah/crunch.svg

The + and – buttons* change the number of notes in the temperament. Each circle in the top row is a note in the selected equal temperament.  Each row is an overtone series.

Hover over a note to see some information about it.  Click on any note to see the distribution of notes that closely match simple musical intervals. Lighter green indicates a better match.

Enjoy!

* If clicking the buttons doesn’t seem to be working, try clicking their edges.  And be aware that it takes a second or 2 to re-render the whole grid.

Super Lisa Bufano

June 6, 2009

Lisa Bufano is a superhero. One with fancy attachments! She spent a long weekend running all over NYC’s streets, bridges, and trains in her crazy cheetah legs, turning heads and fighting crime. And I tracked and documented her on my camera bike. It’s for the first of a series of video podcasts she’s producing about exploring her unique body.

Super Lisa

Your best chance of keeping up with Lisa is through her website:  http://www.lbufano.com

This video is poorly shot and from before she studied dance seriously.  But I love it anyway.

Moving the Busycle

June 6, 2009

This was fun.  One day Matthew Mazzotta asked me to help move the Busycle from an ancient warehouse in the Navy Yards to Cave Dave’s workshop/home in South Boston.

What is the Busycle?  You’ll just have to see:

That thing is *so* not even hardly street legal.  So we were concerned about police as well as high-speed mechanical mayhem.  But the St. Patrick’s Day Parade had just happened in South Boston that day,  which apparently changed the rules.  The police seemed to think we were just some part of the celebration on our way home.  So we somehow got a pass as long as they thought we were all drunk revelers rather than sober engineers and artists.

The Whirly-Bot

June 6, 2009

The Whirly-Bot is my favorite of Ensemble Robots’ musical bots.  It’s 10 feet tall, 8 feet wide, and shakes like a turbine engine trying to lift off.  It’s been said to sound like a chorus of angry angels or “kinda like sniffing a whole fistful of magic markers”.

It uses 7 layers of spinning corrugated plastic tubes to create a fully chromatic range of notes.  Well, sort of.   The 7 fundamental tones are tuned in 12-tone equal temperament.  All other tones are created by spinning the tubes faster to create overtones.  So all other tones are in the overtone series.  It sounds quite unlike anything else.

The concept and design are mine.  Its motor control system was designed and built by William Tremblay and its tubes were created, tuned and tweaked by Erik Nugent.

Whirly-Bot Construction

It was assembled in a raging week of all-nighters.  Additional help came from Alicia Volpicelli, Peter Ford, and Emily Levin.  It debuted at the Wired Magazine NextFest in NYC in 2006.

Make Bike Film Now

June 2, 2009

Have you ever wanted to capture the rare sense of grace and speed you only get from city biking?

I’ve been experimenting with my bike-cam. It’s so easy and fun I want everyone to do it! All together now – Just put a digital camera on a cheap tripod, and use some zip ties to strap the tripod and bicycle frame together in various compromising positions.

What you see here is a compilation of some sweet camera tests. This summer I hope to make some completely bicycle-based films. Maybe even a musical. Everyone on bikes: actors, cameras, crew. Daredevil traffic situations, breathless dialog, and a nice bicycle kiss at the end. So stay tuned.

iTunesCaster

June 2, 2009

Okay. This may be illegal. Or maybe not. Just don’t use it for illegal filesharing and I’m probably fine.

The iTunesCaster 0.1 turns your iTunes playlists into podcasts that can be easily shared with friends or the general public. Think of it as a nice way to make podcasts, not a way to share MP3s with your friends.

Its usage is still a little geeky at this point. You’ll need iTunes (a-doy), Apache HTTP server, and a newish Python interpreter. Macs will have all of that. Windows users will need to install all 3. For Linux users, are there any open-source music players that use the same XML library format as iTunes? If you know of one, drop me a line.

You don’t need to have those 3 programs on the same computer, so long as they can see each other through the filesystem.

Here is the code:

"""
++++++++++ iTunesCaster 0.1 [super alpha] ++++++++++ 

This script turns your iTunes playlists into podcasts.  

Usage:
  1. Create a playlist for a friend in iTunes
  2. Run this script
  3. Give friend the new podcast URL (see Setup below)
    3a. Bask in gushing praise as your friend easily downloads selected files from your computer.
    3b. Take a moment to reflect that this is totally illegal if done wrong.

Requirements:
  1. Python 2.4.x or newer
  2. mod_python 3.2 or newer
  3. Apache HTTPD server 2.0.x or newer
  4. iTunes 6 or newer

Setup:
  0. first off, iTunes, Apache, and your music library must be visible to one another.  They can be on the same machine or on separate network shares.
  1. Update the values in the conf_d dictionary below.
    set iTunesLib_filePath_str to the file path for your "iTunes Music Library.xml" file
    set iTunesDir_filePath_str to the file path for your iTunes Music Folder
    set TracksDir_urlPath_str to the URL path mapped to your iTunes Music Folder in Apache's httpd.conf file (see my httpd.conf example below)
    set podcastDir_filePath_str to the file path where this script will write the podcast files.  This path should be available via the Web. (see my httpd.conf example below)
  2. run script, see new podcasts (XML files) appear in the folder named by podcastDir_filePath_str
  3. Test it.  Copy a podcast URL and subscribe to it in iTunes.  If you can't download the files, check your paths in conf_d, httpd.conf, and in the podcast.

  +++++++++ PERTINENT PART OF MY HTTPD.CONF +++++++++
  # this is what makes this file work on my site.  Note the correlation between the directories here and those in the script's configuration dictionary ("conf_d") 

    DocumentRoot C:server/websites/podcast.nervebox.com
    ServerName podcast.nervebox.com
    CustomLog logs/podcast_nervebox_com.log combined
    DefaultType text/html

        Options Indexes MultiViews
        IndexIgnore . ..
        AllowOverride None
        Order allow,deny
        Allow from all
        AddHandler mod_python .py
        PythonHandler server
       	PythonPath "[  (Put your own Python Path vals in here)  ]"
        PythonDebug On

    Alias /mp3s "C:server/data/music"

        Options Indexes MultiViews
        IndexIgnore . ..
        Order allow,deny
        Allow from all

"""

import sys, os
from mod_python import apache
from xml.dom import minidom

def handler(req):
  global tracks_d
  global playlists_l
  global req_global
  global debugOut_str
  global conf_d
  req_global = req
  debugOut_str = ""
  conf_d = {
    "iTunesLib_filePath_str":"C:Documents and SettingsAdministratorMy DocumentsMy MusiciTunesiTunes Music Library.xml",
    "iTunesDir_filePath_str":"file://localhost/C:/server/data/music/",
    "TracksDir_urlPath_str":"http://podcast.nervebox.com/mp3s/",
    "podcastDir_filePath_str":"C:server/websites/podcast.nervebox.com/podcasts"
  }
  req.content_type="text/plain"
  iTunesLib().parseMusicLibrary()
  podcast().scanPlaylists()
  req.write(debugOut_str)
  return apache.OK

class iTunesLib:
  def __init__(self):
    global conf_d
    self.XMLPath_str = conf_d["iTunesLib_filePath_str"]
    self._tracks_d = {}
    self._playlists_l = []
  def parseMusicLibrary(self):
    global tracks_d
    global playlists_l
    fields_l = ["Track ID","Name","Artist","Total Time","Date Modified","Location"]
    itml_doc = minidom.parse(self.XMLPath_str)
    plist_node = itml_doc.childNodes.item(1).childNodes.item(1)
    for ni in range(plist_node.childNodes.length):
      if plist_node.childNodes.item(ni).nodeName == "dict": # filter for the 1 dict (tracks) in plist
        tracks_node = plist_node.childNodes.item(ni)
      if plist_node.childNodes.item(ni).nodeName == "array": # filter for the 1 array (playlists) in plist
        playlists_node = plist_node.childNodes.item(ni)
    # get tracks
    trackNodes_coll = tracks_node.getElementsByTagName("dict") # collect references to all track defs in an HTMLCollection
    for ti in range(trackNodes_coll.length): # loop through all tracks
      thisTrackData_coll = trackNodes_coll[ti].childNodes # reference to this track node
      for ttdi in range(thisTrackData_coll.length): # loop through properties of each track
        if thisTrackData_coll[ttdi].nodeName == "key":
          if fields_l.count(thisTrackData_coll[ttdi].firstChild.data)>0:
            if thisTrackData_coll[ttdi].firstChild.data== "Track ID": # should will first property of the track data.  so we can set this here
              trackID_str = str(thisTrackData_coll[ttdi+1].firstChild.data)
              self._tracks_d[trackID_str] = {"Track ID":"", "Name":"", "Artist":"", "Total Time":"", "Date Modified":"", "Location":""}
            try:
              self._tracks_d[trackID_str][str(thisTrackData_coll[ttdi].firstChild.data)] = str(thisTrackData_coll[ttdi+1].firstChild.data)
            except:
              self._tracks_d[trackID_str][str(thisTrackData_coll[ttdi].firstChild.data)] = "bugginess: characters out of ascii range"
    tracks_d = self._tracks_d
    # get playlists
    playlistNodes_coll = playlists_node.childNodes
    for pni in range(playlistNodes_coll.length):
      if playlistNodes_coll[pni].nodeName == "dict": # filter out textNodes
        tempPlaylist_d = {"name":"","trackIDs":[]}
        thisPlaylist_coll = playlistNodes_coll[pni].childNodes
        for ppi in range(thisPlaylist_coll.length):
          if thisPlaylist_coll[ppi].nodeName == "key" and thisPlaylist_coll[ppi].firstChild.data == "Name":
            tempPlaylist_d["name"] = str(thisPlaylist_coll[ppi+1].firstChild.data)
          if thisPlaylist_coll[ppi].nodeName == "array":
            dicts_l = thisPlaylist_coll[ppi].getElementsByTagName("dict")
            for di in range(dicts_l.length): # loop through the dicts in the playlist array
              integer_node = dicts_l[di].getElementsByTagName("integer")[0]
              tempPlaylist_d["trackIDs"].append(str(integer_node.firstChild.data))
        self._playlists_l.append(tempPlaylist_d)
    playlists_l = self._playlists_l

class podcast:
  def __init__(self):
    global conf_d
    self.TracksDir_urlPath_str = conf_d["TracksDir_urlPath_str"]
    self.iTunesDir_filePath_str = conf_d["iTunesDir_filePath_str"]
    self.podcastDir_filePath_str = conf_d["podcastDir_filePath_str"]

  def scanPlaylists(self):
    global playlists_l
    for pli in range(len(playlists_l)): # loop through playlists, making a podcast for each
      self.makeFile(playlists_l[pli])

  def makeFile(self, playlist_ref):
    global debugOut_str
    global tracks_d
    impl = minidom.getDOMImplementation()# make podcast doc object
    podcast_doc = impl.createDocument(None, "rss", None)
    rss_node = podcast_doc.getElementsByTagName("rss").item(0)
    self.setAttribute(rss_node, "xmlns:itunes", "http://www.itunes.com/dtds/podcast-1.0.dtd")
    self.setAttribute(rss_node, "version", "2.0")
    # populate podcast doc object
    channel_node = podcast_doc.createElement("channel")
    rss_node.appendChild(channel_node)
    self.addSimpleTag(podcast_doc,channel_node,"title",playlist_ref["name"])
    self.addSimpleTag(podcast_doc,channel_node,"link","http://www.nervebox.com")
    self.addSimpleTag(podcast_doc,channel_node,"description","Nervebox Experimental iTunes-podcast Bridge")
    self.addSimpleTag(podcast_doc,channel_node,"itunes:summary","Nervebox Experimental iTunes-podcast Bridge")
    self.addSimpleTag(podcast_doc,channel_node,"itunes:subtitle","Nervebox Experimental iTunes-podcast Bridge")
    self.addSimpleTag(podcast_doc,channel_node,"language","en-us")
    self.addSimpleTag(podcast_doc,channel_node,"lastBuildDate","Fri, 9 Dec 2005 09:00:00 EST") # date should be dynamic: date of script execution
    self.addSimpleTag(podcast_doc,channel_node,"itunes:author","Nervebox Studio")
    # add podcast items
    for ii in range(len(playlist_ref["trackIDs"])):
      if tracks_d.has_key(playlist_ref["trackIDs"][ii]): # omit playlist items that are not found among tracks
        thisItem_node = podcast_doc.createElement("item")
        thisTrack_l = tracks_d[playlist_ref["trackIDs"][ii]]
        location_str = thisTrack_l["Location"].replace(self.iTunesDir_filePath_str,self.TracksDir_urlPath_str)
        self.addSimpleTag(podcast_doc,thisItem_node,"title",thisTrack_l["Name"] + "-" + thisTrack_l["Artist"])
        self.addSimpleTag(podcast_doc,thisItem_node,"pubDate",thisTrack_l["Date Modified"])
        self.addSimpleTag(podcast_doc,thisItem_node,"itunes:author",thisTrack_l["Artist"])
        self.addSimpleTag(podcast_doc,thisItem_node,"guid",location_str)
        enclosure_node = self.addSimpleTag(podcast_doc,thisItem_node,"enclosure")
        self.setAttribute(enclosure_node, "url", location_str)
        self.setAttribute(enclosure_node, "length", "100000") # of course this is not the correct length.  works fine anyway.
        self.setAttribute(enclosure_node, "type", "audio/x-mp3")
        channel_node.appendChild(thisItem_node)
    # serialize podcast doc object to XML(!)
    debugOut_str = debugOut_str + podcast_doc.toxml("utf-8") + "

"
    # write XML to filesystem
    podcastFilePath_str = self.podcastDir_filePath_str + playlist_ref["name"] + ".xml"
    podcast_file = file(podcastFilePath_str, 'w')
    podcast_file.write(podcast_doc.toxml("utf-8"))
    podcast_file.close()
    podcast_doc.unlink()

  def setAttribute(self,target_node,name_str,val_str):
    document = minidom.Document()# make podcast doc object
    _att= document.createAttribute(name_str);
    _att.value = val_str
    target_node.attributes.setNamedItem(_att)

  def addSimpleTag(self,doc_ref,parent_node,tagName_str,content_str=False):
    newTag_node = doc_ref.createElement(tagName_str)
    if content_str:
      t_node = doc_ref.createTextNode(content_str)
      newTag_node.appendChild(t_node)
    parent_node.appendChild(newTag_node)
    return newTag_node

Future development: (feel free to jump in here)

  • Faster XML parser! It currently takes over a minute to parse my 4000-track iTunes library.
  • Easy installers for Windows and MacOS
  • On Windows, uses Python’s HTTP server if none is found.
  • A fancy new desktop interface:
    • shows user’s current IP address and URLs for the podcasts.
    • allows user to select which playlists to convert to podcasts.
    • runs as desktop program (with Tkinter) instead of requiring Apache and mod_python

Some of the dorkier among you might believe that this is a perfect place to use XSLT. Au Contraire! It’s true that this program simply transforms one flavor of XML file into a set of other-flavored XML files. But the logic required seems beyond any XSLT-fu that I possess. If you’d like to give it a hack, go right ahead. Tell me how it went. :)

Tallbike Rodeo

June 1, 2009

tb1

The Boston/Cambridge area has a preponderance of artists, engineers, and just plain freaks who build things. What happens if you put ‘em all in a pot and stir ‘em up? I wanted to find out. And a Tallbike Rodeo seemed like a perfect juncture of art, engineering, and daredevil badass-ness.

The idea lounged around in the back of my head for a while. But it became a reality when Lisa Monrose from the Museum of Science took it on as a project. She got people working and even wrangled some grant money and sponsorship.

What we didn’t know was that all the tallbike freaks already knew each other through SCUL. And there was a lot of suspicion among them that we were trying to co-opt their home-grown culture to promote some corporate agenda. Heavens forefend!

tb2

Eventually we met with Fleet Admiral Skunk of SCUL who turned out to be an awesome and genuinely sweet guy. He saw that we were just working to promote creativity and ingenuity through bicycle culture. And he could totally get behind that. So we had Skunk to thank for the ten tallbikes and riders who showed up for the big day!

tb3

So anyway …. races, obstacles, and a demolition derby — all on tallbikes. What could possibly go wrong? Enjoy the photos. Many were taken by Monica Parker-James.

Follow

Get every new post delivered to your Inbox.