Archive for the ‘software’ Category

JambaLaya approaching final candidate

Wednesday, March 19th, 2008

I’ve been working on it like crazy, stomping bugs and adding polish. It is pretty close to RC. Still need to add registration and complete the website infrastructure to automate dispensing of serials. I’m sure it will be huge because I just added the killer must-have feature.

Behold – Murph and the MagicTones Mode!

murphaboutbox.png

JambaLaya covered in “Thick Red Shag!” No other Audio Units host has that! Take that MainStage!

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.

Looking for work again

Monday, March 3rd, 2008

I’ve been doing a lot of PHP lately – shopping cart integration, web service clients, lots of ecommerce stuff. But I’m coming to the end of these projects and am looking for new challenges. Got a project or position that I can do remotely? Drop me a line.

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.

I’m a Google Summer of Code Mentor!

Wednesday, March 21st, 2007

Are you a student? Want to make some cash? Want to do it with Smalltalk? Sign up for one of my Google Summer of Code projects.

Cheshire Cat at the Seaside

Monday, February 26th, 2007

`Would you tell me, please, which way I ought to go from
here?’

Alice speaks to Cheshire Cat

`That depends a good deal on where you want to get to,’ said the
Cat.

`I don’t much care where–’ said Alice.

`Then it doesn’t matter which way you go,’ said the Cat.

Steve Jobs coined a saying Real Artists Ship during the development of the Macintosh. He was trying to bring the chaos under control a bit and keep people goal oriented.

During any software project, there is a chaotic early period during which the vision is formed and experiments performed to test approaches towards achieving the vision. The code base during this time will be unstable and generally unusable. Eventually, the general principles will get worked out, the architecture will gel, and the rest of the project is spent converging towards a stable implementation. When the software reaches an appropriate level of quality and stability, a release is made.

Releases are forks in the software history. For the supported lifetime of the release, every fix has to be done twice. Once in the mainline code base that continues to evolve, and once in the stable release branch. This is just part of the cost of having users. It is extra work, but without it, you will not attract users.

Software projects without releases are shifting sands and lack the stability required to support great buildings. I think this is part of what is holding back Squeak and I was really pleased to see Ralph Johnson of “Design Patterns” fame offer to take on the management of the Squeak 3.10 release. He is focused on improving the quality of the base system. This is good because over the years Squeak has accreted a lot of experimental code that sort of works, but isn’t really production quality. Now progress is difficult to make because the lack of polish is hindering the development of new capabilities. Time to clean house to prepare for the next great wave of innovation.

Sadly, the Seaside project hasn’t taken the same position. There are a few developers, unpaid who are doing this in their spare time, like all open source projects. They’ve done great things to improve the platform. However they’ve been working on something they called version 2.7a for awhile and recently announced they were starting 2.8a. No release for 2.7 was really declared and the criteria for bumping the versions doesn’t seem well expressed.

Compare this to Ruby On Rails site, where you can find a big red arrow inviting you to download the latest stable release to get started. Seaside has no equivalent, and a newbie coming to the seaside project would likely be paralyzed with indecision with all the various branches, labels, and versions available – nothing clearly labeled ’stable release – start here’.

People are not inclined to bet their livelihoods on software of indeterminate stability. I hope a release culture emerges soon.

Seaside update complete!

Tuesday, February 6th, 2007

That was easier than I thought – thanks to a nearly complete port done by my predecessor. However, I have finally thrown in the towel and upgraded the entire application to a 3.8 Squeak image. 3.8 brings a lot of baggage I don’t need – like character encodings and true type font rendering.

However, enough tools such as squeakmap and glorp have become dependent on the newer apis that integrating new code was getting to be too hard. So I started with a clean 3.8 image and started loading Monticello packages and eventually ended up with a completely new build. Then I upgraded the Seaside version.

With a new build and upgraded infrastructure, I then successfully loaded Magritte into the image. So now I have been working through the tutorials.

More on that later.

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.

Object Oriented Databases (OODBMS)

Wednesday, December 20th, 2006

I used to be something of an expert on Object Oriented Database Systems, how to use them, etc. The way you get to be an expert at a technology is to simply get ahead of the adoption curve and spend a bunch of time figuring out how to make the technology work before it becomes common knowledge. At this point, you can charge premium rates as you have scarce knowledge in your posession.

I got to this position by working for some adventurous folks that were willing to take a chance on the hype. The first one I came across was ObjectStore with C++. The project didn’t launch, but we built lots of prototypes and I got a good indoctrination into the ups and downs of OODBMS lore. Later on I was exposed to some others, Versant, Poet, and some lesser known ones. Like relational databases, once you get the hang of one, the rest are pretty similar. The key concepts are:

1) OODBS allow you to create one or more named “roots”. A root is basically a variable – you ask for the object at root “foo” and get it back. Some only give you one root. If you only get one root, then almost always you just stick a hash/map/dictionary at the root and pretend you have several anyhow. The root is your entry point to the data.

2) All object manipulations/accesses must be done within a transaction context. So you end up digging through your app looking for sensible transaction boundaries. For a web app, you typically begin a transaction at the beginning of a request and commit it just before sending the response. You want to keep transactions short so as not to have other users waiting on locks.

3) Objects become part of the database via “reachability”. The OODBMS will “trace” your object graph starting at the root upon commit, calculate changes to the graph, and then write the changes to the database. Any new objects reachable from the root object automatically becomes part of the database. While this might sound expensive, it generally is quite cheap.

So you generally open a transaction, lookup an object from a root, navigate to the object of interest, make changes, and then commit the transaction. Many also let you hang onto an object reference across transactions. The object reference can only be accessed within a transaction – trying to read data from it outside of a transaction will fail with an exception. This makes redrawing user interfaces problematic.

OODBs come from the CAD world where you have a network of a zillion objects, all slightly different, where mapping them to a regular container like a db table would be really expensive. They’re really good at this object persistence game.

OODBs are seductive. They are easy to get started with. For one thing, you don’t have to do a data model, just your object model. Your code is your model. You make objects, stick them in containers, and forget about them. Sounds great, right?

But as anyone who has lived with an OODBMS for any period of time knows, Object databases are great, until they’re not, and then they truly suck. Here’s why:

1) Concurrency is very poor. As I mentioned, OODBs come from the CAD world and work well for storing complex cad models. But CAD models are seldom updated concurrently by large numbers of people. As you modify objects within a transaction, the OODB has to obtain locks on your modified objects to guarantee consistency. Unfortunately, none of them (that I know of) implement object level locking. Most implement locking at the memory page level. Spurious lock conflicts where two unrelated objects share a memory page can be common. Resolving these conflicts can be expensive. Because, all work must happen withing a transaction, transactions tend to be on the long side.

2) Constant re-fetching of data every transaction makes keeping user interface elements up to date very expensive. There is no user level in-memory caching without writing user level code to create transient copies.

3) Schema migration is hard, if not impossible. Your object defines your format. Adding a field to a class makes your in-memory model inconsistent with the slabs of bits you wrote out before you added the field. There are ways around this. The usual one is to have one ivar that is a dictionary. Otherwise, there are usually some very user un-friendly scripts that have to be run. In many cases, the database must be taken offline to do this. So much for your three nines availability.

4) Death by a trillion bug fixes. I can’t speak for all, but ObjectStore would require the database be taken offline and an update script be run for every upgrade. For a site that is supposed to be up all the time, this isn’t acceptable. So upgrades were deferred. When we did this, we found that

5) OODBMS providers have limited resources and will only support versions up to one year old. If you get too far out of date and your db goes down, you are flat out of luck. The support people won’t help you. Only a really large organization could afford to keep up with all the little point fix releases ObjectStore made in a year – we couldn’t afford the man power or the down time.

