The Fugue Counterpoint by Hans Fugal

7Jan/111

On TODO lists

Lately in my TODO usage, a pattern has emerged. It's actually rather helpful, and it's dead simple.

I just keep a text file; one for work, one for my dissertation, and one for personal stuff. It's free-form, but roughly divided into 3 sections. First the page table, then the core, and finally the metadata. Yes, I really just pretended my TODO list is a virtual memory system.

In English, the first part is a refresher/jump starter. It's just a list of what's next: what I hope to accomplish today or this week. The hardest part is always starting the next task, and I find it immensely helpful to do the prioritizing once in the morning or perhaps the beginning of the week, and then I can refer to a short and ordered list of things I plan to do, and no effort is wasted hem-hawing about what to do next. (Often, life is interrupt-driven and I don't actually get to what's on my list.)

Then I have the actual list of all the things I need to do, categorized however is useful for that particular file. For work, I have sections of the various task priorities (unbreak now, hi-pri, mid-pri, low-pri), and also groupings by project (most low-pri stuff makes more sense grouped by project because it's usually done opportunistically and/or when I feel like working on that project.) Most of the tasks of any substance actually have tasks in our task tracking thing for collaboration, and most of the details are there. These are just one-line reminders. For my dissertation, it's a more traditional list and an outline of what still needs writing. A little structure but no rules.

Finally, the "metadata" is just notes and free-form text that is relevant to the TODO list but not an actual list itself. Totally free-form.

It's been working well for me, it's surprisingly effective. Together with my notebook where I regularly write out my thoughts as a sort of log to get my juices flowing (especially when I'm feeling stuck), it's a great low(ish) tech solution to staying on top of things and avoiding that overwhelming panic feeling when you can't figure out what to do next, or worse can't remember everything you have to do.

12Oct/099

Graph-paper Weight Loss

I think I've posted on this before, but I want to post my method for weight loss again, and try to do it in as few words as possible this time. If you want more information about the principles upon which I based this method, read Total Fitness in 30 Minutes a Week by Laurence Morehouse, and The Hacker's Diet by John Walker.

First, why you should care: I lost 40 pounds eating whatever I wanted and have kept it off even after joining Facebook and eating up to 3 meals a day at the gourmet buffet plus micro kitchens.

Second, why you may not care: you want to lose more than 2 lb / week (good luck with that) or you are a masochist.

Here's what you need: a piece of graph paper, a pencil, and a bathroom scale (digital is best). Oh, and a little patience and a small amount of willpower (much less than your average diet requires). To set up all you need to do is draw a line in the corner that goes over 5 squares and down 1 square for a weight loss rate of 1 pound per 5 days, you can choose any weight loss or gain rate that you want as long as it's not more than 2 pounds per week. Obviously, the faster you lose the more willpower you need, but no matter how much willpower you have it won't work if you go much over 2 pounds per week.

Here's what you do every day: just after you wake up you use the bathroom and then step on the scale—before eating or drinking anything. Mark your weight on the graph paper. Then, imagine the line that best fits the marks for the past 5 days (least squares fit for you mathy types).

Is that line sloping down more steeply than your reference line? Congratulations: you can eat as much of anything you want today. Don't gorge yourself, but don't fret it. Want that ice cream? Go for it. Candy bar? Sure. Triple burger with guacamole? Ok, but think about making it a double man.

Is that line flatter than your reference line (or going up)? Then today you still get to eat (almost) whatever you want! But, we need to restrict quantity. In specific, we need to eat about 500–800 calories less than your normal intake. You can accomplish this easily by eating smaller portions at meals and skipping dessert.

That's it. Fast forward 1 year and if you have stuck with the plan you will have lost as much as 70 pounds. But we know life (and holidays) happens, so be realistic and figure you'll fall off the horse a few times. No big deal, this system is designed to keep your morale up and recover gracefully. To be safe, expect 40 pounds per year, then be pleasantly surprised.

There is no correlation between what you eat today and what you weigh tomorrow. Well that's technically a lie, but there are much bigger effects than what you ate. Your weight will fluctuate as much as 2 lb due to various effects. You have no control over this. The method is not relying on any correlation between today's diet and tomorrow's weight. It is relying on the long-term effects of eating smaller portions approximately half the days (maybe even less). If you weigh more today it doesn't mean you failed yesterday! If you weigh less today it doesn't mean you succeeded yesterday! You have to take the long-term view and just stick with the program.

Now some short notes. First, exercise is a good idea. No, a great idea. Although it's not essential to this plan, I think you'll find it helps you with appetite control and to just feel better all-around. Second, eating healthy doesn't hurt either, obviously.

16Jun/0910

Why I chose Facebook over Google

May was a busy month, and not in a "fast-track-to-finish-dissertation" kind of way. I zoomed to Florida for a family reunion, then zoomed across the country to San Fransisco for an interview at Facebook, then the next week zoomed over to NYC for an interview at Google in their Manhattan office. Then, lest the last week in May should feel left out, I zoomed up to Utah for an interview with AST. As May drew to a close and June entered the scene, I found myself with four offers of employment, including Google and Facebook (Boeing just did a phone interview). Wow! I was feeling pretty pleased with myself.

Now dear casual reader from yonder internet, I know what you're thinking. It's exactly what I would have thought a few months ago. "No-brainer. Work for Google." But I found myself perplexingly unable to decide between Google and Facebook. It didn't help that Google was in New York and my wife wasn't keen on New York, and that the Google position offered was a Site Reliability Engineer (SRE) which is, to grossly oversimplify, a glorified systems programmer. Not faced with an attractive competing offer like the one from Facebook, I'd have been headed to Manhattan to be an SRE in a heartbeat. But as it was I was torn.

I applied for the Facebook position on a whim. I had been on Facebook only a month or two and I noticed an ad on the side about how they were hiring. I clicked on it, looked at their careers page for awhile, liked what I saw, and decided to apply for a Machine Learning Engineer position. I came home and told my wife, who was instantly convinced I had lost my marbles. She is not a programmer, so how could she know that although I'm not a big social network guy and had only grudgingly signed up for Facebook a month or two before, I could be intensely interested in working on such a site.

Theoretically speaking, I love networks, and that includes graph theory, computer networks, and yes even social networks. I knew Facebook problems would be about networks and about scale. They would be hard problems. They would be fun problems. The perks page looked good, and I think it would be fun to be in Silicon Valley. So I applied.

Facebook called and had me do one of their pre-screening programming puzzles (you can enjoy them even if you're not being considered for employment). I got a little obsessed and did 3 over the better part of a week. Then there was a phone screen, and then the invitation to interview on-site (This all happened over several weeks time, of course, and more or less in parallel with a similar process for Google, except that Google didn't have a puzzle step).

I'll skip to the end, lest I bore you. Facebook continually impressed me the more I interacted with them and the more I saw. I was similarily surprised at how enjoyable, useful, and private Facebook was as a user since I had signed up. Facebook skyrocketed from just another one of those pesky social networking sites to a daily habit and potential employer in a few short months.

Still, it can't compare to Google right? Well, actually they try very hard to compare to Google. It's quite obvious that most of the perks and benefits are directly inspired by Google's, compensation was comparable, and I found that they have smart people working on hard problems just like Google. In addition, there are two things they have that Google doesn't anymore: they're still small and they're pre-IPO. Of course, Google has more smart people and Facebook is more-or-less a one-trick pony. Publishing would probably be easier at Google. Here I go again… I was back and forth for a solid week. It drove me and my wife crazy. I talked to people at Facebook who had been at Google (at my request). I talked to people at Google. I talked to friends and family. I tried every decision making trick in the book. Both sides sweetened the deal a bit to make it easier for me to choose them (Google switched the offer to be in Mountain View as a Software Engineer, Facebook offered a bit more money). I just couldn't make up my mind.

Well finally I did, and this is what it came down to: time off, impact, and stock.

Although both were generous with paid time off (PTO) compared to most US companies, Facebook offered more, especially in the area of paternity leave (my wife is expecting). As much as I anticipate enjoying either job, I enjoy being at home or on vacation with my family more.

Facebook is small—they're hiring like crazy because they're smaller than they want to be. Because of that, I feel that my wacky background in system administration, agileish pragmatic open-source programming, and high-brow CS theory and research would be more broadly brought to bear than in some team at Google responsible for some small part of one of the many wonderful Google products. I would have more impact—not on the world at large but on the thing that we are doing. Then there's also the draw of quick seniority in a small and fast-growing company.

Finally, although both companies offered restricted stock units to vest over 4 years, Facebook's offering has the potential to be worth several times what Google's offering would. Or, Facebook's offering could be worth about what Google's would be. Or, in a really really bad scenario, worth less. I ran the numbers, pulled some probabilities out of the air, and decided at least as good as Google and possibly much better was the most likely scenario. Since they're RSUs and not options, the only risk is in making less extra cash than I would have made at the other company. Google was the safer choice, Facebook the more potentially lucrative. I tried to leave this out of the decision making process as much as possible, because I didn't want my decision to be about money. I wanted it to be about the work. But I have to admit there's a draw here.

I believe more strongly now than I did before that Google is a fantastic place to work, and I would reapply there in a heartbeat down the road if my path takes me there. I wish I could just be in two places at once and work at both, at least long enough to know for sure where I really want to be. But life doesn't work that way. I've decided to take the path that seems the most exciting and unsure out of a sheer sense of adventure. Google will be there in a few years if I want to reapply, and they may even want me then as they do now. The time to really make a difference at Facebook is now, while they're small.

You may think I'm crazy to turn down Google. I think that sometimes too. One thing is certain: the next few years will be an amazing adventure that I never dared dream would come to me. Thanks to everyone who has helped me to this point in my life, especially my loving and supportive wife and kids, my parents who taught me to love learning, and my brothers who "let" me hog the computer when we were young (ha!).

Well, I think I have a dissertation lying around here that needs finishing.

24Nov/081

Bald

Well, the time finally came and so my hair went. Involuntarily, this time.


bald top
bald front
bald side
bald back

Now, these pictures aren't very high resolution so you may not be able to tell, but this first shave isn't particularly close. Even then it took awhile to do. I'm going to shave it every day until I get a reasonably close shave (I'm thinking tomorrow I should get pretty close), then I'll take some higher-resolution pictures and wear something more tough-looking, so you can all feel properly intimidated.

I mostly used a 2-blade cartridge razor, but I was getting annoyed with it doing basically nothing and tried switching to the safety razor a few times. This worked well on the parts I could see in the mirror, but it would take some practice to be able to shave the back with a safety razor because you have to get the angle right and some foresight on the upcoming curves helps a lot.

The HeadBlade seems to be popular for getting a good and quick shave. It's only $13 and my local WalGreens has it, so if I decide to stick with it I may not be able to resist trying it out (it's called Brush/Soap/Razor Acquisition Disorder). The downside would of course be that it uses expensive cartridges and there's something like 3x as much hair on your pate as your face. DE blades are much cheaper in the long haul. I wonder if there is a HeadBlade DE adapter…

