whistles

3d printable Irish whistles
git clone https://git.woozle.org/neale/whistles.git

Neale Pickett  ·  2025-05-04

whistle.scad

  1include <common.scad>
  2include <tonehole.scad>
  3
  4// Which section to render for printing
  5Section = -1; // [-1:Everything, 0:Bottom, 1:Middle, 2:Top]
  6
  7// Height of the instrument
  8Height = 469;
  9// Inner bore diameter
 10InnerDiameter = 20; // 0.1
 11// Outer diameter
 12OuterDiameter = 25.0; // 0.1
 13
 14// Diameter of the rubber O ring
 15ORingDiameter = 1.50; // 0.01
 16// How far beyond the outer diameter the O ring should protrude
 17ORingProtrusion = 0.80; // 0.01
 18
 19// Cuts to make for a sectioned print
 20Cuts = [190, 340];
 21
 22// Use Axianov tone holes?
 23AxianovHoles = true;
 24
 25// Text to display on the back
 26Text = "d";
 27
 28Hole1 = [90, 7.45];
 29Hole2 = [130, 11.0];
 30Hole3 = [161, 9.45];
 31Hole4 = [221, 7.45];
 32Hole5 = [254, 9.50];
 33Hole6 = [293, 7.45];
 34
 35/* [ Esoterics ] */
 36// Angle for tone holes. Anything other than 45 will create challenging overhangs!
 37AxianovHoleAngle = 45;
 38
 39// Depth of the tenon joint. Shallower will be less stable.
 40TenonDepth = 20;
 41
 42/* [ Hidden ] */
 43sortedCuts = [min(Cuts[0], Cuts[1]), max(Cuts[0], Cuts[1])];
 44
 45module whistle() {
 46  difference() {
 47    tube(h=Height, id=InnerDiameter, od=OuterDiameter);
 48
 49    // O ring grooves
 50    for (z = [Height-7, Height-20]) {
 51      translate([0, 0, z]) tube(h=ORingDiameter*1.3, id=OuterDiameter - ORingDiameter*2 + ORingProtrusion, od=OuterDiameter+1);
 52    }
 53
 54    // Decoration
 55    for (i = [0, sortedCuts[0], sortedCuts[1]]) {
 56      translate([0, 0, i + 2]) {
 57        translate([0, 0, 0]) tube(h=1, id=OuterDiameter-0.5, od=OuterDiameter+0.5);
 58        translate([0, 0, 4]) tube(h=1, id=OuterDiameter-0.5, od=OuterDiameter+0.5);
 59      }
 60    }
 61
 62    for (hole = [Hole1, Hole2, Hole3, Hole4, Hole5, Hole6]) {
 63      if (AxianovHoles) {
 64        z = hole[0];
 65        top = (z>sortedCuts[0]) || (z>sortedCuts[1]);
 66        m = [0, top?0:1, 0];
 67        r = [90, top?90-AxianovHoleAngle:90+AxianovHoleAngle, 0];
 68        translate([0, 0, hole[0]]) rotate(r) mirror(m) tonehole(d=hole[1]);
 69      } else {
 70        translate([0, 0, hole[0]]) rotate([90, 0, 0]) cylinder(d=hole[1], h=50);
 71      }
 72    }
 73
 74    // Ridges to catch seams
 75    rotate([0, 0,  110]) translate([0, OuterDiameter/2, 0]) cylinder(h=sortedCuts[0], d=1);
 76    rotate([0, 0, -110]) translate([0, OuterDiameter/2, sortedCuts[0]]) cylinder(h=sortedCuts[1]-sortedCuts[0], d=1);
 77    rotate([0, 0,  110]) translate([0, OuterDiameter/2, sortedCuts[1]]) cylinder(h=Height, d=1);
 78
 79    // Inner channel, seems to cause problems with the tenon join being too thin
 80    //rotate([0, 0, 140]) translate([0, InnerDiameter/2, 0]) cylinder(h=Height, d=1);
 81
 82    // Ruby head, and text on the back
 83    // This makes previews slow, so we only do it in final renders.
 84    if (!$preview) {
 85      intersection() {
 86        tube(h=50, id=OuterDiameter-0.5, od=OuterDiameter+10);
 87        union() {
 88          rotate([90, 0, 0]) {
 89            linear_extrude(height=OuterDiameter) {
 90              translate([-5, 0, 0]) import("ruby.svg");
 91            }
 92          }
 93          translate([0, 0, 8]) {
 94            rotate([90, 0, 180]) {
 95              linear_extrude(height=OuterDiameter) {
 96                text(Text, halign="center");
 97              }
 98            }
 99          }
100        }
101      }
102    }
103  }
104}
105
106sectionedCuts = concat([-1000], sortedCuts, [1000]);
107if (Section == -1) {
108  whistle();
109} else {
110  tenon(h=sectionedCuts[Section], od=OuterDiameter, depth=TenonDepth, top=true) {
111    tenon(h=sectionedCuts[Section+1], od=OuterDiameter, depth=TenonDepth, top=false) {
112      whistle();
113    }
114  }
115}