Sunday, November 30, 2008

Imbalanced outputs

Although I earn most of my living writing software, I also work on audio whenever I get the chance. A few days ago one of my clients, a bass player, came to me with a piece of equipment that was causing a buzz.

The equipment in question was powered by an AC wall adapter, and it had a balanced output for sending the signal to the PA system. Balanced signal transmission is used in professional audio in order to reduce induced noise problems: the idea is that the signal is sent along two wires simultaneously, but with opposite polarity. At the receiving end, one voltage is subtracted from the other, eliminating any common noise that might have crept into the cables and leaving only the intended signal. The problem my client had was that whenever he used this output, it caused a terrible buzz in the PA system - rather counterproductive.

It turns out that a lot of what the industry calls "balanced outputs" really aren't. There's a popular belief amongst equipment designers that to make a balanced output, what you need is two signals in opposition - that is, you split the intended signal, send it unaltered onto one wire, and then flip its polarity (that is, multiply the voltage by -1) and send that to the other wire.

This is malarkey, as has been pointed out by luminaries like Douglas Self and Bill Whitlock. Having two voltages in opposition is irrelevant; if that mattered, then when the signal was zero (dead quiet), noise would no longer be eliminated. What actually matters is that the impedance on the two legs is balanced, so that any induced common-mode noise is the same on the two legs.

This is pretty old news, but has been largely ignored in the industry. So I was not surprised to discover, on tracing the circuit in my client's equipment, the following "balanced" output stage (I've eliminated a few unimportant details, like DC blocking capacitors):



It does just what it was meant to - the voltage on pin 3 will always be -1 times the voltage on pin 2. But look at what happens when the AC adapter is plugged into the wall:



There's always a little bit of leakage between the windings in a power transformer, perhaps a few picofarads. 120V from the wall leaks through that capacitance, into the power supply, into the signal ground. From there, I've traced the two main current paths to the output. Notice that one path goes through about 21.8k of resistance before getting to the output, while the other sees only 1k.

This 22:1 imbalance, with the tiny leakage through the transformer, was enough to generate a couple mV of differential signal - a lot, in the terms of audio signals, which rarely exceed one volt. By replacing this output stage with one that was truly impedance-balanced (based on an SSM2142 chip), I was able to reduce the noise signal by a factor of 30 - enough to get it below the noise floor of the unit.

The cost of the extra components was about $8 retail. But frankly, if cost were an issue, it might have been just as good to have gotten rid of the entire inverting amp stage and simply connected pin 3 with a 1k resistor to ground. The impedances would be balanced. The differential voltage would only be half as big as before, but the common-mode noise voltage would be reduced by much more than half, so signal to noise ratio would be better than with the "balanced" output the designer came up with.

Many software errors come from using a common design pattern without understanding the problem it's aimed at. This was the same mistake in hardware.

Sunday, November 16, 2008

Comments

I often hear that comments in source code are A Bad Thing. Comments are evil because they don't accurately describe the code they apply to; because the code gets modified and the comment doesn't; because good code is self-documenting and therefore doesn't need comments; and because you need to read the code anyway to understand what it does and so comments just get in the way.

Horseshit.

This is roughly like saying that synchronization is evil, because it's often done incorrectly, because it gets broken when people update the code without fixing the synchronization, and because good code uses constructs like immutability that don't require synchronization.

If we treated comments as being as important as synchronization, they'd live up to their end of the deal just fine. There is nothing inherent in the idea of a comment that renders it impotent. Think of comments as being like error-handling code or synchronization: bulky, hard to write, even harder to test, but crucial to reliability.

I think the real reason so little code is commented is simply that most code is written in short bursts of effort by highly productive individuals, and while they're writing it, they understand their own assumptions well enough to not need the comments themselves, and they're in too much of a hurry to worry about the next fellow. And because this is what new programmers then see all around them, this is how they in turn learn to program.

If we built buildings this way, instead of having architectural drawings, the carpenters would come in after the foundations were poured, look at where the rebar was sticking out, and frame the walls where it looked like they should probably go. The resulting buildings would be ugly, short, and tend to collapse in a few years. Much like software, in fact.

If I were to design a programming language, any uncommented abstract definition (for instance, a method definition in an interface class) would be a compiler error. Yes, people would work around it by putting in useless comments, but it would be a start.

Tuesday, November 11, 2008

Control Theory

In the last post I mentioned that we programmers don't generally get training in control theory. This is too bad, because I think learning to recognize the behavioral modes of feedback-controlled systems can have a lot of practical benefit for us.

Suppose that you have the following two Java classes, B and Main. Without running this, can you tell how it will behave?

If you do run it, you'll see that the output is spread across a range of numbers. Without changing class B, how would you make that range smaller? What properties of the code determine the range?

