Skip to content

Senior Project, Application Launcher, System Customizations

11-Jan-10, by meyermagic

It has been a while since my last update, and since then we have gotten completely usable two-glove audio output, without accelerometer-based articulations. It is very cool (we can kind of play the Tetris A Theme, but no one is very good at using the gloves yet). Just today we got the accelerometer all working; we have rotation and vertical motion output fit to the correct ranges. Despite having the software all written, we don't have the gloves working with full articulations due to the issue of actually mounting the accelerometers on the gloves and the Arduinos on the wristbands. We have already bought most of the components we'll need to do this, so I expect to have the entire thing working by the end of this weekend. When we finish, I'll post the final parts list, pictures, and latest code, along with instructions for constructing your own pair of musical gloves as a DIY project. Next!

I haven't been very motivated to finish it since the current version works fine, but I'm almost done with a re-write of my application launcher. The new version will by extensible via plugins and will be able to run in the background (as opposed to starting up every time you summon it). Aside from the rewrite, I added some neat shorthand directory launching support, which allows me to open "/home/ifx/Code/Launcher/final" by typing "/lau/f" and pressing enter. Regex is pretty neat.

Finally, I think I'm going to make a post about all the interesting customizations I have made to my Arch Linux install. These would include my mouse gestures (I have some that actually do useful, non-obvious things), my Conky configuration, and, of course, my .bashrc.

Senior Project Update and a Decent Media Manager

26-Nov-09, by meyermagic

Okay! This update is a bit late (by ~1.5 weeks), but we now have full audio output from a single glove. The contact sensors are pretty finicky, though, and improving them is our next goal. We have a few ideas about how to do this. First, we may sew a pattern into the conductive fabric using conductive thread. This plan works on the assumption that the conductive thread has more exposed metal than the conductive fabric, and thus will make more reliable connections. We don't know this for sure, but we need conductive thread anyway, so we'll probably try it out. The second idea was brought to us by Nate's mom. Apparently she has access to some sort of thin metal-coated plastic sheet. I don't remember the intended use of this material, but we'll be trying it out for our contact sensors.

After we achieve reliable contact sensors, we'll move on to inter-glove communication, then finally add the accelerometers, essentially completing the instrument.

In other news, I finally found an audio player / manager that I actually like: Quod Libet. I'll talk more about this later (I'll probably make a post about it when I finish the plugins I'm thinking about).

Please excuse the content-light nature of this post; I only really made it for the Senior Project update.

Things That Have Happened, Things That Have Not, and Senior Project Status

12-Nov-09, by meyermagic

As of the time of posting, I have ten Google Wave invites. I've been playing with Wave a bit, and it is pretty neat, but not extremely impressive. I have seen real-time collaborative document editing before. Using Wave for chat is fairly cumbersome (no send on Enter). I have also gotten a few crashes, and it isn't incredibly snappy.

