From e486a8d86fee39b3bcca59441eb5d1c945cbef19 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Fri, 17 Aug 2007 17:25:20 +0000 Subject: [PATCH] Per-region MIDI CC "automation". Extremely broken in several ways. This commit brought to you by the letters D, R, and my need to switch machines. git-svn-id: svn://localhost/ardour2/trunk@2323 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/SConscript | 1 + gtk2_ardour/automation_line.cc | 10 ++--- gtk2_ardour/automation_region_view.cc | 60 ++++++++++++++++++++++++++ gtk2_ardour/automation_region_view.h | 62 +++++++++++++++++++++++++++ gtk2_ardour/midi_region_view.cc | 44 ++++++++++++++++--- gtk2_ardour/midi_region_view.h | 6 ++- gtk2_ardour/midi_time_axis.cc | 1 + gtk2_ardour/region_view.cc | 8 ++-- gtk2_ardour/route_time_axis.cc | 12 ++++++ gtk2_ardour/route_time_axis.h | 27 +++++++----- libs/ardour/ardour/automatable.h | 5 ++- libs/ardour/ardour/automation_event.h | 2 +- libs/ardour/ardour/midi_model.h | 12 ++++-- libs/ardour/midi_model.cc | 38 ++++++++++------ 14 files changed, 242 insertions(+), 46 deletions(-) create mode 100644 gtk2_ardour/automation_region_view.cc create mode 100644 gtk2_ardour/automation_region_view.h diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript index 06ecf2d0f2..ee80002f13 100644 --- a/gtk2_ardour/SConscript +++ b/gtk2_ardour/SConscript @@ -100,6 +100,7 @@ control_point.cc automation_line.cc automation_time_axis.cc automation_controller.cc +automation_region_view.cc midi_port_dialog.cc midi_time_axis.cc midi_streamview.cc diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index d9d1b827f9..d5805aa3af 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -55,7 +55,7 @@ using namespace PBD; using namespace Editing; using namespace Gnome; // for Canvas -AutomationLine::AutomationLine (const string & name, TimeAxisView& tv, ArdourCanvas::Group& parent, boost::shared_ptr al) +AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanvas::Group& parent, boost::shared_ptr al) : trackview (tv), _name (name), alist (al), @@ -353,7 +353,7 @@ AutomationLine::model_representation (ControlPoint& cp, ModelRepresentation& mr) mr.xval = (nframes_t) floor (cp.get_x()); mr.yval = 1.0 - ( (cp.get_y() - _y_position) / _height); - /* if xval has not changed, set it directly from the model to avoid rounding errors */ + /* if xval has not changed, set it directly from the model to avoid rounding errors */ if (mr.xval == trackview.editor.frame_to_unit((*cp.model())->when)) { mr.xval = (nframes_t) (*cp.model())->when; @@ -879,7 +879,7 @@ AutomationLine::remove_point (ControlPoint& cp) model_representation (cp, mr); trackview.editor.current_session()->begin_reversible_command (_("remove control point")); - XMLNode &before = alist->get_state(); + XMLNode &before = alist->get_state(); alist->erase (mr.start, mr.end); @@ -1044,7 +1044,7 @@ AutomationLine::list_changed () void AutomationLine::reset_callback (const AutomationList& events) { - ALPoints tmp_points; + ALPoints tmp_points; uint32_t npoints = events.size(); if (npoints == 0) { @@ -1086,7 +1086,7 @@ void AutomationLine::clear () { /* parent must create command */ - XMLNode &before = get_state(); + XMLNode &before = get_state(); alist->clear(); trackview.editor.current_session()->add_command (new MementoCommand(*this, &before, &get_state())); trackview.editor.current_session()->commit_reversible_command (); diff --git a/gtk2_ardour/automation_region_view.cc b/gtk2_ardour/automation_region_view.cc new file mode 100644 index 0000000000..885a42b5ea --- /dev/null +++ b/gtk2_ardour/automation_region_view.cc @@ -0,0 +1,60 @@ +/* + Copyright (C) 2007 Paul Davis + Author: Dave Robillard + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "automation_region_view.h" + +AutomationRegionView::AutomationRegionView(ArdourCanvas::Group* parent, + AutomationTimeAxisView& time_axis, + boost::shared_ptr region, + boost::shared_ptr list, + double spu, + Gdk::Color& basic_color) + : RegionView(parent, time_axis, region, spu, basic_color) + , _line(list->parameter().to_string(), time_axis, *group, list) +{ + _line.set_colors(); + _line.show(); + _line.show_all_control_points(); + + group->raise_to_top (); +} + + +void +AutomationRegionView::set_y_position_and_height (double y, double h) +{ + RegionView::set_y_position_and_height(y, h - 1); + + _line.set_y_position_and_height ((uint32_t)y, (uint32_t) rint (h - NAME_HIGHLIGHT_SIZE)); +} + + +void +AutomationRegionView::entered() +{ + _line.track_entered(); +} + + +void +AutomationRegionView::exited() +{ + _line.track_exited(); +} + diff --git a/gtk2_ardour/automation_region_view.h b/gtk2_ardour/automation_region_view.h new file mode 100644 index 0000000000..fc152f6796 --- /dev/null +++ b/gtk2_ardour/automation_region_view.h @@ -0,0 +1,62 @@ +/* + Copyright (C) 2007 Paul Davis + Author: Dave Robillard + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __gtk_ardour_automation_region_view_h__ +#define __gtk_ardour_automation_region_view_h__ + +#include +#include + +#include "region_view.h" +#include "automation_time_axis.h" +#include "automation_line.h" +#include "enums.h" +#include "canvas.h" + +namespace ARDOUR { + class AutomationList; +}; + +class AutomationTimeAxisView; + +class AutomationRegionView : public RegionView +{ +public: + AutomationRegionView(ArdourCanvas::Group*, + AutomationTimeAxisView&, + boost::shared_ptr, + boost::shared_ptr, + double initial_samples_per_unit, + Gdk::Color& basic_color); + + ~AutomationRegionView() {} + + // We are a ghost. Meta ghosts? Crazy talk. + virtual GhostRegion* add_ghost(AutomationTimeAxisView&) { return NULL; } + +protected: + void set_y_position_and_height (double y, double h); + void entered(); + void exited(); + +private: + AutomationLine _line; +}; + +#endif /* __gtk_ardour_automation_region_view_h__ */ diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index c51cd314a2..b12f8ef4d9 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -45,6 +45,8 @@ #include "public_editor.h" #include "ghostregion.h" #include "midi_time_axis.h" +#include "automation_time_axis.h" +#include "automation_region_view.h" #include "utils.h" #include "midi_util.h" #include "gui_thread.h" @@ -58,8 +60,7 @@ using namespace PBD; using namespace Editing; using namespace ArdourCanvas; -MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, - Gdk::Color& basic_color) +MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, Gdk::Color& basic_color) : RegionView (parent, tv, r, spu, basic_color) , _default_note_length(0.0) , _active_notes(0) @@ -71,8 +72,7 @@ MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView & _note_group->raise_to_top(); } -MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, - Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility) +MidiRegionView::MidiRegionView (ArdourCanvas::Group *parent, RouteTimeAxisView &tv, boost::shared_ptr r, double spu, Gdk::Color& basic_color, TimeAxisViewItem::Visibility visibility) : RegionView (parent, tv, r, spu, basic_color, visibility) , _default_note_length(0.0) , _active_notes(0) @@ -399,15 +399,47 @@ MidiRegionView::redisplay_model() return; if (_model) { - + clear_events(); - begin_write(); + + _model->read_lock(); for (size_t i=0; i < _model->n_notes(); ++i) add_note(_model->note_at(i)); end_write(); + + for (Automatable::Controls::const_iterator i = _model->controls().begin(); + i != _model->controls().end(); ++i) { + + assert(i->second); + + boost::shared_ptr at + = midi_view()->automation_child(i->second->parameter()); + if (!at) + continue; + + Gdk::Color col = midi_stream_view()->get_region_color(); + + boost::shared_ptr arv; + + { + Glib::Mutex::Lock list_lock (i->second->list()->lock()); + + arv = boost::shared_ptr( + new AutomationRegionView(at->canvas_display, + *at.get(), _region, i->second->list(), + midi_stream_view()->get_samples_per_unit(), col)); + + _automation_children.insert(std::make_pair(i->second->parameter(), arv)); + } + + arv->init(col, true); + } + + _model->read_unlock(); + } else { cerr << "MidiRegionView::redisplay_model called without a model" << endmsg; } diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index 22196846c5..be05556ce0 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -1,5 +1,5 @@ /* - Copyright (C) 2001-2006 Paul Davis + Copyright (C) 2001-2007 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 @@ -46,6 +46,7 @@ namespace ARDOUR { class MidiTimeAxisView; class GhostRegion; class AutomationTimeAxisView; +class AutomationRegionView; class MidiRegionView : public RegionView { @@ -177,6 +178,9 @@ class MidiRegionView : public RegionView ArdourCanvas::CanvasNote** _active_notes; ArdourCanvas::Group* _note_group; ARDOUR::MidiModel::DeltaCommand* _delta_command; + + typedef std::map > AutomationChildren; + AutomationChildren _automation_children; MouseState _mouse_state; int _pressed_button; diff --git a/gtk2_ardour/midi_time_axis.cc b/gtk2_ardour/midi_time_axis.cc index 9c2808e07c..e728a7c0f6 100644 --- a/gtk2_ardour/midi_time_axis.cc +++ b/gtk2_ardour/midi_time_axis.cc @@ -81,6 +81,7 @@ using namespace Editing; MidiTimeAxisView::MidiTimeAxisView (PublicEditor& ed, Session& sess, boost::shared_ptr rt, Canvas& canvas) : AxisView(sess) // FIXME: won't compile without this, why?? , RouteTimeAxisView(ed, sess, rt, canvas) + , _note_mode(Sustained) , _note_mode_item(NULL) , _percussion_mode_item(NULL) { diff --git a/gtk2_ardour/region_view.cc b/gtk2_ardour/region_view.cc index 27ae6ca7bf..70e82d7d12 100644 --- a/gtk2_ardour/region_view.cc +++ b/gtk2_ardour/region_view.cc @@ -58,11 +58,11 @@ static const int32_t sync_mark_width = 9; sigc::signal RegionView::RegionViewGoingAway; -RegionView::RegionView (ArdourCanvas::Group* parent, - TimeAxisView& tv, +RegionView::RegionView (ArdourCanvas::Group* parent, + TimeAxisView& tv, boost::shared_ptr r, - double spu, - Gdk::Color& basic_color) + double spu, + Gdk::Color& basic_color) : TimeAxisViewItem (r->name(), *parent, tv, spu, basic_color, r->position(), r->length(), TimeAxisViewItem::Visibility (TimeAxisViewItem::ShowNameText| TimeAxisViewItem::ShowNameHighlight| diff --git a/gtk2_ardour/route_time_axis.cc b/gtk2_ardour/route_time_axis.cc index dbfb8b029b..020b682f0a 100644 --- a/gtk2_ardour/route_time_axis.cc +++ b/gtk2_ardour/route_time_axis.cc @@ -1998,3 +1998,15 @@ RouteTimeAxisView::set_layer_display (LayerDisplay d) { _view->set_layer_display (d); } + + +boost::shared_ptr +RouteTimeAxisView::automation_child(ARDOUR::Parameter param) +{ + AutomationTracks::iterator i = _automation_tracks.find(param); + if (i != _automation_tracks.end()) + return i->second->track; + else + return boost::shared_ptr(); +} + diff --git a/gtk2_ardour/route_time_axis.h b/gtk2_ardour/route_time_axis.h index cd92f47322..1331831f39 100644 --- a/gtk2_ardour/route_time_axis.h +++ b/gtk2_ardour/route_time_axis.h @@ -95,9 +95,24 @@ public: void clear_playlist (); void build_playlist_menu (Gtk::Menu *); + + /* This is a bit nasty to expose :/ */ + struct RouteAutomationNode { + ARDOUR::Parameter param; + Gtk::CheckMenuItem* menu_item; + boost::shared_ptr track; + + RouteAutomationNode (ARDOUR::Parameter par, Gtk::CheckMenuItem* mi, boost::shared_ptr tr) + : param (par), menu_item (mi), track (tr) {} + }; virtual void create_automation_child (ARDOUR::Parameter param, bool show) = 0; + typedef map AutomationTracks; + AutomationTracks automation_tracks() { return _automation_tracks; } + + boost::shared_ptr automation_child(ARDOUR::Parameter param); + string name() const; StreamView* view() const { return _view; } ARDOUR::RouteGroup* edit_group() const; @@ -105,18 +120,9 @@ public: protected: friend class StreamView; - - struct RouteAutomationNode { - ARDOUR::Parameter param; - Gtk::CheckMenuItem* menu_item; - boost::shared_ptr track; - - RouteAutomationNode (ARDOUR::Parameter par, Gtk::CheckMenuItem* mi, boost::shared_ptr tr) - : param (par), menu_item (mi), track (tr) {} - }; struct ProcessorAutomationNode { - ARDOUR::Parameter what; + ARDOUR::Parameter what; Gtk::CheckMenuItem* menu_item; boost::shared_ptr view; RouteTimeAxisView& parent; @@ -268,7 +274,6 @@ protected: // Set from XML so context menu automation buttons can be correctly initialized set _show_automation; - typedef map AutomationTracks; AutomationTracks _automation_tracks; sigc::connection modified_connection; diff --git a/libs/ardour/ardour/automatable.h b/libs/ardour/ardour/automatable.h index 0af996f6c0..574d7af129 100644 --- a/libs/ardour/ardour/automatable.h +++ b/libs/ardour/ardour/automatable.h @@ -52,7 +52,8 @@ public: boost::shared_ptr control_factory(boost::shared_ptr list); typedef std::map > Controls; - Controls controls() { return _controls; } + Controls& controls() { return _controls; } + const Controls& controls() const { return _controls; } virtual void add_control(boost::shared_ptr); @@ -79,6 +80,8 @@ public: const std::set& what_can_be_automated() const { return _can_automate_list; } void mark_automation_visible(Parameter, bool); + + Glib::Mutex& automation_lock() const { return _automation_lock; } protected: diff --git a/libs/ardour/ardour/automation_event.h b/libs/ardour/ardour/automation_event.h index 7532ede603..1675dbc822 100644 --- a/libs/ardour/ardour/automation_event.h +++ b/libs/ardour/ardour/automation_event.h @@ -170,7 +170,7 @@ class AutomationList : public PBD::StatefulDestructible Glib::Mutex::Lock lm (_lock); (obj.*method)(*this); } - + sigc::signal StateChanged; XMLNode& get_state(void); diff --git a/libs/ardour/ardour/midi_model.h b/libs/ardour/ardour/midi_model.h index f688c0fe09..27a11fa9fc 100644 --- a/libs/ardour/ardour/midi_model.h +++ b/libs/ardour/ardour/midi_model.h @@ -77,6 +77,12 @@ public: }; MidiModel(Session& s, size_t size=0); + + // This is crap. + void write_lock() { _lock.writer_lock(); _automation_lock.lock(); } + void write_unlock() { _lock.writer_unlock(); _automation_lock.unlock(); } + void read_lock() { _lock.reader_lock(); _automation_lock.lock(); } + void read_unlock() { _lock.reader_unlock(); _automation_lock.unlock(); } void clear() { _notes.clear(); } @@ -167,9 +173,9 @@ private: bool is_sorted() const; #endif - void append_note_on(double time, uint8_t note, uint8_t velocity); - void append_note_off(double time, uint8_t note); - void append_cc(double time, uint8_t number, uint8_t value); + void append_note_on_unlocked(double time, uint8_t note, uint8_t velocity); + void append_note_off_unlocked(double time, uint8_t note); + void append_cc_unlocked(double time, uint8_t number, uint8_t value); Glib::RWLock _lock; diff --git a/libs/ardour/midi_model.cc b/libs/ardour/midi_model.cc index 7c67d40d75..7c80ed9a3b 100644 --- a/libs/ardour/midi_model.cc +++ b/libs/ardour/midi_model.cc @@ -193,9 +193,10 @@ void MidiModel::start_write() { //cerr << "MM " << this << " START WRITE, MODE = " << enum_2_string(_note_mode) << endl; - _lock.writer_lock(); + write_lock(); _writing = true; _write_notes.clear(); + write_unlock(); } @@ -209,6 +210,7 @@ MidiModel::start_write() void MidiModel::end_write(bool delete_stuck) { + write_lock(); assert(_writing); //cerr << "MM " << this << " END WRITE: " << _notes.size() << " NOTES\n"; @@ -226,7 +228,7 @@ MidiModel::end_write(bool delete_stuck) _write_notes.clear(); _writing = false; - _lock.writer_unlock(); + write_unlock(); } @@ -241,12 +243,16 @@ MidiModel::end_write(bool delete_stuck) void MidiModel::append(const MidiBuffer& buf) { + write_lock(); + assert(_writing); for (MidiBuffer::const_iterator i = buf.begin(); i != buf.end(); ++i) { assert(_notes.empty() || (*i).time() >= _notes.back().time()); append(*i); } + + write_unlock(); } @@ -259,22 +265,26 @@ MidiModel::append(const MidiBuffer& buf) void MidiModel::append(const MidiEvent& ev) { + write_lock(); + assert(_notes.empty() || ev.time() >= _notes.back().time()); assert(_writing); if (ev.is_note_on()) - append_note_on(ev.time(), ev.note(), ev.velocity()); + append_note_on_unlocked(ev.time(), ev.note(), ev.velocity()); else if (ev.is_note_off()) - append_note_off(ev.time(), ev.note()); + append_note_off_unlocked(ev.time(), ev.note()); else if (ev.is_cc()) - append_cc(ev.time(), ev.cc_number(), ev.cc_value()); + append_cc_unlocked(ev.time(), ev.cc_number(), ev.cc_value()); else printf("MM Unknown event type %X\n", ev.type()); + + write_unlock(); } void -MidiModel::append_note_on(double time, uint8_t note_num, uint8_t velocity) +MidiModel::append_note_on_unlocked(double time, uint8_t note_num, uint8_t velocity) { //cerr << "MidiModel " << this << " note " << (int)note_num << " on @ " << time << endl; @@ -290,7 +300,7 @@ MidiModel::append_note_on(double time, uint8_t note_num, uint8_t velocity) void -MidiModel::append_note_off(double time, uint8_t note_num) +MidiModel::append_note_off_unlocked(double time, uint8_t note_num) { //cerr << "MidiModel " << this << " note " << (int)note_num << " off @ " << time << endl; @@ -322,7 +332,7 @@ MidiModel::append_note_off(double time, uint8_t note_num) void -MidiModel::append_cc(double time, uint8_t number, uint8_t value) +MidiModel::append_cc_unlocked(double time, uint8_t number, uint8_t value) { Parameter param(MidiCCAutomation, number); @@ -427,7 +437,7 @@ MidiModel::DeltaCommand::operator()() // This could be made much faster by using a priority_queue for added and // removed notes (or sort here), and doing a single iteration over _model - _model._lock.writer_lock(); + _model.write_lock(); for (std::list::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) _model.add_note_unlocked(*i); @@ -435,7 +445,7 @@ MidiModel::DeltaCommand::operator()() for (std::list::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) _model.remove_note_unlocked(*i); - _model._lock.writer_unlock(); + _model.write_unlock(); _model.ContentsChanged(); /* EMIT SIGNAL */ } @@ -447,7 +457,7 @@ MidiModel::DeltaCommand::undo() // This could be made much faster by using a priority_queue for added and // removed notes (or sort here), and doing a single iteration over _model - _model._lock.writer_lock(); + _model.write_lock(); for (std::list::iterator i = _added_notes.begin(); i != _added_notes.end(); ++i) _model.remove_note_unlocked(*i); @@ -455,7 +465,7 @@ MidiModel::DeltaCommand::undo() for (std::list::iterator i = _removed_notes.begin(); i != _removed_notes.end(); ++i) _model.add_note_unlocked(*i); - _model._lock.writer_unlock(); + _model.write_unlock(); _model.ContentsChanged(); /* EMIT SIGNAL */ } @@ -484,7 +494,7 @@ MidiModel::write_to(boost::shared_ptr source) source->append_event_unlocked(ev); }*/ - _lock.reader_lock(); + read_lock(); LaterNoteEndComparator cmp; ActiveNotes active_notes(cmp); @@ -518,7 +528,7 @@ MidiModel::write_to(boost::shared_ptr source) _edited = false; - _lock.reader_unlock(); + read_unlock(); return true; }