Skip to content

Commit

Permalink
A damper editing prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrGlad committed Oct 15, 2023
1 parent faf4ff3 commit e1e8823
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 66 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ I use Pianoteq, but that is a commercial product.

- [ ] Editing sustain events.
- [ ] Note input (with mouse).
- [ ] Automatically create a snapshot on an edit command.
- [ ] Automatically create an undo snapshot on every edit command.
- [ ] Do not save a new snapshot when there are no changes.
- [ ] Time marks on stave (minute:second).
- [ ] Consider TransportTime to be signed (see also StaveTime). There are too many conversions forth and back. We can
- [ ] Time marks on stave (minute:second from the beginning).
- [ ] Consider TransportTime to be signed (see also StaveTime). There are too many conversions forth and back. Times can
be restricted to positives only in the engine.
- [ ] Have a separate edit-position and play-start cursors, so it is easier to jump back and listen to the modified
version.
- [ ] Time bookmarks.
restrict time to positives only in the engine.
- [ ] Find a way to separate actions from view logic with egui. It looks too messy now.
- [ ] Minimize use of unwrap. The biggest contention currently is event data shared between engine and stave.
- [ ] Multi-track UI (for snippets, flight recorder, and copy/paste buffer). Can show only one at a time, though. Use
Expand Down
1 change: 1 addition & 0 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub type VersionId = i64;
111 changes: 75 additions & 36 deletions src/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ use std::path::PathBuf;
use std::sync::atomic::AtomicU64;
use std::sync::atomic::Ordering::SeqCst;

use midly::num::u4;
use midly::num::{u4, u7};
use midly::{MidiMessage, TrackEvent, TrackEventKind};

use crate::common::VersionId;
use crate::engine::TransportTime;
use crate::midi;
use crate::util::{is_ordered, range_contains};
use crate::{midi, util};

pub type Pitch = u8;
pub type ControllerId = u8;
Expand Down Expand Up @@ -102,9 +104,10 @@ impl TimeSelection {

#[derive(Debug, Default)]
pub struct Lane {
// Notes should always be ordered by start time ascending. Not enforced yet.
/* Events should always be kept ordered by start time ascending.
This is a requirement of TrackSource. */
pub events: Vec<LaneEvent>,
version: u64,
pub version: VersionId,
id_seq: AtomicU64,
}

Expand Down Expand Up @@ -151,47 +154,99 @@ impl Lane {
duration: time_range.1 - time_range.0,
}),
};
self.insert_event(ev);
}

fn commit(&mut self) {
assert!(is_ordered(&self.events));
self.version += 1;
}

pub fn insert_event(&mut self, ev: LaneEvent) {
let idx = self.events.partition_point(|x| x < &ev);
self.events.insert(idx, ev);
assert!(is_ordered(&self.events));
self.commit();
}

