Archive for the ‘technology’ Category

Snow Leopard is out

Sunday, August 30th, 2009

and while I don’t have my copy yet, I’ve been getting the betas and I know what’s in it. There’s a lot there, but its not visible to the average user or non-programming so-called technology “pundit”. This is unfortunate. They are tossing it off as a “service pack” but its more like having your car detailed and the tiny single cylinder engine replaced with a fuel injected turbo V8. It looks like the same car – but it can go many times faster. As applications get updated, it will get even faster. How?

The core OS has been restructured to allow it to take advantage of all those cores in newer computers. So while we have the same body, it is all new under the hood.

The truth is that 3GHz is about the theoretical limit for clock speeds for the Intel x86 family. They’re not going to get faster. They are getting cheaper though. So now, instead of getting a processor that’s twice as fast every cycle, you get twice as many 3GHz processors. That’s all well and good, but much of the core OS has been built assuming one processor. So even though I’ve got a dual quad with 8 processors in the box, most of them are idle most of the time. That power is wasted.

Enter Snow Leopard. It restructures the way the OS divides and schedules tasks and supplies new apis for applications to parallelize logic to much improve processing throughput. Besides letting programmers take advantage of all of the Intel cores – OpenCL makes it easier to offload vector processing to the graphics processor – a largely underutilized resource most of the time.

The average non-technical user won’t see much change and that’s no surprise – but if this were “just a service pack” there wouldn’t be so many broken programs that need updating. The change is substantial and at this point, most application updates going forward are going to require Snow Leopard. Its not practical to build apps that take advantage of Snow Leopard and still work on older releases so this is the line in the sand.

Fortunately, Apple realized this and Snow Leopard is priced “almost free”. I’m not one to push people to upgrade working systems without a compelling reason.

However, eventually an update or a new application will come out that you’ll want and it will require Snow Leopard and this is why it is good that Apple made the barrier to updating very low. Certainly a forthcoming update to Jambalaya is going to be Snow Leopard only.

Software Design Tip: Minimize Number of Languages

Tuesday, April 1st, 2008

I was recently asked about taking over support for an existing application. I’ll leave out what it does – suffice it to say it is web based, has a simple 3-5 page UI for the public to buy something, and about a 15-20 page set of back end interfaces for trained customer service and admin people to use. So less than 30 screens overall mKay?

The application was said to be written in Java. I know Java – I don’t like it, but I know it. If its small, I could be persuaded to pick up the maintenance.

However, I got a source drop and I was totally appalled. First, it suffers from the usual Java framework-itus.

Its J2EE – so we got Jetty container. They used Hibernate – so we have generated Java code based on some XML schema files. There’s also a mysql database – which duplicates the information in the Hibernate XML schema files. It uses cocoon – cocoon make use of xml and xsl to define navigation and transformations. There is also reporting that uses xsl. Workflows use flow – more xml only they used the Javascript extension so the workflows are actually defined using server side javascript.

Got all that? We have Spring, Coccon, Flow for Javascript (flowscript), Hibernate, Jetty, Javascript, Html, CSS, SQL, XSL and god knows how many dozens of distinct flavors of XML. For a 25 page web app. Wait, did you notice I didn’t mention Java? There is Java – the Hibernate generated classes are used – but indirectly – they may as well not exist at all since all the application code is some fractured XML or Javascript fragment stashed who knows where.

It seems we’ve lost track of something here. “Locality of Reference” In general, all the stuff that deals with the product selection page ought to be visible by looking into the file representing that page, then maybe drilling into components. The problem here is that, everytime I have to drill down, I have to switch languages. And context switches are bad for programmer productivity – mKay?

I’ve turned down the job. I don’t have time to learn all that junk for this little app. The client needs a new installation – for 25 pages that do mostly CRUD to mysql. If I do it, I’m thinking plain old PHP with an ORM. If I can find an ORM for PHP.

So far I’ve looked at Doctrine (not yet ready for production but it looks cool), Propel – and some others. The really off-putting bit is their slavish insistence on creating the xml mapping file – just like Hibernate. Of course, I have an existing database. The XML file they want is in the db. It just isn’t XML. Still, all the information is available for query – so why are they bothering me with this junk? Read the damn schema and make me some classes. I’ll eliminate the many to many mappings by hand. Sheesh. No wonder there’s a software crisis.

