mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-09 21:26:43 +01:00
wip: refactor into fewer crates
This commit is contained in:
parent
c367a0444e
commit
77703d83a5
105 changed files with 64 additions and 131 deletions
59
deps/vst/src/util/atomic_float.rs
vendored
Normal file
59
deps/vst/src/util/atomic_float.rs
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
/// Simple atomic floating point variable with relaxed ordering.
|
||||
///
|
||||
/// Designed for the common case of sharing VST parameters between
|
||||
/// multiple threads when no synchronization or change notification
|
||||
/// is needed.
|
||||
pub struct AtomicFloat {
|
||||
atomic: AtomicU32,
|
||||
}
|
||||
|
||||
impl AtomicFloat {
|
||||
/// New atomic float with initial value `value`.
|
||||
pub fn new(value: f32) -> AtomicFloat {
|
||||
AtomicFloat {
|
||||
atomic: AtomicU32::new(value.to_bits()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current value of the atomic float.
|
||||
pub fn get(&self) -> f32 {
|
||||
f32::from_bits(self.atomic.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Set the value of the atomic float to `value`.
|
||||
pub fn set(&self, value: f32) {
|
||||
self.atomic.store(value.to_bits(), Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AtomicFloat {
|
||||
fn default() -> Self {
|
||||
AtomicFloat::new(0.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AtomicFloat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(&self.get(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AtomicFloat {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.get(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for AtomicFloat {
|
||||
fn from(value: f32) -> Self {
|
||||
AtomicFloat::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AtomicFloat> for f32 {
|
||||
fn from(value: AtomicFloat) -> Self {
|
||||
value.get()
|
||||
}
|
||||
}
|
||||
7
deps/vst/src/util/mod.rs
vendored
Normal file
7
deps/vst/src/util/mod.rs
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//! Structures for easing the implementation of VST plugins.
|
||||
|
||||
mod atomic_float;
|
||||
mod parameter_transfer;
|
||||
|
||||
pub use self::atomic_float::AtomicFloat;
|
||||
pub use self::parameter_transfer::{ParameterTransfer, ParameterTransferIterator};
|
||||
187
deps/vst/src/util/parameter_transfer.rs
vendored
Normal file
187
deps/vst/src/util/parameter_transfer.rs
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use std::mem::size_of;
|
||||
use std::sync::atomic::{AtomicU32, AtomicUsize, Ordering};
|
||||
|
||||
const USIZE_BITS: usize = size_of::<usize>() * 8;
|
||||
|
||||
fn word_and_bit(index: usize) -> (usize, usize) {
|
||||
(index / USIZE_BITS, 1usize << (index & (USIZE_BITS - 1)))
|
||||
}
|
||||
|
||||
/// A set of parameters that can be shared between threads.
|
||||
///
|
||||
/// Supports efficient iteration over parameters that changed since last iteration.
|
||||
#[derive(Default)]
|
||||
pub struct ParameterTransfer {
|
||||
values: Vec<AtomicU32>,
|
||||
changed: Vec<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl ParameterTransfer {
|
||||
/// Create a new parameter set with `parameter_count` parameters.
|
||||
pub fn new(parameter_count: usize) -> Self {
|
||||
let bit_words = (parameter_count + USIZE_BITS - 1) / USIZE_BITS;
|
||||
ParameterTransfer {
|
||||
values: (0..parameter_count).map(|_| AtomicU32::new(0)).collect(),
|
||||
changed: (0..bit_words).map(|_| AtomicUsize::new(0)).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the value of the parameter with index `index` to `value` and mark
|
||||
/// it as changed.
|
||||
pub fn set_parameter(&self, index: usize, value: f32) {
|
||||
let (word, bit) = word_and_bit(index);
|
||||
self.values[index].store(value.to_bits(), Ordering::Relaxed);
|
||||
self.changed[word].fetch_or(bit, Ordering::AcqRel);
|
||||
}
|
||||
|
||||
/// Get the current value of the parameter with index `index`.
|
||||
pub fn get_parameter(&self, index: usize) -> f32 {
|
||||
f32::from_bits(self.values[index].load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Iterate over all parameters marked as changed. If `acquire` is `true`,
|
||||
/// mark all returned parameters as no longer changed.
|
||||
///
|
||||
/// The iterator returns a pair of `(index, value)` for each changed parameter.
|
||||
///
|
||||
/// When parameters have been changed on the current thread, the iterator is
|
||||
/// precise: it reports all changed parameters with the values they were last
|
||||
/// changed to.
|
||||
///
|
||||
/// When parameters are changed on a different thread, the iterator is
|
||||
/// conservative, in the sense that it is guaranteed to report changed
|
||||
/// parameters eventually, but if a parameter is changed multiple times in
|
||||
/// a short period of time, it may skip some of the changes (but never the
|
||||
/// last) and may report an extra, spurious change at the end.
|
||||
///
|
||||
/// The changed parameters are reported in increasing index order, and the same
|
||||
/// parameter is never reported more than once in the same iteration.
|
||||
pub fn iterate(&self, acquire: bool) -> ParameterTransferIterator {
|
||||
ParameterTransferIterator {
|
||||
pt: self,
|
||||
word: 0,
|
||||
bit: 1,
|
||||
acquire,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over changed parameters.
|
||||
/// Returned by [`iterate`](struct.ParameterTransfer.html#method.iterate).
|
||||
pub struct ParameterTransferIterator<'pt> {
|
||||
pt: &'pt ParameterTransfer,
|
||||
word: usize,
|
||||
bit: usize,
|
||||
acquire: bool,
|
||||
}
|
||||
|
||||
impl<'pt> Iterator for ParameterTransferIterator<'pt> {
|
||||
type Item = (usize, f32);
|
||||
|
||||
fn next(&mut self) -> Option<(usize, f32)> {
|
||||
let bits = loop {
|
||||
if self.word == self.pt.changed.len() {
|
||||
return None;
|
||||
}
|
||||
let bits = self.pt.changed[self.word].load(Ordering::Acquire) & self.bit.wrapping_neg();
|
||||
if bits != 0 {
|
||||
break bits;
|
||||
}
|
||||
self.word += 1;
|
||||
self.bit = 1;
|
||||
};
|
||||
|
||||
let bit_index = bits.trailing_zeros() as usize;
|
||||
let bit = 1usize << bit_index;
|
||||
let index = self.word * USIZE_BITS + bit_index;
|
||||
|
||||
if self.acquire {
|
||||
self.pt.changed[self.word].fetch_and(!bit, Ordering::AcqRel);
|
||||
}
|
||||
|
||||
let next_bit = bit << 1;
|
||||
if next_bit == 0 {
|
||||
self.word += 1;
|
||||
self.bit = 1;
|
||||
} else {
|
||||
self.bit = next_bit;
|
||||
}
|
||||
|
||||
Some((index, self.pt.get_parameter(index)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
extern crate rand;
|
||||
|
||||
use crate::util::ParameterTransfer;
|
||||
|
||||
use std::sync::mpsc::channel;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use self::rand::rngs::StdRng;
|
||||
use self::rand::{Rng, SeedableRng};
|
||||
|
||||
const THREADS: usize = 3;
|
||||
const PARAMETERS: usize = 1000;
|
||||
const UPDATES: usize = 1_000_000;
|
||||
|
||||
#[test]
|
||||
fn parameter_transfer() {
|
||||
let transfer = Arc::new(ParameterTransfer::new(PARAMETERS));
|
||||
let (tx, rx) = channel();
|
||||
|
||||
// Launch threads that change parameters
|
||||
for t in 0..THREADS {
|
||||
let t_transfer = Arc::clone(&transfer);
|
||||
let t_tx = tx.clone();
|
||||
let mut t_rng = StdRng::seed_from_u64(t as u64);
|
||||
thread::spawn(move || {
|
||||
let mut values = vec![0f32; PARAMETERS];
|
||||
for _ in 0..UPDATES {
|
||||
let p: usize = t_rng.gen_range(0..PARAMETERS);
|
||||
let v: f32 = t_rng.gen_range(0.0..1.0);
|
||||
values[p] = v;
|
||||
t_transfer.set_parameter(p, v);
|
||||
}
|
||||
t_tx.send(values).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
// Continually receive updates from threads
|
||||
let mut values = vec![0f32; PARAMETERS];
|
||||
let mut results = vec![];
|
||||
let mut acquire_rng = StdRng::seed_from_u64(42);
|
||||
while results.len() < THREADS {
|
||||
let mut last_p = -1;
|
||||
for (p, v) in transfer.iterate(acquire_rng.gen_bool(0.9)) {
|
||||
assert!(p as isize > last_p);
|
||||
last_p = p as isize;
|
||||
values[p] = v;
|
||||
}
|
||||
thread::sleep(Duration::from_micros(100));
|
||||
while let Ok(result) = rx.try_recv() {
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
|
||||
// One last iteration to pick up all updates
|
||||
let mut last_p = -1;
|
||||
for (p, v) in transfer.iterate(true) {
|
||||
assert!(p as isize > last_p);
|
||||
last_p = p as isize;
|
||||
values[p] = v;
|
||||
}
|
||||
|
||||
// Now there should be no more updates
|
||||
assert!(transfer.iterate(true).next().is_none());
|
||||
|
||||
// Verify final values
|
||||
for p in 0..PARAMETERS {
|
||||
assert!((0..THREADS).any(|t| results[t][p] == values[p]));
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue