Skip to content

Commit

Permalink
Update.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Oct 15, 2024
1 parent 6eb07ab commit 84ed485
Show file tree
Hide file tree
Showing 10 changed files with 73 additions and 18 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- Noise functions now accept seed as `u64` instead of `i64`.
- `sine_phase`, `ramp_phase` and `ramp_hz_phase` opcodes were removed:
there is a new builder notation for setting the initial phase, for example, `sine().phase(0.0)`.
- New builder notation for setting noise generator seed, for example, `noise().seed(1)`.

### Version 0.20

Expand Down
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,10 @@ This means that `noise() | noise()` is a stereo noise source, for example.
Pseudorandom phase is an attempt to decorrelate different channels of audio.
It is also used to pick sample points for envelopes, contributing to a "warmer" sound.

## Oscillators
To override pseudorandom phase in a noise component (`brown`, `mls`, `noise`, `pink` and `white` opcodes),
use the `seed` builder method. For example, `noise().seed(1)`.

### Oscillators

To override pseudorandom phase in an oscillator with an initial phase of your own (in 0...1),
use the `phase` builder method. For example, in `let mut A = sine_hz(220.0).phase(0.0)`
Expand All @@ -632,21 +635,22 @@ The following table lists the oscillator opcodes.

| Opcode | Type | Waveform |
| -------------- | ---------------------- | ------------------------------------------ |
| `dsf_saw` | DSF | saw-like
| `dsf_square` | DSF | square-like
| `dsf_saw` | DSF | [saw](https://en.wikipedia.org/wiki/Sawtooth_wave)-like
| `dsf_square` | DSF | [square](https://en.wikipedia.org/wiki/Square_wave)-like
| `hammond` | wavetable | Hammond-y waveform, which emphasizes first three partials |
| `organ` | wavetable | organ-y waveform, which emphasizes octave partials |
| `poly_pulse` | PolyBLEP | pulse |
| `poly_pulse` | PolyBLEP | [pulse](https://en.wikipedia.org/wiki/Pulse_wave) |
| `poly_saw` | PolyBLEP | saw |
| `poly_square` | PolyBLEP | square |
| `pulse` | wavetable | pulse |
| `saw` | wavetable | saw |
| `sine` | sine | sine |
| `sine` | sine | [sine](https://en.wikipedia.org/wiki/Sine_wave) |
| `soft_saw` | wavetable | soft saw, which falls off like a triangle wave |
| `square` | wavetable | square |
| `triangle` | wavetable | triangle |
| `triangle` | wavetable | [triangle](https://en.wikipedia.org/wiki/Triangle_wave) |

The wavetable oscillator is bandlimited with pristine quality.
The wavetable oscillator is [bandlimited](https://en.wikipedia.org/wiki/Bandlimiting)
with pristine quality.
However, unlike the other types it allocates memory in the form of static wavetables.
The DSF oscillator has similar quality but is somewhat expensive to evaluate.
The PolyBLEP oscillator is a fast approximation with fair quality.
Expand Down
4 changes: 2 additions & 2 deletions examples/input/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ where
let channels = config.channels as usize;

let input = An(InputNode::new(receiver));
let reverb = reverb2_stereo(20.0, 3.0, 1.0, 1.0, highshelf_hz(1000.0, 1.0, db_amp(-1.0)));
let chorus = chorus(0, 0.0, 0.02, 0.3) | chorus(1, 0.0, 0.02, 0.3);
let reverb = reverb2_stereo(20.0, 3.0, 1.0, 0.2, highshelf_hz(1000.0, 1.0, db_amp(-1.0)));
let chorus = chorus(0, 0.0, 0.03, 0.2) | chorus(1, 0.0, 0.03, 0.2);
// Here is the final input-to-output processing chain.
let graph = input >> chorus >> (0.8 * reverb & 0.2 * multipass());
let mut graph = BlockRateAdapter::new(Box::new(graph));
Expand Down
2 changes: 1 addition & 1 deletion examples/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ where
>> ((1.0 - var(&chorus_amount) >> follow(0.01) >> split()) * multipass()
& (var(&chorus_amount) >> follow(0.01) >> split())
* 2.0
* (chorus(0, 0.0, 0.02, 0.3) | chorus(1, 0.0, 0.02, 0.3)));
* (chorus(0, 0.0, 0.03, 0.2) | chorus(1, 0.0, 0.03, 0.2)));
net = net >> phaser >> flanger;
net = net
>> ((1.0 - var(&reverb_amount) >> follow(0.01) >> split::<U2>()) * multipass()
Expand Down
9 changes: 9 additions & 0 deletions src/combinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ impl<X: AudioNode> An<X> {
self.reset();
self
}

/// This builder method sets noise generator seed,
/// overriding pseudorandom phase.
/// Works with opcodes `mls`, `noise`, `white`, `pink` and `brown`.
pub fn seed(mut self, seed: u64) -> Self {
self.set(Setting::seed(seed).left());
self.reset();
self
}
}

impl<X> Neg for An<X>
Expand Down
26 changes: 23 additions & 3 deletions src/noise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,17 @@ impl MlsState {
#[derive(Clone)]
pub struct Mls {
mls: MlsState,
seed: Option<u64>,
hash: u64,
}

impl Mls {
pub fn new(mls: MlsState) -> Self {
Self { mls, hash: 0 }
Self {
mls,
seed: None,
hash: 0,
}
}
}

Expand All @@ -115,7 +120,8 @@ impl AudioNode for Mls {
type Outputs = typenum::U1;

fn reset(&mut self) {
self.mls = MlsState::new_with_seed(self.mls.n, (self.hash >> 32) as u32);
let hash = self.seed.unwrap_or(self.hash);
self.mls = MlsState::new_with_seed(self.mls.n, (hash ^ (hash >> 32)) as u32);
}

#[inline]
Expand All @@ -125,6 +131,12 @@ impl AudioNode for Mls {
[value * 2.0 - 1.0].into()
}

fn set(&mut self, setting: Setting) {
if let Parameter::Seed(seed) = setting.parameter() {
self.seed = Some(*seed);
}
}

fn set_hash(&mut self, hash: u64) {
self.hash = hash;
self.reset();
Expand Down Expand Up @@ -160,6 +172,7 @@ fn hash32x_simd(x: U32x) -> U32x {
#[derive(Default, Clone)]
pub struct Noise {
state: u32,
seed: Option<u64>,
hash: u64,
}

Expand All @@ -177,7 +190,8 @@ impl AudioNode for Noise {
type Outputs = typenum::U1;

fn reset(&mut self) {
self.state = self.hash as u32;
let hash = self.seed.unwrap_or(self.hash);
self.state = (hash ^ (hash >> 32)) as u32;
}

#[inline]
Expand All @@ -203,6 +217,12 @@ impl AudioNode for Noise {
self.state = self.state.wrapping_add(size as u32);
}

fn set(&mut self, setting: Setting) {
if let Parameter::Seed(seed) = setting.parameter() {
self.seed = Some(*seed);
}
}

fn set_hash(&mut self, hash: u64) {
self.hash = hash;
self.reset();
Expand Down
9 changes: 9 additions & 0 deletions src/setting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub enum Parameter {
AttackRelease(f32, f32),
/// Oscillator initial phase in 0...1.
Phase(f32),
/// Generator seed.
Seed(u64),
}

/// Address specifies location to apply setting in a graph.
Expand Down Expand Up @@ -159,6 +161,13 @@ impl Setting {
address: ArrayVec::new(),
}
}
/// Create setting for generator seed.
pub fn seed(seed: u64) -> Self {
Self {
parameter: Parameter::Seed(seed),
address: ArrayVec::new(),
}
}
/// Add indexed address to setting.
pub fn index(mut self, index: usize) -> Self {
self.address.push(Address::Index(index));
Expand Down
2 changes: 1 addition & 1 deletion src/wave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ impl Wave {
self.length() as f64 / self.sample_rate()
}

/// Resizes the wave in-place. Any new samples are set to zero.
/// Resizes the wave in-place to `length` samples. Any new samples are set to zero.
/// The wave must have a non-zero number of channels.
///
/// ### Example
Expand Down
2 changes: 1 addition & 1 deletion src/wavetable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn optimal4x44<T: Num>(a0: T, a1: T, a2: T, a3: T, x: T) -> T {
/// Assume sample rate is at least 44.1 kHz.
/// `phase(i)` is phase in 0...1 for partial `i` (1, 2, ...).
/// `amplitude(p, i)` is amplitude for fundamental `p` Hz partial `i` (with frequency `p * i`).
pub fn make_wave<P, A>(pitch: f64, phase: &P, amplitude: &A) -> Vec<f32>
fn make_wave<P, A>(pitch: f64, phase: &P, amplitude: &A) -> Vec<f32>
where
P: Fn(u32) -> f64,
A: Fn(f64, u32) -> f64,
Expand Down
18 changes: 15 additions & 3 deletions tests/test_basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ fn test_basic() {

// Wave rendering, tick vs. process rendering, node reseting.
check_wave(noise() >> declick() | noise() + noise());
check_wave(noise() * noise() | busi::<U4, _, _>(|i| mls_bits(10 + i)));
check_wave(noise() & noise() | sine_hz(440.0) & -noise());
check_wave(noise().seed(1) * noise() | busi::<U4, _, _>(|i| mls_bits(10 + i)));
check_wave(pink().seed(2) & noise() | sine_hz(440.0) & -noise());
check_wave(
lfo(|t| xerp(110.0, 220.0, clamp01(t))) >> sine()
| (envelope(|t| xerp(220.0, 440.0, clamp01(t))) >> pass() >> sine()) & mls(),
Expand Down Expand Up @@ -197,7 +197,7 @@ fn test_basic() {
| ((mls() | dc(880.0)) >> !butterpass() >> butterpass()),
);
check_wave(
(noise() | dc(440.0)) >> pipei::<U4, _, _>(|_| !peak_q(1.0)) >> bell_q(1.0, 2.0)
(brown().seed(2) | dc(440.0)) >> pipei::<U4, _, _>(|_| !peak_q(1.0)) >> bell_q(1.0, 2.0)
| ((mls() | dc(880.0)) >> !lowshelf_q(1.0, 0.5) >> highshelf_q(2.0, 2.0)),
);
check_wave(
Expand Down Expand Up @@ -450,6 +450,18 @@ fn test_basic() {
assert!(is_equal_unit(&mut rnd, &mut add_2_3, &mut add_net));
add_net.check();

let mut front_net = Net::new(0, 1);
let dc_id = front_net.chain(Box::new(dc(1.0)));
front_net.set_sample_rate(48000.0);
let mut back_net = front_net.backend();
assert_eq!(back_net.get_mono(), 1.0);
front_net.set(Setting::value(2.0).node(dc_id));
assert_eq!(back_net.get_mono(), 2.0);
front_net.replace(dc_id, Box::new(dc(3.0)));
assert_eq!(back_net.get_mono(), 2.0);
front_net.commit();
assert_eq!(back_net.get_mono(), 3.0);

// Test multichannel constants vs. stacked constants.
assert!(is_equal(
&mut rnd,
Expand Down

0 comments on commit 84ed485

Please sign in to comment.