13Oct/084

How Short?

My hair on top has thinned to the point that my old standby hairstyle is now dysfunctional. So now I have to decide what to do. Should I shave it? Buzz it? Grow it long? Short but not quite buzzed? If you have any idea (as if anyone cares) now is the time to weigh in.

Pictures of the situation are here.

Crown

Top

Tagged as: , , , , , 4 Comments
25Sep/082

Bread PDF Update

I've updated my bread/sourdough PDF to reflect the recipe and methods I have settled on.

The bread recipe didn't really change, though I adjusted a few minor details in wording, etc. The sourdough pancakes recipe is completely new—the one from Joe Pastry which is so much better than the one I came up with. The biscuit recipe is the old biscuit recipe from the old sourdough cards that my family got with our start. I don't know if that source has a name or author, but I do have scanned images at http://hans.fugal.net/sourdough/. The consensus of all who eat these biscuits is that they must be served at Thanksgiving dinner in Heaven.

biscuits1
biscuits2

19Sep/080

IMMS

So Apple added this Genius thing to iTunes recently. Not being the type to get excited about new iPod styles, it looks like the most interesting thing they could come up with this year. I gave it a try. I am not impressed.

I think it's because I've been spoiled. 5 years ago I was using what I still consider to be the peak of intelligent listening software, IMMS. Genius isn't half as cool as IMMS was then, and while IMMS hasn't made any quantum leaps in coolness, quite a few rough edges have been rounded off in the meantime.

