nanometer demo

nanometer is a small framework I made in TypeScript to control ILDA show lasers via the Ether Dream DAC.

It consists of two parts:

  • A WebSocket server, which requests a stream of points (defined as an X/Y position and RGB color values) from a connected client and forwards them to the DAC using its TCP interface.
  • A library for writing clients, which connect to the server and provide points. Included in the library are helper classes for defining computational point generators, as well as functions for assembling multiple point generators into a scene, complete with 3-D translation, rotation, and projection!

Splitting it up like this over WebSockers lets you rapidly iterate without having to reconnect to the laser and lets you write a client from within a web browser including within a framework like p5.js.

nanometer uses @laser-dac's Ether Dream driver and its virtual laser which can run inside of a web browser to give the developer freedom to prototype a client without a physical laser.

Here's an example demo that simply generates points, and here's another one that uses the included vector graphics helper library.

This labor day weekend I decided to see if I could complete a small web project that might be of use to Philadelphians and maybe learn React in the process. I came up with a mobile friendly page that will tell you how far away your bus is with the API that SEPTA makes available.

Behind the scenes, it grabs your geolocation from your device, finds stops near your location, filters all SEPTA vehicles by the routes serving the stops near your location, calculates bearing to the vehicles, compares the bearings to the headings to see if the vehicles are incoming, and displays it in a list sorted by distance.

It does all this without sending your location anywhere.

SEPTA does not provide a big list of stops and the routes that serve them. I had to generate that from their GTFS data with a small script.

Visit it on your desktop or phone at Accuracy is not guaranteed; the data is only as good as SEPTA provides it. The data has been known to be shaky and completely wrong at times, even in SEPTA's official app. Check out the source code at GitLab.

Let me know what you think!

A quick and dirty dot density map of contibutions to Blackwell and Gauthier

After creating a small analysis of campaign finance data for the Blackwell and Gauthier campaigns, I thought it would be interesting to see on a map where financial contributions come from.

I extended my script to geocode all of the addresses of donors and output them to a CSV file. I wrote another script to read this CSV file and write another file with a point per $50, with the location randomized within its square on a grid.

I then loaded this file into QGIS and overlayed it on top of Philadelphia Neighboorhood outlines and county outlines.


  • This is a very quick analysis. Some entries were dropped because of geocoding errors, and I didn't follow up to see if they were big donations or small donations.
  • As noted in my earlier analysis, the donor reported addresses aren't always useful for determining residency (but they are probably good enough in most cases.)
  • This misses a whole lot of donations that came from outside the map. I didn't even spend the time to find out how much, but it's probably quite a bit. See the list of donors to get a sense of how much came from other cities or states.

As always, questions, comments, etc are welcome at

Some small analysis on Blackwell vs Gauthier campaign finance

With campaign finance an issue in the 2019 election for councilperson for Philadelphia's 3rd district, I thought I would list and sort sources of funding for both candidates for both 2018 + 2019 to see if there is anything that can be learned. This information is from the public record at, but after processing the listing I generated sums of donations from the same donor, rather than listing each donation separately.

These data can't account for non-cash support from entities, for example the flyers and door hangers from Philadelphia 3.0 or flyers of undisclosed origin in support of the Blackwell campaign.

If you have comments about this presentation of data or otherwise want to get in touch, at

In full disclosure, I am a Gauthier supporter and have donated to her campaign.

Updates since publishing

  • May 8, 10:16am - A misspelling in the source data caused "Laborers District Council (PAC)" to not be summed as one donor. This has been corrected.
  • May 9, 10:09am - I re-ran and added the analysis for 2019 only.


  • I wrote a python script to parse exported data from to sort donors for each candidate by total contribution, and also generated binned counts of donors by total donation size.
  • The 2019 CSV produced by Philadelphia's site has many errors due to commas not being escaped. (2018 uses tabs instead of commas. As far as machine readability it was totally fine.) I had to write my script to raise an error when input was not in the correct format so I could find the erroneous lines and correct them.
  • Once I was generating output, I noticed a couple of records that should have been summed together but were not because of a truncation error or because a word in one address line was abbreviated in one record but not in another. I wrote code into my script to find these records and associate them on the fly, instead of changing the data files. The files were only altered to assist with parsing badly formed comma separated fields, as above. I was still seeing some of these until recently. If you catch one I haven't, email

Initial observations

  • Jamie Gauthier made a donation to her own campaign, as did some relatives or family-members (presumably) who share the Gauthier name. The name Blackwell does not appear in Jannie Blackwell's list of donors.
  • Blackwell's top funders: unions and real estate.
  • Gauthier's top funders: an environmental conservation organization and venture capitalists; yes, Perelman is in there too. Those accused of making dark in-kind donations to Gauthier through Philadelphia 3.0 are also here in plain sight.
  • Gauthier has a strong base of small individual donors where Blackwell does not.
  • Address may in some cases be misleading for determining residency, for example in the case of Gauthier supporters Josh and Rena Kopelman who used "4040 Locust St," the address of …


I've got some pictures from when I attended SHA2017. image

The Sansa Fuze Clip+ is the best digital audio player

I used to store a copy of my music collection on my Android phone. For various reasons it became necessary to buy a new phone, and as I did the phones lost their microSD card which held the most of the music. And then the capacity of the internal memory on the phones actually got smaller. All of this is to presumably encourage users to use Google Music or otherwise coerce users to use cloud music services.

As I write this Apple has annouced the iPhone 7, which will lack a headphone jack. This got me thinking: phones are actually terrible devices for listening to music anyways.

Sandisk Sanza Fuze+

So this week I bought an old favorite device for listening to music, the Sanza Fuze Clip+. Unfortunately, the device is a few years old and out of production, but in my opinion it is the best music player ever made.

It plays many different types of music files, including MP3, OGG, M4A, and FLAC. It has a built in FM radio and voice recorder. It has an microSD expansion slot so it can actually hold all my music. It is very tiny and has an old school looking OLED display. It can drive headphones well and sounds very transparent.

Sandisk Sanza Fuze+ FM Radio Screen

But the best thing about it is that you can install Rockbox on it. Rockbox is a custom firmware that adds audio processors, including crossfeed and a compressor, and a number of other features, like skins and audio metering, to the device.

You can still buy these devices, but it is a shame they stopped making them. is rebooting

A small part of Philly's skyline after a storm

After not updating my personal website for a long time, I've decided to relaunch it as a blog. Stay tuned!