mirror of
https://github.com/storytold/soundcraft.git
synced 2026-10-14 14:26:46 -04:00
Merge #306's latest into the landing branch
This commit is contained in:
commit
7a6df4cbbd
4 files changed
+66
-2
No files matched your search
@@ -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);
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in new issue
Block a user