1use crate::error::SampleError;
7use crate::physics::Solver;
8
9use crate::physics::bound::Bound::*;
10use crate::physics::bound::all;
11use crate::physics::hammer::Hammer;
12
13#[derive(Clone, Debug, PartialEq)]
14pub struct ChaigneDoutautParams {
15 pub f0: f64,
16 pub strike_pos: f64,
17 pub vel: f64,
18 pub hammer_mass: f64,
19 pub hammer_k: f64,
20 pub hammer_p: f64,
21 pub damp_dc: f64,
22 pub damp_freq: f64,
23}
24
25impl ChaigneDoutautParams {
26 pub fn at(f0: f64) -> ChaigneDoutautParams {
28 ChaigneDoutautParams {
29 f0,
30 strike_pos: 0.15,
31 vel: 3.2,
32 hammer_mass: 2.9e-3,
33 hammer_k: 2.6646e8,
34 hammer_p: 2.5,
35 damp_dc: 0.6,
36 damp_freq: 1.6e-4,
37 }
38 }
39}
40
41impl ChaigneDoutautParams {
42 pub fn valid(&self) -> bool {
43 all(&[
44 (self.f0, Positive),
45 (self.strike_pos, OpenUnit),
46 (self.vel, Positive),
47 (self.hammer_mass, Positive),
48 (self.hammer_k, Positive),
49 (self.hammer_p, Positive),
50 (self.damp_dc, NonNegative),
51 (self.damp_freq, NonNegative),
52 ])
53 }
54}
55
56const BAR_YOUNGS_MODULUS_PA: f64 = 7.0e10;
58const BAR_DENSITY_KG_M3: f64 = 2700.0;
59const BAR_THICKNESS_M: f64 = 0.01;
60const BAR_WIDTH_M: f64 = 1.0;
62const BETA1_L: f64 = 4.730040744862704;
64const PICKUP_POS: f64 = 0.93;
66
67#[derive(Clone)]
68pub(crate) struct BarGrid {
69 pub(crate) u_now: Vec<f64>,
70 pub(crate) u_prev: Vec<f64>,
71 pub(crate) u_next: Vec<f64>,
72 pub(crate) n: usize,
73 pub(crate) dx: f64,
74 pub(crate) rho: f64,
75 #[allow(dead_code)]
76 pub(crate) kappa: f64,
77 pub(crate) stiff_sq: f64,
78 pub(crate) damp_a: f64,
79 pub(crate) damp_b: f64,
80 pub(crate) bar_substeps: usize,
81}
82
83pub(crate) fn free_ghost(u: &[f64], n: usize, idx: isize) -> f64 {
86 if idx >= 0 && idx as usize <= n {
87 return u[idx as usize];
88 }
89 if idx < 0 {
90 match idx {
91 -1 => 2.0 * u[0] - u[1],
92 -2 => 4.0 * u[0] - 4.0 * u[1] + u[2],
93 _ => unreachable!(),
94 }
95 } else {
96 match idx as usize - n {
97 1 => 2.0 * u[n] - u[n - 1],
98 2 => 4.0 * u[n] - 4.0 * u[n - 1] + u[n - 2],
99 _ => unreachable!(),
100 }
101 }
102}
103
104pub(crate) fn bar_grid(
106 rho: f64,
107 kappa: f64,
108 length: f64,
109 damp_dc: f64,
110 damp_freq: f64,
111 sr: f64,
112) -> BarGrid {
113 let dt = 1.0 / sr;
114 let mu_max = 0.5;
115 let mu_target = 0.9 * mu_max;
116 let dx_target = (kappa * dt / mu_target).sqrt();
117 let n = ((length / dx_target).round() as usize).max(4);
118 let dx = length / n as f64;
119 let mu_at_dt = kappa * dt / (dx * dx);
121 let bar_substeps = (mu_at_dt / mu_target).ceil().max(1.0) as usize;
122 let dt_sub = dt / bar_substeps as f64;
123 let stiff_sq = kappa * kappa * dt_sub * dt_sub / dx.powi(4);
124
125 BarGrid {
126 u_now: vec![0.0; n + 1],
127 u_prev: vec![0.0; n + 1],
128 u_next: vec![0.0; n + 1],
129 n,
130 dx,
131 rho,
132 kappa,
133 stiff_sq,
134 damp_a: 2.0 * damp_dc * dt_sub,
135 damp_b: 2.0 * damp_freq * dt_sub / (dx * dx),
136 bar_substeps,
137 }
138}
139
140fn build_grid(params: &ChaigneDoutautParams, sr: f64) -> BarGrid {
142 let kappa =
143 (BAR_YOUNGS_MODULUS_PA / BAR_DENSITY_KG_M3).sqrt() * BAR_THICKNESS_M / 12.0f64.sqrt();
144 let length = (kappa * BETA1_L * BETA1_L / (2.0 * std::f64::consts::PI * params.f0)).sqrt();
145 let rho = BAR_DENSITY_KG_M3 * BAR_WIDTH_M * BAR_THICKNESS_M;
146 bar_grid(rho, kappa, length, params.damp_dc, params.damp_freq, sr)
147}
148
149#[derive(Clone)]
150pub struct ChaigneDoutautSite {
151 bar: BarGrid,
152 hammer: Hammer,
153 detached: bool,
154 contact_index: usize,
155 pickup_index: usize,
156 dt: f64,
157}
158
159impl ChaigneDoutautSite {
160 pub fn new(params: &ChaigneDoutautParams, sr: f64) -> ChaigneDoutautSite {
161 let bar = build_grid(params, sr);
162 let contact_index = (params.strike_pos * bar.n as f64)
163 .round()
164 .clamp(1.0, (bar.n - 1) as f64) as usize;
165 let pickup_index = (PICKUP_POS * bar.n as f64)
166 .round()
167 .clamp(1.0, (bar.n - 1) as f64) as usize;
168 let dt = 1.0 / sr;
169 ChaigneDoutautSite {
170 bar,
171 hammer: Hammer::new(
172 params.hammer_mass,
173 params.hammer_k,
174 params.hammer_p,
175 params.vel,
176 dt,
177 ),
178 detached: false,
179 contact_index,
180 pickup_index,
181 dt,
182 }
183 }
184}
185
186impl ChaigneDoutautSite {
187 fn advance(&mut self) -> f64 {
188 let bar = &mut self.bar;
189 let n = bar.n;
190 let u_h = bar.u_now[self.contact_index];
191
192 let mut forces = [0.0f64; 1];
193 self.hammer.substeps(
194 self.dt,
195 &[u_h],
196 std::slice::from_mut(&mut self.detached),
197 &mut forces,
198 );
199 let force = forces[0];
200
201 let sample = bar.u_now[self.pickup_index];
202
203 let dt_sub = self.dt / bar.bar_substeps as f64;
204 let injection = (dt_sub * dt_sub / (bar.rho * bar.dx)) * force;
205 for _ in 0..bar.bar_substeps {
206 for i in 0..=n {
207 let ii = i as isize;
208 let lap_now = free_ghost(&bar.u_now, n, ii + 1) - 2.0 * bar.u_now[i]
209 + free_ghost(&bar.u_now, n, ii - 1);
210 let biharm = free_ghost(&bar.u_now, n, ii + 2)
211 - 4.0 * free_ghost(&bar.u_now, n, ii + 1)
212 + 6.0 * bar.u_now[i]
213 - 4.0 * free_ghost(&bar.u_now, n, ii - 1)
214 + free_ghost(&bar.u_now, n, ii - 2);
215 let lap_prev = free_ghost(&bar.u_prev, n, ii + 1) - 2.0 * bar.u_prev[i]
216 + free_ghost(&bar.u_prev, n, ii - 1);
217 let mut next = 2.0 * bar.u_now[i]
218 - bar.u_prev[i]
219 - bar.stiff_sq * biharm
220 - bar.damp_a * (bar.u_now[i] - bar.u_prev[i])
221 + bar.damp_b * (lap_now - lap_prev);
222 if i == self.contact_index {
223 next += injection;
224 }
225 bar.u_next[i] = next;
226 }
227 std::mem::swap(&mut bar.u_prev, &mut bar.u_now);
228 std::mem::swap(&mut bar.u_now, &mut bar.u_next);
229 }
230
231 sample
232 }
233}
234
235impl Solver for ChaigneDoutautSite {
236 fn bytes(&self) -> usize {
237 let bar = &self.bar;
238 size_of::<Self>()
239 + super::floats(&bar.u_now)
240 + super::floats(&bar.u_prev)
241 + super::floats(&bar.u_next)
242 }
243
244 fn step(&mut self, _args: &[f64]) -> Result<f64, SampleError> {
245 Ok(self.advance())
246 }
247}