diff --git a/README.md b/README.md index 5ec85b0..b79fac9 100644 --- a/README.md +++ b/README.md @@ -1079,7 +1079,7 @@ The type parameters in the table refer to the hacker preludes. | `branchi::(f)`| `f` | `U * f` | Branch into `U` nodes from indexed generator `f`. | | `branchf::(f)`| `f` | `U * f` | Branch into `U` nodes from fractional generator `f`, e.g., `\| x \| resonator_hz(xerp(20.0, 20_000.0, x), xerp(5.0, 5_000.0, x))`. | | `bus(x, y)` | `x = y` | `x = y` | Bus `x` and `y`. Identical with `x & y`. | -| `busi::(f)` | `f` | `f` | Bus together `U` nodes from indexed generator `f`, e.g., `\| i \| mul(i as f64 + 1.0) >> sine()`. | +| `busi::(f)` | `f` | `f` | Bus together `U` nodes from indexed generator `f`, e.g., `\| i \| mul(i as f32 + 1.0) >> sine()`. | | `busf::(f)` | `f` | `f` | Bus together `U` nodes from fractional generator `f`. | | `butterpass()` | 2 (audio, frequency) | 1 | Butterworth lowpass filter (2nd order). | | `butterpass_hz(f)` | 1 | 1 | Butterworth lowpass filter (2nd order) with cutoff frequency `f` Hz. | @@ -1108,7 +1108,7 @@ The type parameters in the table refer to the hacker preludes. | `envelope(f)` | - | `f` | Time-varying control `f` with scalar or tuple output, e.g., `\|t\| exp(-t)`. Synonymous with `lfo`. | | `envelope2(f)` | 1 (x) | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, x\| exp(-t * x)`. Synonymous with `lfo2`. | | `envelope3(f)` | 2 (x, y) | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, x, y\| y * exp(-t * x)`. Synonymous with `lfo3`. | -| `envelope_in(f)` | `f` | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, i: &Frame\| exp(-t * i[0])`. Synonymous with `lfo_in`. | +| `envelope_in(f)` | `f` | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, i: &Frame\| exp(-t * i[0])`. Synonymous with `lfo_in`. | | `fbell(shape)` | 4 (audio, frequency, Q, gain) | 1 | Feedback biquad bell equalizer (2nd order) with feedback `shape`, for example, `Tanh(1.0)`. | | `fbell_hz(shape, f, q, gain)` | 1 | 1 | Feedback biquad bell equalizer (2nd order) with feedback `shape`, center `f` Hz, Q value `q` and amplitude gain `gain`. | | `fdn(x)` | `x` | `x` | Feedback Delay Network: enclose feedback circuit `x` (with equal number of inputs and outputs) using diffusive [Hadamard](https://en.wikipedia.org/wiki/Hadamard_matrix) feedback. | @@ -1142,7 +1142,7 @@ The type parameters in the table refer to the hacker preludes. | `lfo(f)` | - | `f` | Time-varying control `f` with scalar or tuple output, e.g., `\|t\| exp(-t)`. Synonymous with `envelope`. | | `lfo2(f)` | 1 (x) | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, x\| exp(-t * x)`. Synonymous with `envelope2`. | | `lfo3(f)` | 2 (x, y) | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, x, y\| y * exp(-t * x)`. Synonymous with `envelope3`. | -| `lfo_in(f)` | `f` | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, i: &Frame\| exp(-t * i[0])`. Synonymous with `envelope_in`. | +| `lfo_in(f)` | `f` | `f` | Time-varying, input dependent control `f` with scalar or tuple output, e.g., `\|t, i: &Frame\| exp(-t * i[0])`. Synonymous with `envelope_in`. | | `limiter(a, r)` | 1 | 1 | Look-ahead limiter with attack time (and latency) `a` seconds and release time `r` seconds. | | `limiter_stereo(a, r)` | 2 | 2 | Stereo look-ahead limiter with attack time (and latency) `a` seconds and release time `r` seconds. | | `lorenz()` | 1 (frequency) | 1 | [Lorenz dynamical system](https://en.wikipedia.org/wiki/Lorenz_system) oscillator. | @@ -1157,7 +1157,7 @@ The type parameters in the table refer to the hacker preludes. | `lowshelf()` | 4 (audio, frequency, Q, gain) | 1 | Low shelf filter (2nd order) with adjustable amplitude gain. | | `lowshelf_hz(f, q, gain)`| 1 | 1 | Low shelf filter (2nd order) centered at `f` Hz with Q `q` and amplitude gain `gain`. | | `lowshelf_q(q, gain)` | 2 (audio, frequency) | 1 | Low shelf filter (2nd order) with Q `q` and amplitude gain `gain`. | -| `map(f)` | `f` | `f` | Map channels freely, e.g., `map(\|i: &Frame\| max(i[0], i[1]))`. | +| `map(f)` | `f` | `f` | Map channels freely, e.g., `map(\|i: &Frame\| max(i[0], i[1]))`. | | `meter(mode)` | 1 | 1 (meter) | Analyze input and output a summary according to the metering mode. | | `mls()` | - | 1 | White [MLS noise](https://en.wikipedia.org/wiki/Maximum_length_sequence) source. | | `mls_bits(n)` | - | 1 | White MLS noise source from `n`-bit MLS sequence (1 <= `n` <= 31). | diff --git a/src/audionode.rs b/src/audionode.rs index d987645..818cd2c 100644 --- a/src/audionode.rs +++ b/src/audionode.rs @@ -20,17 +20,6 @@ impl Size for A {} /// between `AudioNode` instances. pub type Frame = NumericArray; -unsafe fn uninitialized_f32_array() -> [f32; 8] { - #[allow(clippy::uninit_assumed_init)] - #[allow(invalid_value)] - core::mem::MaybeUninit::uninit().assume_init() -} - -unsafe fn uninitialized_frame() -> Frame { - #[allow(clippy::uninit_assumed_init)] - core::mem::MaybeUninit::uninit().assume_init() -} - /* Order of type arguments in nodes: 1. Basic input and output arities excepting filter input selector arities. @@ -95,7 +84,6 @@ pub trait AudioNode: Clone + Sync + Send { /// ``` fn tick(&mut self, input: &Frame) -> Frame; - /* /// Process up to 64 (`MAX_BUFFER_SIZE`) samples. /// If `size` is zero then this is a no-op, which is permitted. fn process(&mut self, size: usize, input: &BufferRef, output: &mut BufferMut) { @@ -119,61 +107,6 @@ pub trait AudioNode: Clone + Sync + Send { } } } - */ - - /// Process up to 64 (`MAX_BUFFER_SIZE`) samples. - /// If `size` is zero then this is a no-op, which is permitted. - fn process(&mut self, size: usize, input: &BufferRef, output: &mut BufferMut) { - // The default implementation is a fallback that calls into `tick`. - debug_assert!(size <= MAX_BUFFER_SIZE); - debug_assert!(input.channels() == self.inputs()); - debug_assert!(output.channels() == self.outputs()); - - // Actually unrolling this seems to improve performance. - let mut input_frame0: Frame = unsafe { uninitialized_frame() }; - let mut input_frame1: Frame = unsafe { uninitialized_frame() }; - let mut input_frame2: Frame = unsafe { uninitialized_frame() }; - let mut input_frame3: Frame = unsafe { uninitialized_frame() }; - let mut input_frame4: Frame = unsafe { uninitialized_frame() }; - let mut input_frame5: Frame = unsafe { uninitialized_frame() }; - let mut input_frame6: Frame = unsafe { uninitialized_frame() }; - let mut input_frame7: Frame = unsafe { uninitialized_frame() }; - for i in 0..full_simd_items(size) { - for channel in 0..self.inputs() { - let at = input.at(channel, i).to_array(); - input_frame0[channel] = at[0]; - input_frame1[channel] = at[1]; - input_frame2[channel] = at[2]; - input_frame3[channel] = at[3]; - input_frame4[channel] = at[4]; - input_frame5[channel] = at[5]; - input_frame6[channel] = at[6]; - input_frame7[channel] = at[7]; - } - let output_frame0 = self.tick(&input_frame0); - let output_frame1 = self.tick(&input_frame1); - let output_frame2 = self.tick(&input_frame2); - let output_frame3 = self.tick(&input_frame3); - let output_frame4 = self.tick(&input_frame4); - let output_frame5 = self.tick(&input_frame5); - let output_frame6 = self.tick(&input_frame6); - let output_frame7 = self.tick(&input_frame7); - for channel in 0..self.outputs() { - let mut set: [f32; 8] = unsafe { uninitialized_f32_array() }; - set[0] = output_frame0[channel]; - set[1] = output_frame1[channel]; - set[2] = output_frame2[channel]; - set[3] = output_frame3[channel]; - set[4] = output_frame4[channel]; - set[5] = output_frame5[channel]; - set[6] = output_frame6[channel]; - set[7] = output_frame7[channel]; - output.set(channel, i, F32x::from(set)); - } - } - - self.process_remainder(size, input, output); - } /// Process samples left over using `tick` after processing all full SIMD items. /// This is a convenience method for implementers. diff --git a/src/net.rs b/src/net.rs index a20f254..14157cb 100644 --- a/src/net.rs +++ b/src/net.rs @@ -588,9 +588,11 @@ impl Net { self.vertex.len() } - /// Assuming this network is a chain of processing units ordered by insertion order, - /// add a new unit to the chain. Global outputs will be assigned to the outputs of the unit - /// if possible. The number of inputs to the unit must match the number of outputs of the + /// Assuming this network is a chain of processing units, + /// add a new unit to the chain. Global outputs will be assigned to the outputs of the unit. + /// If there are more global outputs than there are outputs in the unit, then a modulo + /// is taken to plug all of them. The previous global output sources become inputs to the unit. + /// The number of inputs to the unit must match the number of outputs of the /// previous unit, or the number of network inputs if there is no previous unit. /// Returns the ID of the new unit. /// @@ -607,16 +609,21 @@ impl Net { let unit_outputs = unit.outputs(); let id = self.push(unit); let index = self.node_index[&id]; - if self.outputs() == unit_outputs { - self.pipe_output(id); - } - if unit_inputs > 0 { - if self.size() > 1 { - self.pipe(self.vertex[index - 1].id, id); - } else { + + if self.size() == 1 { + if self.inputs() > 0 { self.pipe_input(id); } + } else { + for i in 0..unit_inputs { + self.vertex[index].source[i].source = self.output_edge[i % self.outputs()].source; + } } + + for i in 0..self.outputs() { + self.output_edge[i].source = Port::Local(index, i % unit_outputs); + } + self.invalidate_order(); id } @@ -963,6 +970,7 @@ impl Net { } /// Process one sample using the supplied `sender` to deallocate units. + #[inline] pub(crate) fn tick_2( &mut self, input: &[f32], @@ -999,6 +1007,7 @@ impl Net { } /// Process a block of samples using the supplied `sender` to deallocate units. + #[inline] pub(crate) fn process_2( &mut self, size: usize, diff --git a/tests/test_basic.rs b/tests/test_basic.rs index ddf2b8a..6460343 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -287,6 +287,13 @@ fn test_basic() { net.check(); check_wave(net); + let mut net = Net::wrap(Box::new(sine_hz(42.))); + net = net.clone() | net; + let verb = Net::wrap(Box::new(reverb_stereo(10., 5., 0.5))); + net.chain(Box::new(verb)); + net.check(); + check_wave(net); + check_wave((noise() | envelope(|t| spline_noise(1, t * 10.0))) >> panner()); check_wave(impulse::());