From a0e2ce83da692fbd9683132fd99793250e9765b6 Mon Sep 17 00:00:00 2001 From: subalterngames Date: Thu, 7 Dec 2023 12:10:32 -0500 Subject: [PATCH] compiles --- common/src/lib.rs | 2 - common/src/music.rs | 3 + common/src/selectable.rs | 24 ----- common/src/selection.rs | 22 ++--- data/text.csv | 8 +- io/src/lib.rs | 3 + io/src/piano_roll/piano_roll_panel.rs | 16 +-- io/src/snapshot.rs | 1 + render/src/panels.rs | 2 +- render/src/piano_roll_panel.rs | 98 +++++++++++-------- render/src/piano_roll_panel/multi_track.rs | 2 +- render/src/piano_roll_panel/top_bar.rs | 11 ++- render/src/piano_roll_panel/viewable_notes.rs | 65 +++++------- text/src/lib.rs | 20 +++- 14 files changed, 137 insertions(+), 140 deletions(-) delete mode 100644 common/src/selectable.rs diff --git a/common/src/lib.rs b/common/src/lib.rs index 9f4395dd..f2b9da47 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -43,9 +43,7 @@ use view::View; mod edit_mode; pub mod music_panel_field; pub use edit_mode::*; -mod selectable; mod selection; -pub use selectable::Selectable; pub use selection::Selection; mod piano_roll_mode; pub use piano_roll_mode::PianoRollMode; diff --git a/common/src/music.rs b/common/src/music.rs index 3ab9ec98..a656c364 100644 --- a/common/src/music.rs +++ b/common/src/music.rs @@ -8,6 +8,9 @@ pub struct Music { pub midi_tracks: Vec, /// The index of the selected track. pub selected: Option, + /// If true, at least one note or effect has been changed. + #[serde(skip_serializing, skip_deserializing)] + pub dirty: bool, } impl Music { diff --git a/common/src/selectable.rs b/common/src/selectable.rs deleted file mode 100644 index 25d0b691..00000000 --- a/common/src/selectable.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::{Effect, Note}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub enum Selectable { - Effect(Effect), - Note(Note), -} - -impl Selectable { - pub fn get_start_time(&self) -> u64 { - match self { - Self::Effect(effect) => effect.time, - Self::Note(note) => note.start, - } - } - - pub fn get_end_time(&self) -> u64 { - match self { - Self::Effect(effect) => effect.time, - Self::Note(note) => note.end, - } - } -} diff --git a/common/src/selection.rs b/common/src/selection.rs index 9bb3f2ab..8b75d392 100644 --- a/common/src/selection.rs +++ b/common/src/selection.rs @@ -21,13 +21,12 @@ impl Selection { /// Returns the selected notes and effects. pub fn get_selection<'a>(&self, music: &'a Music) -> Option<(Vec<&'a Note>, Vec<&'a Effect>)> { - match music.get_selected_track() { - None => None, - Some(track) => Some(( + music.get_selected_track().map(|track| { + ( self.notes.iter().map(|i| &track.notes[*i]).collect(), self.effects.iter().map(|i| &track.effects[*i]).collect(), - )), - } + ) + }) } /// Returns mutable selected notes and effects. @@ -187,10 +186,11 @@ impl Selection { pub fn get_dt(&self, music: &Music) -> Option<(u64, u64)> { match self.get_events(music) { Some(events) => match events.iter().map(|s| s.get_start_time()).min() { - Some(min) => match events.iter().map(|s| s.get_end_time()).max() { - Some(max) => Some((min, max)), - None => None, - }, + Some(min) => events + .iter() + .map(|s| s.get_end_time()) + .max() + .map(|max| (min, max)), None => None, }, None => None, @@ -213,7 +213,7 @@ impl Selection { .map(|(index, effect)| Event::Effect { effect, index }), ); // Sort the selectables by start time. - events.sort_by(|a, b| a.get_start_time().cmp(&b.get_start_time())); + events.sort_by_key(|e| e.get_start_time()); Some(events) } None => None, @@ -237,7 +237,7 @@ impl Selection { .enumerate() .map(|(index, effect)| Event::Effect { effect, index }), ); - events.sort_by(|a, b| a.get_start_time().cmp(&b.get_start_time())); + events.sort_by_key(|e| e.get_start_time()); Some(events) } None => None, diff --git a/data/text.csv b/data/text.csv index 66dd3f4a..41870f5f 100644 --- a/data/text.csv +++ b/data/text.csv @@ -494,4 +494,10 @@ LINKS_PANEL_INPUT_TTS_2,\0 to open an invite link to the Cacophony Discord serve LINKS_PANEL_INPUT_TTS_3,\0 to open an Cacophony repo. LINKS_PANEL_INPUT_TTS_4,\0 to close this panel. EXPORT_PANEL_APPENDING_DECAY,Appending decay... -EXPORT_PANEL_WRITING,Writing to disk... \ No newline at end of file +EXPORT_PANEL_WRITING,Writing to disk... +EFFECT_TYPE_CHORUS,Chorus +EFFECT_TYPE_PAN,Pan +EFFECT_TYPE_REVERB,Reverb +EFFECT_TYPE_PITCH_BEND,Pitch Bend +EFFECT_TYPE_CHANNEL_PRESSURE,Channel Pressure +EFFECT_TYPE_POLYPHONIC_KEY_PRESSURE,Aftertouch \ No newline at end of file diff --git a/io/src/lib.rs b/io/src/lib.rs index 668a20c5..9d637945 100644 --- a/io/src/lib.rs +++ b/io/src/lib.rs @@ -225,6 +225,9 @@ impl IO { return false; } + // Mark the music as not dirty. + state.music.dirty = false; + // Alphanumeric input. if state.input.alphanumeric_input { // Get the focused panel. diff --git a/io/src/piano_roll/piano_roll_panel.rs b/io/src/piano_roll/piano_roll_panel.rs index 3e485018..6479bb38 100644 --- a/io/src/piano_roll/piano_roll_panel.rs +++ b/io/src/piano_roll/piano_roll_panel.rs @@ -18,14 +18,16 @@ enum CopiedEvent { Note(Note), } -impl CopiedEvent { - fn from(event: Event<'_>) -> Self { - match event { - Event::Effect { effect, index: _ } => Self::Effect(effect.clone()), - Event::Note { note, index: _ } => Self::Note(note.clone()), +impl From> for CopiedEvent { + fn from(value: Event) -> Self { + match value { + Event::Effect { effect, index: _ } => Self::Effect(*effect), + Event::Note { note, index: _ } => Self::Note(*note), } } +} +impl CopiedEvent { fn get_start_time(&self) -> u64 { match self { Self::Effect(effect) => effect.time, @@ -458,12 +460,12 @@ impl Panel for PianoRollPanel { for event in self.copy_buffer.iter() { match event { CopiedEvent::Effect(effect) => { - let mut effect = effect.clone(); + let mut effect = *effect; effect.time += state.time.cursor; track.effects.push(effect); } CopiedEvent::Note(note) => { - let mut note = note.clone(); + let mut note = *note; let dt = note.end - note.start; note.start = (note.start - min_time) + state.time.cursor; note.end = note.start + dt; diff --git a/io/src/snapshot.rs b/io/src/snapshot.rs index d8fefc6a..b431c2f3 100644 --- a/io/src/snapshot.rs +++ b/io/src/snapshot.rs @@ -49,6 +49,7 @@ impl Snapshot { /// - `from_state` The initial state of the delta. This is usually a clone of a `State` prior to modifying the primary `State`. /// - `to_state` The final state of the delta. This is a reference to the primary `State`. pub fn from_states(from_state: State, to_state: &mut State) -> Self { + to_state.music.dirty = true; Self { from_state: Some(from_state), to_state: Some(to_state.clone()), diff --git a/render/src/panels.rs b/render/src/panels.rs index be8cc8f1..54d4712f 100644 --- a/render/src/panels.rs +++ b/render/src/panels.rs @@ -108,6 +108,6 @@ impl Panels { self.quit_panel.popup.late_update(state, renderer); self.links_panel.popup.late_update(state, renderer); self.main_menu.late_update(renderer, conn); - self.piano_roll_panel.late_update(state, renderer); + self.piano_roll_panel.late_update(state, conn, renderer); } } diff --git a/render/src/piano_roll_panel.rs b/render/src/piano_roll_panel.rs index 02ddb998..3d031726 100644 --- a/render/src/piano_roll_panel.rs +++ b/render/src/piano_roll_panel.rs @@ -7,7 +7,7 @@ mod multi_track; mod top_bar; mod viewable_notes; mod volume; -use common::{SelectMode, State, U64orF32, NOTE_NAMES, PPQ_U}; +use common::{State, U64orF32, NOTE_NAMES, PPQ_U}; use hashbrown::HashSet; use multi_track::MultiTrack; use text::ppq_to_string; @@ -43,6 +43,8 @@ pub struct PianoRollPanel { time_horizontal_line_y: f32, /// The bottom y coordinates for time lines in single- and multi- track modes. time_line_bottoms: [f32; 2], + /// The notes currently in the viewport. + viewable_notes: ViewableNotes, } impl PianoRollPanel { @@ -108,10 +110,23 @@ impl PianoRollPanel { volume, multi_track, time_line_bottoms, + viewable_notes: ViewableNotes::default(), } } - pub fn late_update(&mut self, state: &State, renderer: &Renderer) { + pub fn late_update(&mut self, state: &State, conn: &Conn, renderer: &Renderer) { + if state.music.dirty { + let (_, focus) = self.get_panel_and_focus(state); + let dt = Self::get_view_dt(state, conn).map(U64orF32::from); + self.viewable_notes = ViewableNotes::new( + self.piano_roll_rows_rect[0], + self.piano_roll_rows_rect[2], + state, + conn, + focus, + dt, + ); + } self.piano_roll_rows.late_update(state, renderer); } @@ -195,16 +210,21 @@ impl PianoRollPanel { fn get_play_state(play_state: &SharedPlayState) -> PlayState { *play_state.lock() } -} -impl Drawable for PianoRollPanel { - fn update(&self, renderer: &Renderer, state: &State, conn: &Conn, text: &Text, _: &PathsState) { + fn get_panel_and_focus(&self, state: &State) -> (&Panel, bool) { let panel = if state.view.single_track { &self.panel_single_track } else { &self.panel_multi_track }; let focus = panel.has_focus(state); + (panel, focus) + } +} + +impl Drawable for PianoRollPanel { + fn update(&self, renderer: &Renderer, state: &State, conn: &Conn, text: &Text, _: &PathsState) { + let (panel, focus) = self.get_panel_and_focus(state); // Panel background. panel.update(focus, renderer); @@ -223,17 +243,9 @@ impl Drawable for PianoRollPanel { if state.view.single_track { // Piano roll rows. self.piano_roll_rows.update(renderer); - // Get the viewable notes. - let notes = ViewableNotes::new( - self.piano_roll_rows_rect[0], - self.piano_roll_rows_rect[2], - state, - conn, - focus, - dt, - ); // Draw the selection background. - let selected = notes + let selected = self + .viewable_notes .notes .iter() .filter(|n| n.selected && n.in_pitch_range) @@ -251,7 +263,7 @@ impl Drawable for PianoRollPanel { }; let x1 = ViewableNotes::get_note_x( select_1.note.end, - notes.pulses_per_pixel, + self.viewable_notes.pulses_per_pixel, self.piano_roll_rows_rect[0], &dt, ); @@ -263,8 +275,12 @@ impl Drawable for PianoRollPanel { } } - let in_pitch_range: Vec<&ViewableNote> = - notes.notes.iter().filter(|n| n.in_pitch_range).collect(); + let in_pitch_range: Vec<&ViewableNote> = self + .viewable_notes + .notes + .iter() + .filter(|n| n.in_pitch_range) + .collect(); let selected_pitches: Vec = selected .iter() .map(|n| n.note.note) @@ -274,14 +290,13 @@ impl Drawable for PianoRollPanel { // Draw the notes. for note in in_pitch_range.iter() { - let w = notes.get_note_w(note); // Get the y value from the pitch. let y = self.piano_roll_rows_rect[1] + ((state.view.dn[0] - note.note.note) as f32) * self.cell_size[1]; - renderer.rectangle_pixel([note.x, y], [w, self.cell_size[1]], ¬e.color) + renderer.rectangle_pixel([note.x, y], [note.w, self.cell_size[1]], ¬e.color) } // Volume. - self.volume.update(¬es, renderer, state); + self.volume.update(&self.viewable_notes, renderer, state); // Note names. let note_name_color = if focus { &ColorKey::Separator @@ -339,36 +354,33 @@ impl Drawable for PianoRollPanel { let playback_string_width = playback_string.chars().count() as u32; let playback_line_x0 = playback_x + playback_string_width / 2; let selection_x = playback_x + playback_string_width + TIME_PADDING; - let (selection_string, selected) = match &state.select_mode { - SelectMode::Single(index) => match index { - Some(index) => { - let note = &state.music.get_selected_track().unwrap().notes[*index]; + let (selection_string, selected) = match state.selection.get_events(&state.music) { + Some(events) => { + if state.selection.single { ( text.get_with_values( "PIANO_ROLL_PANEL_SELECTED_SINGLE", - &[note.get_name(), &(note.start / PPQ_U).to_string()], + &[ + &text.get_event_name(&events[0]), + &(events[0].get_start_time() / PPQ_U).to_string(), + ], ), true, ) - } - None => (text.get("PIANO_ROLL_PANEL_SELECTED_NONE"), false), - }, - SelectMode::Many(indices) => match indices { - Some(_) => { - let mut notes = state.select_mode.get_notes(&state.music).unwrap(); - notes.sort(); - let min = notes[0].start / PPQ_U; - let max = notes.last().unwrap().end / PPQ_U; - ( - text.get_with_values( - "PIANO_ROLL_PANEL_SELECTED_MANY", - &[&min.to_string(), &max.to_string()], + } else { + match state.selection.get_dt(&state.music) { + Some((min, max)) => ( + text.get_with_values( + "PIANO_ROLL_PANEL_SELECTED_MANY", + &[&min.to_string(), &max.to_string()], + ), + true, ), - true, - ) + None => (text.get("PIANO_ROLL_PANEL_SELECTED_NONE"), false), + } } - None => (text.get("PIANO_ROLL_PANEL_SELECTED_NONE"), false), - }, + } + None => (text.get("PIANO_ROLL_PANEL_SELECTED_NONE"), false), }; let playback_label = Label { text: playback_string, diff --git a/render/src/piano_roll_panel/multi_track.rs b/render/src/piano_roll_panel/multi_track.rs index e83bdc3b..53048b64 100644 --- a/render/src/piano_roll_panel/multi_track.rs +++ b/render/src/piano_roll_panel/multi_track.rs @@ -158,9 +158,9 @@ impl MultiTrack { // Draw some notes. for note in notes.notes.iter() { let note_y = note_y + (1.0 - ((note.note.note - MIN_NOTE) as f32) / DN_F) * h; - let mut note_w = notes.get_note_w(note); // Clamp the note. let note_x0 = note.x.clamp(self.rect_f[0], x1); + let mut note_w = note.w; let note_x1 = note.x + note_w; if note_x1 > x1 { note_w = x1 - note_x0; diff --git a/render/src/piano_roll_panel/top_bar.rs b/render/src/piano_roll_panel/top_bar.rs index 66c7aa1a..e8071f05 100644 --- a/render/src/piano_roll_panel/top_bar.rs +++ b/render/src/piano_roll_panel/top_bar.rs @@ -1,5 +1,5 @@ use crate::panel::*; -use common::{EditMode, IndexedEditModes, PianoRollMode, SelectMode}; +use common::{EditMode, IndexedEditModes, PianoRollMode}; use hashbrown::HashMap; use text::ppq_to_string; @@ -165,10 +165,11 @@ impl TopBar { // Edit mode. let edit_mode = match state.piano_roll_mode { PianoRollMode::Edit => Self::get_edit_mode_text(&state.edit_mode, text), - PianoRollMode::Select => match state.select_mode { - SelectMode::Single(_) => text.get_ref("PIANO_ROLL_PANEL_EDIT_MODE_SINGLE"), - SelectMode::Many(_) => text.get_ref("PIANO_ROLL_PANEL_EDIT_MODE_MANY"), - }, + PianoRollMode::Select => text.get_ref(if state.selection.single { + "PIANO_ROLL_PANEL_EDIT_MODE_SINGLE" + } else { + "PIANO_ROLL_PANEL_EDIT_MODE_MANY" + }), PianoRollMode::Time => Self::get_edit_mode_text(&state.time.mode, text), PianoRollMode::View => Self::get_edit_mode_text(&state.view.mode, text), }; diff --git a/render/src/piano_roll_panel/viewable_notes.rs b/render/src/piano_roll_panel/viewable_notes.rs index 21d40bca..de36d3f6 100644 --- a/render/src/piano_roll_panel/viewable_notes.rs +++ b/render/src/piano_roll_panel/viewable_notes.rs @@ -3,11 +3,13 @@ use audio::play_state::PlayState; use common::*; /// A viewable note. -pub(crate) struct ViewableNote<'a> { +pub(crate) struct ViewableNote { /// The note. - pub note: &'a Note, + pub note: Note, /// The x pixel coordinate of the note. pub x: f32, + /// The pixel width of the note. + pub w: f32, /// If true, this note is being played. pub playing: bool, /// If true, this note is selected. @@ -21,36 +23,27 @@ pub(crate) struct ViewableNote<'a> { /// Render information for all notes that are in the viewport. /// This information is shared between the piano roll and volume sub-panels. -pub(crate) struct ViewableNotes<'a> { +#[derive(Default)] +pub(crate) struct ViewableNotes { /// The notes that are in view. - pub notes: Vec>, - /// Cached viewport dt in PPQ. - dt: [U64orF32; 2], + pub notes: Vec, /// The number of pulses in 1 pixel. pub pulses_per_pixel: u64, } -impl<'a> ViewableNotes<'a> { +impl ViewableNotes { /// - `x` The x pixel coordinate of the note's position. /// - `w` The pixel width of the note. /// - `state` The app state. /// - `conn` The audio conn. /// - `focus` If true, the piano roll panel has focus. /// - `dt` The time delta. - pub fn new( - x: f32, - w: f32, - state: &'a State, - conn: &Conn, - focus: bool, - dt: [U64orF32; 2], - ) -> Self { + pub fn new(x: f32, w: f32, state: &State, conn: &Conn, focus: bool, dt: [U64orF32; 2]) -> Self { match state.music.get_selected_track() { Some(track) => Self::new_from_track(x, w, track, state, conn, focus, dt, state.view.dn), None => Self { pulses_per_pixel: Self::get_pulses_per_pixel(&dt, w), notes: vec![], - dt, }, } } @@ -68,8 +61,8 @@ impl<'a> ViewableNotes<'a> { pub fn new_from_track( x: f32, w: f32, - track: &'a MidiTrack, - state: &'a State, + track: &MidiTrack, + state: &State, conn: &Conn, focus: bool, dt: [U64orF32; 2], @@ -83,22 +76,19 @@ impl<'a> ViewableNotes<'a> { }; // Get the selected notes. - let selected = match state.select_mode.get_notes(&state.music) { - Some(selected) => selected, + let selected = match state.selection.get_selection(&state.music) { + Some((notes, _)) => notes, None => vec![], }; + let (t0, t1) = (dt[0].get_u(), dt[1].get_u()); let mut notes = vec![]; for note in track.notes.iter() { // Is the note in view? - if note.end <= dt[0].get_u() || note.start >= dt[1].get_u() { + if note.end <= t0 || note.start >= t1 { continue; } // Get the start time of the note. This could be the start of the viewport. - let t = if note.start < dt[0].get_u() { - dt[0].get_u() - } else { - note.start - }; + let t = if note.start < t0 { t1 } else { note.start }; // Get the x coordinate of the note. let x_note = Self::get_note_x(t, pulses_per_pixel, x, &dt); // Is this note in the selection? @@ -121,10 +111,15 @@ impl<'a> ViewableNotes<'a> { ColorKey::NoFocus }; let in_pitch_range = note.note <= dn[0] && note.note > dn[1]; + // Get the width of the note. + let note_t0 = if note.start < t0 { t0 } else { note.start }; + let note_t1 = if note.end > t1 { t1 } else { note.end }; + let w_note = ((note_t1 - note_t0) / pulses_per_pixel) as f32; // Add the note. notes.push(ViewableNote { - note, + note: *note, x: x_note, + w: w_note, color, selected, playing, @@ -133,26 +128,10 @@ impl<'a> ViewableNotes<'a> { } Self { notes, - dt, pulses_per_pixel, } } - /// Returns the width of a note. - pub fn get_note_w(&self, note: &ViewableNote) -> f32 { - let t0 = if note.note.start < self.dt[0].get_u() { - self.dt[0].get_u() - } else { - note.note.start - }; - let t1 = if note.note.end > self.dt[1].get_u() { - self.dt[1].get_u() - } else { - note.note.end - }; - ((t1 - t0) / self.pulses_per_pixel) as f32 - } - /// Returns the x pixel coordinate corresonding with time `t` within the viewport defined by `x`, `w` and `dt`. /// /// - `t` The time in PPQ. diff --git a/text/src/lib.rs b/text/src/lib.rs index b442d381..448bd17c 100644 --- a/text/src/lib.rs +++ b/text/src/lib.rs @@ -12,7 +12,7 @@ use std::path::Path; pub use value_map::ValueMap; mod tts_string; use common::config::parse; -use common::{EditMode, Paths, PianoRollMode, Time, MIN_NOTE, PPQ_F, PPQ_U}; +use common::{EditMode, EffectType, Event, Paths, PianoRollMode, Time, MIN_NOTE, PPQ_F, PPQ_U}; use csv::Reader; use hashbrown::HashMap; use ini::Ini; @@ -164,7 +164,7 @@ pub struct Text { piano_roll_modes: HashMap, /// The name of each MIDI note. note_names: Vec, - /// Boolean dislay + /// Boolean display strings. booleans: ValueMap, } @@ -337,6 +337,22 @@ impl Text { self.get_with_values("ERROR", &[error]) } + pub fn get_event_name(&self, event: &Event<'_>) -> String { + match event { + Event::Effect { effect, index: _ } => self.get(match effect.effect { + EffectType::Chorus(_) => "EFFECT_TYPE_CHORUS", + EffectType::Pan(_) => "EFFECT_TYPE_PAN", + EffectType::Reverb(_) => "EFFECT_TYPE_REVERB", + EffectType::PitchBend(_) => "EFFECT_TYPE_PITCH_BEND", + EffectType::ChannelPressure(_) => "EFFECT_TYPE_CHANNEL_PRESSURE", + EffectType::PolyphonicKeyPressure { key: _, value: _ } => { + "EFFECT_TYPE_POLYPHONIC_KEY_PRESSURE" + } + }), + Event::Note { note, index: _ } => note.get_name().to_string(), + } + } + /// Returns the name of the note. pub fn get_note_name(&self, note: u8) -> &str { &self.note_names[(note - MIN_NOTE) as usize]