use sva_formula::modal::bar::BarGeom;
use sva_formula::modal::bore::{BoreGeom, Ends, helmholtz};
use sva_formula::modal::membrane::MembraneGeom;
use sva_formula::modal::room::RoomGeom;
use sva_formula::modal::string::StringGeom;
use sva_formula::modal::{HammerGeom, mode, strike};
use sva_formula::{Body, Damping, Excitation, Geometry, ModalBank, Mode, modes};
use crate::error::EngineError;
use crate::lower::{Lowering, Piece};
const MODE_COUNT: usize = 256;
const REFERENCE_VELOCITY: f64 = 3.2;
const REFERENCE_CONTACT: f64 = 0.0015;
const REFERENCE_FORCE: f64 = 500.0;
struct Args<'a> {
positional: &'a [f64],
named: &'a [(&'a str, f64)],
}
impl Args<'_> {
fn at(&self, slot: usize, key: &str, fallback: f64) -> f64 {
match self.positional.get(slot) {
Some(v) => *v,
None => super::calls::named_or(self.named, key, fallback),
}
}
fn named(&self, key: &str, fallback: f64) -> f64 {
super::calls::named_or(self.named, key, fallback)
}
fn damping(&self) -> Damping {
Damping {
dc: self.named("damp_dc", 0.6),
per_square_hz: self.named("damp_freq", 1.6e-4),
}
}
fn count(&self) -> usize {
(self.named("modes", MODE_COUNT as f64) as usize).clamp(1, 4096)
}
fn excitation(&self) -> Excitation {
let at = self.named("at", 0.0);
match self.named.iter().find(|(k, _)| *k == "vel") {
Some((_, vel)) => strike(
*vel,
&HammerGeom {
reference_velocity: REFERENCE_VELOCITY,
reference_contact: self.named("contact", REFERENCE_CONTACT),
stiffness_exponent: self.named("p", 2.5),
force: self.named("force", REFERENCE_FORCE),
},
at,
),
None => Excitation::Impulse { t0: at },
}
}
}
impl Lowering<'_, '_> {
pub(super) fn modal(
&mut self,
name: &str,
positional: &[f64],
named: &[(&str, f64)],
) -> Result<Piece, EngineError> {
let args = Args { positional, named };
let bad = || EngineError::BadArity(name.to_string());
let geometry = match name {
"string" => Geometry::String(StringGeom {
f0: *positional.first().ok_or_else(bad)?,
inharmonicity: args.named("inharmonicity", 0.00021),
strike: args.named("strike", 0.125),
damping: args.damping(),
}),
"membrane" => Geometry::Membrane(MembraneGeom {
lx: *positional.first().ok_or_else(bad)?,
ly: args.at(1, "ly", 0.36),
tension: args.named("tension", 3000.0),
density: args.named("density", 0.26),
strike: (args.named("strike_x", 0.3), args.named("strike_y", 0.5)),
damping: args.damping(),
}),
"bar" => Geometry::Bar(BarGeom {
length: *positional.first().ok_or_else(bad)?,
thickness: args.named("thickness", 0.009),
young: args.named("young", 6.9e10),
density: args.named("density", 2700.0),
damping: args.damping(),
}),
"bore" => Geometry::Bore(BoreGeom {
length: *positional.first().ok_or_else(bad)?,
radius: args.named("radius", 0.0075),
speed: args.named("speed", 343.0),
ends: match args.named("closed", 0.0) == 0.0 {
true => Ends::OpenOpen,
false => Ends::ClosedOpen,
},
damping: args.damping(),
}),
"room" => Geometry::Room(RoomGeom {
lx: *positional.first().ok_or_else(bad)?,
ly: args.at(1, "ly", 4.0),
lz: args.at(2, "lz", 2.7),
speed: args.named("speed", 343.0),
damping: args.damping(),
}),
"helmholtz" => return Ok(self.cavity(&args, positional.first().ok_or_else(bad)?)),
"hammer_pulse" => return Ok(self.contact(&args, *positional.first().ok_or_else(bad)?)),
other => unreachable!("{other} is not one of the modal names"),
};
Ok(self.bank(modes(&geometry, args.count()), args.excitation()))
}
fn cavity(&mut self, args: &Args, volume: &f64) -> Piece {
let hz = helmholtz(
*volume,
args.named("neck_area", 0.0005),
args.named("neck_length", 0.02),
args.named("radius", 0.0126),
args.named("speed", 343.0),
);
self.bank(vec![mode(hz, 1.0, args.damping())], args.excitation())
}
fn contact(&mut self, args: &Args, vel: f64) -> Piece {
let excite = strike(
vel,
&HammerGeom {
reference_velocity: REFERENCE_VELOCITY,
reference_contact: args.named("contact", REFERENCE_CONTACT),
stiffness_exponent: args.named("p", 2.5),
force: args.named("force", REFERENCE_FORCE),
},
args.named("at", 0.0),
);
let carrier = Mode {
omega: 0.0,
tau: f64::INFINITY,
amp: 1.0,
phase: 0.0,
};
self.bank(vec![carrier], excite)
}
fn bank(&mut self, modes: Vec<Mode>, excite: Excitation) -> Piece {
Piece::ClosedForm(Body::Modal(ModalBank { modes, excite }))
}
}