Skip to content

Commit

Permalink
Update.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Jan 9, 2024
1 parent 40bdc4d commit 1a98f3e
Show file tree
Hide file tree
Showing 21 changed files with 566 additions and 79 deletions.
8 changes: 8 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
## Changes

### Version 0.17 (Next Version)

- `Wave32/64`: `silence` is now `zero`.
- New opcode `impulse`.
- Optimization of reverb delay times in the example `optimize`.
- New opcodes `node64` and `node32` for converting an `AudioUnit` into an `AudioNode`.
- New reverb opcode `reverb2_stereo`.

### Version 0.16

- `AudioNode` now requires `Send` and `Sync`.
Expand Down
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,23 @@ duplicate = "1.0.0"
dyn-clone = "1.0.16"
symphonia = { version = "0.5.3", optional = true, features = ["all"] }
thingbuf = "0.1.4"
funutd = "0.12.1"
funutd = "0.14.0"

[features]
default = ["files"]
files = ["dep:symphonia"]

[dev-dependencies]
cpal = "0.15.2"
anyhow = "1.0.77"
anyhow = "1.0.79"
plotters = "0.3.5"
criterion = "0.5.1"
midi-msg = "0.5.0"
midir = "0.9.1"
read_input = "0.8.6"
assert_no_alloc = "1.1.2"
eframe = "0.24.1"
eframe = "0.25.0"
rayon = "1.8.0"

[[bench]]
name = "benchmark"
Expand Down Expand Up @@ -84,6 +85,10 @@ path = "examples/network.rs"
name = "keys"
path = "examples/keys.rs"

[[example]]
name = "optimize"
path = "examples/optimize.rs"

[package.metadata.docs.rs]
all-features = true
rustc-args = ["--cfg", "docsrs"]
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -561,8 +561,9 @@ Due to nonlinearity, we do not attempt to calculate frequency responses for thes

### Frequency Domain Resynthesis