Hey framework people, stop making me write monkey code – figure out how to eliminate extra work – not make it. One meta model is enough.

Mike Arrington, Software Pirate

Monday, March 24th, 2008

The ever clueless and abrasive Mike Arrington says something even more ridiculous than usual. I can see both sides of the Bebo thing and frankly, social networking makes me yawn. I have no patience or interest in it. Seems like a phenomenal waste of bits to me. Musicians uploaded their music without expectation of compensation. So sure, they’re probably not ‘entitled’ to a payout. OTOH, Bragg’s point is well taken – the musicians made them successful and it would be a nice gesture to give back. But I wouldn’t hold my breath on that. They’re internet entrepreneurs. Thus greedy bastards by definition.

OTOH, the thing that really ticks me off is this little comment by Arrington.

…it costs exactly the same to produce one copy of the song (the first one) as 10 million copies. Simple economics takes over. Free.

Which, if true, implies that Microsoft ought to be giving away Vista, Office, and everything else. Because Music is Software. It costs as much to develop as software, it takes every bit as much time, talent, and skill.

Just because you can make a second copy for a buck, doesn’t mean that’s all it is worth. If it costs $30k to record a song (which is kind of typical these days), and we are expecting to sell 100 copies, then this song needs to make $300 per copy to break even. If its 10 copies, then its $3000 a copy. The same economics apply as software. Just like there is free software, shareware, and commercial software, there are many business models around music. No single model is right all of the time.

Arrington should stick to his little blog about the shiny things – he obviously knows fuck all about the economics of creation.

Curse You Google!

Thursday, March 20th, 2008

A friend sent me an email telling me my site has been blacklisted by Google – largely because some spammer (die, spammers, die) hacked a message and put a bunch of link spam in. I really have to question the security of wordpress. If they can’t keep it clean, I’ll have to migrate to something else.

There is an interface to request removal from the blacklist, but while you can be blacklisted in seconds, it apparently takes several days to get off of the blacklist. Nice.

Addressing HAL Devices with Apple’s Core Audio in JambaLaya

Sunday, March 16th, 2008

I play guitar and keyboards in a rock band for fun. I’m basically a guitarist, but I have this knack for programming things, like computers and, as it turns out, synthesizers. Thus, I always end up doing some keyboard work in any band I play with. I have a lot of classic hardware synthesizers from the 80’s. But these days a lot of software emulations for hardware have come out and I use these as much as possible.

Occasionally, however, there is no replacement for an external synthesizer. It would be great if JambaLaya could present and allow routing to the hardware instruments in the same way as it does software instruments. While I’m at it, I might as well add support for audio routing. Currently JambaLaya just uses the default audio device and plays to its default outputs.

An AudioUnit MusicDevice takes in MIDI control information and outputs audio signals. A hardware synth does the same, but to get its audio, I have to map it to an audio input in my audio interface and then sort of pretend that the audio is generated from the device. The idea being a hardware synth is a midi endpoint, channel, and audio input set. While I’m at it, I might as well provide HAL support and allow any kind of audio routing the user wants. My live audio interface is a MOTU UltraLite. It has 12 audio inputs and 14 audio outputs. It also acts as my MIDI interface. The goal is to make a hardware synthesizer look and act just like an AudioUnit MusicDevice.

AUGraph Channels vs Busses

AUGraph connections connect BUSSES. A buss can contain many channels. How many is determined by setting the AudioStreamBasicDescription on the kAudioUnitProperty_StreamFormat for the buss. Each node in an AUGraph has a number of input and output busses. For some AudioUnits, the number of busses are fixed. Most MusicDevices just have one output buss. Others, like the MatrixMixer can be configured with any number of busses.

Connecting Busses

To connect busses, you make node connections using

AUGraphConnectNodeInput(graph, sourceNode,sourceBussNumber, destinationNode,destinationBussNumber)

Before you can do this though, you have to configure the busses on both sides by setting kAudioUnitProperty_StreamFormat using an AudioStreamBasicDescription on both sides to make sure the signal formats and number of channels is compatible. If you don’t do this, things don’t work the way you expect. This is especially true when connecting a mixer because the number of channels in the stream format determines the number of channels in mixer. The total number of input channels in the mixer is the sum of the number of channels of each of its input busses. Same for the outputs. This is why configuring stream formats is so important. Furthermore, you cannot change the stream format after making the connection – you’ll get an error. So, always configure both stream formats prior to connecting nodes.

