Skip to content

Commit

Permalink
Note edit commands
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrGlad committed Oct 9, 2023
1 parent b43017b commit 268b75d
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 46 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ Off grid MIDI editor with following goals:

* Not a DAW (would like it to be but do not have enough time for that).
* Do not care (much) about measures. Primarily aimed at piano real recordings without strict tempo/bars.
* A feature absent in _any_ other midi editor I could get my hands on (both commercial and free ones): removing a note
can shift the tail of the track left to fill the gap. In some editors this is actually doable but cumbersome at best.
* A feature absent in other midi editors I could get my hands on (both commercial and free ones): removing a piece
of MIDI recording as one can remove a time fragment from PCM recording. For some reason DAW authors insist on handling
MIDI recordings differently from PCM sound recordings. In some editors this is actually doable but cumbersome at best.
* Playing/editing very long (up to about 20K of events) files.
Those files are usually recordings of real performances (e.g. from MIDI keyboard).
* Comfortable workflow with keyboard.
Expand Down Expand Up @@ -43,6 +44,8 @@ I use Pianoteq, but that is a commercial product.
- [ ] Multi-track UI (for snippets and copy/paste buffer).
- [ ] Time marks on stave.
- [ ] Time bookmarks.
- [ ] Consider TransportTime to be signed (see also StaveTime). There are too many conversions forth and back. We can
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.
- [x] Note selection.
Expand Down
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use eframe::{self, egui, CreationContext};
use egui_extras::{Size, StripBuilder};

use crate::engine::{Engine, EngineCommand, StatusEvent, TransportTime};
use crate::lane::Lane;
use crate::project::Project;
use crate::stave::{Stave, StaveTime};
use crate::track::Lane;

enum Message {
UpdateTransportTime(TransportTime),
Expand Down
2 changes: 1 addition & 1 deletion src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use midly::MidiMessage;
use vst::event::Event;
use vst::plugin::Plugin;

use crate::lane::MIDI_CC_SUSTAIN;
use crate::midi_vst::Vst;
use crate::track::MIDI_CC_SUSTAIN;

/// uSecs from the start.
pub type TransportTime = u64;
Expand Down
5 changes: 3 additions & 2 deletions src/track.rs → src/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,17 +173,18 @@ impl Lane {
}
}

