Skip to content

Commit

Permalink
New note input
Browse files Browse the repository at this point in the history
  • Loading branch information
PetrGlad committed Oct 13, 2023
1 parent d4fc0a3 commit ccc7b37
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 16 deletions.
4 changes: 3 additions & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
The project's code and accompanying files are distributed under Apache License Version 2.0, with exception of
the music MIDI file samples that are published under Creative Commons Attribution-NonCommercial (BY-NC) license.
the music MIDI file samples and artwork that is is licensed under Attribution-NonCommercial 4.0 International.
To view a copy of this license, visit http://creativecommons.org/licenses/by-nc/4.0/

Dependencies of the project may have their own licenses that are different from the project's.

-------------------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions src/lane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ impl Lane {
.expect(&*format!("Cannot save to {}", &file_path.display()));
}

pub fn add_note(
&mut self,
time_range: (TransportTime, TransportTime),
pitch: Pitch,
level: Level,
) {
let ev = LaneEvent {
id: self.id_seq.fetch_add(1, SeqCst),
at: time_range.0,
event: LaneEventType::Note(Note {
pitch,
velocity: level,
duration: time_range.1 - time_range.0,
}),
};
let idx = self.events.partition_point(|x| x < &ev);
self.events.insert(idx, ev);
}

pub fn tape_cut(&mut self, time_selection: &TimeSelection) {
dbg!("tape_cut", time_selection);
self.version += 1;
Expand Down Expand Up @@ -303,3 +322,31 @@ pub fn to_midi_events(events: &Vec<LaneEvent>, usec_per_tick: u32) -> Vec<TrackE
}
midi_events
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn lane_load() {
let mut lane = Lane::default();
assert!(lane.events.is_empty());

let path = PathBuf::from("./target/test_lane_load.mid");
lane.save_to(&path);
lane.load_from(&path);
assert!(lane.events.is_empty());

let short = PathBuf::from("./test/files/short.mid");
lane.load_from(&short);
assert_eq!(lane.events.len(), 10);
lane.save_to(&path);

// The recorded SMD may have some additional system/heartbeat events,
// so comparing the sequence only after a save.
let mut lane_loaded = Lane::default();
lane_loaded.load_from(&path);
assert_eq!(lane_loaded.events.len(), 10);
assert_eq!(lane.events, lane_loaded.events);
}
}
112 changes: 97 additions & 15 deletions src/stave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use eframe::egui::{
};
use egui::Rgba;
use ordered_float::OrderedFloat;
use toml::value::Time;

use crate::engine::TransportTime;
use crate::lane::{
Expand Down Expand Up @@ -43,6 +44,18 @@ pub struct TimeSelection {
pub to: StaveTime,
}

impl TimeSelection {
pub fn is_empty(&self) -> bool {
self.to - self.from > 0
}
}

#[derive(Debug)]
pub struct NoteDraw {
time: TimeSelection,
pitch: Pitch,
}

#[derive(Debug, Default)]
pub struct NotesSelection {
selected: HashSet<EventId>,
Expand Down Expand Up @@ -84,6 +97,7 @@ pub struct Stave {
pub cursor_position: StaveTime,

pub time_selection: Option<TimeSelection>,
pub note_draw: Option<NoteDraw>,
pub note_selection: NotesSelection,
}

Expand All @@ -100,7 +114,7 @@ impl PartialEq for Stave {
const COLOR_SELECTED: Rgba = Rgba::from_rgb(0.2, 0.5, 0.55);
const COLOR_HOVERED: Rgba = COLOR_SELECTED;

struct StaveUiResponse {
pub struct StaveUiResponse {
response: Response,
pitch_hovered: Option<Pitch>,
time_hovered: Option<StaveTime>,
Expand All @@ -116,6 +130,7 @@ impl Stave {
view_rect: Rect::NOTHING,
cursor_position: 0,
time_selection: None,
note_draw: None,
note_selection: NotesSelection::default(),
}
}
Expand Down Expand Up @@ -259,6 +274,17 @@ impl Stave {
Rgba::from_rgba_unmultiplied(0.1, 0.7, 0.1, 0.7).into(),
);

if let Some(new_note) = &self.note_draw {
self.draw_note(
&painter,
64,
(new_note.time.from, new_note.time.to),
*key_ys.get(&new_note.pitch).unwrap(),
half_tone_step,
true,
);
}

StaveUiResponse {
response: ui.allocate_response(bounds.size(), Sense::click_and_drag()),
pitch_hovered,
Expand All @@ -274,13 +300,18 @@ impl Stave {

if let Some(note_id) = stave_response.note_hovered {
let clicked = ui.input(|i| i.pointer.button_clicked(PointerButton::Primary));
if (clicked) {
if clicked {
self.note_selection.toggle(&note_id);
}
}

let inner = stave_response.response;
self.update_time_selection(&inner);
self.update_note_draw(
&inner,
&stave_response.time_hovered,
&stave_response.pitch_hovered,
);
self.update_time_selection(&inner, &stave_response.time_hovered);
self.handle_commands(&inner);

inner
Expand Down Expand Up @@ -449,25 +480,76 @@ impl Stave {
);
}

fn update_time_selection(&mut self, response: &Response) {
fn update_time_selection(&mut self, response: &Response, time: &Option<StaveTime>) {
let drag_button = PointerButton::Primary;
let hover_pos = &response.hover_pos();
if response.clicked_by(drag_button) {
self.time_selection = None;
} else if response.drag_started_by(drag_button) {
let x = hover_pos.unwrap().x;
let time = self.time_from_x(x);
self.time_selection = Some(TimeSelection {
from: time,
to: time,
});
if let Some(time) = time {
self.time_selection = Some(TimeSelection {
from: *time,
to: *time,
});
}
} else if response.drag_released_by(drag_button) {
// Just documenting how it can be handled
} else if response.dragged_by(drag_button) {
if let Some(Pos2 { x, .. }) = hover_pos {
let time = self.time_from_x(*x);
let selection = self.time_selection.as_mut().unwrap();
selection.to = time;
if let Some(time) = time {
if let Some(selection) = &mut self.time_selection {
selection.to = *time;
}
}
}
}

// TODO Extract the drag procedure? See also time_selection.
fn update_note_draw(
&mut self,
response: &Response,
time: &Option<StaveTime>,
pitch: &Option<Pitch>,
) {
let drag_button = PointerButton::Middle;
if response.clicked_by(drag_button) {
self.note_draw = None;
} else if response.drag_started_by(drag_button) {
if let Some(time) = time {
if let Some(pitch) = pitch {
self.note_draw = Some(NoteDraw {
time: TimeSelection {
from: *time,
to: *time,
},
pitch: *pitch,
});
}
}
} else if response.drag_released_by(drag_button) {
dbg!("drag_released", &self.note_draw);
// TODO (implement) Add the note or CC to the lane.
if let Some(draw) = &mut self.note_draw {
if let Ok(track) = &mut self.track.try_write() {
if draw.pitch == PIANO_DAMPER_LINE {
// track.set_damper(); // TODO Need both: setting "on" and "off" range.
todo!();
} else if draw.time.is_empty() {
track.add_note(
(
draw.time.from as TransportTime,
draw.time.to as TransportTime,
),
draw.pitch,
64,
);
}
}
}
self.note_draw = None;
} else if response.dragged_by(drag_button) {
if let Some(time) = time {
if let Some(draw) = &mut self.note_draw {
draw.time.to = *time;
}
}
}
}
Expand Down
File renamed without changes.
Binary file added test/files/short.mid
Binary file not shown.

0 comments on commit ccc7b37

Please sign in to comment.