Mapping Audio IO in HAL Devices

HAL devices present their IO channels all in one buss. All the audio inputs (in jacks) are presented on a single output buss for the device. All of the audio outputs are presented on a single input buss. Confused? THE TERMINOLOGY OF INPUT vs OUTPUT in a HAL DEVICE is VERY CONFUSING. This took me many days of experimentation to figure out and is the primary motivator for writing this article.

To begin with, all HAL devices are referred to as Output Devices, regardless of whether they do input, output, or both. In the case of the MOTU UltraLite, one device does both jobs, but you could use different devices for input and output. HAL devices are not at all configurable with respect to what audio appears where – so to do any kind of routing, you want to stick a matrix mixer in front of it, then work with that to do signal routing. Thus, your application will most likely have a matrix mixer representing the audio inputs, and a matrix mixer representing the audio outputs.

AudioIO2

Creating the Output Device

First you need to add a node to your AUGraph to represent your HAL device.

AUGraph graph;
OSStatus status=NewAUGraph(&graph);

// Description for HAL Output Device
ComponentDescription halAudioOutputDescription={
kAudioUnitType_Output,
kAudioUnitSubType_HALOutput,
kAudioUnitManufacturer_Apple};

// Create a node to represent the IO device
AUNode outputDeviceNode;
status=AUGraphNewNode(graph, &halAudioOuputDescription, 0, 0, &outputDeviceNode);

// get the component instance so we can initialize the device -
// nothing works until the device is initialized
ComponentInstance instance = 0;
status=AUGraphGetNodeInfo(graph, outputDeviceNode, 0, 0, 0, &instance);

// initialize the device
status=AudioUnitInitialize(instance);

//Get the identifiers of the default audio input and output devices

AudioDeviceID outputDeviceID;
AudioDeviceID inputDeviceID;
UInt32 size=sizeof(AudioDeviceID);

status=AudioHardwareGetProperty ( kAudioHardwarePropertyDefaultOutputDevice, &size, &outputDeviceID );
status=AudioHardwareGetProperty ( kAudioHardwarePropertyDefaultInputDevice, &size, &inputDeviceID );

//Now you must enable the device for audio output.

UInt32 value = 1;
status=AudioUnitSetProperty(instance, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &value, sizeof(value));

If we are using the same device for input, enable it for input too. IMPORTANT! The hardware’s OUTPUT channels appear on BUSS 0. But the INPUT channels are on BUSS 1! Why? What’s the significance? I DON’T KNOW! But keep this in mind as it will come back to bite you when connecting the input mixer.

if(inputDeviceID == outputDeviceID)
{
inputDeviceNode=outputDeviceNode;
status=AudioUnitSetProperty(instance, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &value, sizeof(value));
}
else // signal an error - you can only have one output device in the graph

// Set the AudioDeviceID on the HAL device to map it to the right piece of hardware
status=AudioUnitSetProperty(instance, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &outputDeviceID, sizeof(outputDeviceID));

Creating the Output Mixer

If you were just interested in mapping hardware inputs to outputs, you could get by with a single matrix mixer and set matrix channel volumes to route audio. But it seems more natural to connect nodes to make connections – especially when mixing in Audio Unit Music Devices (virtual instruments). So I create a mixer for each interface, then connect various busses between them like a patch bay. You create the mixer pretty much the same way as the HAL device.

// Description for the matrix mixer
ComponentDescription matrixMixerDescription={kAudioUnitType_Mixer, kAudioUnitSubType_MatrixMixer,kAudioUnitManufacturer_Apple};

// Create a node to represent the mixer
AUNode outputMixerNode;
status=AUGraphNewNode(graph, &matrixMixerDescription, 0, 0, &outputMixerNode);

// get the component instance so we can initialize the device - nothing works until the device is initialized
ComponentInstance instance = 0;
status=AUGraphGetNodeInfo(graph, outputMixerNode, 0, 0, 0, &instance);

// initialize the mixer
status=AudioUnitInitialize(instance);

The output device node’s audio is fed into INPUT BUSS 0 which has one channel in it for each OUTPUT jack on the device. So you only need a single OUTPUT BUSS on the mixer, which you will connect to the INPUT BUSS 0 of the OUTPUT DEVICE. Refer to the picture if you’re getting confused.

