diff --git a/gtk2_ardour/ghost_event.h b/gtk2_ardour/ghost_event.h new file mode 100644 index 0000000000..2957d3a54f --- /dev/null +++ b/gtk2_ardour/ghost_event.h @@ -0,0 +1,33 @@ +#ifndef __gtk2_ardour_ghost_event_h__ +#define __gtk2_ardour_ghost_event_h__ + +#include + +namespace ArdourCanvas { + class Container; + class Item; +} + +class NoteBase; + +class GhostEvent : public sigc::trackable +{ + public: + GhostEvent (::NoteBase *, ArdourCanvas::Container *); + GhostEvent (::NoteBase *, ArdourCanvas::Container *, ArdourCanvas::Item* i); + virtual ~GhostEvent (); + + NoteBase* event; + ArdourCanvas::Item* item; + bool is_hit; + int velocity_while_editing; + + /* must match typedef in NoteBase */ + typedef Evoral::Note NoteType; + typedef boost::unordered_map, GhostEvent* > EventList; + + static GhostEvent* find (std::shared_ptr parent, EventList& events, EventList::iterator& opti); +}; + + +#endif /* __gtk2_ardour_ghost_event_h__ */ diff --git a/gtk2_ardour/ghostregion.cc b/gtk2_ardour/ghostregion.cc index a92dd3f748..cd75109a68 100644 --- a/gtk2_ardour/ghostregion.cc +++ b/gtk2_ardour/ghostregion.cc @@ -210,7 +210,7 @@ MidiGhostRegion::~MidiGhostRegion() delete _note_group; } -MidiGhostRegion::GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Container* g, ArdourCanvas::Item* i) +GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Container* g, ArdourCanvas::Item* i) : event (e) , item (i) , is_hit (false) @@ -221,7 +221,7 @@ MidiGhostRegion::GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Container* g } } -MidiGhostRegion::GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Container* g) +GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Container* g) : event (e) { if (dynamic_cast(e)) { @@ -244,11 +244,37 @@ MidiGhostRegion::GhostEvent::GhostEvent (NoteBase* e, ArdourCanvas::Container* g CANVAS_DEBUG_NAME (item, "ghost note item"); } -MidiGhostRegion::GhostEvent::~GhostEvent () +GhostEvent::~GhostEvent () { /* event is not ours to delete */ delete item; } +/** Given a note in our parent region (ie the actual MidiRegionView), find our + * representation of it. + * @return Our Event, or 0 if not found. + */ +GhostEvent * +GhostEvent::find (std::shared_ptr parent, EventList& events, EventList::iterator& opti) +{ + /* we are using _optimization_iterator to speed up the common case where a caller + is going through our notes in order. + */ + + if (opti != events.end()) { + ++opti; + if (opti != events.end() && opti->first == parent) { + return opti->second; + } + } + + opti = events.find (parent); + if (opti != events.end()) { + return opti->second; + } + + return nullptr; +} + void MidiGhostRegion::set_samples_per_pixel (double /*spu*/) @@ -280,7 +306,7 @@ MidiGhostRegion::set_colors() GhostRegion::set_colors(); _outline = UIConfiguration::instance().color ("ghost track midi outline"); - for (EventList::iterator it = events.begin(); it != events.end(); ++it) { + for (GhostEvent::EventList::iterator it = events.begin(); it != events.end(); ++it) { it->second->item->set_fill_color (UIConfiguration::instance().color_mod((*it).second->event->base_color(), "ghost track midi fill")); it->second->item->set_outline_color (_outline); } @@ -316,7 +342,7 @@ MidiGhostRegion::update_contents_height () double const h = note_height(trackview, mv); - for (EventList::iterator it = events.begin(); it != events.end(); ++it) { + for (GhostEvent::EventList::iterator it = events.begin(); it != events.end(); ++it) { uint8_t const note_num = it->second->event->note()->note(); double const y = note_y(trackview, mv, note_num); @@ -427,7 +453,7 @@ MidiGhostRegion::update_hit (GhostEvent* ev) void MidiGhostRegion::remove_note (NoteBase* note) { - EventList::iterator f = events.find (note->note()); + GhostEvent::EventList::iterator f = events.find (note->note()); if (f == events.end()) { return; } @@ -448,9 +474,9 @@ void MidiGhostRegion::model_changed () { /* we rely on the parent MRV having removed notes not in the model */ - for (EventList::iterator i = events.begin(); i != events.end(); ) { + for (GhostEvent::EventList::iterator i = events.begin(); i != events.end(); ) { - std::shared_ptr note = i->first; + std::shared_ptr note = i->first; GhostEvent* cne = i->second; const bool visible = (note->note() >= parent_mrv.midi_context().lowest_note()) && (note->note() <= parent_mrv.midi_context().highest_note()); @@ -470,28 +496,3 @@ MidiGhostRegion::model_changed () } } -/** Given a note in our parent region (ie the actual MidiRegionView), find our - * representation of it. - * @return Our Event, or 0 if not found. - */ -MidiGhostRegion::GhostEvent * -MidiGhostRegion::find_event (std::shared_ptr parent) -{ - /* we are using _optimization_iterator to speed up the common case where a caller - is going through our notes in order. - */ - - if (_optimization_iterator != events.end()) { - ++_optimization_iterator; - if (_optimization_iterator != events.end() && _optimization_iterator->first == parent) { - return _optimization_iterator->second; - } - } - - _optimization_iterator = events.find (parent); - if (_optimization_iterator != events.end()) { - return _optimization_iterator->second; - } - - return 0; -} diff --git a/gtk2_ardour/ghostregion.h b/gtk2_ardour/ghostregion.h index 6c869f49e0..fbeeb800c0 100644 --- a/gtk2_ardour/ghostregion.h +++ b/gtk2_ardour/ghostregion.h @@ -26,13 +26,14 @@ #define __ardour_gtk_ghost_region_h__ #include -#include #include "evoral/Note.h" #include "pbd/signals.h" #include "gtkmm2ext/colors.h" +#include "ghost_event.h" + namespace ArdourWaveView { class WaveView; } @@ -99,19 +100,6 @@ public: class MidiGhostRegion : public GhostRegion { public: - class GhostEvent : public sigc::trackable - { - public: - GhostEvent (::NoteBase *, ArdourCanvas::Container *); - GhostEvent (::NoteBase *, ArdourCanvas::Container *, ArdourCanvas::Item* i); - virtual ~GhostEvent (); - - NoteBase* event; - ArdourCanvas::Item* item; - bool is_hit; - int velocity_while_editing; - }; - MidiGhostRegion(MidiRegionView& rv, TimeAxisView& tv, TimeAxisView& source_tv, @@ -143,13 +131,10 @@ public: ArdourCanvas::Polygon* _tmp_poly; MidiRegionView& parent_mrv; - /* must match typedef in NoteBase */ - typedef Evoral::Note NoteType; - MidiGhostRegion::GhostEvent* find_event (std::shared_ptr); + GhostEvent* find_event (std::shared_ptr); - typedef boost::unordered_map, MidiGhostRegion::GhostEvent* > EventList; - EventList events; - EventList::iterator _optimization_iterator; + GhostEvent::EventList events; + GhostEvent::EventList::iterator _optimization_iterator; }; #endif /* __ardour_gtk_ghost_region_h__ */ diff --git a/gtk2_ardour/midi_view.h b/gtk2_ardour/midi_view.h index dcbc22d0cd..38048d40a3 100644 --- a/gtk2_ardour/midi_view.h +++ b/gtk2_ardour/midi_view.h @@ -529,7 +529,7 @@ class MidiView : public virtual sigc::trackable std::shared_ptr find_canvas_patch_change (ARDOUR::MidiModel::PatchChangePtr p); std::shared_ptr find_canvas_sys_ex (ARDOUR::MidiModel::SysExPtr s); - friend class VelocityGhostRegion; + friend class VelocityDisplay; void sync_velocity_drag (double factor); void update_note (NoteBase*, bool update_ghost_regions = true); diff --git a/gtk2_ardour/velocity_display.cc b/gtk2_ardour/velocity_display.cc new file mode 100644 index 0000000000..db838480bb --- /dev/null +++ b/gtk2_ardour/velocity_display.cc @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2022 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include + +#include "pbd/memento_command.h" + +#include "ardour/automation_control.h" +#include "ardour/event_type_map.h" +#include "ardour/midi_automation_list_binder.h" +#include "ardour/midi_region.h" +#include "ardour/midi_track.h" +#include "ardour/session.h" + +#include "gtkmm2ext/keyboard.h" +#include "gtkmm2ext/utils.h" + +#include "canvas/lollipop.h" + +#include "editing.h" +#include "editing_context.h" +#include "editor_drag.h" +#include "ghost_event.h" +#include "gui_thread.h" +#include "midi_automation_line.h" +#include "midi_region_view.h" +#include "midi_view.h" +#include "midi_view_background.h" +#include "note_base.h" +#include "ui_config.h" +#include "velocity_display.h" +#include "verbose_cursor.h" + +#include "pbd/i18n.h" + +using namespace Temporal; + +static double const lollipop_radius = 6.0; + +VelocityDisplay::VelocityDisplay (EditingContext& ec, MidiViewBackground& background, MidiView& mv, ArdourCanvas::Rectangle& base_rect, ArdourCanvas::Container& lc, + GhostEvent::EventList& el, Gtkmm2ext::Color oc) + : editing_context (ec) + , bg (background) + , view (mv) + , base (base_rect) + , lolli_container (&lc) + , events (el) + , _outline (oc) + , dragging (false) + , dragging_line (nullptr) + , last_drag_x (-1) + , drag_did_change (false) + , selected (false) + , _optimization_iterator (events.end()) +{ + base.set_data (X_("ghostregionview"), this); + base.Event.connect (sigc::mem_fun (*this, &VelocityDisplay::base_event)); + base.set_fill_color (UIConfiguration::instance().color_mod ("ghost track base", "ghost track midi fill")); + base.set_outline_color (UIConfiguration::instance().color ("automation track outline")); + base.set_outline (true); + base.set_outline_what (ArdourCanvas::Rectangle::What (ArdourCanvas::Rectangle::LEFT|ArdourCanvas::Rectangle::RIGHT)); + +} + +VelocityDisplay::~VelocityDisplay () +{ +} + +bool +VelocityDisplay::line_draw_motion (ArdourCanvas::Duple const & d, ArdourCanvas::Rectangle const & r, double last_x) +{ + std::vector affected_lollis; + + if (last_x < 0) { + lollis_close_to_x (d.x, 20., affected_lollis); + } else if (last_x < d.x) { + /* rightward, "later" motion */ + lollis_between (last_x, d.x, affected_lollis); + } else { + /* leftward, "earlier" motion */ + lollis_between (d.x, last_x, affected_lollis); + } + + if (affected_lollis.empty()) { + return false; + } + + int velocity = y_position_to_velocity (r.height() - (r.y1() - d.y)); + + for (auto & lolli : affected_lollis) { + lolli->velocity_while_editing = velocity; + set_size_and_position (*lolli); + } + + return true; +} + +bool +VelocityDisplay::line_extended (ArdourCanvas::Duple const & from, ArdourCanvas::Duple const & to, ArdourCanvas::Rectangle const & r, double last_x) +{ + std::vector affected_lollis; + + lollis_between (from.x, to.x, affected_lollis); + + if (affected_lollis.empty()) { + return false; + } + + if (to.x == from.x) { + /* no x-axis motion */ + return false; + } + + double slope = (to.y - from.y) / (to.x - from.x); + + for (auto const & lolli : affected_lollis) { + ArdourCanvas::Item* item = lolli->item; + ArdourCanvas::Duple pos = item->item_to_canvas (ArdourCanvas::Duple (lolli->event->x0(), 0.0)); + int y = from.y + (slope * (pos.x - from.x)); + lolli->velocity_while_editing = y_position_to_velocity (r.height() - (r.y1() - y)); + set_size_and_position (*lolli); + } + + return true; +} + +void +VelocityDisplay::update_contents_height () +{ + for (auto const & i : events) { + set_size_and_position (*i.second); + } +} + +void +VelocityDisplay::add_note (NoteBase* nb) +{ + ArdourCanvas::Lollipop* l = new ArdourCanvas::Lollipop (lolli_container); + l->set_bounding_parent (&base); + + GhostEvent* event = new GhostEvent (nb, lolli_container, l); + events.insert (std::make_pair (nb->note(), event)); + + l->Event.connect (sigc::bind (sigc::mem_fun (*this, &VelocityDisplay::lollevent), event)); + l->set_ignore_events (true); + l->raise_to_top (); + l->set_data (X_("ghostregionview"), this); + l->set_data (X_("note"), nb); + l->set_fill_color (nb->base_color()); + l->set_outline_color (_outline); + + if (view.note_in_region_time_range (nb->note())) { + set_size_and_position (*event); + } else { + l->hide(); + } +} + +void +VelocityDisplay::set_size_and_position (GhostEvent& gev) +{ + ArdourCanvas::Lollipop* l = dynamic_cast (gev.item); + const double available_height = base.y1(); + const double actual_height = ((dragging ? gev.velocity_while_editing : gev.event->note()->velocity()) / 127.0) * available_height; + const double scale = UIConfiguration::instance ().get_ui_scale (); + + if (gev.is_hit) { + /* compare to Hit::points , offset by w/2 */ + l->set (ArdourCanvas::Duple (gev.event->x0() + (gev.event->x1() - gev.event->x0()) / 2, base.y1() - actual_height), actual_height, lollipop_radius * scale); + } else { + l->set (ArdourCanvas::Duple (gev.event->x0(), base.y1() - actual_height), actual_height, lollipop_radius * scale); + } +} + +void +VelocityDisplay::update_note (GhostEvent* gev) +{ + set_size_and_position (*gev); + gev->item->set_fill_color (gev->event->base_color()); +} + +void +VelocityDisplay::update_hit (GhostEvent* gev) +{ + set_size_and_position (*gev); + gev->item->set_fill_color (gev->event->base_color()); +} + +void +VelocityDisplay::set_colors () +{ + base.set_fill_color (UIConfiguration::instance().color_mod ("ghost track base", "ghost track midi fill")); + + for (auto & gev : events) { + gev.second->item->set_fill_color (gev.second->event->base_color()); + } +} + +void +VelocityDisplay::drag_lolli (ArdourCanvas::Lollipop* l, GdkEventMotion* ev) +{ + ArdourCanvas::Rect r (base.item_to_canvas (base.get())); + + /* translate event y-coord so that zero matches the top of base + * (event coordinates use window coordinate space) + */ + + ev->y -= r.y0; + + /* clamp y to be within the range defined by the base height minus + * the lollipop radius at top and bottom + */ + + const double effective_y = std::max (0.0, std::min (r.height(), ev->y)); + const double newlen = r.height() - effective_y; + const double delta = newlen - l->length(); + + /* This will redraw the velocity bars for the selected notes, without + * changing the note velocities. + */ + + const double factor = newlen / base.height(); + view.sync_velocity_drag (factor); + + MidiRegionView::Selection const & sel (view.selection()); + int verbose_velocity = -1; + GhostEvent* primary_ghost = 0; + const double scale = UIConfiguration::instance ().get_ui_scale (); + + for (auto & s : sel) { + GhostEvent* x = GhostEvent::find (s->note(), events, _optimization_iterator); + + if (x) { + ArdourCanvas::Lollipop* lolli = dynamic_cast (x->item); + lolli->set (ArdourCanvas::Duple (lolli->x(), lolli->y0() - delta), lolli->length() + delta, lollipop_radius * scale); + /* note: length is now set to the new value */ + const int newvel = floor (127. * (l->length() / r.height())); + /* since we're not actually changing the note velocity + (yet), we have to use the static method to compute + the color. + */ + lolli->set_fill_color (NoteBase::base_color (newvel, bg.color_mode(), bg.region_color(), x->event->note()->channel(), true)); + + if (l == lolli) { + /* This is the value we will display */ + verbose_velocity = newvel; + primary_ghost = x; + } + } + } + + assert (verbose_velocity >= 0); + char buf[128]; + const int oldvel = primary_ghost->event->note()->velocity(); + + if (verbose_velocity > oldvel) { + snprintf (buf, sizeof (buf), "Velocity %d (+%d)", verbose_velocity, verbose_velocity - oldvel); + } else if (verbose_velocity == oldvel) { + snprintf (buf, sizeof (buf), "Velocity %d", verbose_velocity); + } else { + snprintf (buf, sizeof (buf), "Velocity %d (%d)", verbose_velocity, verbose_velocity - oldvel); + } + + editing_context.verbose_cursor()->set (buf); + editing_context.verbose_cursor()->show (); + editing_context.verbose_cursor()->set_offset (ArdourCanvas::Duple (10., 10.)); +} + +int +VelocityDisplay::y_position_to_velocity (double y) const +{ + const ArdourCanvas::Rect r (base.get()); + int velocity; + + if (y >= r.height()) { + velocity = 0; + } else if (y <= 0.) { + velocity = 127; + } else { + velocity = floor (127. * (1.0 - (y / r.height()))); + } + + return velocity; +} + +void +VelocityDisplay::note_selected (NoteBase* ev) +{ + GhostEvent* gev = GhostEvent::find (ev->note(), events, _optimization_iterator); + + if (!gev) { + return; + } + + ArdourCanvas::Lollipop* lolli = dynamic_cast (gev->item); + lolli->set_outline_color (ev->selected() ? UIConfiguration::instance().color ("midi note selected outline") : 0x000000ff); + lolli->raise_to_top(); +} + +void +VelocityDisplay::lollis_between (int x0, int x1, std::vector& within) +{ + MidiRegionView::Selection const & sel (view.selection()); + bool only_selected = !sel.empty(); + + for (auto & gev : events) { + if (only_selected) { + if (!gev.second->event->selected()) { + continue; + } + } + ArdourCanvas::Lollipop* l = dynamic_cast (gev.second->item); + if (l) { + ArdourCanvas::Duple pos = l->item_to_canvas (ArdourCanvas::Duple (l->x(), l->y0())); + if (pos.x >= x0 && pos.x < x1) { + within.push_back (gev.second); + } + } + } +} + +void +VelocityDisplay::lollis_close_to_x (int x, double distance, std::vector& within) +{ + for (auto & gev : events) { + ArdourCanvas::Lollipop* l = dynamic_cast (gev.second->item); + if (l) { + ArdourCanvas::Duple pos = l->item_to_canvas (ArdourCanvas::Duple (l->x(), l->y0())); + if (std::abs (pos.x - x) < distance) { + within.push_back (gev.second); + } + } + } +} + +void +VelocityDisplay::start_line_drag () +{ + view.begin_drag_edit (_("draw velocities")); + + for (auto & e : events) { + GhostEvent* gev (e.second); + gev->velocity_while_editing = gev->event->note()->velocity(); + } + + dragging = true; + desensitize_lollis (); +} + +void +VelocityDisplay::end_line_drag (bool did_change) +{ + dragging = false; + + if (did_change) { + std::vector notes; + std::vector velocities; + + for (auto & e : events) { + GhostEvent* gev (e.second); + if (gev->event->note()->velocity() != gev->velocity_while_editing) { + notes.push_back (gev->event); + velocities.push_back (gev->velocity_while_editing); + } + } + + view.set_velocities_for_notes (notes, velocities); + } + + view.end_drag_edit (); + sensitize_lollis (); +} + +void +VelocityDisplay::desensitize_lollis () +{ + for (auto & gev : events) { + gev.second->item->set_ignore_events (true); + } +} + +void +VelocityDisplay::sensitize_lollis () +{ + for (auto & gev : events) { + gev.second->item->set_ignore_events (false); + } +} + +void +VelocityDisplay::set_selected (bool yn) +{ + selected = yn; + set_colors (); + + if (yn) { + base.parent()->raise_to_top (); + } +} diff --git a/gtk2_ardour/velocity_display.h b/gtk2_ardour/velocity_display.h new file mode 100644 index 0000000000..c5ea707f48 --- /dev/null +++ b/gtk2_ardour/velocity_display.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007-2014 David Robillard + * Copyright (C) 2009-2010 Carl Hetherington + * Copyright (C) 2009-2017 Paul Davis + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __gtk_ardour_velocity_display_h__ +#define __gtk_ardour_velocity_display_h__ + +#include "canvas/rectangle.h" +#include "canvas/poly_line.h" + +#include "gtkmm2ext/colors.h" + +#include "ghost_event.h" + +namespace ArdourCanvas { + class Container; + class Lollipop; + class Rectangle; +} + +class EditingContext; +class MidiViewBackground; +class MidiView; +class NoteBase; + +class VelocityDisplay +{ + public: + VelocityDisplay (EditingContext&, MidiViewBackground&, MidiView&, ArdourCanvas::Rectangle& base_rect, ArdourCanvas::Container&, GhostEvent::EventList& el, Gtkmm2ext::Color oc); + virtual ~VelocityDisplay (); + + void update_contents_height(); + void add_note(NoteBase*); + void update_note (GhostEvent* note); + void update_hit (GhostEvent* hit); + virtual void remove_note (NoteBase*) = 0; + void note_selected (NoteBase*); + + void set_colors (); + void drag_lolli (ArdourCanvas::Lollipop* l, GdkEventMotion* ev); + + int y_position_to_velocity (double y) const; + + void set_selected (bool); + + bool line_draw_motion (ArdourCanvas::Duple const & d, ArdourCanvas::Rectangle const & r, double last_x); + bool line_extended (ArdourCanvas::Duple const & from, ArdourCanvas::Duple const & to, ArdourCanvas::Rectangle const & r, double last_x); + + void start_line_drag (); + void end_line_drag (bool did_change); + + ArdourCanvas::Rectangle& base_item() { return base; } + + protected: + virtual bool lollevent (GdkEvent*, GhostEvent*) = 0; + + private: + EditingContext& editing_context; + MidiViewBackground& bg; + MidiView& view; + ArdourCanvas::Rectangle& base; + ArdourCanvas::Container* lolli_container; + GhostEvent::EventList& events; + Gtkmm2ext::Color _outline; + bool dragging; + ArdourCanvas::PolyLine* dragging_line; + int last_drag_x; + bool drag_did_change; + bool selected; + GhostEvent::EventList::iterator _optimization_iterator; + + virtual bool base_event (GdkEvent*) = 0; + void set_size_and_position (GhostEvent&); + void lollis_close_to_x (int x, double distance, std::vector& events); + void lollis_between (int x0, int x1, std::vector& events); + void desensitize_lollis (); + void sensitize_lollis (); +}; + +#endif /* __gtk_ardour_velocity_display_h__ */ diff --git a/gtk2_ardour/velocity_ghost_region.cc b/gtk2_ardour/velocity_ghost_region.cc index 47ee361721..8af4c2b8aa 100644 --- a/gtk2_ardour/velocity_ghost_region.cc +++ b/gtk2_ardour/velocity_ghost_region.cc @@ -32,405 +32,58 @@ #include "canvas/lollipop.h" -#include "velocity_ghost_region.h" #include "editing.h" #include "editor.h" #include "editor_drag.h" +#include "ghost_event.h" #include "gui_thread.h" #include "midi_automation_line.h" #include "midi_region_view.h" #include "note_base.h" #include "public_editor.h" #include "ui_config.h" +#include "velocity_ghost_region.h" #include "verbose_cursor.h" #include "pbd/i18n.h" using namespace Temporal; -static double const lollipop_radius = 6.0; - VelocityGhostRegion::VelocityGhostRegion (MidiRegionView& mrv, TimeAxisView& tv, TimeAxisView& source_tv, double initial_unit_pos) : MidiGhostRegion (mrv, tv, source_tv, initial_unit_pos) - , dragging (false) - , dragging_line (nullptr) - , last_drag_x (-1) - , drag_did_change (false) - , selected (false) + , VelocityDisplay (trackview.editor(), *mrv.midi_stream_view(), mrv, *base_rect, *_note_group, MidiGhostRegion::events, MidiGhostRegion::_outline) { - base_rect->set_data (X_("ghostregionview"), this); - base_rect->Event.connect (sigc::mem_fun (*this, &VelocityGhostRegion::base_event)); - base_rect->set_fill_color (UIConfiguration::instance().color_mod ("ghost track base", "ghost track midi fill")); - base_rect->set_outline_color (UIConfiguration::instance().color ("automation track outline")); - base_rect->set_outline (true); - base_rect->set_outline_what (ArdourCanvas::Rectangle::What (ArdourCanvas::Rectangle::LEFT|ArdourCanvas::Rectangle::RIGHT)); } VelocityGhostRegion::~VelocityGhostRegion () { } -bool -VelocityGhostRegion::line_draw_motion (ArdourCanvas::Duple const & d, ArdourCanvas::Rectangle const & r, double last_x) -{ - std::vector affected_lollis; - - if (last_x < 0) { - lollis_close_to_x (d.x, 20., affected_lollis); - } else if (last_x < d.x) { - /* rightward, "later" motion */ - lollis_between (last_x, d.x, affected_lollis); - } else { - /* leftward, "earlier" motion */ - lollis_between (d.x, last_x, affected_lollis); - } - - if (affected_lollis.empty()) { - return false; - } - - int velocity = y_position_to_velocity (r.height() - (r.y1() - d.y)); - - for (auto & lolli : affected_lollis) { - lolli->velocity_while_editing = velocity; - set_size_and_position (*lolli); - } - - return true; -} - -bool -VelocityGhostRegion::line_extended (ArdourCanvas::Duple const & from, ArdourCanvas::Duple const & to, ArdourCanvas::Rectangle const & r, double last_x) -{ - std::vector affected_lollis; - - lollis_between (from.x, to.x, affected_lollis); - - if (affected_lollis.empty()) { - return false; - } - - if (to.x == from.x) { - /* no x-axis motion */ - return false; - } - - double slope = (to.y - from.y) / (to.x - from.x); - - for (auto const & lolli : affected_lollis) { - ArdourCanvas::Item* item = lolli->item; - ArdourCanvas::Duple pos = item->item_to_canvas (ArdourCanvas::Duple (lolli->event->x0(), 0.0)); - int y = from.y + (slope * (pos.x - from.x)); - lolli->velocity_while_editing = y_position_to_velocity (r.height() - (r.y1() - y)); - set_size_and_position (*lolli); - } - - return true; -} - -bool -VelocityGhostRegion::base_event (GdkEvent* ev) -{ - return trackview.editor().canvas_velocity_base_event (ev, base_rect); -} - void -VelocityGhostRegion::update_contents_height () +VelocityGhostRegion::set_colors () { - for (auto const & i : events) { - set_size_and_position (*i.second); + base_rect->set_fill_color (UIConfiguration::instance().color_mod ("ghost track base", "ghost track midi fill")); + + for (auto & gev : MidiGhostRegion::events) { + gev.second->item->set_fill_color (gev.second->event->base_color()); } } -bool -VelocityGhostRegion::lollevent (GdkEvent* ev, MidiGhostRegion::GhostEvent* gev) -{ - return trackview.editor().canvas_velocity_event (ev, gev->item); -} - -void -VelocityGhostRegion::add_note (NoteBase* nb) -{ - ArdourCanvas::Lollipop* l = new ArdourCanvas::Lollipop (_note_group); - l->set_bounding_parent (base_rect); - - GhostEvent* event = new GhostEvent (nb, _note_group, l); - events.insert (std::make_pair (nb->note(), event)); - _optimization_iterator = events.end(); - - l->Event.connect (sigc::bind (sigc::mem_fun (*this, &VelocityGhostRegion::lollevent), event)); - l->set_ignore_events (true); - l->raise_to_top (); - l->set_data (X_("ghostregionview"), this); - l->set_data (X_("note"), nb); - l->set_fill_color (nb->base_color()); - l->set_outline_color (_outline); - - MidiStreamView* mv = midi_view(); - - if (mv) { - MidiRegionView* mrv = dynamic_cast (&parent_rv); - if (mrv->note_in_region_time_range (nb->note())) { - set_size_and_position (*event); - } else { - l->hide(); - } - } -} - -void -VelocityGhostRegion::set_size_and_position (GhostEvent& gev) -{ - ArdourCanvas::Lollipop* l = dynamic_cast (gev.item); - const double available_height = base_rect->y1(); - const double actual_height = ((dragging ? gev.velocity_while_editing : gev.event->note()->velocity()) / 127.0) * available_height; - const double scale = UIConfiguration::instance ().get_ui_scale (); - - if (gev.is_hit) { - /* compare to Hit::points , offset by w/2 */ - l->set (ArdourCanvas::Duple (gev.event->x0() + (gev.event->x1() - gev.event->x0()) / 2, base_rect->y1() - actual_height), actual_height, lollipop_radius * scale); - } else { - l->set (ArdourCanvas::Duple (gev.event->x0(), base_rect->y1() - actual_height), actual_height, lollipop_radius * scale); - } -} - -void -VelocityGhostRegion::update_note (GhostEvent* gev) -{ - set_size_and_position (*gev); - gev->item->set_fill_color (gev->event->base_color()); -} - -void -VelocityGhostRegion::update_hit (GhostEvent* gev) -{ - set_size_and_position (*gev); - gev->item->set_fill_color (gev->event->base_color()); -} - void VelocityGhostRegion::remove_note (NoteBase* nb) { MidiGhostRegion::remove_note (nb); } -void -VelocityGhostRegion::set_colors () +bool +VelocityGhostRegion::base_event (GdkEvent* ev) { - base_rect->set_fill_color (UIConfiguration::instance().color_mod ("ghost track base", "ghost track midi fill")); - - for (auto & gev : events) { - gev.second->item->set_fill_color (gev.second->event->base_color()); - } + return trackview.editor().canvas_velocity_base_event (ev, base_rect); } -void -VelocityGhostRegion::drag_lolli (ArdourCanvas::Lollipop* l, GdkEventMotion* ev) +bool +VelocityGhostRegion::lollevent (GdkEvent* ev, GhostEvent* gev) { - ArdourCanvas::Rect r (base_rect->item_to_canvas (base_rect->get())); - - /* translate event y-coord so that zero matches the top of base_rect - * (event coordinates use window coordinate space) - */ - - ev->y -= r.y0; - - /* clamp y to be within the range defined by the base_rect height minus - * the lollipop radius at top and bottom - */ - - const double effective_y = std::max (0.0, std::min (r.height(), ev->y)); - const double newlen = r.height() - effective_y; - const double delta = newlen - l->length(); - - MidiRegionView* mrv = dynamic_cast (&parent_rv); - assert (mrv); - - /* This will redraw the velocity bars for the selected notes, without - * changing the note velocities. - */ - - const double factor = newlen / base_rect->height(); - mrv->sync_velocity_drag (factor); - - MidiRegionView::Selection const & sel (mrv->selection()); - int verbose_velocity = -1; - GhostEvent* primary_ghost = 0; - const double scale = UIConfiguration::instance ().get_ui_scale (); - - for (auto & s : sel) { - GhostEvent* x = find_event (s->note()); - - if (x) { - ArdourCanvas::Lollipop* lolli = dynamic_cast (x->item); - lolli->set (ArdourCanvas::Duple (lolli->x(), lolli->y0() - delta), lolli->length() + delta, lollipop_radius * scale); - /* note: length is now set to the new value */ - const int newvel = floor (127. * (l->length() / r.height())); - /* since we're not actually changing the note velocity - (yet), we have to use the static method to compute - the color. - */ - lolli->set_fill_color (NoteBase::base_color (newvel, mrv->color_mode(), mrv->midi_stream_view()->get_region_color(), x->event->note()->channel(), true)); - - if (l == lolli) { - /* This is the value we will display */ - verbose_velocity = newvel; - primary_ghost = x; - } - } - } - - assert (verbose_velocity >= 0); - char buf[128]; - const int oldvel = primary_ghost->event->note()->velocity(); - - if (verbose_velocity > oldvel) { - snprintf (buf, sizeof (buf), "Velocity %d (+%d)", verbose_velocity, verbose_velocity - oldvel); - } else if (verbose_velocity == oldvel) { - snprintf (buf, sizeof (buf), "Velocity %d", verbose_velocity); - } else { - snprintf (buf, sizeof (buf), "Velocity %d (%d)", verbose_velocity, verbose_velocity - oldvel); - } - - trackview.editor().verbose_cursor()->set (buf); - trackview.editor().verbose_cursor()->show (); - trackview.editor().verbose_cursor()->set_offset (ArdourCanvas::Duple (10., 10.)); + return trackview.editor().canvas_velocity_event (ev, gev->item); } -int -VelocityGhostRegion::y_position_to_velocity (double y) const -{ - const ArdourCanvas::Rect r (base_rect->get()); - int velocity; - - if (y >= r.height()) { - velocity = 0; - } else if (y <= 0.) { - velocity = 127; - } else { - velocity = floor (127. * (1.0 - (y / r.height()))); - } - - return velocity; -} - -void -VelocityGhostRegion::note_selected (NoteBase* ev) -{ - GhostEvent* gev = find_event (ev->note()); - - if (!gev) { - return; - } - - ArdourCanvas::Lollipop* lolli = dynamic_cast (gev->item); - lolli->set_outline_color (ev->selected() ? UIConfiguration::instance().color ("midi note selected outline") : 0x000000ff); - lolli->raise_to_top(); -} - -void -VelocityGhostRegion::lollis_between (int x0, int x1, std::vector& within) -{ - MidiRegionView* mrv = dynamic_cast (&parent_rv); - assert (mrv); - MidiRegionView::Selection const & sel (mrv->selection()); - bool only_selected = !sel.empty(); - - for (auto & gev : events) { - if (only_selected) { - if (!gev.second->event->selected()) { - continue; - } - } - ArdourCanvas::Lollipop* l = dynamic_cast (gev.second->item); - if (l) { - ArdourCanvas::Duple pos = l->item_to_canvas (ArdourCanvas::Duple (l->x(), l->y0())); - if (pos.x >= x0 && pos.x < x1) { - within.push_back (gev.second); - } - } - } -} - -void -VelocityGhostRegion::lollis_close_to_x (int x, double distance, std::vector& within) -{ - for (auto & gev : events) { - ArdourCanvas::Lollipop* l = dynamic_cast (gev.second->item); - if (l) { - ArdourCanvas::Duple pos = l->item_to_canvas (ArdourCanvas::Duple (l->x(), l->y0())); - if (std::abs (pos.x - x) < distance) { - within.push_back (gev.second); - } - } - } -} - -void -VelocityGhostRegion::start_line_drag () -{ - MidiRegionView* mrv = dynamic_cast (&parent_rv); - - mrv->begin_drag_edit (_("draw velocities")); - - for (auto & e : events) { - GhostEvent* gev (e.second); - gev->velocity_while_editing = gev->event->note()->velocity(); - } - - dragging = true; - desensitize_lollis (); -} - -void -VelocityGhostRegion::end_line_drag (bool did_change) -{ - MidiRegionView* mrv = dynamic_cast (&parent_rv); - - dragging = false; - - if (did_change) { - std::vector notes; - std::vector velocities; - - for (auto & e : events) { - GhostEvent* gev (e.second); - if (gev->event->note()->velocity() != gev->velocity_while_editing) { - notes.push_back (gev->event); - velocities.push_back (gev->velocity_while_editing); - } - } - - mrv->set_velocities_for_notes (notes, velocities); - } - - mrv->end_drag_edit (); - sensitize_lollis (); -} - -void -VelocityGhostRegion::desensitize_lollis () -{ - for (auto & gev : events) { - gev.second->item->set_ignore_events (true); - } -} - -void -VelocityGhostRegion::sensitize_lollis () -{ - for (auto & gev : events) { - gev.second->item->set_ignore_events (false); - } -} - -void -VelocityGhostRegion::set_selected (bool yn) -{ - selected = yn; - set_colors (); - - if (yn) { - group->raise_to_top (); - } -} diff --git a/gtk2_ardour/velocity_ghost_region.h b/gtk2_ardour/velocity_ghost_region.h index 9f2c02d495..e3fd9a3b31 100644 --- a/gtk2_ardour/velocity_ghost_region.h +++ b/gtk2_ardour/velocity_ghost_region.h @@ -24,53 +24,26 @@ #include "canvas/poly_line.h" #include "ghostregion.h" +#include "velocity_display.h" namespace ArdourCanvas { -class Lollipop; + class Lollipop; } -class VelocityGhostRegion : public MidiGhostRegion +class GhostEvent; + +class VelocityGhostRegion : public MidiGhostRegion, public VelocityDisplay { -public: + public: VelocityGhostRegion (MidiRegionView&, TimeAxisView& tv, TimeAxisView& source_tv, double initial_unit_pos); ~VelocityGhostRegion (); - void update_contents_height(); - void add_note(NoteBase*); - void update_note (GhostEvent* note); - void update_hit (GhostEvent* hit); void remove_note (NoteBase*); - void note_selected (NoteBase*); - - void set_colors (); - void drag_lolli (ArdourCanvas::Lollipop* l, GdkEventMotion* ev); - - int y_position_to_velocity (double y) const; - - void set_selected (bool); - - bool line_draw_motion (ArdourCanvas::Duple const & d, ArdourCanvas::Rectangle const & r, double last_x); - bool line_extended (ArdourCanvas::Duple const & from, ArdourCanvas::Duple const & to, ArdourCanvas::Rectangle const & r, double last_x); - - void start_line_drag (); - void end_line_drag (bool did_change); - ArdourCanvas::Rectangle& base_item() { return *base_rect; } - -private: - bool dragging; - ArdourCanvas::PolyLine* dragging_line; - int last_drag_x; - bool drag_did_change; - bool selected; - + void set_colors (); + private: bool base_event (GdkEvent*); - bool lollevent (GdkEvent*, MidiGhostRegion::GhostEvent*); - void set_size_and_position (MidiGhostRegion::GhostEvent&); - void lollis_close_to_x (int x, double distance, std::vector& events); - void lollis_between (int x0, int x1, std::vector& events); - void desensitize_lollis (); - void sensitize_lollis (); + bool lollevent (GdkEvent*, GhostEvent*); }; #endif /* __gtk_ardour_velocity_region_view_h__ */ diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 3d8a77b2f4..50658f664e 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -346,6 +346,7 @@ gtk2_ardour_sources = [ 'view_background.cc', 'transcode_ffmpeg.cc', 'transcode_video_dialog.cc', + 'velocity_display.cc', 'velocity_ghost_region.cc', 'video_server_dialog.cc', 'utils_videotl.cc',