commit a5c7adfb95954021d459f80873c69ce786f8c74a
parent 09a4efe8947563cb18b2cdf659abceab8ea62a99
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Fri, 19 Jun 2026 03:38:45 +0000
feat: implement displacement-based delay line resonance model
Update the acoustic simulation to utilize a displacement-driven delay-line model for physical resonance.
- Refactored `x_history` to `displacement_history` and added `displacement_prev` for state tracking.
- Implemented a new non-linear damping update equation in the audio processing loop.
- Updated resonance decay logic to align with displacement-based wave propagation.
- Added comprehensive documentation in README.md detailing the wave propagation physics and signal flow.
Diffstat:
| M | README.md | | | 78 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/lib.rs | | | 31 | +++++++++++++++++++------------ |
2 files changed, 97 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
@@ -150,3 +150,81 @@ $$f[n] = \pm \frac{1}{2} \rho v_f[n]^2 g[n]$$
$$v_f[n] = \frac{-\alpha + \sqrt{\alpha^2 + 4 B[n] \Gamma[n-1]}}{2 B[n]}$$
#### Resonance Part
+
+<details>
+<summary>Acoustic Simulation Logic: Displacement-Based Delay Line</summary>
+
+Rather than simulating wave reflection through complex fluid dynamics (changes in density or tube stiffness), this model treats acoustic wave propagation as a delay-based system. We rely on the physical principle that acoustic energy dissipates more rapidly at higher frequencies, which we implement as a damping model applied to the displacement velocity.
+
+#### 1. Damping Mechanism
+
+Energy in an acoustic system is proportional to the square of the time derivative of displacement ($(\partial x / \partial t)^2$). We apply a damping constant $a$ to this derivative. This effectively attenuates higher-frequency components, as their energy dissipates faster than lower-frequency components. Given an input displacement $x_n$, a delayed resonant displacement $x_{\text{resonance}}$, and the total previous displacement $x_{\text{prev}}$, the system state is updated as:
+
+$$x = a (x_{\text{prev}} - (x_n + x_{\text{resonance}}))^2$$
+
+#### 2. Physical Validity (D’Alembert’s Solution)
+
+The simplification of representing reflection as a pure time delay with a coefficient is mathematically rooted in the 1D wave equation:
+
+$$\frac{\partial^2 p}{\partial t^2} - c^2 \frac{\partial^2 p}{\partial x^2} = 0$$
+
+According to **D’Alembert’s solution**, any wave $p(x, t)$ can be decomposed into forward-traveling ($f$) and backward-traveling ($g$) waves:
+
+
+$$p(x, t) = f(t - x/c) + g(t + x/c)$$
+
+At the boundary $x=L$, we apply the following conditions:
+
+* **Open End:** Pressure must be zero ($p=0$), leading to $g(t + L/c) = -f(t - L/c)$. The wave reflects with a phase inversion (coefficient $-1$).
+* **Closed End:** Velocity must be zero ($\partial p/\partial x = 0$), leading to $g(t + L/c) = f(t - L/c)$. The wave reflects with its phase preserved (coefficient $+1$).
+
+Consequently, calculating the resonance by multiplying the previously delayed displacement by a reflection coefficient is analytically equivalent to solving the wave equation for linear media.
+
+#### 3. Defining the Delay Time
+
+Given a note frequency $f$ and the speed of sound $c$, the wavelength $\lambda$ is defined as $\lambda = c/f$.
+
+* **Open Pipe:** $\lambda = 2L \implies \text{round-trip time} = 2L/c = 1/f$.
+* **Closed Pipe:** $\lambda = 4L \implies \text{round-trip time} = 4L/c = 2/f$.
+
+Thus, the required delay samples can be derived directly from the frequency $f$ and sample rate $fs$ without needing explicit values for tube length $L$ or sound speed $c$.
+
+*Note: While applying a low-pass filter to the output would achieve a similar spectral result, this implementation utilizes an explicit wave-propagation model to maintain physical rigor and simulate the dynamic behavior of the air column.*
+
+</details>
+
+The core simulation is based on a displacement-driven delay-line model, where the system state at time $n$ is determined by the input $x_n$ and the resonant wave $x_{\text{resonance}}$ returning from the pipe's boundary.
+
+1. System Update Equation
+
+The total displacement $x[n]$ is calculated as a damped non-linear function of the input and the delayed resonant state. Given a damping constant $a$ ($0 < a \le 1$):
+
+$$x[n] = a \cdot \left( x[n-1] - (x_{\text{in}}[n] + x_{\text{resonance}}[n]) \right)^2$$
+
+Where $x[n-1]$ represents the previous total displacement, capturing the system's memory.
+
+2. Resonant Feedback (Delay and Reflection)
+
+The resonant component $x_{\text{resonance}}$ is the delayed state derived from the pipe's boundary conditions. Given a delay buffer $D$ of length $T$ (where $T = f_s / f$), the resonance is defined by the reflection coefficient $R$:
+
+$$x_{\text{resonance}}[n] = R \cdot \text{buffer}[n - T]$$
+
+* **For Open Pipes (Open-Open):**
+* Reflection occurs twice per round-trip with a phase inversion, resulting in $R = 1$ (net phase preserved).
+
+
+* **For Closed Pipes (Closed-Open):**
+* Reflection occurs once with phase inversion and once with phase preservation, resulting in $R = -1$ (net phase inversion per round-trip).
+
+
+
+3. Signal Flow Summary
+
+To maintain a stable simulation without algebraic loops, the signal flow follows this recursive update per sample:
+
+1. **Retrieve:** $x_{\text{res}} = R \cdot \text{delay\_buffer}[\text{ptr}]$
+2. **Compute:** $x_{\text{curr}} = a \cdot (x_{\text{prev}} - (x_{\text{in}} + x_{\text{res}}))^2$
+3. **Update:** $\text{delay\_buffer}[\text{ptr}] = x_{\text{curr}}$
+4. **Advance:** $\text{ptr} = (\text{ptr} + 1) \pmod T$
+
+This approach effectively emulates the harmonic series and spectral decay of real instruments by utilizing the time-domain round-trip of the displacement wave as the primary oscillator, while the non-linear term $( \dots )^2$ provides the necessary harmonic distortion and energy dissipation.
diff --git a/src/lib.rs b/src/lib.rs
@@ -22,8 +22,8 @@ pub struct Aerothesis {
pub v_fluid_prev: f32,
- pub x_history: VecDeque<f32>,
-
+ pub displacement_history: VecDeque<f32>,
+ pub displacement_prev: f32,
pub note_frequency: f32,
}
@@ -104,8 +104,8 @@ impl Default for Aerothesis {
v_bite: 0.0,
v_fluid_prev: 0.0,
- x_history: VecDeque::new(),
-
+ displacement_history: VecDeque::new(),
+ displacement_prev: 0.0,
note_frequency: 0.0,
}
}
@@ -223,7 +223,7 @@ impl Default for AerothesisParams {
resonance_type: EnumParam::new("Resonance Type", ResonanceType::OpenPipe),
resonance_decay: FloatParam::new(
"Resonance Decay",
- 0.9,
+ 0.01,
FloatRange::Skewed {
min: 0.0,
max: 1.0,
@@ -335,20 +335,26 @@ impl Aerothesis {
let x_n = self.step();
let x_oscillator = x_n - self.equilibrium_offset();
- let resonance = if self.resonance_delay_samples() > self.x_history.len() as f32 {
+ let resonance = if self.resonance_delay_samples() > self.displacement_history.len() as f32 {
0.0
} else {
let decay: f32 = if self.params.resonance_type.value() == ResonanceType::OpenPipe {
1.0
} else {
-1.0
- } * self.params.resonance_decay.value();
- let x_delay = self.x_history.pop_front().unwrap_or(0.0);
+ };
+ let x_delay = self.displacement_history.pop_front().unwrap_or(0.0);
decay * x_delay
};
- let x_current = x_oscillator + resonance;
- self.x_history.push_back(x_current);
+ let x_nondamping = x_oscillator + resonance;
+
+ let x_current = x_nondamping
+ * (1.0 - self.params.resonance_decay.value())
+ * (self.displacement_prev - x_nondamping)
+ * (self.displacement_prev - x_nondamping);
+
+ self.displacement_history.push_back(x_current);
x_current
}
@@ -370,7 +376,7 @@ impl Aerothesis {
}
}
fn avg_x_history(&self) -> f32 {
- self.x_history.iter().sum::<f32>() / self.x_history.len() as f32
+ self.displacement_history.iter().sum::<f32>() / self.displacement_history.len() as f32
}
}
@@ -428,7 +434,8 @@ impl Plugin for Aerothesis {
fn reset(&mut self) {
// Reset buffers and envelopes here. This can be called from the audio thread and may not
// allocate. You can remove this function if you do not need it.
- self.x_history.clear();
+
+ // self.displacement_history.clear();
}
fn process(