6) Bugs are forever. If you put a bug into your program that damages the object model, it becomes enshrined in the database. Subsequent read code that finds the malformed chunk of the object model will usually fail. Subtle corruptions build up over time making a full database walk harder and harder to complete over time. Conventional databases can avoid this by implementing appropriate constraints.

7) No security. Any screwball developer can destroy your reference data (usually stored in ordered collections off of the root). A conventional relational database can safeguard important data with roles, permissions, and constraints.

8) Garbage Collection is not universally available. Orphaned junk is common. Some OODBs provide GC utilities, however they can fail if there is corrupt data (see items 6 and 7).

9) No ad hoc query capability. You have to write a new program to view any data at all. You need to write programs to update reference data. You need a program to do anything at all with your data. No fixing problems with a quick line of SQL. Searching for unanticipated patterns is difficult.

I’ve been bitten by all of these issues at one time or another and have recently inherited an application written using a Smalltalk OODB called OmniBase. Debugging this application is extremely painful because launching a debugger results in the transaction being terminated and all object references becoming invalid. Thus, the data that might provide a clue as to the source of the error is gone. Additionally, while the author claims to provide support, he simply collects fees and then tells you that your application doesn’t run in his environment, blames you for writing rotten code, and declines future contact.

So this dog has to go.

Fortunately, you can get most of the benefits of an OODB without the drawbacks by using an Object Relational Mapping framework. I’ve selected GLORP, an open source mapping framework that is improving all the time, and found that I can implement support the part of OmniBase’s API with very little change to the user interface, which is written in Seaside under Squeak.

Next time, I’ll talk a little bit about how this works.

Education vs Training

Wednesday, November 29th, 2006

Scoble points to a post by Steve Sloan who has been teaching a podcasting/new media class at SJSU. It seems the administration would prefer to teach tools rather than theory.

I taught CS at University of Colorado, Denver for several years. Just one course per semester. We had many discussions about just this problem. UCD is a satellite campus. Most courses are taught at night. Students often have full time jobs. They want better jobs. They go to school specifically to get better jobs and they don’t have a lot spare resources to spend on school. They just want to learn the latest hot technologies like Java and .NET and be marketable. In other words, they want job training.

The problem with job training is that the information is perishable. In the mid-90s all the rage was C++. Many universities, ours included, altered their curriculum to use C++ as the teaching language in order to be more appealing to students. After all, you can teach theory using pretty much any general purpose programming language.

Except that C++ is a terrible teaching language. (Actually, its just a terrible langage). It is too complicated and I wasted many classroom hours helping students cope with the quirks in the language instead of focusing on the content. And now, the C++ knowledge is mostly useless. Nearly all C++ work has been supplanted by Java work. So the students need to retrain.

A better idea is to use languages that can most clearly illustrate the concepts with minimum extraneous complexity. More languages means more viewpoints, and tends to make students understand that the language or tool isn’t that important. It is the underlying concepts.

The University is between a rock and a hard place. With education funding cuts, they need to attract students to survive. To attract students, they need to offer classes the students want to take. Students find training classes most attractive as they offer instant gratification. But training classes are like candy. They’re not good for you in the long run and the fix is short lived. Education is more like vegetables. It is good for you, but maybe not so pleasant to digest. The University would like to stick to vegetables, but if noone orders them, they have to sell candy too.

Which is unfortunate. The state of the software industry is deplorable. I think 90% of the people programming out there ought to be doing something else. They suck at their job and aren’t even educated enough to understand that they suck much less how they suck. One trick ponies, they flounder if given a problem that isn’t pre-solved in their platform. FACT: the best indicator that a candidate is likely to fail the interview process at big river books is if they characterize themselves as a “Java Architect” or “Java Developer”. It usually means they don’t know anything else. They think Java (or .NET) is the pinnacle of software achievement. Without a proper education, they can’t conceive of anything else.

The real solution is to properly fund universities as institutes of higher learning and stick to education. If the universites want to also offer vocational training, that’s fine. Just don’t cheapen the academic programs by offering “degrees”. Certificates of completion should be adequate.