// Is it worth it?
pub fn edit_events<
'a,
T: 'a,
Selector: Fn(&'a mut LaneEvent) -> Option<&'a mut T>,
Action: Fn(&'a mut T),
>(
events: &'a mut Vec<LaneEvent>,
&'a mut self,
selector: &Selector,
action: &Action,
) {
for ev in events {
for ev in &mut self.events {
if let Some(x) = selector(ev) {
action(x);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ use eframe::{egui, Theme};

use crate::app::EmApp;
use crate::config::Config;
use crate::lane::Lane;
use crate::midi::SmfSource;
use crate::project::Project;
use crate::track::Lane;
use crate::track_source::TrackSource;

mod app;
mod audio_setup;
mod config;
mod engine;
mod events;
mod lane;
mod midi;
mod midi_vst;
mod project;
mod stave;
mod track;
mod track_source;

pub type Pix = f32;
Expand Down
2 changes: 1 addition & 1 deletion src/midi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use midly::MidiMessage::Controller;
use midly::{Format, Header, MidiMessage, Smf, Timing, Track, TrackEvent};

use crate::engine::{EngineEvent, EventSource, TransportTime};
use crate::track::{ChannelId, ControllerId, Level, Pitch};
use crate::lane::{ChannelId, ControllerId, Level, Pitch};

pub struct SmfSource {
events: Vec<TrackEvent<'static>>,
Expand Down
100 changes: 67 additions & 33 deletions src/stave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ use eframe::egui::{
};
use egui::Rgba;
use ordered_float::OrderedFloat;
use wmidi::Velocity;

use crate::engine::TransportTime;
use crate::track::{
use crate::lane::{
switch_cc_on, EventId, Lane, LaneEvent, LaneEventType, Level, Note, Pitch, MIDI_CC_SUSTAIN,
};
use crate::{track, Pix};
use crate::{lane, Pix};

pub type StaveTime = i64;

Expand Down Expand Up @@ -79,7 +80,9 @@ impl NoteView {
const PIANO_LOWEST_KEY: Pitch = 21;
const PIANO_KEY_COUNT: Pitch = 88;
const PIANO_DAMPER_LINE: Pitch = PIANO_LOWEST_KEY - 1;
const PIANO_KEY_LINES: Range<Pitch> = (PIANO_LOWEST_KEY - 1)..(PIANO_LOWEST_KEY + PIANO_KEY_COUNT);
const PIANO_KEY_LINES: Range<Pitch> = PIANO_LOWEST_KEY..(PIANO_LOWEST_KEY + PIANO_KEY_COUNT);
// Lines including controller values placeholder
const STAVE_KEY_LINES: Range<Pitch> = (PIANO_LOWEST_KEY - 1)..(PIANO_LOWEST_KEY + PIANO_KEY_COUNT);

fn key_line_ys(view_y_range: &Rangef, pitches: Range<Pitch>) -> (BTreeMap<Pitch, Pix>, Pix) {
let mut lines = BTreeMap::new();
Expand Down Expand Up @@ -125,9 +128,9 @@ fn to_transport_time(value: StaveTime) -> TransportTime {
value.max(0) as TransportTime
}

impl From<&TimeSelection> for track::TimeSelection {
impl From<&TimeSelection> for lane::TimeSelection {
fn from(value: &TimeSelection) -> Self {
track::TimeSelection {
lane::TimeSelection {
from: to_transport_time(value.from),
to: to_transport_time(value.to),
}
Expand Down Expand Up @@ -234,7 +237,7 @@ impl Stave {
.show(ui, |ui| {
let bounds = ui.available_rect_before_wrap();
self.view_rect = bounds;
let (key_ys, half_tone_step) = key_line_ys(&bounds.y_range(), PIANO_KEY_LINES);
let (key_ys, half_tone_step) = key_line_ys(&bounds.y_range(), STAVE_KEY_LINES);
let mut pitch_hovered = None;
let mut time_hovered = None;
let pointer_pos = ui.input(|i| i.pointer.hover_pos());
Expand Down Expand Up @@ -399,46 +402,77 @@ impl Stave {

// Note edits
if response.ctx.input(|i| i.key_pressed(Key::H)) {
let mut track = self.track.write().expect("Cannot write to track.");
// TODO Shorten note
Lane::edit_events(
&mut track.events,
&(|ev| {
if self.note_selection.contains(ev) {
if let LaneEventType::Note(note) = &mut ev.event {
Some(note)
} else {
None
}
} else {
None
}
self.edit_selected_notes(
&(|note| {
note.duration = note
.duration
.checked_sub(Stave::KEYBOARD_TIME_STEP as TransportTime)
.unwrap_or(0);
}),
&(|note| note.duration += 123), // TODO Implement
);
}
if response.ctx.input(|i| i.key_pressed(Key::L)) {
let mut track = self.track.write().expect("Cannot write to track.");
// TODO Extend note
self.edit_selected_notes(
&(|note| {
note.duration = note
.duration
.checked_add(Stave::KEYBOARD_TIME_STEP as TransportTime)
.unwrap_or(0);
}),
);
}
if response.ctx.input(|i| i.key_pressed(Key::I)) {
let mut track = self.track.write().expect("Cannot write to track.");
// TODO Move note a half tone up
if response.ctx.input(|i| i.key_pressed(Key::U)) {
self.edit_selected_notes(
&(|note| {
if PIANO_KEY_LINES.contains(&(note.pitch + 1)) {
note.pitch += 1;
}
}),
);
}
if response.ctx.input(|i| i.key_pressed(Key::K)) {
let mut track = self.track.write().expect("Cannot write to track.");
// TODO Move note a half tone down
if response.ctx.input(|i| i.key_pressed(Key::J)) {
self.edit_selected_notes(
&(|note| {
if PIANO_KEY_LINES.contains(&(note.pitch - 1)) {
note.pitch -= 1;
}
}),
);
}
if response.ctx.input(|i| i.key_pressed(Key::I)) {
let mut track = self.track.write().expect("Cannot write to track.");
// TODO Increase velocity
self.edit_selected_notes(
&(|note| {
note.velocity = note.velocity.checked_add(1).unwrap_or(Level::MAX);
}),
);
}
if response.ctx.input(|i| i.key_pressed(Key::K)) {
let mut track = self.track.write().expect("Cannot write to track.");
// TODO Decrease velocity
self.edit_selected_notes(
&(|note| {
note.velocity = note.velocity.checked_sub(1).unwrap_or(Level::MIN);
}),
);
}
}

pub fn edit_selected_notes<Action: Fn(&mut Note)>(&mut self, action: &Action) {
let mut track = self.track.write().expect("Cannot write to track.");
track.edit_events(
&(|ev| {
if self.note_selection.contains(ev) {
if let LaneEventType::Note(note) = &mut ev.event {
Some(note)
} else {
None
}
} else {
None
}
}),
action,
);
}

fn update_time_selection(&mut self, response: &Response) {
let drag_button = PointerButton::Primary;
let hover_pos = &response.hover_pos();
Expand Down
8 changes: 4 additions & 4 deletions src/track_source.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::engine::{EngineEvent, EventSource, TransportTime};
use crate::lane::{Lane, LaneEventType};
use crate::midi::{controller_set, note_off, note_on};
use crate::track::{Lane, LaneEventType};
use std::collections::BinaryHeap;
use std::sync::{Arc, RwLock};

Expand Down Expand Up @@ -93,8 +93,8 @@ impl EventSource for TrackSource {
#[cfg(test)]
mod tests {
use super::*;
use crate::track;
use crate::track::LaneEvent;
use crate::lane;
use crate::lane::LaneEvent;

#[test]
fn empty_lane() {
Expand All @@ -113,7 +113,7 @@ mod tests {
lane.events.push(LaneEvent {
id: 13,
at: 1000,
event: LaneEventType::Note(track::Note {
event: LaneEventType::Note(lane::Note {
pitch: 55,
velocity: 55,
duration: 12,
Expand Down

0 comments on commit 268b75d

Please sign in to comment.