commit 300a8706024a8d05e60d050be90f5bca191d108a
parent 0600c269b7ffc9fcf4198d62a049e135bd6ee2f2
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Sat, 13 Jun 2026 19:01:36 +0900
feat: implement physical model calculation in process loop
- Update README with clarified physical formula notation.
- Rename `.cargo/config` to `.cargo/config.toml`.
- Implement physics calculation methods (`x`, `v`, `m`, `r`, `k`, `f`) in `Aerothesis`.
- Update `process` loop to utilize physical model state and handle sample rate initialization.
- Default breath velocity set to 0.5.
Diffstat:
3 files changed, 86 insertions(+), 19 deletions(-)
diff --git a/.cargo/config b/.cargo/config.toml
diff --git a/README.md b/README.md
@@ -89,8 +89,15 @@ $$v[n] = \frac{2}{T}(x[n] - x[n-1]) - v[n-1]$$
From above, formula for oscillation is
$$x[n] = \frac{1}{\frac{4m[n]}{T^2} + \frac{2r[n]}{T} + k[n]} \left[ \left( \frac{4m[n]}{T^2} + \frac{2r[n]}{T} \right) x[n-1] + 2m[n]v[n-1] + F[n] + \frac{m[n]}{m[n-1]} \left( F[n-1] - r[n-1]v[n-1] - k[n-1]x[n-1] \right) \right]$$
$$v[n] = \frac{2}{T}(x[n] - x[n-1]) - v[n-1]$$
+
Define bite strength $V_{\text{bite}}[n] \in [0.0, 1.0]$,and define breath strength $V_{\text{breath}}[n] \in [0.0, 1.0]$ mass $m[n]$ and others as follows:
-$$m[n] = m_{\text{base}} \cdot \big( 1.0 - \gamma \cdot V_{\text{bite}}[n] \big)$$
-$$r[n] = r_{\text{base}} \cdot \big( 1.0 + \beta \cdot V_{\text{bite}}[n] \big) + \delta \cdot V_{\text{breath}}[n]$$
-$$k[n] = k_{\text{base}} \cdot \big( 1.0 + \alpha \cdot V_{\text{bite}}[n] \big)$$
-$$F[n] = G_{\text{breath}} \cdot V_{\text{breath}}[n] - G_{\text{feedback}} \cdot P_{\text{downstream}}[n]$$$$
+
+$$m[n] = \text{base\_mass} \cdot \big( 1.0 - \text{bite\_mass\_scale} \cdot V_{\text{bite}}[n] \big)$$
+
+$$r[n] = \text{base\_damping} \cdot \big( 1.0 + \text{bite\_damping\_scale} \cdot V_{\text{bite}}[n] \big) + \text{breath\_damping} \cdot V_{\text{breath}}[n]$$
+
+$$k[n] = \text{base\_stiffness} \cdot \big( 1.0 + \text{bite\_stiffness\_scale} \cdot V_{\text{bite}}[n] \big)$$
+
+$$F[n] = \text{pressure\_scale} \cdot V_{\text{breath}}[n] - \text{feedback\_gain} \cdot P_{\text{downstream}}[n]$$
+
+but P_downstream is under construction.
diff --git a/src/lib.rs b/src/lib.rs
@@ -67,7 +67,7 @@ impl Default for Aerothesis {
f_prev: 0.0,
sample_rate: 44100.0,
- v_breath: 0.0,
+ v_breath: 0.5,
v_bite: 0.0,
}
}
@@ -175,6 +175,54 @@ impl Default for AerothesisParams {
}
}
+impl Aerothesis {
+ fn m(&self) -> f32 {
+ self.params.base_mass.value() * (1.0 - self.params.bite_mass_scale.value() * self.v_bite)
+ }
+
+ fn r(&self) -> f32 {
+ self.params.base_damping.value()
+ * (1.0 + self.params.bite_damping_scale.value() * self.v_bite)
+ + self.params.breath_damping.value() * self.v_breath
+ }
+
+ fn k(&self) -> f32 {
+ self.params.base_stiffness.value()
+ * (1.0 + self.params.bite_stiffness_scale.value() * self.v_bite)
+ }
+
+ fn f(&self /*, p_downstream: f32*/) -> f32 {
+ self.params.pressure_scale.value() * self.v_breath
+ /* - self.params.feedback_gain.value() * p_downstream */
+ }
+
+ fn x(&self) -> f32 {
+ let m = self.m();
+ let r = self.r();
+ let k = self.k();
+ let f = self.f();
+
+ let t = 1.0 / self.sample_rate;
+ let t_sq = t * t;
+
+ let a = (4.0 * m) / t_sq + (2.0 * r) / t + k;
+
+ let term1 = (4.0 * m / t_sq + 2.0 * r / t) * self.x_prev;
+ let term2 = 2.0 * m * self.v_prev;
+ let term3 = f;
+ let term4 = (m / self.m_prev)
+ * (self.f_prev - self.r_prev * self.v_prev - self.k_prev * self.x_prev);
+
+ let x_calculated = (1.0 / a) * (term1 + term2 + term3 + term4);
+ x_calculated.clamp(-1.0, 1.0)
+ }
+
+ fn v(&self, x: f32) -> f32 {
+ let t = 1.0 / self.sample_rate;
+ (2.0 / t) * (x - self.x_prev) - self.v_prev
+ }
+}
+
impl Plugin for Aerothesis {
const NAME: &'static str = "Aerothesis";
const VENDOR: &'static str = "Minerva_Juppiter";
@@ -219,12 +267,10 @@ impl Plugin for Aerothesis {
fn initialize(
&mut self,
_audio_io_layout: &AudioIOLayout,
- _buffer_config: &BufferConfig,
+ buffer_config: &BufferConfig,
_context: &mut impl InitContext<Self>,
) -> bool {
- // Resize buffers and perform other potentially expensive initialization operations here.
- // The `reset()` function is always called right after this function. You can remove this
- // function if you do not need it.
+ self.sample_rate = buffer_config.sample_rate;
true
}
@@ -240,24 +286,38 @@ impl Plugin for Aerothesis {
context: &mut impl ProcessContext<Self>,
) -> ProcessStatus {
while let Some(event) = context.next_event() {
- match event {
- NoteEvent::MidiCC { cc, value, .. } => {
- if cc == self.params.breath_cc.value() as u8 {
- self.v_breath = value;
- } else if cc == self.params.bite_cc.value() as u8 {
- self.v_bite = value;
- }
+ if let NoteEvent::MidiCC { cc, value, .. } = event {
+ if cc == self.params.breath_cc.value() as u8 {
+ self.v_breath = value;
+ } else if cc == self.params.bite_cc.value() as u8 {
+ self.v_bite = value;
}
- _ => (),
}
}
for channel_samples in buffer.iter_samples() {
- // Smoothing is optionally built into the parameters themselves
let gain = self.params.gain.smoothed.next();
+ // Calculate current physical parameters
+ let m = self.m();
+ let r = self.r();
+ let k = self.k();
+ let f = self.f();
+
+ // Calculate next displacement x[n] and velocity v[n]
+ let x_n = self.x();
+ let v_n = self.v(x_n);
+
+ // Update state for next sample
+ self.x_prev = x_n;
+ self.v_prev = v_n;
+ self.m_prev = m;
+ self.r_prev = r;
+ self.k_prev = k;
+ self.f_prev = f;
+
for sample in channel_samples {
- *sample *= gain;
+ *sample = x_n * gain;
}
}