UInt32 busCount = 1;
status=AudioUnitSetProperty(instance, kAudioUnitProperty_BusCount, kAudioUnitScope_Output, 0, &busCount, sizeof(busCount));

Prior to connecting the nodes, you want to make sure the number of channels and sample rates match. THIS IS CONFUSING! The stream description for the INPUT bus of the OUTPUT device for the device’s OUTPUTS is gotten by asking for the stream format for the OUTPUT device’s OUTPUT stream format for BUSS 0. Read that a couple times. Because you have to copy that stream description to the INPUT BUSS 0 of the output device and the OUTPUT BUSS 0 of the output device prior to connecting the busses. Here we go.

// get the node instance for the output device
status=AUGraphGetNodeInfo(graph, outputDeviceNode, 0, 0, 0, &instance);

// get the OUTPUT stream description for the OUTPUT device to set on the DEVICE's INPUT BUS 0 and MIXER's OUTPUT BUS 0
AudioStreamBasicDescription streamDescription;
UInt32 size=sizeof(AudioStreamBasicDescription);
status=AudioUnitGetProperty(instance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamDescription, &size);

// set the format on the INPUT BUS 0 of the OUTPUT DEVICE
status=AudioUnitSetProperty(instance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &streamDescription, sizeof(streamDescription));

// get the node instance for the output mixer
status=AUGraphGetNodeInfo(graph, outputMixerNode, 0, 0, 0, &instance);

// set the format on the OUTPUT BUS 0 of the MIXER
status=AudioUnitSetProperty(instance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &streamDescription, sizeof(streamDescription));

// connect the busses - OUTPUT BUS 0 of the MIXER to INPUT BUS 0 of the OUTPUT Device - hard to keep straight I think
status=AUGraphConnectNodeInput(graph, outputMixerNode,0,outputDeviceNode,0);

Now you can configure the input side of this mixer anyway you like. For instance, you could create a bunch of stereo pairs and then attach Audio Unit Music Device’s to them. Or some stereo pairs and a few mono inputs. Think of it as one side of your patch bay.

Creating the Input Mixer

This code is very similar – only for reasons that I don’t understand, all of the action on the INPUT side of an OUTPUT device occurs on OUTPUT BUSS 1. First we create the mixer as before.

// Create a node to represent the mixer
AUNode inputMixerNode;
status=AUGraphNewNode(graph, &matrixMixerDescription, 0, 0, &inputMixerNode);

// get the component instance so we can initialize the device - nothing works until the device is initialized
ComponentInstance instance=0;
status=AUGraphGetNodeInfo(graph, inputMixerNode, 0, 0, 0, &instance);

// initialize the mixer
status=AudioUnitInitialize(instance);

The output device used for INPUT has an OUTPUT BUSS 1 buss with one channel in it for each INPUT jack on the device. So you only need a single INPUT BUSS on the mixer, which you will connect to the OUTPUT BUSS 1 of the output device used for input. Refer to the picture if you’re getting confused.

UInt32 busCount = 1;
status=AudioUnitSetProperty(instance, kAudioUnitProperty_BusCount, kAudioUnitScope_Input, 0, &busCount, sizeof(busCount));

Now we need to setup the stream formats for the OUTPUT BUSS 1 of the DEVICE and INPUT BUSS 0 of the MIXER. You get the correct stream format for the OUTPUT BUSS by getting it from the DEVICE INPUT BUSS 1. THIS IS VERY CONFUSING!

// get the INPUT stream description for the OUTPUT device used for INPUT to set on the DEVICE's OUTPUT BUSS 1 and MIXER's INPUT BUSS 0
// REMEMBER that the INPUT description is found on ELEMENT 1

// get the device instance
status=AUGraphGetNodeInfo(graph, inputDeviceNode, 0, 0, 0, &instance);

AudioStreamBasicDescription streamDescription;
UInt32 size=sizeof(AudioStreamBasicDescription);
status=AudioUnitGetProperty(instance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &streamDescription, &size);

// set the format on the OUTPUT BUS 1 of the DEVICE used for INPUT
status=AudioUnitSetProperty(instance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &streamDescription, sizeof(streamDescription));

// get the node instance for the input mixer
status=AUGraphGetNodeInfo(graph, inputMixerNode, 0, 0, 0, &instance);

// set the format on the INPUT BUS 0 of the MIXER
status=AudioUnitSetProperty(instance,kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input,0,&streamDescription,sizeof(streamDescription));

