Make MIDI region `automation' respect the automation mode so that it is
only played back if the automation mode is set to "Play". Munge AutoState for AutomationRegionViews so that they reflect their AutomationTimeAxisView's setting. Fixes #3135. git-svn-id: svn://localhost/ardour2/branches/3.0@7304 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
ad81fd40d2
commit
ecb0cd5d11
@ -54,7 +54,6 @@ public:
|
||||
inline AutomationTimeAxisView* automation_view() const
|
||||
{ return dynamic_cast<AutomationTimeAxisView*>(&trackview); }
|
||||
|
||||
void set_line(boost::shared_ptr<AutomationLine> line) { _line = line; }
|
||||
boost::shared_ptr<AutomationLine> line() { return _line; }
|
||||
|
||||
// We are a ghost. Meta ghosts? Crazy talk.
|
||||
|
@ -58,6 +58,7 @@ AutomationStreamView::AutomationStreamView (AutomationTimeAxisView& tv)
|
||||
new ArdourCanvas::Group(*tv.canvas_display()))
|
||||
, _controller(tv.controller())
|
||||
, _automation_view(tv)
|
||||
, _pending_automation_state (Off)
|
||||
{
|
||||
//canvas_rect->property_fill_color_rgba() = stream_base_color;
|
||||
canvas_rect->property_outline_color_rgba() = RGBA_BLACK;
|
||||
@ -82,9 +83,9 @@ AutomationStreamView::add_region_view_internal (boost::shared_ptr<Region> region
|
||||
mr->midi_source()->load_model();
|
||||
}
|
||||
|
||||
const boost::shared_ptr<AutomationControl> control
|
||||
= boost::dynamic_pointer_cast<AutomationControl>(
|
||||
region->control(_controller->controllable()->parameter()));
|
||||
const boost::shared_ptr<AutomationControl> control = boost::dynamic_pointer_cast<AutomationControl> (
|
||||
region->control (_controller->controllable()->parameter(), true)
|
||||
);
|
||||
|
||||
boost::shared_ptr<AutomationList> list;
|
||||
if (control) {
|
||||
@ -130,6 +131,12 @@ AutomationStreamView::add_region_view_internal (boost::shared_ptr<Region> region
|
||||
/* catch regionview going away */
|
||||
region->DropReferences.connect (*this, invalidator (*this), boost::bind (&AutomationStreamView::remove_region_view, this, boost::weak_ptr<Region>(region)), gui_context());
|
||||
|
||||
/* setup automation state for this region */
|
||||
boost::shared_ptr<AutomationLine> line = region_view->line ();
|
||||
if (line && line->the_list()) {
|
||||
line->the_list()->set_automation_state (automation_state ());
|
||||
}
|
||||
|
||||
RegionViewAdded (region_view);
|
||||
|
||||
return region_view;
|
||||
@ -144,11 +151,18 @@ AutomationStreamView::display_region(AutomationRegionView* region_view)
|
||||
void
|
||||
AutomationStreamView::set_automation_state (AutoState state)
|
||||
{
|
||||
std::list<RegionView *>::iterator i;
|
||||
for (i = region_views.begin(); i != region_views.end(); ++i) {
|
||||
boost::shared_ptr<AutomationLine> line = ((AutomationRegionView*)(*i))->line();
|
||||
if (line && line->the_list()) {
|
||||
line->the_list()->set_automation_state (state);
|
||||
/* XXX: not sure if this is right, but for now the automation state is basically held by
|
||||
the regions' AutomationLists. Each region is always set to have the same AutoState.
|
||||
*/
|
||||
|
||||
if (region_views.empty()) {
|
||||
_pending_automation_state = state;
|
||||
} else {
|
||||
for (std::list<RegionView *>::iterator i = region_views.begin(); i != region_views.end(); ++i) {
|
||||
boost::shared_ptr<AutomationLine> line = dynamic_cast<AutomationRegionView*>(*i)->line();
|
||||
if (line && line->the_list()) {
|
||||
line->the_list()->set_automation_state (state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -196,10 +210,8 @@ AutomationStreamView::color_handler ()
|
||||
AutoState
|
||||
AutomationStreamView::automation_state () const
|
||||
{
|
||||
/* XXX: bit of a hack: just return the state of our first RegionView */
|
||||
|
||||
if (region_views.empty()) {
|
||||
return Off;
|
||||
return _pending_automation_state;
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationLine> line = ((AutomationRegionView*) region_views.front())->line ();
|
||||
|
@ -69,6 +69,8 @@ class AutomationStreamView : public StreamView
|
||||
boost::shared_ptr<AutomationController> _controller;
|
||||
|
||||
AutomationTimeAxisView& _automation_view;
|
||||
/** automation state that should be applied when this view gets its first RegionView */
|
||||
ARDOUR::AutoState _pending_automation_state;
|
||||
};
|
||||
|
||||
#endif /* __ardour_automation_streamview_h__ */
|
||||
|
@ -270,6 +270,7 @@ AutomationTimeAxisView::set_automation_state (AutoState state)
|
||||
#endif
|
||||
}
|
||||
|
||||
cout << "_view = " << _view << "\n";
|
||||
if (_view) {
|
||||
_view->set_automation_state (state);
|
||||
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include "pbd/signals.h"
|
||||
#include "evoral/ControlSet.hpp"
|
||||
#include "ardour/types.h"
|
||||
|
||||
@ -94,6 +95,9 @@ public:
|
||||
int set_automation_state (const XMLNode&, Evoral::Parameter default_param);
|
||||
XMLNode& get_automation_state();
|
||||
|
||||
/** Emitted when the automation state of one of our controls changes */
|
||||
PBD::Signal1<void, Evoral::Parameter> AutomationStateChanged;
|
||||
|
||||
protected:
|
||||
Session& _a_session;
|
||||
|
||||
@ -109,6 +113,10 @@ public:
|
||||
|
||||
nframes_t _last_automation_snapshot;
|
||||
static nframes_t _automation_interval;
|
||||
|
||||
private:
|
||||
void automation_state_changed (Evoral::Parameter const &);
|
||||
PBD::ScopedConnectionList _control_connections; ///< connections to our controls' signals
|
||||
};
|
||||
|
||||
|
||||
|
@ -146,7 +146,7 @@ public:
|
||||
|
||||
InsertMergePolicy insert_merge_policy () const;
|
||||
void set_insert_merge_policy (InsertMergePolicy);
|
||||
|
||||
|
||||
protected:
|
||||
int resolve_overlaps_unlocked (const NotePtr, void* arg = 0);
|
||||
|
||||
|
@ -115,6 +115,12 @@ class MidiRegion : public Region
|
||||
void set_position_internal (framepos_t pos, bool allow_bbt_recompute);
|
||||
|
||||
void switch_source(boost::shared_ptr<Source> source);
|
||||
void model_changed ();
|
||||
void model_automation_state_changed (Evoral::Parameter const &);
|
||||
|
||||
std::set<Evoral::Parameter> _filtered_parameters; ///< parameters that we ask our source not to return when reading
|
||||
PBD::ScopedConnection _model_connection;
|
||||
PBD::ScopedConnection _source_connection;
|
||||
};
|
||||
|
||||
} /* namespace ARDOUR */
|
||||
|
@ -63,7 +63,10 @@ class MidiSource : virtual public Source
|
||||
virtual nframes_t midi_read (Evoral::EventSink<nframes_t>& dst,
|
||||
sframes_t source_start,
|
||||
sframes_t start, nframes_t cnt,
|
||||
sframes_t stamp_offset, sframes_t negative_stamp_offset, MidiStateTracker*) const;
|
||||
sframes_t stamp_offset,
|
||||
sframes_t negative_stamp_offset,
|
||||
MidiStateTracker*,
|
||||
std::set<Evoral::Parameter> const &) const;
|
||||
|
||||
virtual nframes_t midi_write (MidiRingBuffer<nframes_t>& src,
|
||||
sframes_t source_start,
|
||||
@ -111,9 +114,12 @@ class MidiSource : virtual public Source
|
||||
void set_note_mode(NoteMode mode);
|
||||
|
||||
boost::shared_ptr<MidiModel> model() { return _model; }
|
||||
void set_model(boost::shared_ptr<MidiModel> m) { _model = m; }
|
||||
void set_model (boost::shared_ptr<MidiModel>);
|
||||
void drop_model();
|
||||
|
||||
/** Emitted when a different MidiModel is set */
|
||||
PBD::Signal0<void> ModelChanged;
|
||||
|
||||
protected:
|
||||
virtual void flush_midi() = 0;
|
||||
|
||||
|
@ -66,7 +66,7 @@ public:
|
||||
/** A control that will send "immediate" events to a MIDI track when twiddled */
|
||||
struct MidiControl : public AutomationControl {
|
||||
MidiControl(MidiTrack* route, const Evoral::Parameter& param,
|
||||
boost::shared_ptr<AutomationList> al = boost::shared_ptr<AutomationList>())
|
||||
boost::shared_ptr<AutomationList> al = boost::shared_ptr<AutomationList>())
|
||||
: AutomationControl (route->session(), param, al)
|
||||
, _route (route)
|
||||
{}
|
||||
|
@ -150,6 +150,16 @@ Automatable::add_control(boost::shared_ptr<Evoral::Control> ac)
|
||||
ControlSet::add_control(ac);
|
||||
_can_automate_list.insert(param);
|
||||
auto_state_changed(param); // sync everything up
|
||||
|
||||
/* connect to automation_state_changed so that we can emit a signal when one of our controls'
|
||||
automation state changes
|
||||
*/
|
||||
boost::shared_ptr<AutomationControl> c = boost::dynamic_pointer_cast<AutomationControl> (ac);
|
||||
if (c) {
|
||||
c->alist()->automation_state_changed.connect_same_thread (
|
||||
_control_connections, boost::bind (&Automatable::automation_state_changed, this, c->parameter())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@ -469,3 +479,8 @@ Automatable::automation_control (const Evoral::Parameter& id) const
|
||||
return boost::dynamic_pointer_cast<const AutomationControl>(Evoral::ControlSet::control(id));
|
||||
}
|
||||
|
||||
void
|
||||
Automatable::automation_state_changed (Evoral::Parameter const & p)
|
||||
{
|
||||
AutomationStateChanged (p); /* EMIT SIGNAL */
|
||||
}
|
||||
|
@ -24,7 +24,6 @@
|
||||
|
||||
#include <set>
|
||||
|
||||
|
||||
#include <glibmm/thread.h>
|
||||
|
||||
#include "pbd/basename.h"
|
||||
@ -53,6 +52,8 @@ MidiRegion::MidiRegion (const SourceList& srcs)
|
||||
: Region (srcs)
|
||||
{
|
||||
midi_source(0)->Switched.connect_same_thread (*this, boost::bind (&MidiRegion::switch_source, this, _1));
|
||||
midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
|
||||
model_changed ();
|
||||
assert(_name.val().find("/") == string::npos);
|
||||
assert(_type == DataType::MIDI);
|
||||
}
|
||||
@ -63,6 +64,8 @@ MidiRegion::MidiRegion (boost::shared_ptr<const MidiRegion> other, frameoffset_t
|
||||
{
|
||||
assert(_name.val().find("/") == string::npos);
|
||||
midi_source(0)->Switched.connect_same_thread (*this, boost::bind (&MidiRegion::switch_source, this, _1));
|
||||
midi_source(0)->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
|
||||
model_changed ();
|
||||
}
|
||||
|
||||
MidiRegion::~MidiRegion ()
|
||||
@ -180,7 +183,8 @@ MidiRegion::_read_at (const SourceList& /*srcs*/, Evoral::EventSink<nframes_t>&
|
||||
to_read, // read duration in frames
|
||||
output_buffer_position, // the offset in the output buffer
|
||||
negative_output_buffer_position, // amount to substract from note times
|
||||
tracker
|
||||
tracker,
|
||||
_filtered_parameters
|
||||
) != to_read) {
|
||||
return 0; /* "read nothing" */
|
||||
}
|
||||
@ -246,14 +250,63 @@ MidiRegion::midi_source (uint32_t n) const
|
||||
void
|
||||
MidiRegion::switch_source(boost::shared_ptr<Source> src)
|
||||
{
|
||||
_source_connection.disconnect ();
|
||||
|
||||
boost::shared_ptr<MidiSource> msrc = boost::dynamic_pointer_cast<MidiSource>(src);
|
||||
if (!msrc)
|
||||
if (!msrc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// MIDI regions have only one source
|
||||
_sources.clear();
|
||||
_sources.push_back(msrc);
|
||||
|
||||
set_name(msrc->name());
|
||||
|
||||
msrc->ModelChanged.connect_same_thread (_source_connection, boost::bind (&MidiRegion::model_changed, this));
|
||||
}
|
||||
|
||||
void
|
||||
MidiRegion::model_changed ()
|
||||
{
|
||||
/* build list of filtered Parameters, being those whose automation state is not `Play' */
|
||||
|
||||
_filtered_parameters.clear ();
|
||||
|
||||
Automatable::Controls const & c = model()->controls();
|
||||
|
||||
for (Automatable::Controls::const_iterator i = c.begin(); i != c.end(); ++i) {
|
||||
boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (i->second);
|
||||
assert (ac);
|
||||
if (ac->alist()->automation_state() != Play) {
|
||||
_filtered_parameters.insert (ac->parameter ());
|
||||
}
|
||||
}
|
||||
|
||||
/* watch for changes to controls' AutoState */
|
||||
model()->AutomationStateChanged.connect_same_thread (
|
||||
_model_connection, boost::bind (&MidiRegion::model_automation_state_changed, this, _1)
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
MidiRegion::model_automation_state_changed (Evoral::Parameter const & p)
|
||||
{
|
||||
/* Update our filtered parameters list after a change to a parameter's AutoState */
|
||||
|
||||
boost::shared_ptr<AutomationControl> ac = model()->automation_control (p);
|
||||
assert (ac);
|
||||
|
||||
if (ac->alist()->automation_state() == Play) {
|
||||
_filtered_parameters.erase (p);
|
||||
} else {
|
||||
_filtered_parameters.insert (p);
|
||||
}
|
||||
|
||||
/* the source will have an iterator into the model, and that iterator will have been set up
|
||||
for a given set of filtered_paramters, so now that we've changed that list we must invalidate
|
||||
the iterator.
|
||||
*/
|
||||
Glib::Mutex::Lock lm (midi_source(0)->mutex());
|
||||
midi_source(0)->invalidate ();
|
||||
}
|
||||
|
@ -142,11 +142,13 @@ MidiSource::invalidate ()
|
||||
_model_iter.invalidate();
|
||||
}
|
||||
|
||||
/** @param filtered A set of parameters whose MIDI messages will not be returned */
|
||||
nframes_t
|
||||
MidiSource::midi_read (Evoral::EventSink<nframes_t>& dst, sframes_t source_start,
|
||||
sframes_t start, nframes_t cnt,
|
||||
sframes_t stamp_offset, sframes_t negative_stamp_offset,
|
||||
MidiStateTracker* tracker) const
|
||||
MidiStateTracker* tracker,
|
||||
std::set<Evoral::Parameter> const & filtered) const
|
||||
{
|
||||
Glib::Mutex::Lock lm (_lock);
|
||||
|
||||
@ -158,7 +160,7 @@ MidiSource::midi_read (Evoral::EventSink<nframes_t>& dst, sframes_t source_start
|
||||
// If the cached iterator is invalid, search for the first event past start
|
||||
if (_last_read_end == 0 || start != _last_read_end || !_model_iter_valid) {
|
||||
DEBUG_TRACE (DEBUG::MidiSourceIO, string_compose ("*** %1 search for relevant iterator for %1 / %2\n", _name, source_start, start));
|
||||
for (i = _model->begin(); i != _model->end(); ++i) {
|
||||
for (i = _model->begin(0, filtered); i != _model->end(); ++i) {
|
||||
if (converter.to(i->time()) >= start) {
|
||||
break;
|
||||
}
|
||||
@ -311,4 +313,12 @@ MidiSource::drop_model ()
|
||||
{
|
||||
cerr << name() << " drop model\n";
|
||||
_model.reset();
|
||||
ModelChanged (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
void
|
||||
MidiSource::set_model (boost::shared_ptr<MidiModel> m)
|
||||
{
|
||||
_model = m;
|
||||
ModelChanged (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ public:
|
||||
class const_iterator {
|
||||
public:
|
||||
const_iterator();
|
||||
const_iterator(const Sequence<Time>& seq, Time t);
|
||||
const_iterator(const Sequence<Time>& seq, Time t, std::set<Evoral::Parameter> const &);
|
||||
~const_iterator();
|
||||
|
||||
inline bool valid() const { return !_is_end && _event; }
|
||||
@ -221,7 +221,9 @@ public:
|
||||
ControlIterators::iterator _control_iter;
|
||||
};
|
||||
|
||||
const_iterator begin(Time t=0) const { return const_iterator(*this, t); }
|
||||
const_iterator begin (Time t=0, std::set<Evoral::Parameter> const & f = std::set<Evoral::Parameter> ()) const {
|
||||
return const_iterator (*this, t, f);
|
||||
}
|
||||
const const_iterator& end() const { return _end_iter; }
|
||||
|
||||
typename Notes::const_iterator note_lower_bound (Time t) const;
|
||||
|
@ -53,7 +53,7 @@ Sequence<Time>::const_iterator::const_iterator()
|
||||
}
|
||||
|
||||
template<typename Time>
|
||||
Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t)
|
||||
Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t, std::set<Evoral::Parameter> const & filtered)
|
||||
: _seq(&seq)
|
||||
, _type(NIL)
|
||||
, _is_end((t == DBL_MAX) || seq.empty())
|
||||
@ -90,6 +90,12 @@ Sequence<Time>::const_iterator::const_iterator(const Sequence<Time>& seq, Time t
|
||||
bool found = false;
|
||||
size_t earliest_control_index = 0;
|
||||
for (Controls::const_iterator i = seq._controls.begin(); i != seq._controls.end(); ++i) {
|
||||
|
||||
if (filtered.find (i->first) != filtered.end()) {
|
||||
/* this parameter is filtered, so don't bother setting up an iterator for it */
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_TRACE (DEBUG::Sequence, string_compose ("Iterator: control: %1\n", seq._type_map.to_symbol(i->first)));
|
||||
double x, y;
|
||||
bool ret = i->second->list()->rt_safe_earliest_event_unlocked(t, DBL_MAX, x, y, true);
|
||||
@ -385,7 +391,7 @@ Sequence<Time>::Sequence(const TypeMap& type_map)
|
||||
, _overlap_pitch_resolution (FirstOnFirstOff)
|
||||
, _writing(false)
|
||||
, _type_map(type_map)
|
||||
, _end_iter(*this, DBL_MAX)
|
||||
, _end_iter(*this, DBL_MAX, std::set<Evoral::Parameter> ())
|
||||
, _percussive(false)
|
||||
, _lowest_note(127)
|
||||
, _highest_note(0)
|
||||
@ -403,7 +409,7 @@ Sequence<Time>::Sequence(const Sequence<Time>& other)
|
||||
, _overlap_pitch_resolution (other._overlap_pitch_resolution)
|
||||
, _writing(false)
|
||||
, _type_map(other._type_map)
|
||||
, _end_iter(*this, DBL_MAX)
|
||||
, _end_iter(*this, DBL_MAX, std::set<Evoral::Parameter> ())
|
||||
, _percussive(other._percussive)
|
||||
, _lowest_note(other._lowest_note)
|
||||
, _highest_note(other._highest_note)
|
||||
|
Loading…
Reference in New Issue
Block a user