mirror of
https://github.com/storytold/soundcraft.git
synced 2026-11-11 04:42:09 +00:00
Export only the MIDI notes a trimmed clip still plays
Found while testing crates/engine/src/io.rs:350-362 (export_midi) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
072e548ec8
commit
4740e8d4c4
2 files changed
+27
-2
No files matched your search
@@ -66,12 +66,15 @@ pub fn export_midi(e: &Engine, only: Option<&[TrackId]>) -> Result<Vec<u8>> {
|
||||
for c in t.clips() {
|
||||
if let ClipContent::Midi { sequence } = &c.content {
|
||||
let base = s.tempo.samples_to_ticks(c.start, s.sample_rate);
|
||||
for n in &sequence.notes {
|
||||
// Only what plays: notes starting inside the clip, cut at its end.
|
||||
let clip_end = s.tempo.samples_to_ticks(c.end(), s.sample_rate);
|
||||
for n in sequence.notes.iter().filter(|n| n.start.saturating_add(base) < clip_end) {
|
||||
let mut n = *n;
|
||||
n.start = n.start.saturating_add(base);
|
||||
n.length = n.length.min(clip_end - n.start).max(1);
|
||||
seq.notes.push(n);
|
||||
}
|
||||
for ev in &sequence.ctrls {
|
||||
for ev in sequence.ctrls.iter().filter(|ev| ev.tick.saturating_add(base) < clip_end) {
|
||||
let mut ev = *ev;
|
||||
ev.tick = ev.tick.saturating_add(base);
|
||||
seq.ctrls.push(ev);
|
||||
|
||||
@@ -28,3 +28,25 @@ fn midi_track_offset_moves_the_notes_like_moving_the_clip() {
|
||||
e.execute("edit.move_clips", &json!({"clip": clip, "by": 24_000})).unwrap();
|
||||
assert!(render(&e) == offset, "the offset must match moving the clip by 24000 samples");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn export_midi_leaves_out_what_a_trimmed_clip_no_longer_plays() {
|
||||
let mut e = Engine::default();
|
||||
let r = e.execute("track.new", &json!({"kind": "instrument", "name": "Trim", "instrument": "subtractive_synth"})).unwrap();
|
||||
let track = r["tracks"][0].as_u64().unwrap();
|
||||
let r = e.execute("midi.new_clip", &json!({"track": track, "start": 0, "length": 96_000})).unwrap();
|
||||
let clip = r["clip"].as_u64().unwrap();
|
||||
e.execute("midi.note_add", &json!({"clip": clip, "pitch": 60, "start_ticks": 0, "length_ticks": 960, "velocity": 100})).unwrap();
|
||||
e.execute("midi.note_add", &json!({"clip": clip, "pitch": 62, "start_ticks": 800, "length_ticks": 960, "velocity": 100})).unwrap();
|
||||
e.execute("midi.note_add", &json!({"clip": clip, "pitch": 64, "start_ticks": 2000, "length_ticks": 960, "velocity": 100})).unwrap();
|
||||
// 24000 samples is 960 ticks at 120 bpm.
|
||||
let id = e.session().tracks[0].clips()[0].id;
|
||||
e.session_mut().find_clip_mut(id).unwrap().trim_end_to(24_000);
|
||||
|
||||
let bytes = soundcraft_engine::io::export_midi(&e, None).unwrap();
|
||||
let smf = soundcraft_midi::read_smf(&bytes).unwrap();
|
||||
let notes = &smf.tracks[0].sequence.notes;
|
||||
assert_eq!(notes.len(), 2, "only notes starting inside the clip are exported");
|
||||
let late = notes.iter().find(|n| n.pitch == 62).unwrap();
|
||||
assert_eq!(late.start + late.length, 960, "a note running past the clip end is cut at it");
|
||||
}
|
||||
Reference in new issue
Block a user