I’m making slow progress on my uilleann pipe project. I’ve had a chanter that plays well for about 6 months now: I’m using it instead of the one I bought, because I can hit the hard D on mine, and the back D is easier to get in tune.
It’s not enough to make a good sounding chanter, though: players want something that looks the part. They’re right to be concerned about this. Whether a player will admit it or not, playing the instrument is a spectacle, and unless you’re going for a wildly new feel to your performance, your instrument needs to maintain a certain aesthetic.
So for the last six months, I’ve been working on what my look is going to be. I need to copy certain elemets that pipes all have in common: a dark, almost black, chanter body; silver bits at both ends of the chanter; and ivory flares.
Flares
There’s this common design element on a lot of Uilleann bagpipe chanters: an ivory-colored element I’m calling a “flare”. Almost every “classic” looking chanter I can find has one on the top and one on the bottom.
A full set by Seth Gallagher. Photo by rocks & pipes CC BY 2.0
The top flare acts as a stop for the reed cap, preventing it from going down too low. The bottom flare provides visual symmetry. Both of them also provide a transition between wood and metal, allowing the pipemaker to have a good looking chanter despite minor imperfections.
The feature has two rings of different diameter: the larger ring is the one closest to the end. Joining the rings is a gradually tapered section.
An algorithmic flare
I want an algorithm for making these things on my 3D pipe models. Since there’s at least one flare on each of the seven (7) pipes in a full set, I need something that will look right for a range of heights and diameters.

Most of the drawn plans I’ve found have the same features for the flares:
- The rings are not squashed spheres, but rather cylinders with rounded ends, like a hamburger patty.
- A patty’s height is proportinal-ish to its diameter: the smaller patty in a flare will be shorter than the larger patty.
- The joining section is a logarithmic or parabolic curve, starting parallel to the chanter body at the smaller ring, and moving to around 45° at the upper ring.
The Patty
The core of a patty is just a cylnder. The outside of the patty is a circle with diameter equal to patty height, rotated around the axis.
The smaller patty will be a height based on the overall height of the flare. The larger patty multiplies that by the ratio of the patty diameters. This makes the patties the same size when the diameters are equal. The larger a patty gets relative to the other, the taller it becomes.
The flange
I did a lot of trigonometry, and even some geometric proofs, trying to come up with a scalable model for the flange. What I actually implemented was what I thought of as “making a curve with string art”, and turns out to be a Bézier curve!
Bézier curves with string art, by Wikipedia contributor Cmglee. CC BY-SA 3.0.
I did this by taking the union of muliple cylinders with diameters changing with the heights. I think I may have independently implemented a quadradic Bézier curve, haha.
The Result
It took a few days, and a lot of wrong designs, to get this right, but I’m pretty happy with the results!
My flare function creates a flare given one height and two diameters.
Here’s what that looks like animated through a range of values.
Source Code
// A fillet is a sort of trumpet bell shape
module fillet(h, d1, d2) {
if (d2 > d1) {
translate([0, 0, h]) mirror([0, 0, 1]) fillet(h, d2, d1);
} else {
dd = d1 - d2;
for (i = [0:1/$fa:1]) {
cylinder(h=(h+h*i)/2, d1=d1-dd*i, d2=d2);
}
}
}
// A patty is a cylinder with rounded sides, like a hamburger patty
module patty(h, d) {
d_cyl = max(0, d-h);
cylinder(h=h, d=d_cyl, center=true);
rotate_extrude() {
translate([d_cyl/2, 0]) circle(h);
}
}
// A flare is a fillet with a patty on each end
module flare(h, d1, d2) {
if (d2 > d1) {
translate([0, 0, h]) mirror([0, 0, 1]) flare(h, d2, d1);
} else {
f2 = log(h*20);
f1 = f2 * d1/d2;
translate([0, 0, 0]) patty(f1, d1);
translate([0, 0, f1/2]) fillet(h=h-(f1+f2)/2, d1=d1-f1, d2=d2-f2);
translate([0, 0, h]) patty(f2, d2);
}
}
// Set up the camera
$vpt = [-0.86, 1.98, 10.24];
$vpr = [78.10, 0.00, 23.60];
$vpd = 140.00;
flare(
20 + 10 * sin($t*360*7 + 0),
30 + 10 * sin($t*360*5 + 90),
30 + 10 * sin($t*360*3 + 0)
);