use crate::error::SampleError;
use crate::physics::Solver;
use crate::physics::bound::Bound::*;
use crate::physics::bound::all;
use crate::physics::hammer::Hammer;
#[derive(Clone, Debug, PartialEq)]
pub struct ChaigneDoutautParams {
pub f0: f64,
pub strike_pos: f64,
pub vel: f64,
pub hammer_mass: f64,
pub hammer_k: f64,
pub hammer_p: f64,
pub damp_dc: f64,
pub damp_freq: f64,
}
impl ChaigneDoutautParams {
pub fn at(f0: f64) -> ChaigneDoutautParams {
ChaigneDoutautParams {
f0,
strike_pos: 0.15,
vel: 3.2,
hammer_mass: 2.9e-3,
hammer_k: 2.6646e8,
hammer_p: 2.5,
damp_dc: 0.6,
damp_freq: 1.6e-4,
}
}
}
impl ChaigneDoutautParams {
pub fn valid(&self) -> bool {
all(&[
(self.f0, Positive),
(self.strike_pos, OpenUnit),
(self.vel, Positive),
(self.hammer_mass, Positive),
(self.hammer_k, Positive),
(self.hammer_p, Positive),
(self.damp_dc, NonNegative),
(self.damp_freq, NonNegative),
])
}
}
const BAR_YOUNGS_MODULUS_PA: f64 = 7.0e10;
const BAR_DENSITY_KG_M3: f64 = 2700.0;
const BAR_THICKNESS_M: f64 = 0.01;
const BAR_WIDTH_M: f64 = 1.0;
const BETA1_L: f64 = 4.730040744862704;
const PICKUP_POS: f64 = 0.93;
#[derive(Clone)]
pub(crate) struct BarGrid {
pub(crate) u_now: Vec<f64>,
pub(crate) u_prev: Vec<f64>,
pub(crate) u_next: Vec<f64>,
pub(crate) n: usize,
pub(crate) dx: f64,
pub(crate) rho: f64,
#[allow(dead_code)]
pub(crate) kappa: f64,
pub(crate) stiff_sq: f64,
pub(crate) damp_a: f64,
pub(crate) damp_b: f64,
pub(crate) bar_substeps: usize,
}
pub(crate) fn free_ghost(u: &[f64], n: usize, idx: isize) -> f64 {
if idx >= 0 && idx as usize <= n {
return u[idx as usize];
}
if idx < 0 {
match idx {
-1 => 2.0 * u[0] - u[1],
-2 => 4.0 * u[0] - 4.0 * u[1] + u[2],
_ => unreachable!(),
}
} else {
match idx as usize - n {
1 => 2.0 * u[n] - u[n - 1],
2 => 4.0 * u[n] - 4.0 * u[n - 1] + u[n - 2],
_ => unreachable!(),
}
}
}
pub(crate) fn bar_grid(
rho: f64,
kappa: f64,
length: f64,
damp_dc: f64,
damp_freq: f64,
sr: f64,
) -> BarGrid {
let dt = 1.0 / sr;
let mu_max = 0.5;
let mu_target = 0.9 * mu_max;
let dx_target = (kappa * dt / mu_target).sqrt();
let n = ((length / dx_target).round() as usize).max(4);
let dx = length / n as f64;
let mu_at_dt = kappa * dt / (dx * dx);
let bar_substeps = (mu_at_dt / mu_target).ceil().max(1.0) as usize;
let dt_sub = dt / bar_substeps as f64;
let stiff_sq = kappa * kappa * dt_sub * dt_sub / dx.powi(4);
BarGrid {
u_now: vec![0.0; n + 1],
u_prev: vec![0.0; n + 1],
u_next: vec![0.0; n + 1],
n,
dx,
rho,
kappa,
stiff_sq,
damp_a: 2.0 * damp_dc * dt_sub,
damp_b: 2.0 * damp_freq * dt_sub / (dx * dx),
bar_substeps,
}
}
fn build_grid(params: &ChaigneDoutautParams, sr: f64) -> BarGrid {
let kappa =
(BAR_YOUNGS_MODULUS_PA / BAR_DENSITY_KG_M3).sqrt() * BAR_THICKNESS_M / 12.0f64.sqrt();
let length = (kappa * BETA1_L * BETA1_L / (2.0 * std::f64::consts::PI * params.f0)).sqrt();
let rho = BAR_DENSITY_KG_M3 * BAR_WIDTH_M * BAR_THICKNESS_M;
bar_grid(rho, kappa, length, params.damp_dc, params.damp_freq, sr)
}
#[derive(Clone)]
pub struct ChaigneDoutautSite {
bar: BarGrid,
hammer: Hammer,
detached: bool,
contact_index: usize,
pickup_index: usize,
dt: f64,
}
impl ChaigneDoutautSite {
pub fn new(params: &ChaigneDoutautParams, sr: f64) -> ChaigneDoutautSite {
let bar = build_grid(params, sr);
let contact_index = (params.strike_pos * bar.n as f64)
.round()
.clamp(1.0, (bar.n - 1) as f64) as usize;
let pickup_index = (PICKUP_POS * bar.n as f64)
.round()
.clamp(1.0, (bar.n - 1) as f64) as usize;
let dt = 1.0 / sr;
ChaigneDoutautSite {
bar,
hammer: Hammer::new(
params.hammer_mass,
params.hammer_k,
params.hammer_p,
params.vel,
dt,
),
detached: false,
contact_index,
pickup_index,
dt,
}
}
}
impl ChaigneDoutautSite {
fn advance(&mut self) -> f64 {
let bar = &mut self.bar;
let n = bar.n;
let u_h = bar.u_now[self.contact_index];
let mut forces = [0.0f64; 1];
self.hammer.substeps(
self.dt,
&[u_h],
std::slice::from_mut(&mut self.detached),
&mut forces,
);
let force = forces[0];
let sample = bar.u_now[self.pickup_index];
let dt_sub = self.dt / bar.bar_substeps as f64;
let injection = (dt_sub * dt_sub / (bar.rho * bar.dx)) * force;
for _ in 0..bar.bar_substeps {
for i in 0..=n {
let ii = i as isize;
let lap_now = free_ghost(&bar.u_now, n, ii + 1) - 2.0 * bar.u_now[i]
+ free_ghost(&bar.u_now, n, ii - 1);
let biharm = free_ghost(&bar.u_now, n, ii + 2)
- 4.0 * free_ghost(&bar.u_now, n, ii + 1)
+ 6.0 * bar.u_now[i]
- 4.0 * free_ghost(&bar.u_now, n, ii - 1)
+ free_ghost(&bar.u_now, n, ii - 2);
let lap_prev = free_ghost(&bar.u_prev, n, ii + 1) - 2.0 * bar.u_prev[i]
+ free_ghost(&bar.u_prev, n, ii - 1);
let mut next = 2.0 * bar.u_now[i]
- bar.u_prev[i]
- bar.stiff_sq * biharm
- bar.damp_a * (bar.u_now[i] - bar.u_prev[i])
+ bar.damp_b * (lap_now - lap_prev);
if i == self.contact_index {
next += injection;
}
bar.u_next[i] = next;
}
std::mem::swap(&mut bar.u_prev, &mut bar.u_now);
std::mem::swap(&mut bar.u_now, &mut bar.u_next);
}
sample
}
}
impl Solver for ChaigneDoutautSite {
fn bytes(&self) -> usize {
let bar = &self.bar;
size_of::<Self>()
+ super::floats(&bar.u_now)
+ super::floats(&bar.u_prev)
+ super::floats(&bar.u_next)
}
fn step(&mut self, _args: &[f64]) -> Result<f64, SampleError> {
Ok(self.advance())
}
}