mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-01 06:26:51 -06:00
Mix: clamp clip-FX gain and send-level automation (fixes #884)
A shared session or an MCP clip.effects_set could store clip-FX gain or a send-level automation point far above any fader (up to the +200 dB cap in db_to_gain). Clamp clip-FX gain to the slider's +24 dB, send-level lanes to +12 dB, and every loaded automation point to its AutoParam range on sanitize. Co-Authored-By: opencode beam-free <noreply@opencode.ai> Co-Authored-By: MiniMax M3 <noreply@minimax.io> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
opencode beam-free
MiniMax M3
Claude Opus 5.5
parent
7dc56a4dbe
commit
afa1532aba
@@ -196,8 +196,14 @@ fn unloop(e: &mut Engine, p: &Value) -> Result<Value> {
|
||||
fn effects_set(e: &mut Engine, p: &Value) -> Result<Value> {
|
||||
let ids = clip_ids_param(e, p);
|
||||
let params = p.get("params").and_then(Value::as_object).ok_or_else(|| bad("clip.effects_set", "`params` must be an object of numbers"))?;
|
||||
let vals: Vec<(String, f64)> =
|
||||
params.iter().filter_map(|(k, v)| v.as_f64().filter(|x| x.is_finite()).map(|x| (k.chars().take(64).collect(), x))).collect();
|
||||
let vals: Vec<(String, f64)> = params
|
||||
.iter()
|
||||
.filter_map(|(k, v)| v.as_f64().filter(|x| x.is_finite()).map(|x| (k.chars().take(64).collect(), x)))
|
||||
.map(|(k, v)| {
|
||||
let v = if k == "gain" { v.clamp(f64::from(soundcraft_dsp::MIN_DB), f64::from(soundcraft_mix::CLIP_FX_MAX_GAIN_DB)) } else { v };
|
||||
(k, v)
|
||||
})
|
||||
.collect();
|
||||
let s = e.session_mut();
|
||||
for c in &ids {
|
||||
for (k, v) in &vals {
|
||||
@@ -316,6 +322,13 @@ mod tests {
|
||||
(Engine::new(s), t, ids)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effects_set_caps_gain() {
|
||||
let (mut e, _t, ids) = engine_with(&[(0, 100, 0)]);
|
||||
e.execute("clip.effects_set", &json!({"clip": ids[0].0, "params": {"gain": 500.0}})).unwrap();
|
||||
assert_eq!(e.session().edit.values.get(&format!("clip_fx.{}.gain", ids[0].0)).copied(), Some(24.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unloop_removes_copies() {
|
||||
let (mut e, t, ids) = engine_with(&[(0, 100, 0), (100, 100, 0), (200, 100, 0), (300, 100, 50)]);
|
||||
|
||||
@@ -76,6 +76,11 @@ pub fn main_channels(s: &Session) -> usize {
|
||||
/// (insert slots use their index).
|
||||
pub const INSTRUMENT_SLOT: usize = usize::MAX;
|
||||
|
||||
/// Top of the clip effects gain slider; a stored or wired gain is clamped to it (#884).
|
||||
pub const CLIP_FX_MAX_GAIN_DB: f32 = 24.0;
|
||||
/// Top of a send fader, the same +12 dB `AutoParam::SendLevel` ranges up to (#884).
|
||||
pub const SEND_LEVEL_MAX_DB: f32 = 12.0;
|
||||
|
||||
/// Hosted third-party plugin ids (CLAP or VST3): created off the audio thread in realtime use.
|
||||
pub fn is_third_party(id: &str) -> bool {
|
||||
id.starts_with(soundcraft_clap_host::ID_PREFIX)
|
||||
@@ -1279,7 +1284,7 @@ impl ClipFx {
|
||||
c.set_param(p, v);
|
||||
}
|
||||
} else if k == "gain" {
|
||||
self.gain_db = v;
|
||||
self.gain_db = if v.is_nan() { 0.0 } else { v.clamp(soundcraft_dsp::MIN_DB, CLIP_FX_MAX_GAIN_DB) };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1967,7 +1972,7 @@ fn send_level(t: &Track, i: usize, base: f32, pos: Samples) -> f32 {
|
||||
&& let Some(l) = t.lane(&AutoParam::SendLevel(u8::try_from(i).unwrap_or(0)))
|
||||
&& !l.points.is_empty()
|
||||
{
|
||||
return l.value_at(pos, base);
|
||||
return l.value_at(pos, base).clamp(soundcraft_dsp::MIN_DB, SEND_LEVEL_MAX_DB);
|
||||
}
|
||||
base
|
||||
}
|
||||
|
||||
@@ -626,6 +626,8 @@ impl Session {
|
||||
}
|
||||
for l in &mut t.automation {
|
||||
l.points.retain(|p| p.value.is_finite());
|
||||
let (lo, hi, _) = l.param.range();
|
||||
l.points.iter_mut().for_each(|p| p.value = p.value.clamp(lo, hi));
|
||||
l.points.iter_mut().for_each(|p| bound(&mut p.at));
|
||||
l.points.sort_by_key(|p| p.at);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user