I've been living in a sort of IMMS drought the past couple of years, since I switched to using a laptop primarily. Namely, an Apple laptop. This Genius release spurred me on to rectify that situation. If the best Apple could do was generate a 25-song playlist based on statistics gathered from other people the hopes of someone else hacking up an iTunes plugin to do IMMS or something like it dwindled to obscurity.

The bane of IMMS is, ironically, its most compelling feature. IMMS is cool because you don't have to do anything. It pays attention to your listening habits, and analyzes the audio, and makes intelligent decisions for you when you turn on random. 4 years ago I would show up to work and be in a Depeche Mode mood, so I'd manually queue up a Depeche Mode song or two and the whole day I'd be treated to complementary music. If the occasional happy song slipped through, I just skipped it and IMMS took the hint. Don't underestimate the amazing wow factor of a computer apparently reading your mind.

But this focus on simple non-obtrusive UI has been its biggest technical struggle. Media players are now a dime a dozen, and few of them have the plugin and UI sophistication to support IMMS' modus operandi. IMMS was developed originally as a plugin for XMMS and even then ugly workaround hacks were required. Then someone wrote a queue control patch for XMMS, and if you patched your XMMS you were in heaven. Oh, did I mention that still almost no other media players even have queue functionality, let alone let the plugins control the queue? Then when you consider the set of media players usable on OS X the situation gets laughable.

Somewhere in the middle MPD came along. It fit my situation well because the speakers over on the desktop were a lot nicer than the ones in my laptop. But queues it has not and nobody seems to care. Von bravely came up with an IMMS hack for MPD, but it was too hacky for me—too much like the old XMMS days before the queue control patch (incidentally, queue control is part of XMMS proper now as of version 1.2.11).

So I suffered along with manual or truly random music listening. Until now.

Recently I looked into this again for the desktop, and I was delighted to discover that one of the many XMMS descendants has finally solved the XMMS bitrot without throwing the baby out with the bathwater. Audacious is as cool as XMMS ever was and as modern as your favorite modern player (unless you measure modern by klunky iTunes-like screen-wasting music browsers). What's more, the imms plugin for it is right there in the Ubuntu repository. Just apt-get install imms-audacious and enable the plugin and you're off and running. So I set it up and… didn't use it. As in, we rarely listen to music on the desktop because nobody really sits there for very long. So finally earlier this week I hammered out a simple remote control using Audacious' dbus interface. That's another post, once I knock off a few other TODO points.

Feeling on a roll and feeling left out when at school, I decided to get an IMMS solution on my laptop, running OS X Leopard (10.5.4). I'll spare you the agonizing play-by-play and give you the shortest path to success: install Audacious and then IMMS. Actually the really shortest path is to install XMMS and then IMMS, because XMMS is in MacPorts. But it's the old version of XMMS without queue control, and doesn't have CoreAudio support (you have to use the JACK output plugin) so I don't recommend that.

To install Audacious, install its dependencies (mostly using MacPorts), then build it and its plugins. Installing its dependencies is the hardest part because it's difficult to locate libmcs and libmowgli (they're not where the README says they are, and Google is less than helpful). I just ended up stealing the *.orig.tar.gz files from the Ubuntu packages (apt-get source -d libmcs1 libmowgli). There is one patch you need for the plugins.

 src/CoreAudio/audio.c |    7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

