Files
bass-foundry/plugins/rivet/app/tests/midi.rs
T
nvrl f1366280ff
Check and Windows preview / check (patina) (push) Successful in 3m59s
Check and Windows preview / check (rivet) (push) Successful in 2m2s
Check and Windows preview / previews (push) Successful in 1m8s
Independent plugin release / plan (push) Successful in 7s
Check and Windows preview / windows (push) Successful in 2m41s
Independent plugin release / build (push) Successful in 3m0s
Independent plugin release / publish (push) Successful in 9s
feat(rivet): storyboard composition with flexible lengths and meters
Rivet 0.4.0 tells a motif as a story. A timeline of blocks with roles
(statement, answer, departure, return, rest) replaces tasks, pins and
branches. Rivet writes each block from a motif and joins the seams with
a cut, breath, pickup or approach.

- Motifs of 1-8 bars in a bin of 16; storyboards up to 32 bars.
- Meters 2/4, 3/4, 4/4, 5/4, 6/8, 7/8 and 12/8 for import, writing,
  audition and export. Phrygian, harmonic minor and Mixolydian scales.
- Per-block options, Another take and Keep; templates for new boards.
- Editor rebuilt: motif bin, timeline, large piano roll, inspector,
  tabs for generation, harmony and sound roles, 32-step undo/redo.
- Sessions from 0.1-0.3 open as a storyboard from their source motif.
2026-11-06 02:15:18 +01:00

125 lines
3.6 KiB
Rust

use rivet_app::{AudioEngine, Shared};
use rivet_core::{Note, Phrase, BAR, PPQ};
fn clip(pitch: u8, length: u32) -> Phrase {
Phrase {
length,
notes: vec![Note {
start: 0,
duration: length,
pitch,
velocity: 90,
..Default::default()
}],
}
}
#[test]
fn looping_and_mode_changes_release_and_retrigger_notes() {
let shared = Shared::default();
let mut a = AudioEngine::default();
a.player.set_sample_rate(8000.).unwrap();
shared.preview(&clip(60, PPQ)).unwrap();
a.begin_block(&shared);
let first = a.frame(120., true);
assert_eq!(first.on, Some((60, 90)));
assert_eq!(first.off, None);
let mut repeated = false;
for _ in 0..4001 {
let frame = a.frame(120., true);
if frame.off.is_some() {
assert_eq!(frame.off, Some(60));
assert_eq!(frame.on, Some((60, 90)));
repeated = true;
break;
}
}
assert!(repeated);
let muted = a.frame(120., false);
assert_eq!(muted.off, Some(60));
assert_eq!(muted.on, None);
assert_eq!(a.frame(120., true).on, Some((60, 90)));
shared.stop();
a.begin_block(&shared);
let stopped = a.frame(120., true);
assert_eq!(stopped.off, Some(60));
assert_eq!(stopped.on, None);
}
#[test]
fn adjacent_same_pitch_notes_have_separate_sample_accurate_attacks() {
let shared = Shared::default();
shared
.looping
.store(false, std::sync::atomic::Ordering::Release);
let mut a = AudioEngine::default();
a.player.set_sample_rate(8000.).unwrap();
let p = Phrase {
length: PPQ,
notes: vec![
Note {
start: 0,
duration: PPQ / 2,
pitch: 60,
velocity: 90,
..Default::default()
},
Note {
start: PPQ / 2,
duration: PPQ / 2,
pitch: 60,
velocity: 70,
..Default::default()
},
],
};
shared.preview(&p).unwrap();
a.begin_block(&shared);
let mut events = Vec::new();
for frame in 0i32..4003 {
let e = a.frame(120., true);
if e.off.is_some() || e.on.is_some() {
events.push((frame, e.off, e.on));
}
}
assert_eq!(events.len(), 3);
assert_eq!(events[0], (0, None, Some((60, 90))));
assert!((events[1].0 - 2000).abs() <= 1);
assert_eq!(events[1].1, Some(60));
assert_eq!(events[1].2, Some((60, 70)));
assert!((events[2].0 - 4000).abs() <= 1);
assert_eq!(events[2].1, Some(60));
assert_eq!(events[2].2, None);
}
#[test]
fn queued_audition_switches_at_the_next_bar_line_and_stop_cancels_it() {
let shared = Shared::default();
let mut a = AudioEngine::default();
a.player.set_sample_rate(8000.).unwrap();
shared.preview(&clip(60, 4 * BAR)).unwrap();
a.begin_block(&shared);
for _ in 0..100 {
a.frame(120., true);
}
a.end_block(&shared);
shared.audition(&clip(67, 4 * BAR)).unwrap();
a.begin_block(&shared);
let mut switched = None;
for index in 100i32..16003 {
let f = a.frame(120., true);
if f.on == Some((67, 90)) {
assert_eq!(f.off, Some(60));
switched = Some(index);
break;
}
}
assert!((switched.unwrap() - 16000).abs() <= 1);
a.end_block(&shared);
shared.audition(&clip(72, 2 * BAR)).unwrap();
a.begin_block(&shared);
shared.stop();
a.begin_block(&shared);
let f = a.frame(120., true);
assert_eq!(f.off, Some(67));
assert_eq!(f.on, None);
assert!(!a.player.is_playing());
}