pub fn set_damper_range(&mut self, time_range: (TransportTime, TransportTime), on: bool) {
pub fn set_damper_to(&mut self, time_range: util::Range<TransportTime>, on: bool) {
dbg!("set_damper_range", time_range, on);
let mut i = 0;
loop {
if let Some(ev) = self.events.get(i) {
todo!();
i += 0;
if range_contains(time_range, ev.at) {
if let LaneEventType::Controller(ev) = &ev.event {
if ev.controller_id == MIDI_CC_SUSTAIN_ID {
self.events.remove(i);
continue;
}
}
}
i += 1;
} else {
break;
}
}
// TODO Do not change value(s) at the ens of the range if they already match.
// This implementation flips to the inverse at the end which is usually undesirable.
if on {
let on_ev = self.sustain_on_event(&time_range.0);
self.insert_event(on_ev);
let off_ev = self.sustain_off_event(&time_range.1);
self.insert_event(off_ev);
} else {
let off_ev = self.sustain_off_event(&time_range.0);
self.insert_event(off_ev);
let on_ev = self.sustain_on_event(&time_range.1);
self.insert_event(on_ev);
}
self.commit();
}

fn sustain_off_event(&mut self, at: &TransportTime) -> LaneEvent {
LaneEvent {
id: next_id(&mut self.id_seq),
at: *at,
event: LaneEventType::Controller(ControllerSetValue {
controller_id: MIDI_CC_SUSTAIN_ID,
value: 0,
}),
}
}

fn sustain_on_event(&mut self, at: &TransportTime) -> LaneEvent {
LaneEvent {
id: next_id(&mut self.id_seq),
at: *at,
event: LaneEventType::Controller(ControllerSetValue {
controller_id: MIDI_CC_SUSTAIN_ID,
value: u7::max_value().as_int() as Level,
}),
}
}

pub fn tape_cut(&mut self, time_selection: &TimeSelection) {
dbg!("tape_cut", time_selection);
self.version += 1;
self.events.retain(|ev| !time_selection.contains(ev.at));
self.shift_events(
&|ev| time_selection.before(ev.at),
-(time_selection.length() as i64),
);
assert!(is_ordered(&self.events));
self.commit();
}

pub fn tape_insert(&mut self, time_selection: &TimeSelection) {
dbg!("tape_insert", time_selection);
self.version += 1;
self.shift_events(
&|ev| time_selection.after_start(ev.at),
time_selection.length() as i64,
);
self.commit();
}

pub fn shift_tail(&mut self, at: &TransportTime, dt: i64) {
dbg!("tail_shift", at, dt);
self.version += 1;
self.shift_events(&|ev| &ev.at > at, dt);
assert!(is_ordered(&self.events));
self.commit();
}

pub fn shift_events<Pred: Fn(&LaneEvent) -> bool>(&mut self, selector: &Pred, d: i64) {
Expand All @@ -206,7 +261,7 @@ impl Lane {
}
// Should do this only for out-of-order events. Brute-forcing for now.
self.events.sort();
assert!(is_ordered(&self.events));
self.commit();
}

// Is it worth it?
Expand All @@ -228,11 +283,15 @@ impl Lane {
}

pub fn delete_events(&mut self, event_ids: &HashSet<EventId>) {
self.version += 1;
self.events.retain(|ev| !event_ids.contains(&ev.id));
self.commit();
}
}

fn next_id(id_seq: &mut AtomicU64) -> EventId {
id_seq.fetch_add(1, SeqCst)
}

pub fn to_lane_events(
id_seq: &mut AtomicU64,
events: Vec<TrackEvent<'static>>,
Expand All @@ -254,7 +313,7 @@ pub fn to_lane_events(
match on {
Some((t, MidiMessage::NoteOn { key, vel })) => {
lane_events.push(LaneEvent {
id: id_seq.fetch_add(1, SeqCst),
id: next_id(id_seq),
at: t,
event: LaneEventType::Note(Note {
duration: at - t,
Expand Down Expand Up @@ -340,15 +399,6 @@ pub fn to_midi_events(events: &Vec<LaneEvent>, usec_per_tick: u32) -> Vec<TrackE
midi_events
}

pub fn is_ordered<T: Ord>(seq: &Vec<T>) -> bool {
for (a, b) in seq.iter().zip(seq.iter().skip(1)) {
if a > b {
return false;
}
}
true
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -375,15 +425,4 @@ mod tests {
assert_eq!(lane_loaded.events.len(), 10);
assert_eq!(lane.events, lane_loaded.events);
}

#[test]
fn check_is_ordered() {
assert!(is_ordered::<u64>(&vec![]));
assert!(is_ordered(&vec![0]));
assert!(!is_ordered(&vec![3, 2]));
assert!(is_ordered(&vec![2, 3]));
assert!(is_ordered(&vec![2, 2]));
assert!(!is_ordered(&vec![2, 3, 1]));
assert!(is_ordered(&vec![2, 3, 3]));
}
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::track_source::TrackSource;

mod app;
mod audio_setup;
mod common;
mod config;
mod engine;
mod events;
Expand All @@ -20,6 +21,7 @@ mod midi_vst;
mod project;
mod stave;
mod track_source;
mod util;

pub type Pix = f32;

Expand Down
2 changes: 1 addition & 1 deletion src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::path::PathBuf;

use toml::from_str;

pub type VersionId = i64;
use crate::common::VersionId;

pub struct Project {
pub directory: PathBuf,
Expand Down
Loading

0 comments on commit e1e8823

Please sign in to comment.