Index: audacious-plugins-1.5.1/src/CoreAudio/audio.c
===================================================================
--- audacious-plugins-1.5.1.orig/src/CoreAudio/audio.c  2008-09-19 12:08:01.000000000 -0600
+++ audacious-plugins-1.5.1/src/CoreAudio/audio.c   2008-09-19 12:10:28.000000000 -0600
@@ -326,7 +326,12 @@ gint osx_get_output_time(void)
 {
        gint retval;

-        retval = output_time_offset + ((output_total * sample_size * 1000) / output.bps);
+        if (output.bps == 0)
+        {
+            printf("Avoiding divide by zero in osx_get_output_time()\n");
+            retval = 0;
+        } else
+            retval = output_time_offset + ((output_total * sample_size * 1000) / output.bps);
        retval = (int)((float)retval / user_pitch);

        //printf("osx_get_output_time(): time is %d\n",retval);

Next you need to install IMMS. This is a bit more involved, but should be straightforward with these patches. I'll put them here and talk about each in turn.

First, a missing include for mkdir()

 immsd/immsd.cc |    1 +
 1 file changed, 1 insertion(+)

Index: imms-3.1.0-rc4/immsd/immsd.cc
===================================================================
--- imms-3.1.0-rc4.orig/immsd/immsd.cc  2008-03-02 18:54:06.000000000 -0700
+++ imms-3.1.0-rc4/immsd/immsd.cc   2008-09-19 08:05:58.000000000 -0600
@@ -2,6 +2,7 @@
 #include <errno.h>
 #include <signal.h>
 #include <unistd.h>
+#include <sys/stat.h>

 #include <iostream>
 #include <sstream>

Then, a workaround due to OS X not having an initstate_r() (which I
incidentally couldn't find in the current Linux manpages on Ubuntu or Debian
either). This patch may not apply cleanly by itself, you may need to apply your
cognitive reasoning.

configure.ac         |    3 +++
immsconf.h           |    3 +++
immsconf.h.in        |    3 +++
immscore/immsutil.cc |    9 +++++++++
4 files changed, 18 insertions(+)

Index: imms-3.1.0-rc4/immscore/immsutil.cc
===================================================================
--- imms-3.1.0-rc4.orig/immscore/immsutil.cc    2008-03-02 18:54:06.000000000 -0700
+++ imms-3.1.0-rc4/immscore/immsutil.cc 2008-09-19 08:13:29.000000000 -0600
@@ -27,6 +27,7 @@ int imms_random(int max)
{
    int rand_num;
    static bool initialized = false;
+#ifndef INITSTATE_BUG
    static struct random_data rand_data;
    static char rand_state[256];
    if (!initialized)
@@ -36,6 +37,14 @@ int imms_random(int max)
        initialized = true;
    }
    random_r(&rand_data, &rand_num);
+#else
+    if (!initialized)
+    {
+        srandom(time(0));
+        initialized = true;
+    }
+    rand_num = random();
+#endif
    double cof = rand_num / (RAND_MAX + 1.0);
    return (int)(max * cof);
}
Index: imms-3.1.0-rc4/configure.ac
===================================================================
--- imms-3.1.0-rc4.orig/configure.ac    2008-03-02 18:54:06.000000000 -0700
+++ imms-3.1.0-rc4/configure.ac 2008-09-19 08:17:58.000000000 -0600
@@ -68,6 +68,9 @@ else
    AC_MSG_RESULT([yes])
fi

+AC_DEFINE(INITSTATE_BUG,, [initstate_r is buggy])
+
+
AC_CHECK_LIB(z, compress,, [with_zlib=no])
AC_CHECK_HEADERS(zlib.h,, [with_zlib=no])
if test "$with_zlib" = "no"; then
Index: imms-3.1.0-rc4/immsconf.h
===================================================================
--- imms-3.1.0-rc4.orig/immsconf.h  2008-09-19 08:05:31.000000000 -0600
+++ imms-3.1.0-rc4/immsconf.h   2008-09-19 08:18:23.000000000 -0600
@@ -121,6 +121,9 @@
/* Define to 1 if you have the <zlib.h> header file. */
#define HAVE_ZLIB_H 1

+/* initstate_r is buggy */
+#define INITSTATE_BUG /**/
+
/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "mag@luminal.org"

Index: imms-3.1.0-rc4/immsconf.h.in
===================================================================
--- imms-3.1.0-rc4.orig/immsconf.h.in   2008-09-19 07:48:52.000000000 -0600
+++ imms-3.1.0-rc4/immsconf.h.in    2008-09-19 08:16:32.000000000 -0600
@@ -120,6 +120,9 @@
/* Define to 1 if you have the <zlib.h> header file. */
#undef HAVE_ZLIB_H

+/* initstate_r is buggy */
+#undef INITSTATE_BUG
+
/* Define to the address where bug reports for this package should be sent. */
#undef PACKAGE_BUGREPORT

This patch is just so libpcre can be found

build/Makefile |    1 +
1 file changed, 1 insertion(+)

