Skip to content

Commit

Permalink
Update.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Jul 14, 2024
1 parent 2109988 commit 66691b5
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Changes

### Version 0.19 (Next Version)

- Added choice of interpolation algorithm to `AtomicSynth`.

### Version 0.18.2

- 64-bit atomics were removed in order to support 32-bit targets.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -516,8 +516,9 @@ Connectivity checks are then deferred to runtime.
Networks can also be combined inline with components from the preludes.
The components are first converted to `Net`.

When you need dynamic processing, you can start it with `Net::wrap`,
which converts any unit into a network.
When we need dynamic processing, we can start it with `Net::wrap`,
which converts any unit into a network. Also, we can control when
to cross from the static realm into the dynamic by the placement of `Net::wrap`.

```rust
use fundsp::hacker::*;
Expand Down
4 changes: 2 additions & 2 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ impl<'a> BufferMut<'a> {
/// Create new buffer from a slice. The length of the slice must be divisible by 8.
#[inline]
pub fn new(buffer: &'a mut [F32x]) -> Self {
debug_assert!(buffer.len() & SIMD_M == 0);
debug_assert!(buffer.len() & (SIMD_LEN - 1) == 0);
Self(buffer)
}

Expand Down Expand Up @@ -132,7 +132,7 @@ impl<'a> BufferRef<'a> {
/// Create new buffer from a slice. The length of the slice must be divisible by 8.
#[inline]
pub fn new(buffer: &'a [F32x]) -> Self {
debug_assert!(buffer.len() & SIMD_M == 0);
debug_assert!(buffer.len() & (SIMD_LEN - 1) == 0);
Self(buffer)
}

Expand Down
33 changes: 17 additions & 16 deletions src/noise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,24 +135,24 @@ impl AudioNode for Mls {
}
}

/// 32-bit hash modified from a hash by degski. Extra high quality.
const MUL_X: u32 = 0x45d9f3b;

/// Special 32-bit hash modified from a hash by degski.
#[inline]
fn hash32f(x: u32) -> u32 {
let x = (x ^ (x >> 16)).wrapping_mul(0x45d9f3b);
let x = (x ^ (x >> 16)).wrapping_mul(0x45d9f3b);
let x = (x ^ (x >> 16)).wrapping_mul(0x45d9f3b);
x ^ (x >> 16)
fn hash32x(x: u32) -> u32 {
let x = (x ^ (x >> 16)).wrapping_mul(MUL_X);
let x = (x ^ (x >> 16)).wrapping_mul(MUL_X);
(x ^ (x >> 16)).wrapping_mul(MUL_X)
}

/// 32-bit hash modified from a hash by degski.
/// Hashes many values at once. Extra high quality.
/// Special 32-bit hash modified from a hash by degski.
/// Hashes many values at once.
#[inline]
fn hash32f_simd(x: U32x) -> U32x {
let m = U32x::splat(0x45d9f3b);
let x = (x ^ (x >> 16)) * m;
fn hash32x_simd(x: U32x) -> U32x {
let m = U32x::splat(MUL_X);
let x = (x ^ (x >> 16)) * m;
let x = (x ^ (x >> 16)) * m;
x ^ (x >> 16)
(x ^ (x >> 16)) * m
}

/// White noise component.
Expand All @@ -169,6 +169,8 @@ impl Noise {
}
}

const NOISE_Z: f32 = 2.0 / ((1 << 24) - 1) as f32;

impl AudioNode for Noise {
const ID: u64 = 20;
type Inputs = typenum::U0;
Expand All @@ -181,7 +183,7 @@ impl AudioNode for Noise {
#[inline]
fn tick(&mut self, _input: &Frame<f32, Self::Inputs>) -> Frame<f32, Self::Outputs> {
self.state = self.state.wrapping_add(1);
let value = (hash32f(self.state) >> 8) as f32 * (1.0 / (1 << 23) as f32) - 1.0;
let value = (hash32x(self.state) >> 8) as f32 * NOISE_Z - 1.0;
[value].into()
}

Expand All @@ -190,12 +192,11 @@ impl AudioNode for Noise {
self.state.wrapping_add(i as u32 + 1)
}));
let output = output.channel_f32_mut(0);
let m = 1.0 / (1 << 23) as f32;
for i in 0..simd_items(size) {
let value: U32x = hash32f_simd(state) >> 8;
let value: U32x = hash32x_simd(state) >> 8;
let value_ref = value.as_array_ref();
for j in 0..SIMD_N {
output[(i << SIMD_S) + j] = value_ref[j] as f32 * m - 1.0;
output[(i << SIMD_S) + j] = value_ref[j] as f32 * NOISE_Z - 1.0;
}
state += U32x::splat(SIMD_N as u32);
}
Expand Down
26 changes: 15 additions & 11 deletions src/realseq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use thingbuf::mpsc::{channel, Receiver, Sender};

#[derive(Default, Clone)]
pub(crate) enum Message {
/// Nothing.
/// Reset the sequencer.
#[default]
Null,
Reset,
/// Add new event in absolute time.
Push(Event),
/// Add new event in relative time.
Expand Down Expand Up @@ -62,6 +62,9 @@ impl SequencerBackend {
fn handle_messages(&mut self) {
while let Ok(message) = self.receiver.try_recv() {
match message {
Message::Reset => {
self.reset();
}
Message::Push(event) => {
self.sequencer.push_event(event);
}
Expand All @@ -75,7 +78,6 @@ impl SequencerBackend {
self.sequencer
.edit_relative(id, edit.end_time, edit.fade_out);
}
Message::Null => {}
}
}
}
Expand All @@ -99,14 +101,16 @@ impl AudioUnit for SequencerBackend {

fn reset(&mut self) {
self.handle_messages();
while let Some(event) = self.sequencer.get_past_event() {
if self.sender.try_send(Some(event)).is_ok() {}
}
while let Some(event) = self.sequencer.get_ready_event() {
if self.sender.try_send(Some(event)).is_ok() {}
}
while let Some(event) = self.sequencer.get_active_event() {
if self.sender.try_send(Some(event)).is_ok() {}
if !self.sequencer.replay_events() {
while let Some(event) = self.sequencer.get_past_event() {
if self.sender.try_send(Some(event)).is_ok() {}
}
while let Some(event) = self.sequencer.get_ready_event() {
if self.sender.try_send(Some(event)).is_ok() {}
}
while let Some(event) = self.sequencer.get_active_event() {
if self.sender.try_send(Some(event)).is_ok() {}
}
}
self.sequencer.reset();
}
Expand Down
4 changes: 2 additions & 2 deletions src/sequencer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,8 @@ impl Sequencer {
pub fn backend(&mut self) -> SequencerBackend {
assert!(!self.has_backend());
// Create huge channel buffers to make sure we don't run out of space easily.
let (sender_a, receiver_a) = channel(16384);
let (sender_b, receiver_b) = channel(16384);
let (sender_a, receiver_a) = channel(2048);
let (sender_b, receiver_b) = channel(2048);
let mut sequencer = self.clone();
sequencer.allocate();
self.front = Some((sender_a, receiver_b));
Expand Down
39 changes: 31 additions & 8 deletions src/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,14 @@ impl AudioNode for Timer {
}
}

#[derive(Clone, Default)]
pub enum Interpolation {
#[default]
Nearest,
Linear,
Cubic,
}

/// Atomic wavetable that can be modified on the fly.
pub struct AtomicTable {
table: Vec<AtomicU32>,
Expand Down Expand Up @@ -301,7 +309,7 @@ impl AtomicTable {
}
}

/// Wavetable oscillator with cubic interpolation that reads from an atomic wavetable.
/// Wavetable oscillator with nearest, linear or cubic interpolation that reads from an atomic wavetable.
#[derive(Clone)]
pub struct AtomicSynth<T: Float> {
table: Arc<AtomicTable>,
Expand All @@ -312,9 +320,11 @@ pub struct AtomicSynth<T: Float> {
sample_rate: f32,
sample_duration: f32,
_marker: core::marker::PhantomData<T>,
interpolation: Interpolation,
}

impl<T: Float> AtomicSynth<T> {
/// Create new atomic synth. Interpolation mode is set to nearest.
pub fn new(table: Arc<AtomicTable>) -> Self {
Self {
table,
Expand All @@ -323,8 +333,17 @@ impl<T: Float> AtomicSynth<T> {
sample_rate: DEFAULT_SR as f32,
sample_duration: 1.0 / DEFAULT_SR as f32,
_marker: core::marker::PhantomData,
interpolation: Interpolation::Nearest,
}
}
/// Get current interpolation mode.
pub fn interpolation(&self) -> Interpolation {
self.interpolation.clone()
}
/// Set interpolation mode.
pub fn set_interpolation(&mut self, interpolation: Interpolation) {
self.interpolation = interpolation;
}
}

impl<T: Float> AudioNode for AtomicSynth<T> {
Expand Down Expand Up @@ -352,25 +371,29 @@ impl<T: Float> AudioNode for AtomicSynth<T> {
let delta = frequency * self.sample_duration;
self.phase += delta;
self.phase -= self.phase.floor();
let output = self.table.read_nearest(self.phase);
Frame::splat(convert(output))
let output = match self.interpolation {
Interpolation::Nearest => self.table.read_nearest(self.phase),
Interpolation::Linear => self.table.read_linear(self.phase),
Interpolation::Cubic => self.table.read_cubic(self.phase),
};
[output].into()
}

fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame {
super::signal::Routing::Generator(0.0).route(input, self.outputs())
}
}

/// This thing generates unique 64-bit IDs using 32-bit atomics.
/// This lock-free thing generates unique 64-bit IDs using 32-bit atomics.
#[derive(Default)]
pub struct IdGenerator {
low: AtomicU32,
high: AtomicU32,
}

/// When the low word of an `IdGenerator` enters the danger zone,
/// we attempt to rewind it and increase the high word.
const DANGER: u32 = 0xff000000;
/// we attempt to rewind it and increment the high word.
const DANGER: u32 = 0xfff00000;

impl IdGenerator {
pub const fn new() -> Self {
Expand Down Expand Up @@ -399,8 +422,8 @@ impl IdGenerator {
low as u64 | ((high as u64) << 32)
}
} else {
// We are in the danger zone. Our goal is to wind back the low word to the beginning while manipulating
// the high word to stay unique.
// We are in the danger zone. Our goal is to wind back the low word to the beginning
// while manipulating the high word to stay unique.
let high = self
.high
.fetch_update(Ordering::Release, Ordering::Acquire, |x| {
Expand Down
2 changes: 1 addition & 1 deletion src/wavetable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ impl Wavetable {
pub fn at_simd(&self, i: usize, phase: F32x) -> F32x {
let table: &Vec<f32> = &self.table[i].1;
let p = F32x::splat(table.len() as f32) * phase;
let i1 = (p - 0.5).fast_round_int();
let i1 = p.fast_trunc_int();
let w = p - F32x::new(core::array::from_fn(|j| i1.as_array_ref()[j] as f32));
let mask = I32x::splat(table.len() as i32 - 1);
let i0 = (i1 - 1) & mask;
Expand Down

0 comments on commit 66691b5

Please sign in to comment.