Pitch support by geometric scaling in the exported driver
MIDI pitch scales every structural length by 2^-((pitch - ref)/12), so
the modal spectrum shifts by the semitone ratio exactly (bending modes
go as thickness / length^2; the woodblock's cavity scales consistently).
The exciter stays fixed. The default geometry sits at the reference
note with a two-octave playable range around it.
3 changed files+34−7
export-src/woodblock/export.jsonmodified+16−3View file
@@ -1,10 +1,23 @@
11 {
22 "id": "woodblock",
33 "name": "Woodblock",
4- "description": "Struck woodblock; unpitched, velocity onto mallet speed.",
4+ "description": "Struck woodblock; pitch scales the block around MIDI 76 (64..88), velocity onto mallet speed.",
55 "source": "simulated_instruments/woodblock",
66 "knobs": [
7- { "key": "strike_position", "label": "strike position", "min": 0.05, "max": 0.95, "default": 0.42 },
8- { "key": "mallet_stiffness", "label": "mallet stiffness", "min": 1e7, "max": 3e10, "default": 1e8, "scale": "log" }
7+ {
8+ "key": "strike_position",
9+ "label": "strike position",
10+ "min": 0.05,
11+ "max": 0.95,
12+ "default": 0.42
13+ },
14+ {
15+ "key": "mallet_stiffness",
16+ "label": "mallet stiffness",
17+ "min": 10000000.0,
18+ "max": 30000000000.0,
19+ "default": 100000000.0,
20+ "scale": "log"
21+ }
922 ]
1023 }
export-src/woodblock/render_note.mmodified+16−2View file
@@ -1,7 +1,12 @@
11 function y = render_note(note)
2-% Unpitched: MIDI pitch is ignored, velocity drives the mallet speed.
2+% MIDI pitch scales the block: every length by s = 2^-((pitch - 76)/12),
3+% the participating mass by s^3, and the resonant cavity consistently
4+% (volume s^3, slit area s^2, slit length s), so both the bar modes
5+% (thickness / length^2) and the Helmholtz resonance shift by the
6+% semitone ratio exactly. The mallet stays fixed. Pitch 76 is the
7+% default block, playable 64..88. Velocity drives the mallet speed.
38 % Fixed: normalize off so velocity survives in pascals; the preview tier
4-% caps oversampling at 4 (the model's eigensolve dominates either way).
9+% caps oversampling at 4 (the eigensolve dominates either way).
510 P = woodblock_defaults();
611 P.sample_rate = note.sample_rate;
712 P.pre_delay_ms = 0;
@@ -9,6 +14,15 @@ P.normalize = 0;
914 if note.preview ~= 0
1015 P.oversample = 4;
1116 end
17+s = 2 ^ (-(max(64, min(88, note.pitch)) - 76) / 12);
18+P.length_m = P.length_m * s;
19+P.width_m = P.width_m * s;
20+P.wall_thickness_m = P.wall_thickness_m * s;
21+P.block_height_m = P.block_height_m * s;
22+P.participating_mass_kg = P.participating_mass_kg * s^3;
23+P.cavity_volume_m3 = P.cavity_volume_m3 * s^3;
24+P.slit_area_m2 = P.slit_area_m2 * s^2;
25+P.slit_length_m = P.slit_length_m * s;
1226 P.mallet_velocity = 0.2 + (8 - 0.2) * note.velocity;
1327 P.strike_position = note.params.strike_position;
1428 P.mallet_stiffness = note.params.mallet_stiffness;
exports/woodblock.jsonmodified+2−2View file
@@ -2,7 +2,7 @@
22 "format": "numbl-instrument-v1",
33 "id": "woodblock",
44 "name": "Woodblock",
5- "description": "Struck woodblock; unpitched, velocity onto mallet speed.",
5+ "description": "Struck woodblock; pitch scales the block around MIDI 76 (64..88), velocity onto mallet speed.",
66 "source": "simulated_instruments/woodblock",
77 "knobs": [
88 {
@@ -24,7 +24,7 @@
2424 "files": [
2525 {
2626 "name": "render_note.m",
27- "content": "function y = render_note(note)\n% Unpitched: MIDI pitch is ignored, velocity drives the mallet speed.\n% Fixed: normalize off so velocity survives in pascals; the preview tier\n% caps oversampling at 4 (the model's eigensolve dominates either way).\nP = woodblock_defaults();\nP.sample_rate = note.sample_rate;\nP.pre_delay_ms = 0;\nP.normalize = 0;\nif note.preview ~= 0\n P.oversample = 4;\nend\nP.mallet_velocity = 0.2 + (8 - 0.2) * note.velocity;\nP.strike_position = note.params.strike_position;\nP.mallet_stiffness = note.params.mallet_stiffness;\ny = woodblock_render(P);\nend\n"
27+ "content": "function y = render_note(note)\n% MIDI pitch scales the block: every length by s = 2^-((pitch - 76)/12),\n% the participating mass by s^3, and the resonant cavity consistently\n% (volume s^3, slit area s^2, slit length s), so both the bar modes\n% (thickness / length^2) and the Helmholtz resonance shift by the\n% semitone ratio exactly. The mallet stays fixed. Pitch 76 is the\n% default block, playable 64..88. Velocity drives the mallet speed.\n% Fixed: normalize off so velocity survives in pascals; the preview tier\n% caps oversampling at 4 (the eigensolve dominates either way).\nP = woodblock_defaults();\nP.sample_rate = note.sample_rate;\nP.pre_delay_ms = 0;\nP.normalize = 0;\nif note.preview ~= 0\n P.oversample = 4;\nend\ns = 2 ^ (-(max(64, min(88, note.pitch)) - 76) / 12);\nP.length_m = P.length_m * s;\nP.width_m = P.width_m * s;\nP.wall_thickness_m = P.wall_thickness_m * s;\nP.block_height_m = P.block_height_m * s;\nP.participating_mass_kg = P.participating_mass_kg * s^3;\nP.cavity_volume_m3 = P.cavity_volume_m3 * s^3;\nP.slit_area_m2 = P.slit_area_m2 * s^2;\nP.slit_length_m = P.slit_length_m * s;\nP.mallet_velocity = 0.2 + (8 - 0.2) * note.velocity;\nP.strike_position = note.params.strike_position;\nP.mallet_stiffness = note.params.mallet_stiffness;\ny = woodblock_render(P);\nend\n"
2828 },
2929 {
3030 "name": "woodblock_basis.m",