// connect the busses - OUTPUT BUS 1 of the DEVICE to INPUT BUS 0 of the MIXER - THINK HARD!
status=AUGraphConnectNodeInput(graph,inputDeviceNode,1,inputMixerNode,0);

Now you can configure the outputs of the input mixer into any combination of busses and channels you like. To make a connection, just connect an output buss from the input mixer to an input buss on the output mixer.

Hopefully this will help someone else because it took me days to figure this out as CoreAudio’s documentation is really sketchy.

MIDI Performance Software

Friday, January 11th, 2008

jambalaya1.png

Last spring and summer, I decided to stop dragging a bunch of hardware synthesizers back and forth to band practice and start using a laptop as an all-in-one solution. This is more or less possible thanks to the availability of a variety of virtual instruments in Audio Units format that rival (and in some cases faithfully reproduce) the hardware synths of olde.

Seems simple. I need a MIDI controller keyboard – I chose an 88 key model and figured I’d map various instruments to different ranges of keys. So I started looking for an Audio Units host oriented towards live performance.

I came up empty. Nearly all of them are oriented towards sequencing and multi-track recording. They could kind of do what I wanted, but the user interface wasn’t oriented towards what I needed. There was one product – an independent program called RAX – that seemed right – only its author had just withdrawn it from the market. I managed to get an evaluation license from the developer but I disqualified it because it had a problem handling sustain pedal events and its future was uncertain.

So I set out to build my own and I’ve succeeded. It works well enough, is pretty bare bones, but allows the user to do complex splits and layers very quickly. I call it JambaLaya and I use it all the time. It isn’t quite production quality and there are missing feature I’d like to add. But it is a great feeling to control your own destiny and not have to worry about a critical piece of software disappearing from the market.

Now I’m trying to decide what to do with it. I could polish it up and offer it for sale, but since I wrote it two things have happened. RAX was acquired by a new publisher who is once again selling and supporting it and Apple has released a Logic 8 update with a program called Mainstage that is also oriented towards live performance. In both cases, I prefer the way the JambaLaya works, the UI is much more feature dense and it works the way I expect (little wonder, since I wrote it). I think in the short term, the best thing is to start a community website for it and give it away. So I think that’s the plan unless somebody has a better idea. I just need more time to configure the Drupal site and get that going.

Back to the 80’s

Tuesday, January 8th, 2008

A couple lifetimes ago, I was a full time professional musician. I played the Albuquerque club circuit in the 80’s and the scene was such that a good top-40 cover band could make a good living and have a lot of fun. Playing helped fund college. Eventually I graduated and set out to have a “career” in engineering which somehow ended with programming computers. I boxed up my guitars, keyboards, amps, and floppy disks with songs I had written and pretty much didn’t dig them out until last summer.

Last summer I bought a Mac dual quad, a Digital Performer upgrade, and some Audio Unit virtual instruments, dug out my old material, and began transferring the stuff to DP through a fairly laborious process. The goal? Make that great album I’ve always been meaning to do. Heck, I’m over 40 now. If not now, when?

I also hooked up with a bunch of geezers with a similar history and we’ve started a cover band (average age in the band is something like 48). So I’m splitting my time between learning current songs (and lots of oldies) and polishing my live chops, and getting up to speed on modern recording technology. Lots to learn. It used to be all about the hardware. Now all that gear I used to use in the 80s is available in software for about one tenth the price. Cool. No more tape machines either. All digital. Unlimited tracks and takes.

But it all takes time to learn so I think I’ll start blogging about that a bit.

James Gray Still Missing

Friday, February 9th, 2007

This is a puzzler. Jim Gray disappeared while making a day trip to the Farallone Islands. I did this same trip a few years ago when living aboard in Sausalito. It was fun but didn’t seem at all dangerous or difficult.
Farallone IslandsAnnardDCP65665.JPG
The Farallones are 27 miles offshore. You can’t see them from the coast – they appear on the horizon about half way there. The California coast is pretty high, what with Mt Tam there so you never lose sight of that. We set out around 8:00, crossed under the Golden Gate bridge about 9:00, completed our rounding of the islands about 5:00 pm and ran back to get home about 9:00 pm.

Wind is nearly always onshore so it is a beat out, and run back. Progress is futher slowed by a current that set us south about 1.5 knots. Which is why we made it back about sunset even though we didn’t arrive until 5:00 pm.

