commit 0600c269b7ffc9fcf4198d62a049e135bd6ee2f2
parent dcffd3b14c4cb6bdc3b9207a3233c47aa3e42d49
Author: minerva-jupiter <ryouturn@gmail.com>
Date: Sat, 13 Jun 2026 18:06:43 +0900
feat: implement physical oscillation model and MIDI CC control
- Added parameters for base mass, stiffness, damping, pressure, and feedback.
- Updated Aerothesis struct to store oscillation state and MIDI CC mappings.
- Enabled Basic MIDI input and implemented event processing for breath and bite CC values.
- Updated documentation with the physical oscillation formula.
Diffstat:
| M | README.md | | | 11 | +++++++++-- |
| M | src/lib.rs | | | 135 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
2 files changed, 142 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
@@ -84,6 +84,13 @@ $$v[n] = \frac{2}{T}(x[n] - x[n-1]) - v[n-1]$$
</details>
-##### Formula for Ocillation
+##### Formula for Oscillation
-From above,
+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]$$$$
diff --git a/src/lib.rs b/src/lib.rs
@@ -7,6 +7,18 @@ use std::sync::Arc;
struct Aerothesis {
params: Arc<AerothesisParams>,
+
+ x_prev: f32,
+ v_prev: f32,
+
+ m_prev: f32,
+ r_prev: f32,
+ k_prev: f32,
+ f_prev: f32,
+ sample_rate: f32,
+
+ v_breath: f32,
+ v_bite: f32,
}
#[derive(Params)]
@@ -17,12 +29,46 @@ struct AerothesisParams {
/// gain parameter is stored as linear gain while the values are displayed in decibels.
#[id = "gain"]
pub gain: FloatParam,
+ #[id = "base_mass"]
+ pub base_mass: FloatParam,
+ #[id = "bite_mass_scale"]
+ pub bite_mass_scale: FloatParam,
+ #[id = "base_stiffness"]
+ pub base_stiffness: FloatParam,
+ #[id = "bite_stiffness_scale"]
+ pub bite_stiffness_scale: FloatParam,
+ #[id = "base_damping"]
+ pub base_damping: FloatParam,
+ #[id = "bite_damping_scale"]
+ pub bite_damping_scale: FloatParam,
+ #[id = "breath_damping"]
+ pub breath_damping: FloatParam,
+ #[id = "pressure_scale"]
+ pub pressure_scale: FloatParam,
+ #[id = "feedback_gain"]
+ pub feedback_gain: FloatParam,
+
+ #[id = "breath_cc"]
+ pub breath_cc: IntParam,
+ #[id = "bite_cc"]
+ pub bite_cc: IntParam,
}
impl Default for Aerothesis {
fn default() -> Self {
Self {
params: Arc::new(AerothesisParams::default()),
+ x_prev: 0.0,
+ v_prev: 0.0,
+
+ m_prev: 1.0,
+ r_prev: 1.0,
+ k_prev: 1.0,
+ f_prev: 0.0,
+ sample_rate: 44100.0,
+
+ v_breath: 0.0,
+ v_bite: 0.0,
}
}
}
@@ -53,6 +99,78 @@ impl Default for AerothesisParams {
// `.with_step_size(0.1)` function to get internal rounding.
.with_value_to_string(formatters::v2s_f32_gain_to_db(2))
.with_string_to_value(formatters::s2v_f32_gain_to_db()),
+
+ base_mass: FloatParam::new(
+ "Base Mass",
+ 1.0,
+ FloatRange::Skewed {
+ min: 0.01,
+ max: 10.0,
+ factor: 0.2,
+ },
+ )
+ .with_unit(" mg"),
+ bite_mass_scale: FloatParam::new(
+ "Bite Mass Reduction",
+ 0.5,
+ FloatRange::Linear { min: 0.0, max: 0.9 },
+ ),
+ base_stiffness: FloatParam::new(
+ "Base Stiffness",
+ 1000.0,
+ FloatRange::Skewed {
+ min: 100.0,
+ max: 100000.0,
+ factor: 0.3,
+ },
+ )
+ .with_unit(" N/m"),
+ bite_stiffness_scale: FloatParam::new(
+ "Bite Stiffness Incr.",
+ 1.0,
+ FloatRange::Linear {
+ min: 0.0,
+ max: 10.0,
+ },
+ ),
+ base_damping: FloatParam::new(
+ "Base Damping",
+ 0.1,
+ FloatRange::Skewed {
+ min: 0.001,
+ max: 1.0,
+ factor: 0.2,
+ },
+ ),
+ bite_damping_scale: FloatParam::new(
+ "Bite Damping Incr.",
+ 1.0,
+ FloatRange::Linear {
+ min: 0.0,
+ max: 10.0,
+ },
+ ),
+ breath_damping: FloatParam::new(
+ "Breath Damping",
+ 0.1,
+ FloatRange::Linear { min: 0.0, max: 1.0 },
+ ),
+ pressure_scale: FloatParam::new(
+ "Pressure Gain",
+ 10.0,
+ FloatRange::Linear {
+ min: 0.0,
+ max: 100.0,
+ },
+ ),
+ feedback_gain: FloatParam::new(
+ "Feedback Gain",
+ 0.1,
+ FloatRange::Linear { min: 0.0, max: 1.0 },
+ ),
+
+ breath_cc: IntParam::new("Breath CC", 2, IntRange::Linear { min: 0, max: 127 }),
+ bite_cc: IntParam::new("Bite CC", 11, IntRange::Linear { min: 0, max: 127 }),
}
}
}
@@ -80,7 +198,7 @@ impl Plugin for Aerothesis {
names: PortNames::const_default(),
}];
- const MIDI_INPUT: MidiConfig = MidiConfig::None;
+ const MIDI_INPUT: MidiConfig = MidiConfig::Basic;
const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
@@ -119,8 +237,21 @@ impl Plugin for Aerothesis {
&mut self,
buffer: &mut Buffer,
_aux: &mut AuxiliaryBuffers,
- _context: &mut impl ProcessContext<Self>,
+ 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;
+ }
+ }
+ _ => (),
+ }
+ }
+
for channel_samples in buffer.iter_samples() {
// Smoothing is optionally built into the parameters themselves
let gain = self.params.gain.smoothed.next();