Skip to content

Commit

Permalink
Update.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamiPerttu committed Feb 19, 2024
1 parent 39c3b47 commit 78937c1
Show file tree
Hide file tree
Showing 13 changed files with 943 additions and 216 deletions.
6 changes: 4 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

- `Wave32/64`: `silence` is now `zero`.
- New opcode `impulse`.
- Optimization of reverb delay times in the example `optimize`.
- Attempted 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`.
- New reverb opcodes `reverb2_stereo` and `reverb3_stereo`.
- New opcodes `allnest` and `allnest_c` for nested allpass filters.
- New opcodes `tap_linear` and `multitap_linear` for delay lines with linear interpolation.

### Version 0.16

Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ It is flexible and attempts to conform to Rust practices.

The 64-bit hacker environment (`fundsp::hacker`) for audio hacking
is fully 64-bit to minimize type annotations and maximize audio quality.
The hacker interface uses 1 floating point type (`f64`) and 1 integer type (`i64`) only.

The 32-bit hacker environment (`fundsp::hacker32`) aims to offer
maximum processing speed.
Expand Down Expand Up @@ -541,7 +540,7 @@ Verified frequency responses are available for all linear filters.
| Opcode | Type | Parameters | Family | Notes |
| ------------ | ---------------------- | ------------ | ------------ | --------- |
| `allpass` | allpass (2nd order) | frequency, Q | [Simper SVF](https://cytomic.com/files/dsp/SvfLinearTrapOptimised2.pdf) | |
| `allpole` | allpass (1st order) | delay | 1st order | Adjustable delay in samples. |
| `allpole` | allpass (1st order) | delay | 1st order | Adjustable delay at DC in samples. |
| `bandpass` | bandpass (2nd order) | frequency, Q | Simper SVF | |
| `bell` | peaking (2nd order) | frequency, Q, gain | Simper SVF | Adjustable amplitude gain. |
| `biquad` | biquad (2nd order) | - | [biquad](https://en.wikipedia.org/wiki/Digital_biquad_filter) | Arbitrary biquad with fixed parameters. |
Expand Down Expand Up @@ -714,8 +713,9 @@ The following table summarizes the available settings.

| Opcode | Setting Format |
| ----------------- | --------------------------------- |
| `allnest_c` | delay in samples at DC |
| `allpass_hz` | (center, Q) |
| `allpole_delay` | delay in samples |
| `allpole_delay` | delay in samples at DC |
| `bandpass_hz` | (center, Q) |
| `bell_hz` | (center, Q, gain) |
| `biquad` | (a1, a2, b0, b1, b2) |
Expand Down Expand Up @@ -860,11 +860,13 @@ The type parameters in the table refer to the hacker preludes.
| ---------------------- |:-------:|:-------:| ---------------------------------------------- |
| `add(x)` | `x` | `x` | Add constant `x` to signal. |
| `adsr_live(a, d, s, r)`| 1 | 1 | ADSR envelope. Attack time `a`, decay time `d`, sustain level `s`, and release time `r`. Input > 0.0 starts attack, input <= 0.0 starts release. Output in [0.0, 1.0].|
| `allnest(x)` | 2 (input, coefficient) | 1 | Nested allpass with inner allpass processing `x`. |
| `allnest_c(c, x)` | 1 | 1 | Nested allpass with feedforward coefficient `c` and inner allpass processing `x`. |
| `allpass()` | 3 (audio, frequency, Q) | 1 | Allpass filter (2nd order). |
| `allpass_hz(f, q)` | 1 | 1 | Allpass filter (2nd order) centered at `f` Hz with Q `q`. |
| `allpass_q(q)` | 2 (audio, frequency) | 1 | Allpass filter (2nd order) with Q `q`. |
| `allpole()` | 2 (audio, delay) | 1 | Allpass filter (1st order). 2nd input is delay in samples (`delay` > 0). |
| `allpole_delay(delay)` | 1 | 1 | Allpass filter (1st order) with `delay` in samples (`delay` > 0). |
| `allpole_delay(delay)` | 1 | 1 | Allpass filter (1st order) with `delay` at DC in samples (`delay` > 0). |
| `bandpass()` | 3 (audio, frequency, Q) | 1 | Bandpass filter (2nd order). |
| `bandpass_hz(f, q)` | 1 | 1 | Bandpass filter (2nd order) centered at `f` Hz with Q `q`. |
| `bandpass_q(q)` | 2 (audio, frequency) | 1 | Bandpass filter (2nd order) with Q `q`. |
Expand Down Expand Up @@ -957,6 +959,7 @@ The type parameters in the table refer to the hacker preludes.
| `multisink::<U>()` | `U` | - | Consumes multichannel signal. |
| `multisplit::<M, N>()` | `M` | `M * N` | Split `M` channels into `N` branches. |
| `multitap::<N>(min_delay, max_delay)` | `N + 1` (audio, delay...) | 1 | Tapped delay line with cubic interpolation. Number of taps is `N`. |
| `multitap_linear::<N>(min_delay, max_delay)` | `N + 1` (audio, delay...) | 1 | Tapped delay line with linear 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. |
Expand Down Expand Up @@ -986,7 +989,8 @@ The type parameters in the table refer to the hacker preludes.
| `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 (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. |
| `reverb2_stereo(r, t, m)` | 2 | 2 | Another stereo reverb (32-channel hybrid [FDN](https://ccrma.stanford.edu/~jos/pasp/Feedback_Delay_Networks_FDN.html)) with room size `r` meters (10-30 meters is supported), reverberation time `t` seconds and modulation speed `m` (nominal range 0-1, beyond starts being an effect). |
| `reverb3_stereo(t)` | 2 | 2 | Stereo reverb with reverberation time `t` seconds (at least 1 second), has a slow attack and metallic ring when `t` is near 1 second. |
| `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 All @@ -1007,6 +1011,7 @@ The type parameters in the table refer to the hacker preludes.
| `sum::<U, _, _>(f)` | `U * f` | `f` | Sum `U` nodes from indexed generator `f`. |
| `sumf::<U, _, _>(f)` | `U * f` | `f` | Sum `U` nodes from fractional generator `f`, e.g., `\| x \| delay(xerp(0.1, 0.2, x))`. |
| `tap(min_delay, max_delay)` | 2 (audio, delay) | 1 | Tapped delay line with cubic interpolation. All times are in seconds. |
| `tap_linear(min_delay, max_delay)` | 2 (audio, delay) | 1 | Tapped delay line with linear interpolation. All times are in seconds. |
| `tick()` | 1 | 1 | Single sample delay. |
| `timer(&shared)` | - | - | Maintain current stream time in a shared variable. |
| `triangle()` | 1 (frequency) | 1 | Bandlimited triangle wave oscillator. |
Expand Down
9 changes: 5 additions & 4 deletions examples/keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ where
let chorus_amount = shared(1.0);

let mut net = Net64::wrap(Box::new(sequencer_backend));
let (reverb, reverb_backend) = Slot64::new(Box::new(reverb2_stereo(room_size, reverb_time)));
let (reverb, reverb_backend) =
Slot64::new(Box::new(reverb2_stereo(room_size, reverb_time, 0.5)));
net = net >> pan(0.0);
// Smooth chorus and reverb amounts to prevent discontinuities.
net = net
Expand Down Expand Up @@ -176,7 +177,7 @@ where
net,
waveform: Waveform::Saw,
filter: Filter::None,
vibrato_amount: 0.7,
vibrato_amount: 0.5,
chorus_amount,
reverb_amount,
room_size,
Expand Down Expand Up @@ -274,12 +275,12 @@ impl eframe::App for State {
ui.label("Reverb Time");
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..=100.0).suffix("m"));
ui.add(egui::Slider::new(&mut room_size, 10.0..=30.0).suffix("m"));
if self.room_size != room_size || self.reverb_time != reverb_time {
self.reverb.set(
Fade::Smooth,
0.5,
Box::new(reverb2_stereo(room_size, reverb_time)),
Box::new(reverb2_stereo(room_size, reverb_time, 0.5)),
);
self.room_size = room_size;
self.reverb_time = reverb_time;
Expand Down
121 changes: 81 additions & 40 deletions examples/optimize.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
//! Optimize a stereo reverb. Please run me in release mode!
//! Failed experiment to optimize a stereo reverb. WIP.
//! Please run me in release mode!
use fundsp::hacker32::*;
use fundsp::reverb::*;
use funutd::dna::*;
use funutd::*;
use rayon::prelude::*;

struct Specimen {
pub dna: Dna,
pub fitness: f32,
}

fn specimen(dna: Dna, fitness: f32) -> Specimen {
Specimen { dna, fitness }
}

/// 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.
// Prevent cases where two lines have nearly the same length.
let repeat_weight = 0.0;
let mut repeat_fitness = 0.0;
for i in 0..dna.parameters() {
let i_time = round(44100.0 * dna.parameter(i).value_f32().unwrap());
for j in i + 1..dna.parameters() {
let j_time = round(44100.0 * dna.parameter(j).value_f32().unwrap());
let diff = abs(i_time - j_time);
if diff < 30.0 {
repeat_fitness -= squared(30.0 - diff);
if diff < 20.0 {
repeat_fitness -= repeat_weight * squared(20.0 - diff);
}
}
}
Expand All @@ -26,28 +37,27 @@ fn evaluate_reverb(dna: &mut Dna) -> f32 {

/// Mutate the source Dna. Return the mutated Dna.
/// The probability of mutating each parameter is `mutation_p`.
/// Requires interactive mode.
fn mutate(source: &Dna, seed: u64, mutation_p: f32) -> Dna {
let mut rnd = Rnd::from_u64(seed);
let mut dna = Dna::new(rnd.u64());
let amount = xerp(1.0, (1u64 << 32) as f64, rnd.f64());
for parameter in source.parameter_vector() {
if rnd.f32() < mutation_p {
let mutate_i = rnd.u32_to(source.parameters() as u32) as usize;
let scale = xerp(1.0, (1u64 << 32) as f64, rnd.f64());
for i in 0..source.parameters() {
let parameter = source.parameter(i);
if i == mutate_i || rnd.f32() < mutation_p {
if matches!(parameter.kind(), ParameterKind::Ordered) {
let adjust = if rnd.bool(0.5) {
xerp(
lerp(
1.0,
max(
1.0,
min(parameter.maximum() as f64 - parameter.raw() as f64, amount),
min(parameter.maximum() as f64 - parameter.raw() as f64, scale),
),
rnd.f64(),
)
} else {
-xerp(
1.0,
max(1.0, min(parameter.raw() as f64, amount)),
rnd.f64(),
)
-lerp(1.0, max(1.0, min(parameter.raw() as f64, scale)), rnd.f64())
};
let value = clamp(
0.0,
Expand All @@ -66,63 +76,94 @@ fn mutate(source: &Dna, seed: u64, mutation_p: f32) -> Dna {
fn main() {
let mut rng = Rnd::from_time();

let mut dna = Dna::new(rng.u64());
let mut fitness = evaluate_reverb(&mut dna);
let mut global_dna = Dna::new(rng.u64());
let mut global_fitness = evaluate_reverb(&mut global_dna);
let mut global_i = 0;
let mut rounds = 0;
let candidates_per_round = 192;
let mut accept = 0;
let mut reject = 0;
let population_size = 96;

let mut population = Vec::new();
for _ in 0..population_size {
let mut dna = Dna::new(rng.u64());
let fitness = evaluate_reverb(&mut dna);
population.push(specimen(dna, fitness));
}

loop {
rounds += 1;

let temperature = 1000.0 / (rounds as f32);

let mut seeds = vec![];
for _ in 0..candidates_per_round {
seeds.push((rng.u64(), rng.f32() * rng.f32()));
for _ in 0..population_size {
seeds.push((rng.u64(), squared(rng.f32())));
}
let min_mutation_p = 1.0 / min(30, max(1, rounds / 3)) as f32;
let mutateds: Vec<(Dna, f32)> = seeds
let mut candidates: Vec<Specimen> = seeds
.par_iter()
.map(|(seed, p)| {
let mutation_p = xerp(min_mutation_p, 1.5, *p);
let mut mutated = if mutation_p >= 1.0 {
Dna::new(*seed)
} else {
mutate(&dna, *seed, mutation_p)
};
.zip(&population)
.map(|((seed, p), spec)| {
let mutation_p = xerp(1.0 / 32.0, 1.0, *p);
let mut mutated = mutate(&spec.dna, *seed, mutation_p);
let mutated_fitness = evaluate_reverb(&mut mutated);
(mutated, mutated_fitness)
specimen(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);
for i in 0..population_size {
if candidates[i].fitness > global_fitness {
global_fitness = candidates[i].fitness;
global_dna = candidates[i].dna.clone();
global_i = i;
improved = true;
}
if candidates[i].fitness > population[i].fitness
|| (temperature > 0.0
&& rng.f32()
< exp((candidates[i].fitness - population[i].fitness) / temperature))
{
std::mem::swap(&mut candidates[i], &mut population[i]);
accept += 1;
} else {
reject += 1;
}
}

//population.sort_unstable_by(|a, b| b.fitness.partial_cmp(&a.fitness).unwrap());

if improved || rounds % 1000 == 0 {
println!(
"Rounds {} (Candidates {}) Fitness {}",
"Rounds {} (Accept {}%) Fitness {} Specimen {} Temp {}",
rounds,
rounds * candidates_per_round,
fitness
accept as f32 * 100.0 / (accept + reject) as f32,
global_fitness,
global_i,
temperature,
);
}
if improved {
let mut delays = Vec::new();
let mut samples1 = Vec::new();
let mut samples2 = Vec::new();
for i in 0..dna.parameters() {
let delay = dna.parameter(i).value_f32().unwrap();
for i in 0..global_dna.parameters() {
let delay = global_dna.parameter(i).value_f32().unwrap();
let samples = round(delay * 44100.0) as i32;
if i < 8 {
if i < 16 {
samples1.push(samples);
} else {
samples2.push(samples);
}
println!("{}: {} ({})", dna.parameter(i).name(), delay, samples,);
print!(
" {}: {} ({})",
global_dna.parameter(i).name(),
delay,
samples,
);
if i % 2 == 1 {
println!();
}
delays.push(delay);
}
samples1.sort();
Expand Down
Loading

0 comments on commit 78937c1

Please sign in to comment.