de6cf45cymroom: load a .cymsim file and show every part of it in a 3D scene; the room acoustics solver is not written yetJeremy Magland 1// In-memory record of the audio-rate normal acceleration of every node, quantized to int16
2// against one float32 scale per sample, exactly the encoding of the .cymsim file (see
3// cymsim/docs/format.md, section 6), so that exporting copies blocks without re-encoding and
4// loading keeps the file's blocks as they are. Recording is opt-in (output.record_acceleration):
5// at the default mesh it costs about 0.4 GB per simulated second.
7export interface AccelerationBlock { first: number; count: number; data: Int16Array; scale: Float32Array }
9export class AccelerationStore {
10 readonly nodes: number; readonly fs: number;
11 readonly blocks: AccelerationBlock[] = [];
12 nSamples = 0;
14 constructor(nodes: number, fs: number) { this.nodes = nodes; this.fs = fs; }
16 get bytes() { return this.nSamples * (2 * this.nodes + 4); }
18 /** Bytes a full record of ``nSamples`` samples takes in memory and in the file. */
19 static estimateBytes(nSamples: number, nodes: number): number { return nSamples * (2 * nodes + 4); }
21 /** Encode ``count`` rows of float accelerations (row-major, N per row) that continue the record at ``first``. */
22 push(rows: Float32Array, count: number, first: number) {
23 if (count <= 0) return;
24 if (first !== this.nSamples) throw new Error(`acceleration record is not contiguous: got sample ${first}, expected ${this.nSamples}`);
25 const N = this.nodes, data = new Int16Array(count * N), scale = new Float32Array(count);
26 for (let r = 0; r < count; r++) {
27 let peak = 0;
28 const b = r * N;
29 for (let i = 0; i < N; i++) { const a = Math.abs(rows[b + i]); if (a > peak) peak = a; }
30 const s = peak > 0 ? peak / 32767 : 0;
31 scale[r] = s;
32 if (s > 0) for (let i = 0; i < N; i++) data[b + i] = Math.round(rows[b + i] / s);
33 }
34 this.blocks.push({ first, count, data, scale }); this.nSamples += count;
35 }
37 /** Append an already encoded block (from a file). */
38 pushEncoded(data: Int16Array, scale: Float32Array) {
39 const count = scale.length;
40 if (count === 0) return;
41 if (data.length !== count * this.nodes) throw new Error("encoded acceleration block has the wrong size");
42 this.blocks.push({ first: this.nSamples, count, data, scale }); this.nSamples += count;
43 }
45 /** Decoded accelerations [m/s^2] of samples [j0, j1), row-major. */
46 read(j0: number, j1: number): Float32Array {
47 j0 = Math.max(0, j0); j1 = Math.min(this.nSamples, j1);
48 const N = this.nodes, out = new Float32Array(Math.max(0, j1 - j0) * N);
49 for (const b of this.blocks) {
50 const lo = Math.max(j0, b.first), hi = Math.min(j1, b.first + b.count);
51 for (let j = lo; j < hi; j++) {
52 const s = b.scale[j - b.first], src = (j - b.first) * N, dst = (j - j0) * N;
53 for (let i = 0; i < N; i++) out[dst + i] = b.data[src + i] * s;
54 }
55 }
56 return out;
57 }
58}
60/** The transverse displacement frames kept for the .cymsim file (a strobe for display, not a signal). */
61export class DisplacementStore {
62 readonly nodes: number; readonly rate: number;
63 readonly times: number[] = []; readonly frames: Float32Array[] = [];
64 constructor(nodes: number, rate: number) { this.nodes = nodes; this.rate = rate; }
65 push(w: Float32Array, time: number) { if (w.length === this.nodes) { this.frames.push(w); this.times.push(time); } }
66 get bytes() { return this.frames.length * this.nodes * 4; }
67}