windsor
A Ford 302, modelled from first principles in C++23, for the sound.
make && ./windsor spec
No dependencies. Nothing to configure. It is an engine, and it runs.
1Four numbers on a crankshaft
I wanted to know why an American V8 sounds the way it does. Not well enough to describe it — well enough to point at the part.
A cross-plane V8 burbles. A flat-plane V8 shrieks. Everyone knows this, almost nobody can say why, and for a long time I was comfortably one of them. The reason is not in the exhaust, the camshaft, the displacement or the fuel. It is four numbers on a crankshaft.
Start with the thing that has to be given up first. A 90° V8 fires evenly on either crank — a power stroke every 90° of rotation, eight per cycle, perfectly spaced. At the flywheel the two engines keep identical time. So every explanation that begins “because it fires unevenly” is wrong about the part it is trying to explain, which I mention only because I used to give that explanation myself.
An engine does not exhale through its flywheel. It exhales through two exhaust manifolds, and a manifold is not connected to an engine. It is connected to four cylinders, and it only ever hears those four.
| each bank hears | |
|---|---|
| cross-plane — throws at 0, 90, 180, 270 | 90 · 180 · 270 · 180 |
| flat-plane — throws at 0, 180, 0, 180 | 180 · 180 · 180 · 180 |
That is the entire difference. A cross-plane bank coughs twice in quick succession, waits three quarters of a turn, and coughs again — a limping pulse train beating against the other bank’s and never resolving. That is a Mustang idling outside a diner. A flat-plane bank exhales in even thirds of a revolution, both banks lock in phase, and the harmonics stack cleanly at twice the frequency. That is a 458 leaving a tunnel.
Which gives the whole project a test it cannot argue its way out of. Change one part, and the sound should change and nothing else.
Press play, then change the crank while it is running. Same block, same cam, same nine seconds. The recording does not restart, because nothing about the engine restarted either.
2I would not let myself type in the firing order
1-5-4-2-6-3-7-8 is not a secret. It is cast into the intake manifold of every 302 Ford built in 1968. Typing it in would have taken a second, and it would have ended the project, because a program that has been told the answer cannot surprise you, and a model that cannot surprise you is a lookup table with opinions.
So the source specifies a forging and nothing downstream of it: a bank angle, four journal throws, eight rods hung on them, and which revolution each rod fires on. That is what a foundry decides. The firing order is what falls out — which is why Ford and Chevrolet got different orders out of the same geometry.
Crankshaft{
90.0_deg,
{ 45.0_deg, 315.0_deg, 135.0_deg, 225.0_deg },
{{
// cylinder journal bank revolution
/* 1 */ { 0, Bank::right, 0 },
/* 2 */ { 1, Bank::right, 0 },
/* 3 */ { 2, Bank::right, 1 },
...It will refuse to build a crank that cannot exist, and then it prints a shop manual page with a rule drawn across the middle. Everything above the rule I typed. Everything below it, the program worked out.
---- everything above was specified. everything below came out. ----
CRANKSHAFT
forging cross-plane
firing order 1-5-4-2-6-3-7-8
the engine fires every 90 deg, eight times per cycle
WHAT EACH BANK HEARS
A manifold is not connected to an engine. It is connected to four
cylinders, and it only ever hears those.
right bank cyl 1 4 2 3 fires at 180 - 90 - 180 - 270
left bank cyl 5 6 7 8 fires at 270 - 180 - 90 - 180
LOPSIDED. Each bank coughs twice in quick succession, waits three
quarters of a turn, and coughs again. The two banks are out of step
with each other and the interference between them never resolves.
This is the burble.The first time that line appeared with the right eight numbers on it I sat looking at it for a while. I had not put them there.
3What had to be built to hear it
Between a crankshaft and a sound there is an engine, and it has to be honest or the test proves nothing. Each cylinder is an open thermodynamic system, integrated in crank angle, five terms of the first law per radian: the piston taking or giving work, the fire, the coolant, gas arriving through the valves, and that same gas as mass.
Open rather than closed, because everything anybody cares about happens in the half of the cycle the air-standard Otto cycle deletes — the half where the valves are open and the mass is a variable.
The exhaust is a digital waveguide. A pipe is two delay lines, one per direction, which is d’Alembert’s 1747 result sampled at 176 kHz instead of in the continuum. Junctions scatter by continuity of pressure and conservation of volume flow, and the loop is closed, so a header has a length that matters and not merely a note.
None of which is worth anything unless the engine it adds up to behaves like the one Ford sold. So I put it on a brake.
lb-ft hp
325 | ********** | 225
305 |******** ********** ooooooooooooooooooooooo| 211
284 | ******###oooo | 197
264 | oooooo ******** | 183
244 | ooooo ******* | 169
223 | ooooo ******* | 155
203 | oooo ****** | 141
183 | oooo ***| 127
162 | oooo | 112
142 | oooo | 98
122 | ooo | 84
102 | oooo | 70
81 |oo | 56
61 | | 42
41 | | 28
20 | | 14
------------------------------------------------------------
10 15 20 25 30 35 40 45 50 55 60 rpm/100
* torque o power # both
peak torque 307 lb-ft at 1985 rpm
peak power 206 hp at 4994 rpm
Ford rated the 1968 302-2V at 210 hp / 4400 and 295 lb-ft / 2400, gross.
Nothing here was fitted to those numbers.Within two percent on power and four on torque, with nothing fitted to either. The curve is too broad — torque peaks 400 rpm low, power 600 high — and I know exactly which missing part that is, because I built it three times. Sixty-three checks, and what they check →
4What the burble cost
If the flat crank breathes more evenly and sounds better, the obvious question is why Detroit forged the other one eight million times. The answer is in the same rod table, asked a different question.
looking down the nose of the crank
O
.
.
O.....+.....O
.
.
O
shaking, at 3000 rpm, from 0.78 kg of reciprocating mass per bore:
force couple traces
primary 0 N 1032 Nm a circle counterweights take it
secondary 0 N 0 Nm nothing
NOTHING LEFT OVER. The secondaries cancel each other exactly, and
the primary couple traces a circle, which is a rotating imbalance, which
a counterweight can be drawn to oppose. The engine holds still. It will
idle at 600 rpm on soft mounts without walking across the bay.
This, and not the noise, is why Detroit forged crosses.Nothing bolted to a shaft turning at ω can oppose a force that goes at 2ω. The cross-plane’s secondaries cancel outright and its primary couple traces a circle, which a counterweight can be drawn to oppose. On a flat crank the secondaries trace a line — half a tonne of force, a hundred times a second — and a line is not something you can chase with a rotating weight.
The flat crank sounds better and shakes. That is the bill, and it is why almost nobody pays it.
5Four things I built and took out
A nonlinear gas solver, verified against Sod’s shock tube to one part in 10⁵, made header length worth 14% of torque — and collapsed the difference between the two crankshafts from 118× to 1×. An engine that cannot tell the two cranks apart is of no use to me, however good its shocks are. Out.
Intake runner tuning I built three times and shipped none. The third attempt found something better than a feature: a torque-controlled brake sitting on a rising torque curve is an unstable equilibrium, and ram tuning is precisely what makes a torque curve locally rise. The dyno was not broken. It was doing exactly what a dyno does, at me.
And it still cannot be held between 1000 and 1500 rpm. I do not know why. That sentence is in the source, at the bottom of induction.hpp, because it is more useful than the explanation I would have had to invent in order not to write it. What came out, and what it taught →
6Why C++
Because the domain belongs in the type system. Bore and Stroke are distinct types, since a bore is not a stroke and I should not be able to swap them at four in the afternoon. A Lobe knows its own centreline. A Crankshaft refuses to be constructed with two rods on one journal. The compiler is a machinist checking my work, and it is better at it than I am.
Every abstraction here compiles to the arithmetic I would have written by hand and no more. Not because the cycles matter at this scale — they do not — but because paying for nothing is the habit that makes people write C instead.
The standard library, and nothing else. Every dependency is a bet that something else will still build in fifteen years, and I did not want to make eleven of them. The WAV writer is a 44-byte header and some samples. What is in it, file by file →
7Why
To find out whether the burble falls out of the geometry or has to be put there by hand. It falls out — four numbers on a crankshaft, and everything after them is arithmetic.
What the answer left behind is an engine that turns over in a terminal and cannot move anything, burning fuel that does not exist, wasting a third of it out of a pipe into nowhere, and doing all of that correctly. I am aware this is not useful. It is the most careful thing I have made, and I would do it again.