Follow MIDI control values with automation faders.
authorDavid Robillard <d@drobilla.net>
Thu, 26 Mar 2015 04:47:34 +0000 (00:47 -0400)
committerDavid Robillard <d@drobilla.net>
Thu, 26 Mar 2015 04:47:34 +0000 (00:47 -0400)
Fixes bug #6166 (except record).

This attempts to follow the "current" control value somewhat aggressively:

* On locate, slider is set to the value from the top region at the new
  transport position.

* Playback or MIDI input is followed "live".

* Whenever the slider is moved (including automatically), that value is emitted
  as an immediate event to keep external gear in sync.

General idea is that the Ardour slider should act as a mirror of an external
hardware knob, and both should be synced to whatever the control is at the
current transport position.  Since we lack real playback/touch/etc modes for
these for now, we must choose one behaviour, and this seems like the most
reasonable one.

Follow is handled in the audio thread, which is probably not ideal, but since
these controls have no lists and do not record, should be fine.  Probably.

libs/ardour/ardour/midi_track.h
libs/ardour/ardour/parameter_types.h
libs/ardour/midi_track.cc
libs/evoral/evoral/MIDIEvent.hpp

index 2af5b4cf876b1b85b8a6e0b3e3cdf63e8c674c6d..a12a0c60870be1d5d9618aec3799794ca17d8e30 100644 (file)
@@ -47,6 +47,7 @@ public:
 
        void realtime_handle_transport_stopped ();
        void realtime_locate ();
+       void non_realtime_locate (framepos_t);
 
        boost::shared_ptr<Diskstream> create_diskstream ();
        void set_diskstream (boost::shared_ptr<Diskstream>);
@@ -183,6 +184,9 @@ private:
        void track_input_active (IOChange, void*);
        void map_input_active (bool);
 
+       /** Update automation controls to reflect any changes in buffers. */
+       void update_controls (const BufferSet& bufs);
+
        void filter_channels (BufferSet& bufs, ChannelMode mode, uint32_t mask); 
 
 /* if mode is ForceChannel, force mask to the lowest set channel or 1 if no
index 8442d1f1bffc9a135c6c75b4c4d730c86f2903b2..240ad2d9565dadbd5a02abdc360aac9a3f82d787 100644 (file)
@@ -24,6 +24,7 @@
 #include <stdint.h>
 
 #include "ardour/types.h"
+#include "evoral/Parameter.hpp"
 #include "evoral/midi_events.h"
 
 namespace ARDOUR {
@@ -54,6 +55,26 @@ midi_parameter_type(uint8_t status)
        }
 }
 
+inline Evoral::Parameter
+midi_parameter(const uint8_t* buf, const uint32_t len)
+{
+       const uint8_t channel = buf[0] & 0x0F;
+       switch (midi_parameter_type(buf[0])) {
+       case MidiCCAutomation:
+               return Evoral::Parameter(MidiCCAutomation, channel, buf[1]);
+       case MidiPgmChangeAutomation:
+               return Evoral::Parameter(MidiPgmChangeAutomation, channel);
+       case MidiChannelPressureAutomation:
+               return Evoral::Parameter(MidiChannelPressureAutomation, channel);
+       case MidiPitchBenderAutomation:
+               return Evoral::Parameter(MidiPitchBenderAutomation, channel);
+       case MidiSystemExclusiveAutomation:
+               return Evoral::Parameter(MidiSystemExclusiveAutomation, channel);
+       default:
+               return Evoral::Parameter(NullAutomation);
+       }
+}
+
 inline bool
 parameter_is_midi(AutomationType type)
 {
index 770e4da3fe4946f61291bec53097faf19ca3a560..b8f53a87d08ca0c36314ff9b12a90d5ddeb155c5 100644 (file)
@@ -34,6 +34,7 @@
 #include "pbd/convert.h"
 #include "evoral/midi_util.h"
 
+#include "ardour/beats_frames_converter.h"
 #include "ardour/buffer_set.h"
 #include "ardour/debug.h"
 #include "ardour/delivery.h"
@@ -42,6 +43,7 @@
 #include "ardour/midi_diskstream.h"
 #include "ardour/midi_playlist.h"
 #include "ardour/midi_port.h"
+#include "ardour/midi_region.h"
 #include "ardour/midi_track.h"
 #include "ardour/parameter_types.h"
 #include "ardour/port.h"
@@ -318,6 +320,20 @@ MidiTrack::set_state_part_two ()
        return;
 }
 
+void
+MidiTrack::update_controls(const BufferSet& bufs)
+{
+       const MidiBuffer& buf = bufs.get_midi(0);
+       for (MidiBuffer::const_iterator e = buf.begin(); e != buf.end(); ++e) {
+               const Evoral::MIDIEvent<framepos_t>&     ev      = *e;
+               const Evoral::Parameter                  param   = midi_parameter(ev.buffer(), ev.size());
+               const boost::shared_ptr<Evoral::Control> control = this->control(param);
+               if (control) {
+                       control->set_double(ev.value(), _session.transport_frame(), false);
+               }
+       }
+}
+
 /** @param need_butler to be set to true if this track now needs the butler, otherwise it can be left alone
  *  or set to false.
  */
