Temperaments vs. Overtones
June 6, 2009
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.
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.
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.
NerveMail
June 1, 2009
I just want to claim dibs: I may have written the very first rich and useful AJAX application.

Back in 2001, years before GMail, or Firefox, or even the coining of the term AJAX, I built a webmail system that looked and *worked* like a desktop client. There were no pages, just a client program (in JavaScript) that managed a rich GUI and dynamically loaded data and libraries. It had some interesting features like server push and an error reporting system stored JavaScript exception data in cookies so errors could be reported in the event of a crash. It was also a pretty mature mail client.

It worked more of less the same way Google Docs or other application-in-a-browser systems work. Except this was 2001 and the only browser capable of handling it was Mozilla 0.9x.
After endless tweaking I finally shared this in 2002:
http://www.mozillazine.org/talkback.html?article=2716
What seems obvious now – that rich clients are as good an idea on the Web as they are on the desktop – took more than a paragraph to explain in 2002. And in 2002, at the bottom of the crash, that last thing anyone wanted to hear about was “a new technology that was going to revolutionize the Web”.

So, while AOL offered me a job developing it for them in Mountain View, I never directly turned it into money. There certainly weren’t any companies looking out for this technology they didn’t yet know existed. The release of Google’s GMail had the simultaneous effect of making everybody get it and also making NerveMail far less relevant.
I’ve extended that codebase and still use it to build great things that can’t be built with Dojo or EXT. And I believe I was there before anyone. But I never got any props or cash. So I used to be a little touchy about it.
Just staking my claim.








