1function geo = recorder_geometry(P)
2% RECORDER_GEOMETRY Bore, tone hole lattice, window, and fingering chart.
3%
4% The bore runs from the window (x = 0, where the jet drives it) to the open
5% foot (x = bore_length_m), with a linear taper from r_head to r_foot; real
6% recorder bores are stepped and locally reworked by the maker, so the taper
7% here is a first-order stand-in. Eight tone holes: index 1 is the thumb
8% hole on the back, 2..8 are finger holes 1..7 from the top down. Hole
9% positions are stored as fractions of the design bore length, so scaling
10% bore_length_m transposes the whole instrument; radii scale with
11% P.hole_scale. The positions and radii below were tuned numerically (see
12% the repository README) so that the first-register fingerings land on an
13% equal-tempered C major scale at A4 = 440 with the default geometry.
14%
15% The fingering chart is baroque (English). Each row is one note, columns
16% are the eight holes: 0 closed, 1 open, 0.5 pinched (the thumb nail crack
17% that vents the second register; modeled as a reduced effective radius).
18% Rows 1..10 are the first register C5..D6; rows 11..14 are pinched
19% second-register fingerings E6..A6, whose intonation inherits every
20% simplification of the model and is honestly worse.
22L = P.bore_length_m;
24% Tuned hole lattice (fractions of bore length from the window, and radii in
25% m at the default geometry). Hole 1 is the thumb; 2..8 are finger holes.
26geo.holeFrac = [0.3699 0.4271 0.4640 0.5292 0.6204 0.6519 0.7424 0.8278];
27geo.holeRad = [0.0027 0.0032 0.0030 0.0029 0.0033 0.0026 0.0032 0.0025] * P.hole_scale;
28geo.holePos = geo.holeFrac * L;
29geo.holeChim = P.wall_m * ones(1, 8);
31% Fingering chart: thumb, then finger holes 1..7.
32% T 1 2 3 4 5 6 7
33geo.fingerings = [ ...
34 0 0 0 0 0 0 0 0; % 1 C5
35 0 0 0 0 0 0 0 1; % 2 D5
36 0 0 0 0 0 0 1 1; % 3 E5
37 0 0 0 0 0 1 0 0; % 4 F5 (fork)
38 0 0 0 0 1 1 1 1; % 5 G5
39 0 0 0 1 1 1 1 1; % 6 A5
40 0 0 1 1 1 1 1 1; % 7 B5
41 0 1 0 1 1 1 1 1; % 8 C6
42 1 0 0 1 1 1 1 1; % 9 C#6
43 1 1 0 1 1 1 1 1; % 10 D6
44 0.5 0 0 0 0 0 1 1; % 11 E6 (pinched E5)
45 0.5 0 0 0 0 1 0 1; % 12 F6 (pinched)
46 0.5 0 0 0 1 1 1 1; % 13 G6 (pinched G5)
47 0.5 0 0 1 1 1 1 1; % 14 A6 (pinched A5)
48];
49geo.nNotes = 14;
51% Equal-tempered targets, A4 = 440: semitones of each row above C5.
52semis = [0 2 4 5 7 9 11 12 13 14 16 17 19 21];
53geo.fTarget = 440 * 2^(3 / 12) * 2.^(reshape(semis, 1, 14) / 12);
55% Bore radius at position x (column or row vector), linear taper.
56geo.rHead = P.bore_r_head_m;
57geo.rFoot = P.bore_r_foot_m;
58geo.boreLen = L;
60% Window (mouth) geometry.
61geo.Sm = P.cutup_m * P.window_width_m; % window area
62geo.deltaM = 0.7 * sqrt(geo.Sm); % mouth end correction
63end