commit eb3ae5aded2c3dc3c126fd4c0bb3d5ddce90c9de
parent fb561e985a9da9517946ce8531eb8d9db9e6f308
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Wed, 1 Jul 2026 20:49:28 +0900
feat: refactor oscillation physics and internal state management
- Introduced explicit state fields for velocity, acceleration, and resonance tracking in the Aerothesis struct.
- Refactored core simulation methods, renaming `step` to `osc` and `x` to `osc_x` for clarity.
- Updated pressure calculation logic to integrate resonance and feedback gain.
- Adjusted parameter ranges and default values for feedback gain.
- Added a binary target to Cargo.toml and updated analysis logic in main.rs to support frequency spectrum reporting.
Diffstat:
3 files changed, 56 insertions(+), 28 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -13,6 +13,10 @@ members = ["xtask"]
[lib]
crate-type = ["cdylib", "rlib"]
+[[bin]]
+name = "aerothesis-bin"
+path = "src/main.rs"
+
[dependencies]
# Remove the `assert_process_allocs` feature to allow allocations on the audio
# thread in debug builds.
diff --git a/src/lib.rs b/src/lib.rs
@@ -27,6 +27,11 @@ pub struct Aerothesis {
pub note_frequency: f32,
pub displacement_prev: f32,
+ pub velocity_prev: f32,
+ pub displacement_prev2: f32,
+ pub accel_prev: f32,
+ pub f: f32,
+ pub resonance: f32,
}
#[derive(Enum, PartialEq, Clone, Copy)]
@@ -111,6 +116,11 @@ impl Default for Aerothesis {
note_frequency: 0.0,
displacement_prev: 0.0,
+ velocity_prev: 0.0,
+ displacement_prev2: 0.0,
+ accel_prev: 0.0,
+ f: 0.0,
+ resonance: 0.0,
}
}
}
@@ -217,8 +227,11 @@ impl Default for AerothesisParams {
),
feedback_gain: FloatParam::new(
"Feedback Gain",
- 0.1,
- FloatRange::Linear { min: 0.0, max: 1.0 },
+ 5.0,
+ FloatRange::Linear {
+ min: 0.0,
+ max: 10.0,
+ },
),
breath_cc: IntParam::new("Breath CC", 2, IntRange::Linear { min: 0, max: 127 }),
@@ -239,15 +252,15 @@ impl Default for AerothesisParams {
}
impl Aerothesis {
- pub fn step(&mut self) -> f32 {
- let x_n = self.x();
+ pub fn osc(&mut self) -> f32 {
+ let x_n = self.osc_x();
let v_n = self.v(x_n);
self.x_prev2 = self.x_prev;
self.x_prev = x_n;
self.f_prev2 = self.f_prev;
- self.f_prev = self.f();
+ self.f_prev = self.f;
self.v_prev = v_n;
self.v_fluid_prev = self.vf();
@@ -277,7 +290,9 @@ impl Aerothesis {
let gap_prev = (2.0 - self.x_prev).clamp(EPS, 2.0);
let b_prev = RHO / (4.0 * (gap_prev * gap_prev));
- let c_prev = self.v_breath - b_prev * (self.v_fluid_prev * self.v_fluid_prev);
+ let pressure = self.params.base_damping.value()
+ * (self.v_breath + self.resonance * 100.0 * self.params.feedback_gain.value());
+ let c_prev = pressure - b_prev * (self.v_fluid_prev * self.v_fluid_prev);
// Current gap is also based on x_prev in this discrete model for stability
let gap_curr = (2.0 - self.x_prev).clamp(EPS, 2.0);
@@ -306,14 +321,15 @@ impl Aerothesis {
} else {
0.5 * RHO * (v_fluid_current * v_fluid_current) * gap_curr
};
+
f_current
}
- pub fn x(&self) -> f32 {
+ pub fn osc_x(&mut self) -> f32 {
let m = self.m();
let r = self.r();
let k = self.k();
let t = 1.0 / self.sample_rate;
- let f_current = self.f();
+ let f_current = self.f;
let b0 = t * t;
let b1 = 2.0 * t * t;
@@ -352,28 +368,29 @@ impl Aerothesis {
decay * x_delay
}
}
-
pub fn displacement(&mut self) -> f32 {
- let x_n = self.step();
- let x_oscillator = x_n - self.equilibrium_offset();
-
- let resonance = self.resonance();
-
- let x_current = x_oscillator + resonance;
+ self.resonance = self.resonance();
+ self.f = self.f();
+ let x_n = self.osc();
+ let x_current = x_n - self.equilibrium_offset();
+
+ // let displacement = x_current
+ // * (self.params.resonance_decay.value()
+ // * (x_current - self.displacement_prev)
+ // * (x_current - self.displacement_prev))
+ // .clamp(0.0, 1.0);
+ let displacement = x_current;
- let displacement = x_current
- * (self.params.resonance_decay.value()
- * (x_current - self.displacement_prev)
- * (x_current - self.displacement_prev))
- .clamp(0.0, 1.0);
self.displacement_prev = displacement;
self.x_history.push_front(displacement);
- x_oscillator
+ // displacement + self.resonance
+ displacement
+ // self.resonance
}
fn equilibrium_offset(&self) -> f32 {
- let f = self.f();
+ let f = self.f;
let k = self.k();
if k > 0.0 {
(f / k).clamp(0.0, 1.8)
@@ -465,7 +482,7 @@ impl Plugin for Aerothesis {
value,
} => {
if cc as i32 == self.params.breath_cc.value() {
- self.v_breath = value * self.params.pressure_scale.value();
+ self.v_breath = value;
} else if cc as i32 == self.params.bite_cc.value() {
self.v_bite = value;
}
@@ -486,7 +503,7 @@ impl Plugin for Aerothesis {
for channel_samples in buffer.iter_samples() {
let gain = self.params.gain.smoothed.next();
- let x_current = self.displacement() - self.avg_x_history();
+ let x_current = self.displacement();
for sample in channel_samples {
if self.note_frequency == 0.0 {
diff --git a/src/main.rs b/src/main.rs
@@ -10,8 +10,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let num_samples = (sample_rate * seconds) as usize;
plugin.sample_rate = sample_rate;
- // plugin.note_frequency = util::midi_note_to_freq(48); // C3 (u8)
- plugin.note_frequency = util::midi_note_to_freq(54); // C4 (u8)
+ plugin.note_frequency = util::midi_note_to_freq(60); // C4 (u8)
+ // plugin.note_frequency = util::midi_note_to_freq(54); // F#3 (u8)
// For simulation in main.rs, we use the default parameters from AerothesisParams::default()
// because nih-plug parameters are designed to be managed by a host and don't have
@@ -19,7 +19,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// Default resonance: OpenPipe, Decay: 0.9
let mut displacements = Vec::with_capacity(num_samples);
- let mut resonances = Vec::with_capacity(num_samples);
+ let mut osc = Vec::with_capacity(num_samples);
for i in 0..num_samples {
// let x_current = self.resonance() - self.avg_x_history();
@@ -36,8 +36,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
};
let sample = plugin.displacement() - plugin.avg_x_history();
+ osc.push(plugin.osc_x());
displacements.push(sample);
- resonances.push(plugin.resonance());
}
let len = (sample_rate * 0.05) as usize;
@@ -71,6 +71,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
spectrum_data.push((freq, complex.norm()));
}
+ if let Some(max) = spectrum_data
+ .iter()
+ .enumerate()
+ .max_by(|(_, a), (_, b)| a.1.partial_cmp(&b.1).unwrap())
+ {
+ println!("max freq is {} Hz", max.0);
+ }
println!("\n--- Spectrum (0 - 2000Hz) ---");
Chart::new(180, 60, 0.0, 2000.0)