Index: imms-3.1.0-rc4/build/Makefile
===================================================================
--- imms-3.1.0-rc4.orig/build/Makefile  2008-03-02 18:54:06.000000000 -0700
+++ imms-3.1.0-rc4/build/Makefile   2008-09-19 12:25:05.000000000 -0600
@@ -18,6 +18,7 @@ libimmscore.a: $(call objects,../immscor
libmodel.a: $(call objects,../model) svm-similarity-data.o
        $(AR) $(ARFLAGS) $@ $(filter %.o,$^)

+immstool-LIBS=`pcre-config --libs`
immstool: immstool.o libmodel.a libimmscore.a
training_data: training_data.o libmodel.a libimmscore.a
train_model: train_model.o libmodel.a libimmscore.a

Linking shared libraries on OS X is so much different from on Linux that there is almost always a need to do a patch something like this.

rules.mk |    5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)

Index: imms-3.1.0-rc4/rules.mk
===================================================================
--- imms-3.1.0-rc4.orig/rules.mk    2008-09-19 09:04:13.000000000 -0600
+++ imms-3.1.0-rc4/rules.mk 2008-09-19 12:25:50.000000000 -0600
@@ -14,9 +14,8 @@ link = $(CXX) $(filter-out %.a,$1) $(fil
%.o: %.c; $(call compile, $(CC), $<, $@, $($*-CFLAGS) $(CFLAGS) $($*-CPPFLAGS) $(CPPFLAGS))
%: %.o; $(call link, $^ $($*-OBJ) $(LIBS), $@, $($*-LIBS) $(LDFLAGS))
%.so:
-   $(CXX) $^ $($*-OBJ) $($*-LIBS) $(LIBS) \
-       $(LDFLAGS) \
-            -shared -Wl,-z,defs,-soname,$@ -o $@
+   gcc -flat_namespace -undefined suppress -o $@ -bundle $^ $($*-OBJ) $($*-LIBS) $(LIBS) \
+       $(LDFLAGS) -o $@

%-data.o: %
        $(OBJCOPY) -I binary -O $(OBJCOPYTARGET) -B $(OBJCOPYARCH) --rename-section .data=.rodata,alloc,load,readonly,data,contents $< $@

This final patch fixes IMMS to use the proper interface for audacious (seems like this would have to be done anywhere?)

clients/audacious/audaciousinterface.c |  177 +++++++++++++++++++++++++++++++++
clients/audacious/rules.mk             |    2
2 files changed, 178 insertions(+), 1 deletion(-)

Index: imms-3.1.0-rc4/clients/audacious/audaciousinterface.c
===================================================================
--- /dev/null   1970-01-01 00:00:00.000000000 +0000
+++ imms-3.1.0-rc4/clients/audacious/audaciousinterface.c   2008-09-19 15:30:21.000000000 -0600
@@ -0,0 +1,177 @@
+#include <gtk/gtk.h>
+
+#ifdef BMP
+#include <bmp/configdb.h>
+#include <bmp/util.h>
+#include <bmp/plugin.h>
+#elif AUDACIOUS
+#include <audacious/configdb.h>
+#include <audacious/util.h>
+#include <audacious/plugin.h>
+#endif
+#include "immsconf.h"
+#include "cplugin.h"
+
+
+int use_xidle = 1;
+int poll_tag = 0;
+
+GtkWidget *configure_win = NULL, *about_win = NULL, *xidle_button = NULL;
+
+gint poll_func(gpointer unused)
+{
+    imms_poll();
+    return TRUE;
+}
+
+void read_config(void)
+{
+    ConfigDb *cfgfile;
+
+    if ((cfgfile = cfg_db_open()) != NULL)
+    {
+        cfg_db_get_int(cfgfile, "imms", "xidle", &use_xidle);
+        cfg_db_close(cfgfile);
+    }
+}
+
+void init(void)
+{
+    imms_init();
+    read_config();
+    imms_setup(use_xidle);
+    poll_tag = gtk_timeout_add(200, poll_func, NULL);
+}
+
+void cleanup(void)
+{
+    imms_cleanup();
+
+    if (poll_tag)
+        gtk_timeout_remove(poll_tag);
+
+    poll_tag = 0;
+}
+
+void configure_ok_cb(gpointer data)
+{
+    ConfigDb *cfgfile = cfg_db_open();
+
+    use_xidle = !!GTK_TOGGLE_BUTTON(xidle_button)->active;
+
+    cfg_db_set_int(cfgfile, "imms", "xidle", use_xidle);
+    cfg_db_close(cfgfile);
+
+    imms_setup(use_xidle);
+    gtk_widget_destroy(configure_win);
+}
+
+#define ADD_CONFIG_CHECKBOX(pref, title, label, descr)                          \
+    pref##_frame = gtk_frame_new(title);                                        \
+    gtk_box_pack_start(GTK_BOX(configure_vbox), pref##_frame, FALSE, FALSE, 0); \
+    pref##_vbox = gtk_vbox_new(FALSE, 10);                                      \
+    gtk_container_set_border_width(GTK_CONTAINER(pref##_vbox), 5);              \
+    gtk_container_add(GTK_CONTAINER(pref##_frame), pref##_vbox);                \
+                                                                                \
+    pref##_desc = gtk_label_new(label);                                         \
+                                                                                \
+    gtk_label_set_line_wrap(GTK_LABEL(pref##_desc), TRUE);                      \
+    gtk_label_set_justify(GTK_LABEL(pref##_desc), GTK_JUSTIFY_LEFT);            \
+    gtk_misc_set_alignment(GTK_MISC(pref##_desc), 0, 0.5);                      \
+    gtk_box_pack_start(GTK_BOX(pref##_vbox), pref##_desc, FALSE, FALSE, 0);     \
+    gtk_widget_show(pref##_desc);                                               \
+                                                                                \
+    pref##_hbox = gtk_hbox_new(FALSE, 5);                                       \
+    gtk_box_pack_start(GTK_BOX(pref##_vbox), pref##_hbox, FALSE, FALSE, 0);     \
+                                                                                \
+    pref##_button = gtk_check_button_new_with_label(descr);                     \
+    gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pref##_button), use_##pref); \
+    gtk_box_pack_start(GTK_BOX(pref##_hbox), pref##_button, FALSE, FALSE, 0);   \
+                                                                                \
+    gtk_widget_show(pref##_frame);                                              \
+    gtk_widget_show(pref##_vbox);                                               \
+    gtk_widget_show(pref##_button);                                             \
+    gtk_widget_show(pref##_hbox);
+
+void configure(void)
+{
+    GtkWidget *configure_vbox;
+    GtkWidget *xidle_hbox, *xidle_vbox, *xidle_frame, *xidle_desc;
+    GtkWidget *configure_bbox, *configure_ok, *configure_cancel;
+
+    if (configure_win)
+        return;
+
+    read_config();
+
+    configure_win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+    gtk_signal_connect(GTK_OBJECT(configure_win), "destroy",
+            GTK_SIGNAL_FUNC(gtk_widget_destroyed), &configure_win);
+    gtk_window_set_title(GTK_WINDOW(configure_win), "IMMS Configuration");
+
+    gtk_container_set_border_width(GTK_CONTAINER(configure_win), 10);
+
+    configure_vbox = gtk_vbox_new(FALSE, 10);
+    gtk_container_add(GTK_CONTAINER(configure_win), configure_vbox);
+
+    ADD_CONFIG_CHECKBOX(xidle, "Idleness",
+#ifdef BMP
+            "Disable this option if you use BEEP on a dedicated machine",
+#elif AUDACIOUS
+            "Disable this option if you use Audacious on a dedicated machine",
+#endif
+            "Use X idleness statistics");
+
+    /* Buttons */
+    configure_bbox = gtk_hbutton_box_new();
+    gtk_button_box_set_layout(GTK_BUTTON_BOX(configure_bbox), GTK_BUTTONBOX_END);
+    gtk_button_box_set_spacing(GTK_BUTTON_BOX(configure_bbox), 5);
+    gtk_box_pack_start(GTK_BOX(configure_vbox), configure_bbox, FALSE, FALSE, 0);
+
+    configure_ok = gtk_button_new_with_label("Ok");
+    gtk_signal_connect(GTK_OBJECT(configure_ok), "clicked",
+            GTK_SIGNAL_FUNC(configure_ok_cb), NULL);
+    GTK_WIDGET_SET_FLAGS(configure_ok, GTK_CAN_DEFAULT);
+    gtk_box_pack_start(GTK_BOX(configure_bbox), configure_ok, TRUE, TRUE, 0);
+    gtk_widget_show(configure_ok);
+    gtk_widget_grab_default(configure_ok);
+
+    configure_cancel = gtk_button_new_with_label("Cancel");
+    gtk_signal_connect_object(GTK_OBJECT(configure_cancel), "clicked",
+            GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(configure_win));
+    GTK_WIDGET_SET_FLAGS(configure_cancel, GTK_CAN_DEFAULT);
+    gtk_box_pack_start(GTK_BOX(configure_bbox), configure_cancel, TRUE, TRUE, 0);
+    gtk_widget_show(configure_cancel);
+    gtk_widget_show(configure_bbox);
+    gtk_widget_show(configure_vbox);
+    gtk_widget_show(configure_win);
+}
+
+void about(void)
+{
+    if (about_win)
+        return;
+
+    about_win =
+#ifdef AUDACIOUS
+        audacious_info_dialog(
+#else
+        xmms_show_message(
+#endif
+            "About IMMS",
+            PACKAGE_STRING "\n\n"
+            "Intelligent Multimedia Management System" "\n\n"
+            "IMMS is an intelligent playlist plug-in for BPM" "\n"
+            "that tracks your listening patterns" "\n"
+            "and dynamically adapts to your taste." "\n\n"
+            "It is incredibly unobtrusive and easy to use" "\n"
+            "as it requires no direct user interaction." "\n\n"
+            "For more information please visit" "\n"
+            "http://www.luminal.org/wiki/index.php/IMMS" "\n\n"
+            "Written by" "\n"
+            "Michael \"mag\" Grigoriev <mag@luminal.org>",
+            "Dismiss", FALSE, NULL, NULL);
+
+    gtk_signal_connect(GTK_OBJECT(about_win), "destroy",
+            GTK_SIGNAL_FUNC(gtk_widget_destroyed), &about_win);
+}
Index: imms-3.1.0-rc4/clients/audacious/rules.mk
===================================================================
--- imms-3.1.0-rc4.orig/clients/audacious/rules.mk  2008-03-02 18:54:06.000000000 -0700
+++ imms-3.1.0-rc4/clients/audacious/rules.mk   2008-09-19 15:28:17.000000000 -0600
@@ -7,7 +7,7 @@ libaudaciousimms-LIBS = $(AUDACIOUSLDFLA
audaciousinterface-CPPFLAGS=$(AUDACIOUSCPPFLAGS)
audplugin-CPPFLAGS=$(AUDACIOUSCPPFLAGS)

-audaciousinterface.o: bmpinterface.c
+audaciousinterface.o: audaciousinterface.c
        $(call compile, $(CC), $<, $@, $($*-CFLAGS) $(CFLAGS) $($*-CPPFLAGS) $(CPPFLAGS))

AUDACIOUSDESTDIR=""

Phew. And that's not all. When you build IMMS you need to have OBJDUMP=gobjdump if you're using the default binutils variant from MacPorts, and this patch:

 rules.mk   |    2 +-
 vars.mk    |    6 +++---
 vars.mk.in |    1 +
 3 files changed, 5 insertions(+), 4 deletions(-)

Index: imms-3.1.0-rc4/rules.mk
===================================================================
--- imms-3.1.0-rc4.orig/rules.mk        2008-09-19 08:49:43.000000000 -0600
+++ imms-3.1.0-rc4/rules.mk     2008-09-19 16:17:33.000000000 -0600
@@ -19,7 +19,7 @@ link = $(CXX) $(filter-out %.a,$1) $(fil
             -shared -Wl,-z,defs,-soname,$@ -o $@

 %-data.o: %
-       objcopy -I binary -O $(OBJCOPYTARGET) -B $(OBJCOPYARCH) --rename-section .data=.rodata,alloc,load,readonly,data,contents $< $@
+       $(OBJCOPY) -I binary -O $(OBJCOPYTARGET) -B $(OBJCOPYARCH) --rename-section .data=.rodata,alloc,load,readonly,data,contents $< $@

 # macros that expand to the object files in the given directories
 objects=$(sort $(notdir $(foreach type,c cc,$(call objects_$(type),$1))))
Index: imms-3.1.0-rc4/vars.mk
===================================================================
--- imms-3.1.0-rc4.orig/vars.mk 2008-09-19 09:03:05.000000000 -0600
+++ imms-3.1.0-rc4/vars.mk      2008-09-19 15:07:44.000000000 -0600
@@ -5,8 +5,8 @@ INSTALL = /opt/local/bin/ginstall -c
 prefix = /usr
 PREFIX = $(prefix)
 OBJCOPY = gobjcopy
-OBJCOPYTARGET =
-OBJCOPYARCH =
+OBJCOPYTARGET = mach-o-le
+OBJCOPYARCH = i386
 exec_prefix = ${prefix}
 bindir = ${exec_prefix}/bin
 datadir = ${prefix}/share
@@ -15,7 +15,7 @@ VPATH = ../immscore:../analyzer:../model
 ARFLAGS = rs

 SHELL = bash
-PLUGINS = libxmmsimms.so
+PLUGINS = libxmmsimms.so libaudaciousimms.so
 OPTIONAL = immsremote analyzer

 GLIB2CPPFLAGS=`pkg-config glib-2.0 --cflags`
Index: imms-3.1.0-rc4/vars.mk.in
===================================================================
--- imms-3.1.0-rc4.orig/vars.mk.in      2008-03-02 18:54:06.000000000 -0700
+++ imms-3.1.0-rc4/vars.mk.in   2008-09-19 16:17:24.000000000 -0600
@@ -4,6 +4,7 @@ VERSION = @PACKAGE_VERSION@
 INSTALL = @INSTALL@
 prefix = @prefix@
 PREFIX = $(prefix)
+OBJCOPY = @OBJCOPY@
 OBJCOPYTARGET = @OBJCOPYTARGET@
 OBJCOPYARCH = @OBJCOPYARCH@
 exec_prefix = @exec_prefix@

Finally, make install doesn't finish the job.

cp build/libaudaciousimms.so /usr/local/lib/General/imms.impl

Well I think that's all the information you need, though it may not go smoothly. Hopefully we can get this all worked into IMMS proper and the 3.1.0 release will just work. If you use linux give Audacious+IMMS a try—it's easy and painless. If you think Audacious is for sissies, learn about the queue and the jump feature and try out IMMS for a week or two before you pass final judgement.

Oh, two final notes: Installing Torch can be a real pain and Audacious keyboard shortcuts don't work well with the gtk2 +quartz variant in MacPorts, so you want to stick with X11 gtk2. Oh, and though Audacious has a Last.fm plugin I haven't yet been able to figure out how to get it to stay enabled.

11Sep/082

Touch Typing

Steve Yegge has posted an funny, irreverent, and above all excellent argument for touch typing. I highly recommend it even if you do touch type.

I am downright flabbergasted by some of the comments though. There are hunt-and-peck folks defending their inability to touch type as lifestyle choice based on the belief that they will get RSI. People justifying typing slow because it
saves them from wasting time chatting or writing long emails. There are some interesting claims from 2–4 finger typists that they can type without looking at 70wpm (this is certainly possible), and choosing not to learn to touch type
the "right way" because it will give them RSI (this is certainly ludicrous).

Typing is a vital skill if you work in IT, especially programming. Of this there is no doubt, rationalizing strangers aside. I simply cannot imagine being stuck typing at 10wpm or less. It would be like being stuck behind a pair of tractors on the freeway going 20 mph. For the rest of your life.

Last I checked I type about 65 wpm on average. I could probably go faster, but I never have felt the need. I can type as fast as I think when programming. If
I were a stream-of-consciousness novelist or a secretary, I could probably make use of a faster typing speed. As it is, 60–70wpm seems to be a sweet spot
for me.

I'd like to discuss the ridiculous RSI claims. Yes, if you type all day without breaks you can get RSI. There are certain things you can do to mitigate or exacerbate this for a given amount of typing and typing speed. If you tried to go lightning fast all the time you might hasten the onset of RSI. But I argue that the absolute worst thing you can do if you're afraid of RSI is to not touch type. RSI means Repetitive Stress Injury, from repetitively performing certain motions until your body starts to break down. Smaller more relaxed motions are less stressful on your body than large stiff motions. If you touch
type well, your hands are relaxed, your fingers float over the keys, and movement is minimal. Of course you still need to take care—take breaks, spend some time thinking without typing or drawing pictures on paper, proper nutrition, etc. But the biggest thing you can do to prevent RSI is to have proper form. And maybe learn dvorak (I still use qwerty because I find sysadmin and programming to be tedious with dvorak, but I don't spend most of my time actually typing).

On the other hand, if you hunt and peck 24/7 guess how much more movement—repetitive movement—your body is enduring? Ever heard of tennis elbow? RSI isn't the exclusive privilege of touch typists. If you type slow enough that you can't possible get RSI, you are irrelevant. If you type fast enough to be productive but don't have good form, you are setting yourself up for RSI. If you're RSI-prone or just paranoid, go learn dvorak now or find a job that doesn't require much typing.

I'd add to Steve's exhortation to learn to touch type, that if you do touch type but you feel your form is off, you have low accuracy, or you feel that your fingers are stiff, do some conscientious practice. Focus on accuracy and relaxation first, then speed. Enhancing your typing skills is a great benefit if you spend a lot of time typing, although learning to touch type in the first place is obviously a much bigger payoff.

Let's continue to learn from musicians: correct form (including relaxed posture and keep those wrists off the keyboard/desk), accuracy, then speed.

11Aug/081

Hipster Redux

I've talked about the Hipster PDA a few times. I finally stabilized on a system that works for me. I've been using it steadily for months with no major changes now, so I thought I'd share with you.

My brief foray into the hipmod was fun, but too restrictive and small in the end. I understand others enjoy it though, so I'm glad I did it.

I find the classic hipster with a few modifications works best. My biggest beef with the original hipster is that it falls apart and it's not very user-friendly. That binder clip had to go. So I got some binding rings (½" I think, but the exact size isn't critical) and use a standard 3-hole punch to punch 2 holes in the index cards, and bind it with 2 rings. This makes a more book-like planner, which nicely folds over on itself.

Now, those rings can be pesky to open and close so I decided not to. I snip a little cut from the edge of the cards I want to be removeable to the holes. They stay in but will come right out and go right in without struggle.

I also like to print some forms (as you've seen). These I just print on regular paper and trim to size with a guillotine then hole punch (no snips, that works best on cardstock).

I made front and back covers out of a cereal box and duct tape, and even a pen holder out of duct tape. I'll post a picture soon so you can see.

My planner consists of a few reference pages I printed out (including a circle of fifths, a few airport kneeboards, performance data for my favorite planes, and morse code… anything you can find a PDF for.), my weekly calendar/todo list pages, and a bunch of index cards that I use for notes, moments of inspiration, or whatever else they come in handy for. Oh, and a paperclip to mark the current week. I only have to reprint/refill the weekly pages about once every 3 months or so.

For printing things, I wrote a script that automates some of what I mentioned in previous posts.

30Jul/080

Use a balloon to estimate RV

So it's been awhile since I estimated my residual lung volume (RV), and I figured it was time to do it again.

I'm a big guy, so my lungs hold a lot of air. When you're blowing all your air into containers in the bathtub, and you're ⅔ or more exhaled, is not the best time for pausing to move your straw to another container—or worse, refilling the container. This time I decided to use a balloon.

I took a deep breath, exhaled maxmially into the balloon, then blew the rest into the container through a straw. (Wait, did he just say "the rest" after "maximally exhaled"?) I heard that. Yes, you can't exhale all of your air because the pressure in the balloon is higher than atmospheric pressure. In my case, I had another 400ml of air.

Then I emptied the air out of the balloon into the container. This is easy and leisurely once you figure out the trick, but it can seem next to impossible at first. Hint: don't try to submerge the balloon. If you grab the lip of the balloon mouth only, and avoid pinching the neck, you can control the air flow very well. There, I had measured my vital lung capacity (VC).

It worked great, and compared with the last circus event when I measured VC it was much easier.

Now I had to figure out how to get from VC to RV. The clown who wrote http://hans.fugal.net/density kind of left this step vague. I've remedied that and added a page to my spreadsheet. For the curious, my RV is up from 2.0 liters to 2.2 liters, and a total lung capacity of 8.2 liters.