All that aside, it is very cool and I do see how it can be useful. Even if just discussing a random topic, the fact that you can reply to related comments and have the whole discussion appear in a tree promotes organization. I am using a neat LaTeX extension that allows me to embed editable LaTeX markup which is automatically rendered and displayed inline. Related to this, I am using Wave with the math teacher to plan for my third trimester class "Seminar in Independent Mathematical Research" (I'm very excited, I might post about my topic ideas later).

I started learning Go recently. For practice, I'm re-writing my Python solutions for Project Euler in Go. Here is Problem 259:

package main
 
import (
    "fmt";
    "strconv";
    "strings";
    "./fraction";
    "time";
)
 
func simplify(nums []*fraction.Fraction) map[string]*fraction.Fraction {
	out := make(map[string]*fraction.Fraction);
	var r *fraction.Fraction;
	if len(nums) == 1 {
		out[nums[0].String()] = nums[0];
		return out;
	}
	for offset := 1; offset < len(nums); offset++ {
		lefts := simplify(nums[0:offset]);
		rights := simplify(nums[offset:len(nums)]);
		for _, left := range lefts {
			for _, right := range rights {
				r = left.Add(right);
				out[r.String()] = r;
				r = left.Sub(right);
				out[r.String()] = r;
				r = left.Mul(right);
				out[r.String()] = r;
				if right.IsZero() != true {
					r = left.Quo(right);
					out[r.String()] = r;
				}
			}
		}
	}
	return out;
}
 
func groups(symbols string) ([]string, int) {
	out := new([512]string);
	out[0] = symbols;
	c := 1;
	for i := 1; i < len(symbols); i++ {
		gout, gcount := groups(symbols[i:len(symbols)]);
		for g := 0; g < gcount; g++ {
			group := gout[g];
			out[c] = symbols[0:i] + "|" + group;
			c += 1;
		}
	}
	return out, c
}
 
func numlist(s string) []*fraction.Fraction {
	vals := strings.Split(s, "|", 0);
	out := make([]*fraction.Fraction, len(vals));
	for i := 0; i < len(vals); i++ {
		t, _ := strconv.Atoi64(vals[i]);
		out[i] = fraction.New(t, 1);
	}
	return out;
}
 
func simplify_nums(nums string) map[string]*fraction.Fraction {
	return simplify(numlist(nums));
}
 
/*
func simplify_nums(nums string, out chan map[string]*fraction.Fraction) {
	out <- simplify(numlist(nums));
}
*/
func main() {
	start := time.Seconds();
	reachable := make(map[uint64]bool);
	seqs, nseqs := groups("123456789");
	for i := 0; i < nseqs; i++ {
		reps := simplify_nums(seqs[i]);
		for _, k := range reps {
			if k.IsPos() && k.IsInt() {
				reachable[uint64(k.Num())] = false;
			}
		}
	}
	/*
	channels := make([]chan map[string]*fraction.Fraction, nseqs);
	for i := 0; i < nseqs; i++ {
		channels[i] = make(chan map[string]*fraction.Fraction);
		go simplify_nums(seqs[i], channels[i]);
	}
	for i := 0; i < nseqs; i++ {
		reps := <-channels[i];
		for _, k := range reps {
			if k.IsPos() && k.IsInt() {
				reachable[uint64(k.Num())] = false;
			}
		}
	}
	*/
	var total uint64 = 0;
	for k, _ := range reachable {
		total += k;
	}
	fmt.Printf("Sum: %d\n", total);
	fmt.Printf("Total Time: %d\n", time.Seconds() - start);
}

If anyone wants "fraction.go", I can upload it.
I tried using goroutines (commented in the code above) to speed things up via concurrency, but it didn't seem to have any effect on the speed (though memory usage increased) and CPU usage only spiked on one core. Not sure what the deal is with that; I'll look into it later.

So far as I have played with it, I like the language. Go has a respectable standard library, which is something many new programming languages (that I have seen) lack. If I code anything interesting enough, I'll make a post about it.

About my Application Launcher: I'll be splitting it up into a daemon process and a GUI process which will communicate using D-Bus (probably, haven't decided yet). I will be adding filesystem searching using Glimpse and locate. Glimpse is pretty damn impressive. It provides very fast searching of file contents, support for fuzzy matching, and the index is quite small (for everything under my home directory the index is ~34 MB).

Related to this, I am going to be looking into Fuzzy Matching once more (see music on the command line). Already existing for Python, I found this. Haven't tested it yet. I downloaded a few papers on Damerau-Levenshtein distance and Levenshtein automata, but probably won't start implementing anything for the next week or two.

Finally, my Senior Project. By the end of this trimester (~1 week) Nate and I plan to have audible note output using data from the contact sensors. We will integrate the accelerometers next trimester, and should have plenty of time to debug and test afterwards. At the moment, though, due to a combination of college applications, homework, and sickness, we are a bit rushed. I don't expect it to be an issue: a good, solid work day this weekend should be enough to catch up.

As a last note, I'll mention the album I've been listening to while writing this up: "Suzumiya Haruhi no Gensou". Orchestral versions of many songs from Suzumiya Haruhi no Yuutsu. Surprisingly good.

