Merge #306's latest into the landing branch

This commit is contained in:
Brandon Thomas committed 2026-10-13 06:25:56 -04:00
commit 7a6df4cbbd
4 files changed
+66 -2

No files matched your search

+3 -2
View File
@@ -2722,10 +2722,11 @@ mod tests {
let mut eng = MixEngine::new(48_000.0, 512);
eng.set_external_instances(true);
let sets = Arc::new(std::sync::atomic::AtomicUsize::new(0));
let make = |blob: &[u8]| {
let make = |db: f32| {
let mut gain = soundcraft_dsp::create("gain").unwrap();
gain.prepare(48_000.0, 512, 1);
Box::new(Stateful { inner: gain, blob: blob.to_vec(), sets: Arc::clone(&sets) })
gain.set_param("gain", db);
Box::new(Stateful { inner: gain, sets: Arc::clone(&sets) })
};
eng.adopt(vec![PreparedInstance { track: t, slot: 0, id: "clap:test.fake".into(), ch: 1, plugin: make(b"old") }]);
render_block(&mut eng, &s, 0);
+56
View File
@@ -1587,6 +1587,48 @@ fn rect_size(r: &sb::ViewRect) -> (f64, f64) {
(f64::from(w), f64::from(h))
}
/// Normalized values the host set (automation, the session) that the edit controller hasn't been
/// told yet, one slot per parameter. The audio thread fills a slot without locking or allocating;
/// [`EditorLink::save_state`] sends what is waiting before it reads the controller's state, so
/// values the controller keeps in its own state are saved as set.
pub(crate) struct ControllerPending {
ids: Vec<u32>,
slots: Vec<AtomicU64>,
}
/// An empty slot: a NaN bit pattern, which `set` never stores.
const NO_VALUE: u64 = u64::MAX;
impl ControllerPending {
pub fn new(ids: &[u32]) -> ControllerPending {
ControllerPending { ids: ids.to_vec(), slots: ids.iter().map(|_| AtomicU64::new(NO_VALUE)).collect() }
}
/// Notes the value of parameter `i` (index into the plugin's parameter list).
pub fn set(&self, i: usize, n: f64) {
if let Some(s) = self.slots.get(i)
&& n.is_finite()
{
s.store(n.clamp(0.0, 1.0).to_bits(), Ordering::Release);
}
}
/// Forgets every waiting value (a loaded state replaces them).
pub fn clear(&self) {
for s in &self.slots {
s.store(NO_VALUE, Ordering::Release);
}
}
/// Takes the waiting values as `(ParamID, normalized)`.
fn take(&self) -> impl Iterator<Item = (u32, f64)> + '_ {
self.ids.iter().zip(&self.slots).filter_map(|(&id, s)| {
let b = s.swap(NO_VALUE, Ordering::AcqRel);
(b != NO_VALUE).then(|| (id, f64::from_bits(b)))
})
}
}
#[derive(Default)]
struct EditorInner {
/// `None` once the instance is gone.
@@ -1596,6 +1638,8 @@ struct EditorInner {
view: Option<ComPtr<sb::IPlugView>>,
frame: Option<ComWrapper<PlugFrame>>,
window: Option<window::HostWindow>,
/// Host-set values for the controller, sent before its state is read.
pending: Option<Arc<ControllerPending>>,
}
/// The link between an instance and its editor and state handles. The instance may process on
@@ -1723,6 +1767,13 @@ impl EditorLink {
pub fn save_state(&self) -> Option<Vec<u8>> {
let g = self.lock();
let component = g.component.as_ref()?;
if let (Some(c), Some(p)) = (&g.controller, &g.pending) {
for (id, n) in p.take() {
// SAFETY: valid controller (cleared under this lock before the instance
// terminates); UI thread, as `getState` below.
unsafe { c.setParamNormalized(id, n) };
}
}
// SAFETY: valid component (the instance clears it under this lock before it terminates);
// the stream lives in `read_state` for the whole call.
let comp = Instance::read_state(|s| unsafe { component.getState(s) })?;
@@ -1740,6 +1791,11 @@ impl EditorLink {
Some(out)
}
/// Shares the instance's table of host-set values with the state reader.
pub fn set_controller_pending(&self, p: Arc<ControllerPending>) {
self.lock().pending = Some(p);
}
/// Closes the editor. Main thread only (no-op elsewhere).
pub fn close(&self) {
if window::on_main_thread() {
+2
View File
@@ -114,6 +114,7 @@ impl Vst3Plugin {
})
.collect();
p.values = values;
p.inst.state_link().set_controller_pending(Arc::clone(&p.controller_pending));
p
}
@@ -234,6 +235,7 @@ impl Plugin for Vst3Plugin {
} else if self.pending.len() < self.pending.capacity() {
self.pending.push((vid, n));
}
self.controller_pending.set(i, n);
true
}
+5
View File
@@ -265,6 +265,11 @@ fn host_set_controller_param_survives_state_round_trip() {
assert!(q.load_state(&blob));
assert!((q.param("7").unwrap() - 1.5).abs() < 1e-4, "{:?}", q.param("7"));
assert!((q.param("12").unwrap() - 200.0).abs() < 0.01, "{:?}", q.param("12"));
assert!(q.set_param("12", 300.0));
assert!(q.load_state(&blob));
let mut r = soundcraft_vst3_host::create(&id).unwrap();
assert!(r.load_state(&q.save_state().unwrap()));
assert!((r.param("12").unwrap() - 200.0).abs() < 0.01, "a loaded state replaces earlier edits: {:?}", r.param("12"));
}
#[test]