import type { ProblemSpec } from "../spec/types"; import { formatNumber as n, formatString as s } from "./format"; /** * Render the specification as the MATLAB `params` struct. The controls edit the * spec; this block is what the solver actually reads, so the two cannot drift. */ export function generateParamsBlock(spec: ProblemSpec): string { const { plate, material, hole, thread, hammer, strike, probe, mic, render, rr, fem } = spec; const lines = [ "% Problem specification, generated from the controls. SI units: m, s, N, Pa, kg/m^3.", "% Coordinates are measured from the plate's centre, x to the right, y towards the threaded top edge.", "% Edit anything here or in the library files; the controls regenerate only this block.", "params = struct();", `params.plate = struct('width', ${n(plate.width)}, 'height', ${n(plate.height)}, 'thickness', ${n(plate.thickness)}); % sides and thickness [m]`, `params.material = struct('name', ${s(material.name)}, 'E', ${n(material.E)}, 'nu', ${n(material.nu)}, 'rho', ${n(material.rho)}, 'Q', ${n(material.Q)}); % Young's modulus [Pa], Poisson's ratio, density [kg/m^3], structural quality factor`, `params.hole = struct('diameter', ${n(hole.diameter)}, 'edge_offset', ${n(hole.edge_offset)}); % the suspension hole on the centreline, this far below the top edge [m]`, `params.thread = struct('length', ${n(thread.length)}, 'dashpot', ${n(thread.dashpot)}); % thread length [m] (pendulum spring m g / L) and the loss at the hole [N s/m]`, `params.hammer = struct('mass', ${n(hammer.mass)}, 'tip_radius', ${n(hammer.tip_radius)}, 'speed', ${n(hammer.speed)}, 'E', ${n(hammer.E)}, 'nu', ${n(hammer.nu)}, 'restitution', ${n(hammer.restitution)}); % mass [kg], tip radius [m], approach speed [m/s], tip modulus [Pa] and Poisson's ratio, nominal restitution`, `params.strike = struct('x', ${n(strike.x)}, 'y', ${n(strike.y)}); % where the hammer lands [m from the centre]`, `params.probe = struct('x', ${n(probe.x)}, 'y', ${n(probe.y)}); % where the time series are recorded [m from the centre]`, `params.mic = struct('distance', ${n(mic.distance)}, 'air_density', ${n(mic.air_density)}, 'sound_speed', ${n(mic.sound_speed)}); % microphone on the plate's normal [m], air [kg/m^3], [m/s]`, `params.render = struct('fs', ${n(render.fs)}, 'duration', ${n(render.duration)}, 'modes', ${n(render.modes)}, 'normalize_dbfs', ${n(render.normalize_dbfs)}, 'nframes', ${n(render.nframes)}, 'frame_span', ${n(render.frame_span)}); % sample rate [Hz] (modes kept below 0.47 fs), duration [s], mode cap, export peak [dBFS], frames over the first frame_span [s]`, `params.rr = struct('basis_order', ${n(rr.basis_order)}, 'quadrature_order', ${n(rr.quadrature_order)}, 'grid', ${n(rr.grid)}); % Rayleigh-Ritz: Legendre order per side, quadrature points per side, display grid per side`, `params.fem = struct('mesh_x', ${n(fem.mesh_x)}, 'mesh_y', ${n(fem.mesh_y)}); % finite elements: Q4 elements along x and y`, ]; return lines.join("\n"); }