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