This program is an analog of an elementary problem in control theory, so basic that even my espresso machine implements a solution to it. But I suspect that most computer programmers, coming upon behavior like this while performance-tuning an application, wouldn't immediately recognize it.


public class B implements Runnable {
private static final int ELEMENTS = 100;
private double[] v = new double[ELEMENTS];
private boolean state;
private volatile boolean done = false;

public void run() {
while (!done) {
synchronized(this) {
v[0] = state ? 200.0 : 0.0;
// propagate gradually through the array
int i;
for (i = 1; i < v.length - 1; ++i) {
v[i] = (v[i - 1] + v[i] + v[i + 1]) / 3.0;
}
v[i] = (v[i - 1] + v[i]) / 2.0;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
}
}

public synchronized int get() {
return (int)v[v.length - 1];
}

public synchronized void set(boolean state) {
this.state = state;
}

public void finish() {
done = true;
}
}


public class Main {
final Object l = new Object();
final long end = System.currentTimeMillis() + 60000; // 1 minute

public static void main(String[] args) {
new Main().run();
}

private void run() {
B b = new B();
new Thread(b).start();

while (System.currentTimeMillis() < end) {
double t = b.get();
boolean state = t < 50.0;
System.out.println("t = " + t +
" - state is " + (state ? "true" : "false"));

b.set(t < 50.0);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
b.finish();
}
}

Wednesday, November 5, 2008

Self-reference and Statistical Density

Programming languages are ways of mapping between three things: a set of problems in some problem domain; a set of envisioned solutions to those problems in a computer programmer's mind; and a set of instructions that can be executed by a computer.

Computers are very stupid, and not at all lazy. Programmers are very lazy, and hopefully quite smart. So, the language has a big gap to close.

For example, suppose I have an object that stores some numbers "a", "b", ..., "y", and "z", and I want to provide "fudge" and "muddle" operations on these numbers. I could write functions called fudgeA, fudgeB, muddleA, muddleB, and so on. Boring! Once I've written the code for fudge and muddle for A, I'd really like to just say "and do the same thing for the rest". So I want a language with that sort of expressive power, a language that can contain concepts like "the rest" and "the same thing". A language, that is, that can refer to itself, not only to the entities in a problem domain.

Taken to the extreme, what this leads to is programs of maximal statistical density: that is, programs that contain no repetition, but lots of self-reference and automatic code generation. There may in fact be many nested layers of this stuff.

As with Zappa's opus of statistical density, The Black Page, the problem in practice is that this becomes devilishly hard to play. Highly self-modifying programs save the original programmer's time, but generally make life for everyone else worse. The goal of making code more concise is seriously flawed. The goal should be to make code more understandable, not more concise.

I say "generally", because there's one case where it works. That's when the self-referential features of the language fit with the way that programmers think about solutions. For instance, "do the same thing to all these numbers" is the way that a programmer would typically describe the solution; to have to spell out each function separately isn't just repetitive, it makes the program code be less like what's in the programmer's brain in the first place.

My point is that I think that language designers should learn more about how human minds work, and should prefer native constructs and shy away from constructs that require unusual intelligence and training to master. Computers do not think like people; so programming languages need to.

There is a lot of overlap between computer science and cognitive psychology, but so far as I know this premise has not been applied; quite the opposite, it seems that new programming languages are more often used as a vehicle for showing how smart the programmer is.

Specifically, there are certain constructs that are demonstrably hard for most people to reason about. The ability to reason about recursion, for instance, is often used as an interview test to separate the "real programmers" from the mediocre. Recursion is actually one of the few things that computers innately know how to do, so it's not surprising that it's in computer languages too; but if it makes it so that only really smart people can write good programs, then I think it's something we should be trying to get rid of, not use more of. If a car is hard to steer for people of normal strength, we give it power steering, we don't just assume that Driving is a job open only to the abnormally strong.

Of course, I'm blowing into the wind here, because cognitive psychology has not held up its end of the deal. Language designers can't learn much more about how the mind works, because cognitive scientists haven't figured it out yet either. We know very little about what processing contructs are "native" to the mind.

But as a starting point: three things that seem hard for most people are feedback loops (that is, control theory, home of things like proportional-integral-derivative algorithms); recursion (especially if the rules change depending on the depth); and n-dimensional geometry. Electrical and industrial engineers get advanced training in handling feedback loops; I've never met a computer scientist who has, and I've never met a non-specialist who has. Physicists and mathematicians work with n-dimensional manifolds, but EEs and programmers rarely go beyond three. Programmers get advanced training in recursion, but no one else does. In each case, the fact that training is required and that these are all exclusive fields says something about the cognitive difficulty.

The question is not how we can make programming more efficient for a vanishingly small number of people. The question is how we can make it more efficient for a larger number of people.