+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);
+ if (!ac || ac->alist()->automation_state() == Play) {
+ /* It should be "impossible" for ac to be NULL, but if it is, don't
+ filter the parameter so events aren't lost. */
+ _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_parameters, so now that we've changed that list we must invalidate
+ the iterator.
+ */
+ Glib::Threads::Mutex::Lock lm (midi_source(0)->mutex(), Glib::Threads::TRY_LOCK);
+ if (lm.locked()) {
+ /* TODO: This is too aggressive, we need more fine-grained invalidation. */
+ midi_source(0)->invalidate (lm);
+ }
+}
+
+/** This is called when a trim drag has resulted in a -ve _start time for this region.
+ * Fix it up by adding some empty space to the source.
+ */
+void
+MidiRegion::fix_negative_start ()
+{
+ BeatsFramesConverter c (_session.tempo_map(), _position);
+
+ model()->insert_silence_at_start (c.from (-_start));
+ _start = 0;
+ _start_beats = Evoral::Beats();
+}
+
+void
+MidiRegion::set_start_internal (framecnt_t s, const int32_t sub_num)
+{
+ Region::set_start_internal (s, sub_num);
+
+ if (position_lock_style() == AudioTime) {
+ set_start_beats_from_start_frames ();
+ }
+}
+
+void
+MidiRegion::trim_to_internal (framepos_t position, framecnt_t length, const int32_t sub_num)
+{
+ if (locked()) {
+ return;
+ }
+
+ PropertyChange what_changed;
+
+ /* beat has been set exactly by set_position_internal, but the source starts on a frame.
+ working in beats seems the correct thing to do, but reports of a missing first note
+ on playback suggest otherwise. for now, we work in exact beats.
+ */
+ const double pos_pulse = _session.tempo_map().exact_qn_at_frame (position, sub_num) / 4.0;
+ const double pulse_delta = pos_pulse - pulse();
+
+ /* Set position before length, otherwise for MIDI regions this bad thing happens:
+ * 1. we call set_length_internal; length in beats is computed using the region's current
+ * (soon-to-be old) position
+ * 2. we call set_position_internal; position is set and length in frames re-computed using
+ * length in beats from (1) but at the new position, which is wrong if the region
+ * straddles a tempo/meter change.
+ */
+
+ if (_position != position) {
+ /* sets _beat to new position.*/
+ set_position_internal (position, true, sub_num);
+ what_changed.add (Properties::position);
+
+ const double new_start_pulse = (_start_beats.val().to_double() / 4.0) + pulse_delta;
+ const framepos_t new_start = _position - _session.tempo_map().frame_at_pulse (pulse() - new_start_pulse);
+
+ if (!verify_start_and_length (new_start, length)) {
+ return;
+ }
+
+ _start_beats = Evoral::Beats (new_start_pulse * 4.0);
+ what_changed.add (Properties::start_beats);
+
+ set_start_internal (new_start, sub_num);
+ what_changed.add (Properties::start);
+ }
+
+ if (_length != length) {
+
+ if (!verify_start_and_length (_start, length)) {
+ return;
+ }
+
+ set_length_internal (length, sub_num);
+ what_changed.add (Properties::length);
+ what_changed.add (Properties::length_beats);
+ }
+
+ set_whole_file (false);
+
+ PropertyChange start_and_length;
+
+ start_and_length.add (Properties::start);
+ start_and_length.add (Properties::length);
+
+ if (what_changed.contains (start_and_length)) {
+ first_edit ();
+ }
+
+ if (!what_changed.empty()) {
+ send_change (what_changed);
+ }
+}