@@ -470,6 +486,42 @@ MidiTrack::realtime_handle_transport_stopped ()
        }
 }
 
+void
+MidiTrack::non_realtime_locate (framepos_t pos)
+{
+       Track::non_realtime_locate(pos);
+
+       boost::shared_ptr<MidiPlaylist> playlist = midi_diskstream()->midi_playlist();
+       if (!playlist) {
+               return;
+       }
+
+       /* Get the top unmuted region at this position. */
+       boost::shared_ptr<MidiRegion> region = boost::dynamic_pointer_cast<MidiRegion>(
+               playlist->top_unmuted_region_at(pos));
+       if (!region) {
+               return;
+       }
+
+       Glib::Threads::Mutex::Lock lm (_control_lock, Glib::Threads::TRY_LOCK);
+       if (!lm.locked()) {
+               return;
+       }
+
+       /* Update track controllers based on its "automation". */
+       const framepos_t     origin = region->position() - region->start();
+       BeatsFramesConverter bfc(_session.tempo_map(), origin);
+       for (Controls::const_iterator c = _controls.begin(); c != _controls.end(); ++c) {
+               boost::shared_ptr<MidiTrack::MidiControl> tcontrol;
+               boost::shared_ptr<Evoral::Control>        rcontrol;
+               if ((tcontrol = boost::dynamic_pointer_cast<MidiTrack::MidiControl>(c->second)) &&
+                   (rcontrol = region->control(tcontrol->parameter()))) {
+                       const Evoral::Beats pos_beats = bfc.from(pos - origin);
+                       tcontrol->set_value(rcontrol->list()->eval(pos_beats.to_double()));
+               }
+       }
+}
+
 void
 MidiTrack::push_midi_input_to_step_edit_ringbuffer (framecnt_t nframes)
 {
@@ -539,6 +591,8 @@ MidiTrack::write_out_of_band_data (BufferSet& bufs, framepos_t /*start*/, framep
 {
        MidiBuffer& buf (bufs.get_midi (0));
 
+       update_controls (bufs);
+
        // Append immediate events
 
        if (_immediate_events.read_space()) {
index 9b1d72c400bb8c30a98f28925921112172e563ae..f0c9c745890d7fe395bca9f403268740986826a8 100644 (file)
@@ -104,6 +104,21 @@ public:
                return this->size() == 10    && this->_buf[0] == 0xf0 && this->_buf[1] == 0x7f && 
                       this->_buf[3] == 0x01 && this->_buf[4] == 0x01;
        }
+
+       inline uint16_t value() const {
+               switch (type()) {
+               case MIDI_CMD_CONTROL:
+                       return cc_value();
+               case MIDI_CMD_BENDER:
+                       return pitch_bender_value();
+               case MIDI_CMD_NOTE_PRESSURE:
+                       return aftertouch();
+               case MIDI_CMD_CHANNEL_PRESSURE:
+                       return channel_pressure();
+               default:
+                       return 0;
+               }
+       }
 };
 
 } // namespace Evoral