Bash, Senior Project, and Application Launcher

24-Oct-09, by meyermagic

A while ago I installed a bunch of lib32-* packages as optional dependencies for something. I ended up not needing them for whatever reason (don't remember anymore), so I decided to remove them. The problem was that I also wanted to keep the lib32-* packages that I was actually using. Now, I had my pacman install logs, so I could easily get a list of the specific packages I wanted to remove. If I wanted to be a bit fancier, I could have python parse a section of the logs to generate a remove command to use. This wasn't good enough, though. I decided that I wanted to use a bash one-liner.

After a few hours, I changed my mind. Instead, I have this 7-liner:

TMP1=`mktemp`
TMP2=`mktemp`
yaourt --textonly -Ss lib32-* | grep installed | cut -d "/" -f 2 | cut -d " " -f 1 > $TMP1
yaourt --textonly -Q -t | cut -d "/" -f 2 | cut -d " " -f 1 > $TMP2
comm -12 $TMP1 $TMP2 | xargs yaourt -R
rm $TMP1
rm $TMP2

There might be an easier way, but if you have yaourt installed (this is for Arch Linux users) you can use this little script to remove all the packages matching some search (lib32-* in my case) that aren't required as dependencies for anything (including each other. Run multiple times to get those too.)

Next.

A while ago we received our conductive fabric, and we got nicer output from the accelerometer. It will still need to be "calibrated" at startup to some extent, but this will only require the user to have it not tilted more than 180 degrees from the desired neutral position when the calibration routine is run (which just deals with the first set of outputs from the accelerometer differently from all the others).

And finally:

I have uploaded a working implementation of the application launcher code I wrote a while ago. It doesn't use the panel applet GUI (my end goal), but it does work. Missing features include:

  • "Learning" from previous searches
  • Autodetection of environment (you need to set a few configuration variables manually)
  • Scrolling through results (you can only run the first result at the moment)

All this will be added in time.

Accelerometer Working!

05-Oct-09, by meyermagic

Today we got the accelerometer working! We'll need to change how it is set up to get cleaner output, but we do in fact have some output. Also, our conductive fabric (which we had previously been told would be delayed for four weeks) will be here much sooner, though we needed to sacrifice the privilege of using pink fabric for the grey which we are now getting.

Next up is writing the code to use the accelerometer data to produce a rotation and vertical acceleration.

We also decided to document our work with photos, so I'll be uploading photos either to this site or to some photo hosting site at some point.

As a side note, I am also writing a program to use a wacom tablet as instrument with Python + Pygame. Pressure will map to volume, vertical position to frequency, and horizontal position to time until note is played. The interface will be a leftward-scrolling window in which you can draw with the wacom tablet. When whatever you have drawn hits the left side of the window, it will make sound. I'll post more about this later, but if anyone has a good way of getting Wacom Tablet pressure data in Python, feel free to tell me. For some reason reading from /dev/input/wacom (a symlink to whatever /dev/input/event* the tablet is actually mapped to) doesn't work, and I can only get position data using Python's Xlib bindings. It also does not show up as a joystick in Pygame (my laptop's accelerometer does, though), which would have been nice.

Design Changes

29-Sep-09, by meyermagic

It turns out that Hall Effect sensors weren't the best idea. First of all, they are latched. This is easy to work around, but the fact that they are dependent on orientation isn't. One of the guys at Noisebridge suggested we just use conductive pads on all the fingers, while the pad on the thumb is charged. Nate and I had thought of this before, but for some reason had dropped the idea. By the end of the day I had contact sensors for each finger working using aluminum foil and tape.

Taking into account the easy with which this solution was put together and the simplicity of handling the data with the Arduino, we'll be changing our design to use conductive fabric and thread (probably modifying the gloves we already have, rather than getting a pattern and sewing our own gloves).

We are looking at the various types of fabric offered here: http://www.lessemf.com/fabric.html.

Noisebridge already has conductive thread, so we'll probably be using that. If we need to get our own, though, we'll purchase it from SparkFun, where we bought most of our other parts (our first order shipped incredibly fast, and the packaging was very nice. We have been keeping most of the components together in the red SparkFun box that they arrived in.).