Filtering and other effects can be done in the frequency domain as well
with the `resynth` opcode.
Filtering and other effects can be done in the
[frequency domain](https://en.wikipedia.org/wiki/Frequency_domain)
as well with the `resynth` opcode.

The resynthesizer
[Fourier transforms](https://en.wikipedia.org/wiki/Discrete-time_Fourier_transform)
Expand Down Expand Up @@ -633,6 +634,13 @@ Later we can set the amplitude from anywhere:
amp.set_value(0.5);
```

A useful pattern is piping a shared variable through a `follow` filter
to smooth parameter changes, here with a 0.1 second response time:

```rust
let amp_controlled = noise() * (var(&amp) >> follow(0.1));
```

The `timer` opcode maintains stream time in a shared variable.
The timer node has no inputs or outputs and can be joined to any node by stacking.

Expand Down Expand Up @@ -888,6 +896,7 @@ The type parameters in the table refer to the hacker preludes.
| `highshelf_q(q, gain)` | 2 (audio, frequency) | 1 | High shelf filter (2nd order) with Q `q` and amplitude gain `gain`. |
| `hold(v)` | 2 (signal, frequency) | 1 | Sample-and-hold component with hold time variability `v` in 0...1. |
| `hold_hz(f, v)` | 1 | 1 | Sample-and-hold component at `f` Hz with hold time variability `v` in 0...1. |
| `impulse::<U>()` | - | `U` | `U`-channel impulse; on each channel the first sample is one, the rest are zeros.
| `join::<U>()` | `U` | 1 | Average together `U` channels. Inverse of `split`. |
| `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`. |
Expand Down Expand Up @@ -925,6 +934,8 @@ The type parameters in the table refer to the hacker preludes.
| `multitap::<N>(min_delay, max_delay)` | `N + 1` (audio, delay...) | 1 | Tapped delay line with cubic interpolation. Number of taps is `N`. |
| `multitick::<U>()` | `U` | `U` | Multichannel single sample delay. |
| `multizero::<U>()` | - | `U` | Multichannel zero signal. |
| `node32::<I, O>(unit)` | `I` | `O` | Convert an `AudioUnit32` into an `AudioNode` with `I` inputs and `O` outputs. |
| `node64::<I, O>(unit)` | `I` | `O` | Convert an `AudioUnit64` into an `AudioNode` with `I` inputs and `O` outputs. |
| `noise()` | - | 1 | [White noise](https://en.wikipedia.org/wiki/White_noise) source. Synonymous with `white`. |
| `notch()` | 3 (audio, frequency, Q) | 1 | Notch filter (2nd order). |
| `notch_hz(f, q)` | 1 | 1 | Notch filter (2nd order) centered at `f` Hz with Q `q`. |
Expand All @@ -940,7 +951,7 @@ The type parameters in the table refer to the hacker preludes.
| `peak_q(q)` | 2 (audio, frequency) | 1 | Peaking filter (2nd order) with Q `q`. |
| `phaser(fb, f)` | 1 | 1 | Phaser effect with feedback amount `fb` and modulation function `f`, e.g., `\|t\| sin_hz(0.1, t) * 0.5 + 0.5`. |
| `pink()` | - | 1 | [Pink noise](https://en.wikipedia.org/wiki/Pink_noise) source. |
| `pinkpass()` | 1 | 1 | Pinking filter (3 dB/octave). |
| `pinkpass()` | 1 | 1 | Pinking filter (3 dB/octave lowpass). |
| `pipe::<U, _, _>(f)` | `f` | `f` | Chain `U` nodes from indexed generator `f`. |
| `pipef::<U, _, _>(f)` | `f` | `f` | Chain `U` nodes from fractional generator `f`. |
| `pluck(f, gain, damping)` | 1 (excitation) | 1 | [Karplus-Strong](https://en.wikipedia.org/wiki/Karplus%E2%80%93Strong_string_synthesis) plucked string oscillator with frequency `f` Hz, `gain` per second (`gain` <= 1) and high frequency `damping` in 0...1. |
Expand All @@ -949,7 +960,8 @@ The type parameters in the table refer to the hacker preludes.
| `resonator()` | 3 (audio, frequency, bandwidth) | 1 | Constant-gain bandpass resonator (2nd order). |
| `resonator_hz(f, bw)` | 1 | 1 | Constant-gain bandpass resonator (2nd order) with center frequency `f` Hz and bandwidth `bw` Hz. |
| `resynth::<I, O, _>(w, f)` | `I` | `O` | Frequency domain resynthesis with window length `w` and processing function `f`. |
| `reverb_stereo(r, t)` | 2 | 2 | Stereo reverb with room size `r` meters (10 is average) and reverberation time `t` seconds. |
| `reverb_stereo(r, t)` | 2 | 2 | Stereo reverb (32-channel [FDN](https://ccrma.stanford.edu/~jos/pasp/Feedback_Delay_Networks_FDN.html)) with room size `r` meters (10 is average) and reverberation time `t` seconds. |
| `reverb2_stereo(r, t)` | 2 | 2 | Another stereo reverb (8-channel and 16-channel [FDN](https://ccrma.stanford.edu/~jos/pasp/Feedback_Delay_Networks_FDN.html)s in series) with room size `r` meters (10 is average) and reverberation time `t` seconds. |
| `reverse::<N>()` | `N` | `N` | Reverse channel order, e.g., swap left and right channels. |
| `rossler()` | 1 (frequency) | 1 | [Rössler dynamical system](https://en.wikipedia.org/wiki/R%C3%B6ssler_attractor) oscillator. |
| `saw()` | 1 (frequency) | 1 | Bandlimited saw wave oscillator. |
Expand Down
7 changes: 1 addition & 6 deletions benches/benchmark.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,7 @@ fn reverb_bench(_dummy: usize) -> Wave32 {
Wave32::render(
44100.0,
1.0,
&mut (noise()
>> split()
>> fdn::<U32, _>(stack::<U32, _, _>(|i| {
delay(0.005 + 0.002 * i as f32) >> fir((0.22, 0.44, 0.22))
}))
>> join()),
&mut ((noise() | noise()) >> reverb_stereo(10.0, 1.0)),
)
}

Expand Down
2 changes: 1 addition & 1 deletion examples/grain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ where
let mut dna = Dna::new(36);
let mut c = Net64::wrap(gen_granular(2, &scale, 2.4, 30, &mut dna));

for parameter in dna.parameters().iter() {
for parameter in dna.parameter_vector().iter() {
println!("{}: {}", parameter.name(), parameter.value());
}

Expand Down
4 changes: 2 additions & 2 deletions examples/grain2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ where
let mut dna = Dna::new(10);
let mut c = gen_granular(1, &scale, 2.0, 30, &mut dna);

for parameter in dna.parameters().iter() {
for parameter in dna.parameter_vector().iter() {
println!("{}: {}", parameter.name(), parameter.value());
}

let mut dna2 = Dna::new(7);
let mut fx = gen_effect(&mut dna2);
for parameter in dna2.parameters().iter() {
for parameter in dna2.parameter_vector().iter() {
println!("{}: {}", parameter.name(), parameter.value());
}

Expand Down
47 changes: 30 additions & 17 deletions examples/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct State {
waveform: Waveform,
/// Selected filter.
filter: Filter,
/// Vibrato amount in 0...1.
vibrato_amount: f64,
/// Chorus amount.
chorus_amount: Shared<f64>,
/// Reverb amount.
Expand Down Expand Up @@ -128,7 +130,7 @@ where
let chorus_amount = shared(1.0);

let mut net = Net64::wrap(Box::new(sequencer_backend));
let (reverb, reverb_backend) = Slot64::new(Box::new(reverb_stereo(room_size, reverb_time)));
let (reverb, reverb_backend) = Slot64::new(Box::new(reverb2_stereo(room_size, reverb_time)));
net = net >> pan(0.0);
// Smooth chorus and reverb amounts to prevent discontinuities.
net = net
Expand Down Expand Up @@ -160,7 +162,7 @@ where
)?;
stream.play()?;

let viewport = ViewportBuilder::default().with_min_inner_size(vec2(360.0, 420.0));
let viewport = ViewportBuilder::default().with_min_inner_size(vec2(360.0, 480.0));

let options = eframe::NativeOptions {
viewport,
Expand All @@ -174,6 +176,7 @@ where
net,
waveform: Waveform::Saw,
filter: Filter::None,
vibrato_amount: 0.7,
chorus_amount,
reverb_amount,
room_size,
Expand Down Expand Up @@ -237,6 +240,13 @@ impl eframe::App for State {
ui.separator();
ui.end_row();

ui.label("Vibrato Amount");
let mut vibrato = self.vibrato_amount * 100.0;
ui.add(egui::Slider::new(&mut vibrato, 0.0..=100.0).suffix("%"));
self.vibrato_amount = vibrato * 0.01;
ui.separator();
ui.end_row();

ui.label("Filter");
ui.horizontal(|ui| {
ui.selectable_value(&mut self.filter, Filter::None, "None");
Expand All @@ -262,14 +272,14 @@ impl eframe::App for State {
let mut reverb_time = self.reverb_time;
let mut room_size = self.room_size;
ui.label("Reverb Time");
ui.add(egui::Slider::new(&mut reverb_time, 1.0..=5.0).suffix("s"));
ui.add(egui::Slider::new(&mut reverb_time, 1.0..=10.0).suffix("s"));
ui.label("Reverb Room Size");
ui.add(egui::Slider::new(&mut room_size, 3.0..=30.0).suffix("m"));
ui.add(egui::Slider::new(&mut room_size, 3.0..=100.0).suffix("m"));
if self.room_size != room_size || self.reverb_time != reverb_time {
self.reverb.set(
Fade::Smooth,
0.5,
Box::new(reverb_stereo(room_size, reverb_time)),
Box::new(reverb2_stereo(room_size, reverb_time)),
);
self.room_size = room_size;
self.reverb_time = reverb_time;
Expand Down Expand Up @@ -325,26 +335,29 @@ impl eframe::App for State {
}
}
if ctx.input(|c| c.key_down(KEYS[i])) && self.id[i].is_none() {
let pitch = midi_hz(40.0 + i as f64);
let pitch_hz = midi_hz(40.0 + i as f64);
let v = self.vibrato_amount * 0.003;
let pitch = lfo(move |t| {
pitch_hz * xerp11(1.0 / (1.0 + v), 1.0 + v, sin_hz(6.0, t) + sin_hz(6.1, t))
});
let waveform = match self.waveform {
Waveform::Sine => Net64::wrap(Box::new(sine_hz(pitch) * 0.1)),
Waveform::Saw => Net64::wrap(Box::new(saw_hz(pitch) * 0.5)),
Waveform::Square => Net64::wrap(Box::new(square_hz(pitch) * 0.5)),
Waveform::Triangle => Net64::wrap(Box::new(triangle_hz(pitch) * 0.5)),
Waveform::Organ => Net64::wrap(Box::new(organ_hz(pitch) * 0.5)),
Waveform::Hammond => Net64::wrap(Box::new(hammond_hz(pitch) * 0.5)),
Waveform::Sine => Net64::wrap(Box::new(pitch * 2.0 >> sine() * 0.1)),
Waveform::Saw => Net64::wrap(Box::new(pitch >> saw() * 0.5)),
Waveform::Square => Net64::wrap(Box::new(pitch >> square() * 0.5)),
Waveform::Triangle => Net64::wrap(Box::new(pitch >> triangle() * 0.5)),
Waveform::Organ => Net64::wrap(Box::new(pitch >> organ() * 0.5)),
Waveform::Hammond => Net64::wrap(Box::new(pitch >> hammond() * 0.5)),
Waveform::Pulse => Net64::wrap(Box::new(
lfo(move |t| (pitch, lerp11(0.01, 0.99, sin_hz(0.1, t))))
(pitch | lfo(move |t| lerp11(0.01, 0.99, sin_hz(0.1, t))))
>> pulse() * 0.5,
)),
Waveform::Pluck => {
Net64::wrap(Box::new(zero() >> pluck(pitch, 0.5, 0.5) * 0.5))
Net64::wrap(Box::new(zero() >> pluck(pitch_hz, 0.5, 0.5) * 0.5))
}
Waveform::Noise => Net64::wrap(Box::new(
(noise()
| lfo(move |t| {
(pitch, funutd::math::lerp(100.0, 10.0, clamp01(t * 5.0)))
}))
| pitch * 4.0
| lfo(move |t| funutd::math::lerp(100.0, 20.0, clamp01(t * 5.0))))
>> !resonator()
>> resonator()
>> shape(fundsp::shape::Shape::AdaptiveTanh(0.01, 0.1)),
Expand Down
82 changes: 82 additions & 0 deletions examples/optimize.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! Optimize a stereo reverb. Please run me in release mode!
use fundsp::hacker32::*;
use fundsp::reverb::*;
use funutd::dna::*;
use funutd::*;
use rayon::prelude::*;

/// Evaluate reverb quality from its genotype.
fn evaluate_reverb(dna: &mut Dna) -> f32 {
let reverb = generate_reverb(dna);
// Prevent cases where two lines have the same length.
let mut repeat_fitness = 0.0;
for i in 0..dna.parameters() {
let i_time = dna.parameter(i).value_f32().unwrap();
for j in i + 1..dna.parameters() {
let j_time = dna.parameter(j).value_f32().unwrap();
if round(44100.0 * i_time) == round(44100.0 * j_time) {
repeat_fitness -= 100.0;
}
}
}
repeat_fitness + reverb_fitness(reverb)
}

fn main() {
let mut rng = Rnd::from_time();

let mut dna = Dna::new(rng.u64());
let mut fitness = evaluate_reverb(&mut dna);
let mut rounds = 0;

loop {
rounds += 1;

let mut seeds = vec![];
for _ in 0..360 {
seeds.push((rng.u64(), rng.f32() * rng.f32()));
}
let mutateds: Vec<(Dna, f32)> = seeds
.par_iter()
.map(|(seed, p)| {
let mutation_p = xerp(1.0 / 30.0, 1.5, *p).min(1.0);
let mut mutated = Dna::mutate(&dna, *seed, mutation_p);
let mutated_fitness = evaluate_reverb(&mut mutated);
(mutated, mutated_fitness)
})
.collect();

let mut improved = false;
for (mut mutated, mutated_fitness) in mutateds {
if mutated_fitness > fitness {
fitness = mutated_fitness;
std::mem::swap(&mut dna, &mut mutated);
improved = true;
}
}

if improved || rounds % 1000 == 0 {
println!(
"Rounds {} (Candidates {}) Fitness {}",
rounds,
rounds * 360,
fitness
);
}
if improved {
let mut delays = Vec::new();
for i in 0..dna.parameters() {
let delay = dna.parameter(i).value_f32().unwrap();
println!(
"{}: {} ({})",
dna.parameter(i).name(),
delay,
round(delay * 44100.0) as i32
);
delays.push(delay);
}
println!("{:?}", delays);
}
}
}
Loading

0 comments on commit 1a98f3e

Please sign in to comment.