Skip to content

Commit

Permalink
Add Benchmarks (#25)
Browse files Browse the repository at this point in the history
* add benchmarks using Criterion

- added cycle benches to test nested group parsing

* add rhythm and scripted rhythm benchmarks (create, clone & run)

* run benchmark on pull requests
  • Loading branch information
emuell authored Jun 27, 2024
1 parent 55261a8 commit 8620a12
Show file tree
Hide file tree
Showing 8 changed files with 442 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Benchmark

on:
pull_request:
branches: [ "master" ]

env:
CARGO_TERM_COLOR: always

jobs:
runBenchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: boa-dev/criterion-compare-action@v3
with:
defaultFeatures: false
features: "scripting"
branchName: ${{ github.base_ref }}
File renamed without changes.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ afplay = { git = "https://github.com/emuell/afplay", default-features = false, f
[dev-dependencies]
notify = { version = "^6.1" }
ctrlc = { version = "^3.4" }
criterion = { version = "^0.5" }

[profile.release]
debug = 1
Expand All @@ -48,6 +49,13 @@ scripting = ["mlua"]
player = ["crossbeam-channel", "afplay"]
default = ["scripting"]

[lib]
bench = false

[[bench]]
name = "benches"
harness = false

[[example]]
name = "play"
required-features = ["player"]
Expand Down
20 changes: 20 additions & 0 deletions benches/benches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use criterion::criterion_main;

// ---------------------------------------------------------------------------------------------

mod benchmarks;

// ---------------------------------------------------------------------------------------------

#[cfg(feature = "scripting")]
criterion_main!(
benchmarks::scripted::scripted, //
benchmarks::rhythm::rhythm,
benchmarks::cycle::cycle,
);

#[cfg(not(feature = "scripting"))]
criterion_main!(
benchmarks::rhythm::rhythm, //
benchmarks::cycle::cycle,
);
29 changes: 29 additions & 0 deletions benches/benchmarks/cycle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use criterion::{criterion_group, Criterion};

use afseq::tidal::Cycle;

// ---------------------------------------------------------------------------------------------

pub fn parser(c: &mut Criterion) {
let mut group = c.benchmark_group("Cycle Parser");
group.bench_function("Mildly complex", |b| {
b.iter(|| Cycle::from("[a b c {a b}%4 ! !]").unwrap())
});
group.bench_function("Pretty complex", |b| {
b.iter(|| Cycle::from("<[{x@2 {x@3}}%4 x@3 x@4 ~!4] bd(3, 5) [[x@2 x@3 x@3]!2]>").unwrap())
});
group.bench_function("Very complex", |b| {
b.iter(|| Cycle::from("<[{x@2 {x@3}}%4 [0@2 <7 5>@2 3@2 5@1 0@1 3@2 <[7 2 3] [3 2 0]>@6] x@4 ~!4] bd(3, 5) [[x@2 x@3 x@3]!2]>").unwrap())});
group.bench_function("Very Nested", |b| {
b.iter(|| Cycle::from("{<[{[<>]}]>}{[]}{[[[]]]}").unwrap())
});
group.finish();
}

// ---------------------------------------------------------------------------------------------

criterion_group! {
name = cycle;
config = Criterion::default().sample_size(50);
targets = parser
}
4 changes: 4 additions & 0 deletions benches/benchmarks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub(crate) mod cycle;
pub(crate) mod rhythm;
#[cfg(feature = "scripting")]
pub(crate) mod scripted;
190 changes: 190 additions & 0 deletions benches/benchmarks/rhythm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
use criterion::{black_box, criterion_group, Criterion};

use afseq::prelude::*;

// ---------------------------------------------------------------------------------------------

fn create_phrase() -> Phrase {
let beat_time = BeatTimeBase {
samples_per_sec: 44100,
beats_per_min: 130.0,
beats_per_bar: 4,
};

let kick_cycle =
new_cycle_event("bd [~ bd] ~ ~ bd [~ bd] _ ~ bd [~ bd] ~ ~ bd [~ bd] [_ bd2] [~ bd _ ~]")
.unwrap()
.with_mappings(&[
("bd", vec![new_note("c4")]),
("bd2", vec![new_note(("c4", None, 0.5))]),
]);
let kick_pattern = beat_time.every_nth_beat(16.0).trigger(kick_cycle);

let snare_pattern = beat_time
.every_nth_beat(2.0)
.with_offset(BeatTimeStep::Beats(1.0))
.trigger(new_note_event("C_5"));

let hihat_pattern = beat_time
.every_nth_sixteenth(2.0)
.trigger(new_note_event("C_5").mutate({
let mut step = 0;
move |mut event| {
if let Event::NoteEvents(notes) = &mut event {
for note in notes.iter_mut().flatten() {
note.volume = 1.0 / (step + 1) as f32;
step += 1;
if step >= 3 {
step = 0;
}
}
}
event
}
}));

let hihat_pattern2 = beat_time
.every_nth_sixteenth(2.0)
.with_offset(BeatTimeStep::Sixteenth(1.0))
.trigger(new_note_event("C_5").mutate({
let mut vel_step = 0;
let mut note_step = 0;
move |mut event| {
if let Event::NoteEvents(notes) = &mut event {
for note in notes.iter_mut().flatten() {
note.volume = 1.0 / (vel_step + 1) as f32 * 0.5;
vel_step += 1;
if vel_step >= 3 {
vel_step = 0;
}
note.note = Note::from((Note::C4 as u8) + 32 - note_step);
note_step += 1;
if note_step >= 32 {
note_step = 0;
}
}
}
event
}
}));

let hihat_rhythm = Phrase::new(
beat_time,
vec![hihat_pattern, hihat_pattern2],
BeatTimeStep::Bar(4.0),
);

let bass_notes = Scale::try_from((Note::C5, "aeolian")).unwrap().notes();
let bass_pattern = beat_time
.every_nth_eighth(1.0)
.with_pattern([1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1].to_pattern())
.trigger(new_note_event_sequence(vec![
new_note((bass_notes[0], None, 0.5)),
new_note((bass_notes[2], None, 0.5)),
new_note((bass_notes[3], None, 0.5)),
new_note((bass_notes[0], None, 0.5)),
new_note((bass_notes[2], None, 0.5)),
new_note((bass_notes[3], None, 0.5)),
new_note((bass_notes[6].transposed(-12), None, 0.5)),
]));

let synth_pattern = beat_time
.every_nth_bar(4.0)
.trigger(new_polyphonic_note_sequence_event(vec![
vec![
new_note(("C 4", None, 0.3)),
new_note(("D#4", None, 0.3)),
new_note(("G 4", None, 0.3)),
],
vec![
new_note(("C 4", None, 0.3)),
new_note(("D#4", None, 0.3)),
new_note(("F 4", None, 0.3)),
],
vec![
new_note(("C 4", None, 0.3)),
new_note(("D#4", None, 0.3)),
new_note(("G 4", None, 0.3)),
],
vec![
new_note(("C 4", None, 0.3)),
new_note(("D#4", None, 0.3)),
new_note(("A#4", None, 0.3)),
],
]));

let fx_pattern = beat_time
.every_nth_seconds(8.0)
.trigger(new_polyphonic_note_sequence_event(vec![
vec![new_note(("C 4", None, 0.2)), None, None],
vec![None, new_note(("C 4", None, 0.2)), None],
vec![None, None, new_note(("F 4", None, 0.2))],
]));

Phrase::new(
beat_time,
vec![
RhythmSlot::from(kick_pattern),
RhythmSlot::from(snare_pattern),
RhythmSlot::from(hihat_rhythm),
RhythmSlot::from(bass_pattern),
RhythmSlot::from(fx_pattern),
RhythmSlot::from(synth_pattern),
],
BeatTimeStep::Bar(8.0),
)
}

// ---------------------------------------------------------------------------------------------

pub fn create(c: &mut Criterion) {
let mut group = c.benchmark_group("Rust Phrase");
group.bench_function("Create", |b| {
b.iter(|| {
black_box(create_phrase());
})
});
group.finish();
}

pub fn clone(c: &mut Criterion) {
let mut group = c.benchmark_group("Rust Phrase");
let phrase = create_phrase();
group.bench_function("Clone", |b| {
b.iter(|| {
black_box(phrase.clone());
})
});
group.finish();
}

pub fn run(c: &mut Criterion) {
let event_count = 5000;
let mut group = c.benchmark_group("Rust Phrase");
group.measurement_time(std::time::Duration::from_secs(10));
let phrase = create_phrase();
group.throughput(criterion::Throughput::Elements(event_count));
group.bench_function("Run", |b| {
b.iter(|| {
let sample_time = SampleTime::MAX;
let mut phrase = phrase.clone();
let mut num_events = 0;
while let Some(event) = phrase.run_until_time(sample_time) {
black_box(event);
num_events += 1;
if num_events >= event_count {
break;
}
}
})
});
group.finish();
}

// ---------------------------------------------------------------------------------------------

criterion_group! {
name = rhythm;
config = Criterion::default().sample_size(50);
targets = create, clone, run
}
Loading

0 comments on commit 8620a12

Please sign in to comment.