Senior Project – Musical Gloves

16-Sep-09, by meyermagic

Nate (a friend of mine) and I decided to work together for our Senior Projects a while ago. After some brainstorming, we thought an awesome idea would be to build a motion capture or gesture input glove. We discussed various ideas for how we might accomplish this, including cameras, resistive bend sensors, stretch sensors, and optical fiber (I hoped that as the fiber was bent, less light would remain trapped by total internal reflection such that a photosenor, an LED, and some calibration would allow us to measure bend. As it turns out, our mentor used a similar technique in the construction of a similar glove at the company for which he worked.).

We eventually scrapped all these ideas in favor of hall effect sensors. Although I only considered them because of an early idea for a glove which would only be used for typing, we decided that hall effect sensors would be perfect for the musical application which had become the main goal of the project.

The new idea is to design the glove specifically for music. Ideas for the interface can be found here, and our parts list (which might not be kept up to date during the project, but will probably be updated at the end to serve as a reference for others) is available here.

I'll be adding a section to the blog for this project, and probably a page for projects in general. We plan to blog the project as we go, so to my maybe one or two readers, check back!

Browsing Webcomics with the Arrow Keys

27-Jul-09, by meyermagic

So, I just picked up a new webcomic, Gunnerkrigg Court, and found it annoying that I need to switch between using the arrow keys to scoll down the pages (monitor can't fit an entire comic vertically on one screen) and the mouse to click the next button. I decided to write a Greasemonkey script which would solve this problem by allowing me to jump to the next and previous pages by using the arrow keys.

Now revised to work for more than one webcomic.

?View Code JAVASCRIPT
// ==UserScript==
// @name           Navigate Webcomics with the Arrow Keys
// @namespace      www.integerzero.net
// @description    A script which allows you to navigate webcomics with the arrow keys.
// @include        http://www.gunnerkrigg.com/archive_page.php?comicID=*
// @include        http://questionablecontent.net/view.php?comic=*
// ==/UserScript==
 
function getParameter(name2) {
 
   var url = window.location.href;
 
   var paramsStart = url.indexOf("?");
 
 
   if(paramsStart != -1) {
      var paramString = url.substr(paramsStart + 1);
      var tokenStart = paramString.indexOf(name2);
 
      if(tokenStart != -1) {
         paramToEnd = paramString.substr(tokenStart + name2.length + 1);
 
         var delimiterPos = paramToEnd.indexOf("&");
 
         if(delimiterPos == -1) {
 
            return paramToEnd;
         }
         else {
 
            return paramToEnd.substr(0, delimiterPos);
 
         }
 
      }
   }
}
 
var comics = new Object;
comics['www.gunnerkrigg.com'] = Array("http://www.gunnerkrigg.com/archive_page.php?", "comicID");
comics['questionablecontent.net'] = Array("http://questionablecontent.net/view.php?", "comic");
//Add support for more webcomics here
 
var base = comics[location.href.substring(7,location.href.lastIndexOf('/'))][0];
var field = comics[location.href.substring(7,location.href.lastIndexOf('/'))][1];
var num = getParameter(field);
 
 
num = parseInt(num);
 
p = (num-1)+'';
 
n = (num+1)+'';
 
 
function handleArrowKeys(evt) {
   evt = (evt) ? evt : ((window.event) ? event : null);
 
   if (evt) {
 
      if (evt.keyCode == 37) {
 
         window.location = base+field+"="+p;
      }
      else {
         if (evt.keyCode == 39) {
            window.location = base+field+"="+n;
 
    		}
    	}
   }
}
 
 
window.addEventListener('keyup', handleArrowKeys, true);
 
document.onkeyup = handleArrowKeys;

The script is available here at userscripts.org.

Managing Music from the Command Line

30-Jun-09, by meyermagic

Okay. I've been curious for a while about trying to manage my music from the command line. All the media managers I have used so far have been a bit too bloated for my tastes. Because of this I decided to try out MPD (Music Player Daemon). It looked pretty cool, but I didn't have a good way to quickly queue up a bunch of songs I wanted to listen to. After trying out a few solutions by others, I wrote two of my own solutions.

The first was a fuzzy matching system, and the second was a "blob" matching system. Both allow me to queue up an entire album by typing something short.

Because I haven't finished tweaking the first solution, and the second one is working impressively, I'll be posting about it only.

What You'll Need:

  • mpd
  • mpc
  • Python 2.6

Here is the code:

?View Code PYTHON
#!/usr/bin/python
from subprocess import Popen, PIPE
import sys
 
class Database:
	def __init__(self, terms=None):
		self.terms = set()
		self.tree = (set(), dict())
		if terms is not None:
			self.populate(terms)
 
	def populate(self, terms):
		for term in terms:
			self.add(term)
 
	def variations(self, word):
		result = set([word])
		if word.startswith("0") and word != "0":
			#This allows "02" to be found as "2"
			result.add(word)
		elif word in frozenset(["i", "ii", "iii"]):
			#This allows "II" to be found as "2"
			result.add(str(len(word)))
		return result
 
	def words(self, term):
		term = term.lower()
		words = set()
		buf = ""
		for char in term:
			if char.isalnum():
				buf += char
			else:
				if buf != "":
					words = words.union(self.variations(buf))
					buf = ""
		if buf != "":
			words = words.union(self.variations(buf))
		return words
 
	def add(self, term):
		self.terms.add(term)
		for word in self.words(term):
			current = self.tree
			for char in word:
				if char not in current[1]:
					current[1][char] = (set(), dict())
				current = current[1][char]
			current[0].add(term)
 
	def search(self, blob, union=False):
		#This is for excluding words from a search
		if "~" in blob:
			#Excludes titles with ANY words from exclude
			blob, remove = blob.lower().split("~")
			mode = True
		elif "#" in blob:
			#Excludes titles with ALL words from exclude
			blob, remove = blob.lower().split("#")
			mode = False
		else:
			remove = None
			blob = blob.lower()
		found = dict()
		current = self.tree
		i = 0
		for char in blob:
			if char in current[1]:
				current = current[1][char]
				i += 1
				if len(current[0]) > 0:
					found[blob[i:]] = current[0]
			else:
				break
		final = set()
		for k, v in found.items():
			if k == "":
				final |= v
			else:
				if union:
					final |= v | self.search(k)
				else:
					final |= v & self.search(k)
		if remove is not None:
			remove = self.search(remove, mode)
			final -= remove
		return final
 
	def __contains__(self, other):
		return other in self.terms
 
#Initialize and populate album database
db = Database()
#with open("/var/lib/mpd/tag_cache") as f:
#	for line in f:
#		if line.startswith("Album: "):
#			album = line[7:-1]
#			if album not in db:
#				db.add(album)
#We'll get the album list from mpc instead of the tag_cache file. I haven't done a benchmark.
p1 = Popen(["mpc", "list", "album"], stdout=PIPE)
p1.wait()
with p1.stdout as f:
	for line in f:
		album = line[:-1]
		if album != "":
			db.add(album)
 
#Parse arguments
if len(sys.argv) == 2:
	searches = [sys.argv[1]]
else:
	print "No album specified. Exiting..."
	sys.exit()
 
#Perform search
r = db.search(searches[0])
r = list(r)
 
#If more than one album is returned, ask the user to choose which one we will play.
if len(r) > 1:
	for i in xrange(len(r)):
		print " " + str(i+1) + ")", r[i]
	r = r[int(raw_input("Enter a number: ")) - 1]
elif len(r) == 1:
	r = r[0]
else:
	print "No albums found. Exiting..."
	sys.exit()
 
print "Playing album '" + r + "'"
 
#Find the tracks in the album
p1 = Popen(['mpc', 'find', 'album', r], stdout=PIPE)
p1.wait()
 
#Clear the current playlist
Popen(['mpc', 'clear'], stdout=PIPE).wait()
 
#Queue up all the tracks in the album
with p1.stdout as f:
	for line in f:
		Popen(['mpc', 'add', line[:-1]], stdout=PIPE).wait()
 
#Let MPC print a list of the tracks we have queued
Popen(['mpc', 'playlist']).wait()
 
#Start playback
Popen(['mpc', 'play'], stdout=PIPE).wait()

Save that as "album" or whatever you would like. Put it in your home directory or wherever you would like. Finally, open ~/.bashrc (for Ubuntu users, at least), and add an alias pointing to the script. I use "alias album='~/album'".

Make sure you set the script to executable.

Finally, because I enjoy having my listening history available on last.fm, I also installed lastmp to scrobble songs played with MPD.

Oh yeah. I almost forgot usage instructions. With the setup described above, one could just type "album wolfsrain" to play the Wolf's Rain OST (the script would prompt you to select either the first or second volume) or "album fiction" to play Yuki Kajiura's solo album "Fiction".

I call this album search method a "blob search" because you can type any words from an album title, in any order and without spaces, and still find the album. For instance, both "album wishyou" and "album hereyouwish" will start the playback of Wish You Were Here in my library.

Embed Terminal Into Desktop

28-May-09, by Captain Thomas

There are some neat things that I have been looking into for customizing my installation of Ubuntu 9.04, one of which is embedding a terminal into the desktop so that I don't have to worry about closing it on accident (not to mention that it looks cool, too). Seeing as I have had to do this multiple times, I decided to write down the steps. While I know there are other tutorials, I spent too long looking for one that worked correctly (read: to my needs). This is cross-posted from The League of Magnificent Scoundrels.

Software Required

  • Compiz must be running. You'll also need CompizConfig, as detailed below.
  • Terminal (duh)

1. Getting Software

The first thing we will do is install CompizConfig. For this we will open up the Add/Remove application. For those new to Ubuntu, you click Applications, and select Add/Remove. After this opens, search for compiz and check Advanced Desktop Effects Settings (ccsm). Now search for irssi and check it. Click apply, and when the programs are finished installing, you can close the window.

2. Creating a Terminal Profile

For the embedding to work, we need to create a new profile for terminal. We need to do this because Compiz will change the behavior of the terminal depending on it's title.

Open up a terminal (press Alt+F2 and type gnome-terminal). On the menu bar, select Edit, then Profiles. Click New and name it "deskie" (or whatever else you want to as long as it's a unique name).

Under the General tab, uncheck "Show menubar by default". Moving over to the Title tab, name it deskie (or what you named it before, if different) and set to "keep initial title". Under the Colors tab, select White on Black, or whatever will suit your theme. Next we will move to Background. Set the Transparency to 0% or whatever fits your theme. Under the Scrolling tab, disable the scrollbar. After you've done what you want with this, close out of the profile editor.

3. Setting up CompizConfig

Now open CompizConfig (under System -> Preferences -> CompizConfig). The first thing we'll do here is search for Window Decoration, and click on it. Under Decoration Window, we'll replace "any" with "!title=deskie", without the quotes. Now search for Window Rules. Enable it, then click on it. We'll be adding "title=deskie" (without quotes) to:

  • Skip taskbar
  • Skip pager
  • Below
  • Sticky
  • Non resizable windows
  • Non minimizable windows
  • Non maximizable windows
  • Non closable windows

After these are filled in, go to the Size rules tab. Click New and add "title=deskie" and set the height and width you want the window to be. When you are satisfied, close CompizConfig.

4. The Aftermath

If you want to see what you've done, press Alt+F2 and type "gnome-terminal --window-with-profile=deskie" as the command and hit enter. You should now have a neat "embedded" terminal. To move it where you want, hold Alt, then click and drag.

If you want one to load on startup, go to System -> Preferences -> Startup Applications, and click Add. Name it "Embedded Terminal" and put "gnome-terminal --window-with-profile=deskie" as the command.

That should work, as it has for me. If you have any questions, comments, or concerns, feel free to let me know!