It sounds like Gray had similar conditions so I really can’t imagine how he was able to disappear unless he meant to, perhaps hugging the coast and heading south, which would throw off the search.

In one of the most imaginative uses of Amazon’s Mechanical Turk, volunteers examined satellite photos to try to locate his boat. Ongoing info is at http://helpfindjim.com/

Database Transplant Successful

Wednesday, January 31st, 2007

OmniBase is dead.

I killed it.

The replacement with GLORP has been successful. Rate of error discovery has stabilized and, after about 3 weeks of production experience and many small fixes, is now below OmniBase’s on its best day.

A few things I’ve learned along the way:

1) Put limits on all search queries. All of them. Too many times in the last couple weeks the system became unresponsive for a couple minutes as it fetched every blessed row in the database. If you’re getting that much stuff, you’re not gonna find it. I’ve limited all searches to 20 rows.

2) Use plain old objects wherever possible. OODBs have a history of being rotten at schema migration. So all objects used dictionaries for ivar storage. This makes it easy to add or remove ivars without getting messed up in the OODB as the object’s ‘format’ is always the same. However, all that hashing and fetching, and searching, isn’t free. I didn’t realize how expensive it was until I ripped it out and replaced them with vanilla ivars. Zooooom!

Because the mapping is from the objects to the RDBMS through GLORP on an attribute level rather than relying on blitting a whole object as a chunk, the format of the objects is decoupled from the database representation. Changes in the object’s binary format has no bearing on the database representation.

3) When generating accessors – add lazy initialization where nil is not an acceptable value.

4) Keep it meta. The meta model is the most important thing we have. With a good meta model all else can be rapidly replaced. Consequently, I think the next step in evolution – conversion of the meta model to use Magritte and then replacement of the UI to use Magritte as well, will radically improve agility.

Meanwhile, to prepare, I’m updating the version of Seaside that the application is built on. This is turning out to be harder than I thought as Seaside has moved on quite a bit and many classes in my old version have been simply replaced in the newer one.

The really great thing about using meta facilities like glorp and magritte, is that the application continues to shrink until it is mostly just the meta model.

Less code, less bugs.

Downriver

Monday, November 27th, 2006

For the past 2 and a half years I worked at Amazon.com. It was fun for the first year – so many old assumptions and prejudices shattered. But Amazon is a special case. For most normal sized systems, my old design sense was pretty solid.

Still, it was a horizon broadening experience and I enjoyed that. I managed teams of people and we built software and I liked that as a change from the endless parade of crummy short term java contracts I was getting.

But I left last month. I joined as something of a new manager. My pay grade was commensurate with my lack of experience in that area. But eventually I grew weary of it and was itching to get back to doing nifty code if I could find a way to do it on my terms. Which means dynamic expressive languages and I get creative control of the technology. No “You’re the architect – so you’ll use this language and that vendor’s solution”. Huh, I thought I was the architect.

The other main driver to leave is no work/life balance. This isn’t Amazon specific. This is US company specfific. In the US, if you work for an established company, this is just how it is. You get 2, maybe 3 weeks of vacation and a few holidays here and there. You are expected to put in 50 hours a week. With ever rising property values and congested highways, you have to live about an hour away from work, meaning you lose 2 unbillable hours a day just travelling to work. You’re working your butt off, but you can’t enjoy the fruits of your labor.

I lived in France for about half a year. I’ve seen how Europeans live. They take 5-6 weeks of paid vacation. They can take long leaves of absence. They are able to travel the world. In the US, you can’t get enough days off to drive across the country, much less travel abroad. No wonder we are such an ignorant xenophobic lot.

I have a boat. I’d like to take the boat in the summer and explore Puget Sound, where I live. I’ll need about 4 contiguous weeks to do it. I couldn’t get the time off. Why have a boat if I can’t take the time to enjoy it?

I have friends abroad. I can never get the time to go see them. I have the money. Just not the time. Again, this is lame. So I walked. I give up on work camp America. US companies say they can’t find qualified workers. We’re around. But your terms stink. Improve them or go pound sand.

I left the big company to work for myself. I build software using tools I like. Unconventional, but productive and low-cost tools like Squeak and Seaside. I use other things too, depending on requirements. I work when I want to, from anywhere I like.

I think this is the future as more and more of my colleagues are opting for this kind of situation. The big company life holds no attraction for the seasoned employee.