/ simulated_instruments / triangle
Sign in
simulated_instruments / triangle
Default to a twelve-strike roll
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 59c69d1f7ad7 parent 96aec9c Browse files
7 changed files+19−14
README.mdmodified+2−2View file
@@ -1,6 +1,6 @@
11 # Triangle
22
3-A physically-based simulation of an orchestral triangle strike, sampled as acoustic pressure at a point in space. The instrument is a steel rod bent into an open equilateral triangle; a Hertz beater contact excites the bending, stretching, and torsional modes of the bent free-free rod, computed by a small finite element eigensolve, and each element of the rod radiates as a point dipole to a listener point.
3+A physically-based simulation of an orchestral triangle, struck or rolled, sampled as acoustic pressure at a point in space. The instrument is a steel rod bent into an open equilateral triangle; Hertz beater contacts excite the bending, stretching, and torsional modes of the bent free-free rod, computed by a small finite element eigensolve, and each element of the rod radiates as a point dipole to a listener point.
44
55 There is no microphone model: no directivity, no capsule response, no gain staging. The output is p(r, t) in pascals.
66
@@ -42,4 +42,4 @@ As in the sibling `maraca` repository, there is no separate Python reference imp
4242
4343 ## Defaults
4444
45-A 15.2 cm (6 inch) steel triangle of 8 mm rod with a 15 mm corner opening, struck once at 1.5 m/s on the middle of the base with a 22 g steel beater, 20 degrees off the plane. That yields 40 modes between 164 Hz and 21.6 kHz (19 in-plane, 21 out-of-plane), a 57 µs contact, and a peak of 7.2 Pa (111 dB SPL) at the 45 cm listener before normalization, ringing through a five-second tail.
45+A 15.2 cm (6 inch) steel triangle of 8 mm rod with a 15 mm corner opening, rolled with a 22 g steel beater at 1.5 m/s and 20 degrees off the plane: twelve hits at 12 Hz alternating between the middle of the base and the right leg, with 6 ms of timing jitter and 15% velocity spread. That yields 40 modes between 164 Hz and 21.6 kHz (19 in-plane, 21 out-of-plane), a 57 µs mean contact, and a peak of 13.8 Pa (117 dB SPL) at the 45 cm listener before normalization, ringing through a five-second tail. Setting `strike_count` to 1 gives a single, fully deterministic strike, which is the configuration the physics is easiest to hear in.
matlab/triangle_defaults.mmodified+1−1View file
@@ -34,7 +34,7 @@ P.restitution = 0.60;
3434 P.strike_velocity = 1.5;
3535 P.strike_pos = 0.17; % arclength fraction: middle of the base
3636 P.strike_angle_deg = 20.0; % 0 = in the plane of the triangle, 90 = normal to it
37-P.strike_count = 1;
37+P.strike_count = 12;
3838 P.strike_rate_hz = 12.0;
3939 P.roll_pos2 = 0.40; % second contact point of a roll (the right leg)
4040 P.timing_jitter_ms = 6.0;
matlab/triangle_demo.mmodified+4−3View file
@@ -1,4 +1,5 @@
1-% Render a triangle strike with default parameters and print diagnostics.
1+% Render a triangle roll with default parameters and print diagnostics.
2+% Set P.strike_count = 1 for a single strike.
23 % Run with: numbl run triangle_demo.m
34
45 P = triangle_defaults();
@@ -10,8 +11,8 @@ fprintf('modes kept : %d (%d in-plane, %d out-of-plane)\n', basis.n, dinfo.modes
1011 fprintf('mode freq : ');
1112 fprintf('%.0f ', basis.f);
1213 fprintf('Hz\n');
13-fprintf('strikes : %d (mean v %.2f m/s, mean contact %.0f us, peak force %.0f N)\n', ...
14- dinfo.strikeCount, dinfo.meanV, dinfo.meanTauUs, dinfo.peakForceN);
14+fprintf('strikes : %d at %.1f Hz (mean v %.2f m/s, mean contact %.0f us, peak force %.0f N)\n', ...
15+ dinfo.strikeCount, P.strike_rate_hz, dinfo.meanV, dinfo.meanTauUs, dinfo.peakForceN);
1516 fprintf('peak press.: %.4f Pa (%.1f dB SPL) at %.0f cm\n', ...
1617 dinfo.peakPa, dinfo.peakSplDb, dinfo.listenerDistM * 100);
1718 fprintf('length : %.2f s @ %d Hz\n', length(y) / P.sample_rate, P.sample_rate);
matlab/triangle_validate.mmodified+8−4View file
@@ -61,8 +61,10 @@ fprintf('eigensolve: orthonormality %.1e, diagonalization %.1e %s\n', eOrth, eDi
6161
6262 % ---- 3. Hertz contact-time scaling tau ~ v^(-1/5) ----
6363 % With a beater much lighter than the engaged rod mass the reduced mass is a
64-% constant and the free-sphere scaling must emerge.
64+% constant and the free-sphere scaling must emerge. A single strike, so the
65+% check does not depend on the default gesture or on the velocity spread.
6566 Q = P;
67+Q.strike_count = 1;
6668 Q.beater_mass_kg = 5e-4;
6769 [~, ~, ~, tau1, ~, ~, ~] = triangle_strikes(Q);
6870 Q.strike_velocity = 2 * P.strike_velocity;
@@ -96,6 +98,7 @@ fprintf('roll (seed %d): %d strikes, mean v %.6f m/s, sum J %.9f N s %s\n', ...
9698 % the ratio by a few tenths of a dB, again physics rather than error.
9799 Q = P;
98100 Q.normalize = 0;
101+Q.strike_count = 1;
99102 Q.tail_s = 1.0;
100103 Q.max_mode_hz = 8000;
101104 Q.listener_x_m = 0.6; Q.listener_y_m = 0.5; Q.listener_z_m = 1.8;
@@ -109,16 +112,17 @@ fprintf('falloff 2 m -> 4 m: %.2f dB rms (target -6.02) %s\n', drop, tv_verdict(
109112
110113 % ---- 6. pinned end-to-end diagnostics at the defaults ----
111114 [y, basis, dinfo] = triangle_render(P);
112-ok = abs(dinfo.peakPa - 7.217688) < 0.02 ...
115+ok = abs(dinfo.peakPa - 13.769445) < 0.02 ...
113116 && abs(basis.f(1) - 163.9200) < 0.1 ...
114117 && basis.n == 40;
115118 fails = fails + (~ok);
116119 fprintf('pinned render: peak %.4f Pa (target %.4f), mode1 %.2f Hz (target %.2f), %d modes %s\n', ...
117- dinfo.peakPa, 7.217688, basis.f(1), 163.9200, basis.n, tv_verdict(ok));
120+ dinfo.peakPa, 13.769445, basis.f(1), 163.9200, basis.n, tv_verdict(ok));
118121
119122 % ---- 7. the ring decays ----
120123 % A triangle rings for seconds, so the tail is not silent; but the energy in
121-% the last half second must sit well below the first half second's.
124+% the last half second must sit well below that of the opening half second,
125+% which at the defaults carries the first six hits of the roll.
122126 fs = P.sample_rate;
123127 n = length(y);
124128 head = y(1:round(0.5 * fs));
webapp/README.mdmodified+1−1View file
@@ -2,7 +2,7 @@
22
33 Interactive browser implementation of the physical model described in `../README.md`. The algorithm itself is written in MATLAB syntax, in `../matlab/*.m`, and executed client-side by [numbl](https://numbl.org) inside a Web Worker; the TypeScript here is only the UI, the plots, the WAV encoder, and the marshalling of parameters and tensors in and out of the numbl runtime.
44
5-The signal chain: a Hertz beater contact (or a seeded roll of them), the bending, stretching, and torsional modes of the open bent rod from a finite element eigensolve, a coherent sum of element dipoles to the listener point, and Kaiser-windowed anti-aliased decimation to 48 kHz.
5+The signal chain: a seeded roll of Hertz beater contacts (or a single strike, with `Strikes` set to 1), the bending, stretching, and torsional modes of the open bent rod from a finite element eigensolve, a coherent sum of element dipoles to the listener point, and Kaiser-windowed anti-aliased decimation to 48 kHz.
66
77 ## Usage
88
webapp/src/App.tsxmodified+2−2View file
@@ -133,7 +133,7 @@ export default function App() {
133133 title={
134134 result?.quality === 'preview'
135135 ? 'Playing the fast preview render; press Render for full quality'
136- : 'Play the rendered strike'
136+ : 'Play the rendered strike or roll'
137137 }
138138 >
139139 ▶ Play{result?.quality === 'preview' ? ' (preview)' : ''}
@@ -142,7 +142,7 @@ export default function App() {
142142 className={`loop ${looping ? 'active' : ''}`}
143143 onClick={() => setLooping((v) => !v)}
144144 disabled={!result && !looping}
145- title="Repeat the strike continuously so parameter changes are audible immediately"
145+ title="Repeat the render continuously so parameter changes are audible immediately"
146146 >
147147 ⟳ Loop{looping ? ' on' : ''}
148148 </button>
webapp/src/params.tsmodified+1−1View file
@@ -81,7 +81,7 @@ export const DEFAULTS: TriangleParams = {
8181 strike_velocity: 1.5,
8282 strike_pos: 0.17,
8383 strike_angle_deg: 20.0,
84- strike_count: 1,
84+ strike_count: 12,
8585 strike_rate_hz: 12.0,
8686 roll_pos2: 0.4,
8787 timing_jitter_ms: 6.0,
↑↓ move↵openescclose