/ proof-of-concept / cymplay
Sign in
proof-of-concept / cymplay
cymplay / tests / transfer.ts
49 lines · 2.8 KBCodeBlameHistory
ae4a407cymplay: design and play a cymsim cymbal in the browserJeremy Magland 1// Pair-invariant comparison of two modes files: the transfer function from a point force to the
2// listener pressure, |H(w)| = |sum_m G_m phi_m(p) (-w^2) / (w0^2 - w^2 + 2 i sigma w)|, on a
3// frequency grid, and the profile of the frequency differences along the spectrum.
4// node tests/transfer.ts a.bin b.bin
5import { readFileSync } from "node:fs";
6import { locate, modeValuesAt, parseModes, type ModesData } from "../src/modes.ts";
8const sigmaOf = (d: ModesData) => {
9 const mat = (d.meta.config as any).material;
10 return Float64Array.from(d.dampQ, (q) => mat.sigma0 + mat.sigma1 * q);
11};
13const A = parseModes(readFileSync(process.argv[2]).buffer as ArrayBuffer);
14const B = parseModes(readFileSync(process.argv[3]).buffer as ArrayBuffer);
15const n = Math.min(A.nModes, B.nModes);
16for (const [lo, hi] of [[0, 100], [100, 300], [300, 600], [600, 1000], [1000, n]]) {
17 let worst = 0, mean = 0, cnt = 0;
18 for (let m = lo; m < Math.min(hi, n); m++) { const r = Math.abs(A.freq[m] - B.freq[m]) / B.freq[m]; worst = Math.max(worst, r); mean += r; cnt++; }
19 console.log(`modes ${lo}-${Math.min(hi, n)}: freq rel diff mean ${(mean / cnt).toExponential(2)} max ${worst.toExponential(2)}`);
21function transfer(d: ModesData, px: number, py: number, freqs: number[]): number[] {
22 const sig = sigmaOf(d);
23 const loc = locate(d, px, py);
24 if (!loc) throw new Error("point outside mesh");
25 const phi = new Float32Array(d.nModes);
26 modeValuesAt(d, loc.nodes, loc.weights, phi, 0);
27 return freqs.map((f) => {
28 const w = 2 * Math.PI * f;
29 let re = 0, im = 0;
30 for (let m = 0; m < d.nModes; m++) {
31 const w0 = 2 * Math.PI * d.freq[m], s = sig[m];
32 const dr = w0 * w0 - w * w, di = 2 * s * w;
33 const den = dr * dr + di * di;
34 const hr = dr / den, hi = -di / den; // 1 / (dr + i di)
35 const g = -w * w * phi[m];
36 const gr = (d.gainFront[2 * m] - d.gainBack[2 * m]) * g, gi = (d.gainFront[2 * m + 1] - d.gainBack[2 * m + 1]) * g;
37 re += gr * hr - gi * hi; im += gr * hi + gi * hr;
38 }
39 return Math.hypot(re, im);
40 });
42const freqs = Array.from({ length: 60 }, (_, i) => 80 * Math.pow(15000 / 80, i / 59));
43for (const [px, py] of [[0.137 * Math.cos(0.3), 0.137 * Math.sin(0.3)], [0.19 * Math.cos(2), 0.19 * Math.sin(2)]]) {
44 const ha = transfer(A, px, py, freqs), hb = transfer(B, px, py, freqs);
45 let worst = 0, mean = 0;
46 for (let i = 0; i < freqs.length; i++) { const r = Math.abs(ha[i] - hb[i]) / hb[i]; worst = Math.max(worst, r); mean += r / freqs.length; }
47 console.log(`|H| at (${px.toFixed(3)}, ${py.toFixed(3)}): mean rel diff ${mean.toExponential(2)}, max ${worst.toExponential(2)}`);
48 console.log(" sample |H| (A|B) at 100, 1k, 5k, 12k Hz:", [0, 22, 38, 54].map((i) => `${ha[i].toExponential(3)}|${hb[i].toExponential(3)} @${freqs[i].toFixed(0)}`).join(" "));
↑↓ move↵openescclose