set sidechain port pretty name
[ardour.git] / libs / ardour / midi_scene_changer.cc
index 65503cb0c1e35ac27af262c152f5edc0dc29374e..7f6c865922cf720602bbb8d9f7d59c7a3ae65222 100644 (file)
@@ -22,7 +22,9 @@
 #include "midi++/parser.h"
 #include "midi++/port.h"
 
+#include "ardour/async_midi_port.h"
 #include "ardour/event_type_map.h"
+#include "ardour/midi_buffer.h"
 #include "ardour/midi_port.h"
 #include "ardour/midi_scene_change.h"
 #include "ardour/midi_scene_changer.h"
@@ -35,14 +37,19 @@ using namespace ARDOUR;
 MIDISceneChanger::MIDISceneChanger (Session& s)
        : SceneChanger (s)
        , _recording (true)
-       , last_bank_message_time (-1)
+       , have_seen_bank_changes (false)
        , last_program_message_time (-1)
        , last_delivered_program (-1)
        , last_delivered_bank (-1)
-         
+
 {
-       _session.locations()->changed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this, _1));
-       Location::scene_changed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::gather, this));
+       /* catch any add/remove/clear etc. for all Locations */
+       _session.locations()->changed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
+       _session.locations()->added.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
+       _session.locations()->removed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
+
+       /* catch class-based signal that notifies of us changes in the scene change state of any Location */
+       Location::scene_changed.connect_same_thread (*this, boost::bind (&MIDISceneChanger::locations_changed, this));
 }
 
 MIDISceneChanger::~MIDISceneChanger ()
@@ -50,21 +57,22 @@ MIDISceneChanger::~MIDISceneChanger ()
 }
 
 void
-MIDISceneChanger::locations_changed (Locations::Change)
+MIDISceneChanger::locations_changed ()
 {
-       gather ();
+       _session.locations()->apply (*this, &MIDISceneChanger::gather);
 }
 
 /** Use the session's list of locations to collect all patch changes.
- * 
+ *
  * This is called whenever the locations change in anyway.
  */
 void
-MIDISceneChanger::gather ()
+MIDISceneChanger::gather (const Locations::LocationList& locations)
 {
-       const Locations::LocationList& locations (_session.locations()->list());
        boost::shared_ptr<SceneChange> sc;
 
+       Glib::Threads::RWLock::WriterLock lm (scene_lock);
+
        scenes.clear ();
 
        for (Locations::LocationList::const_iterator l = locations.begin(); l != locations.end(); ++l) {
@@ -74,18 +82,29 @@ MIDISceneChanger::gather ()
                        boost::shared_ptr<MIDISceneChange> msc = boost::dynamic_pointer_cast<MIDISceneChange> (sc);
 
                        if (msc) {
-                               scenes.insert (std::make_pair (msc->time(), msc));
+
+                               if (msc->bank() >= 0) {
+                                       have_seen_bank_changes = true;
+                               }
+
+                               scenes.insert (std::make_pair ((*l)->start(), msc));
                        }
                }
        }
 }
 
 void
-MIDISceneChanger::deliver (MidiBuffer& mbuf, framepos_t when, boost::shared_ptr<MIDISceneChange> msc)
+MIDISceneChanger::rt_deliver (MidiBuffer& mbuf, framepos_t when, boost::shared_ptr<MIDISceneChange> msc)
 {
+        if (!msc->active()) {
+                return;
+        }
+
        uint8_t buf[4];
        size_t cnt;
 
+       MIDIOutputActivity (); /* EMIT SIGNAL */
+
        if ((cnt = msc->get_bank_msb_message (buf, sizeof (buf))) > 0) {
                mbuf.push_back (when, cnt, buf);
 
@@ -102,12 +121,51 @@ MIDISceneChanger::deliver (MidiBuffer& mbuf, framepos_t when, boost::shared_ptr<
                last_delivered_program = msc->program();
        }
 }
-                       
+
+void
+MIDISceneChanger::non_rt_deliver (boost::shared_ptr<MIDISceneChange> msc)
+{
+        if (!msc->active()) {
+                return;
+        }
+
+       uint8_t buf[4];
+       size_t cnt;
+       boost::shared_ptr<AsyncMIDIPort> aport = boost::dynamic_pointer_cast<AsyncMIDIPort>(output_port);
+
+       /* We use zero as the timestamp for these messages because we are in a
+          non-RT/process context. Using zero means "deliver them as early as
+          possible" (practically speaking, in the next process callback).
+       */
+
+       MIDIOutputActivity (); /* EMIT SIGNAL */
+
+       if ((cnt = msc->get_bank_msb_message (buf, sizeof (buf))) > 0) {
+               aport->write (buf, cnt, 0);
+
+               if ((cnt = msc->get_bank_lsb_message (buf, sizeof (buf))) > 0) {
+                       aport->write (buf, cnt, 0);
+               }
+
+               last_delivered_bank = msc->bank();
+       }
+
+       if ((cnt = msc->get_program_message (buf, sizeof (buf))) > 0) {
+               aport->write (buf, cnt, 0);
+               last_delivered_program = msc->program();
+       }
+}
 
 void
 MIDISceneChanger::run (framepos_t start, framepos_t end)
 {
-       if (!output_port || recording()) {
+       if (!output_port || recording() || !_session.transport_rolling()) {
+               return;
+       }
+
+       Glib::Threads::RWLock::ReaderLock lm (scene_lock, Glib::Threads::TRY_LOCK);
+
+       if (!lm.locked()) {
                return;
        }
 
@@ -121,8 +179,8 @@ MIDISceneChanger::run (framepos_t start, framepos_t end)
                if (i->first >= end) {
                        break;
                }
-               
-               deliver (mbuf, i->first - start, i->second);
+
+               rt_deliver (mbuf, i->first - start, i->second);
 
                ++i;
        }
@@ -131,35 +189,62 @@ MIDISceneChanger::run (framepos_t start, framepos_t end)
 void
 MIDISceneChanger::locate (framepos_t pos)
 {
-       Scenes::const_iterator i = scenes.upper_bound (pos);
+       boost::shared_ptr<MIDISceneChange> msc;
 
-       if (i == scenes.end()) {
-               return;
+       {
+               Glib::Threads::RWLock::ReaderLock lm (scene_lock);
+
+               if (scenes.empty()) {
+                       return;
+               }
+
+               Scenes::const_iterator i = scenes.lower_bound (pos);
+
+               if (i != scenes.end()) {
+
+                       if (i->first != pos) {
+                               /* i points to first scene with position > pos, so back
+                                * up, if possible.
+                                */
+                               if (i != scenes.begin()) {
+                                       --i;
+                               } else {
+                                       return;
+                               }
+                       }
+               } else {
+                       /* go back to the final scene and use it */
+                       --i;
+               }
+
+               msc = i->second;
        }
 
-       if (i->second->program() != last_delivered_program || i->second->bank() != last_delivered_bank) {
-               // MidiBuffer& mbuf (output_port->get_midi_buffer (end-start));
-               // deliver (mbuf, i->first, i->second);
+       if (msc->program() != last_delivered_program || msc->bank() != last_delivered_bank) {
+               non_rt_deliver (msc);
        }
-}              
+}
 
 void
-MIDISceneChanger::set_input_port (MIDI::Port* mp)
+MIDISceneChanger::set_input_port (boost::shared_ptr<MidiPort> mp)
 {
-       input_port = mp;
-
        incoming_connections.drop_connections();
-       
-       if (input_port) {
-               
+       input_port.reset ();
+
+       boost::shared_ptr<AsyncMIDIPort> async = boost::dynamic_pointer_cast<AsyncMIDIPort> (mp);
+
+       if (async) {
+
+               input_port = mp;
+
                /* midi port is asynchronous. MIDI parsing will be carried out
                 * by the MIDI UI thread which will emit the relevant signals
                 * and thus invoke our callbacks as necessary.
                 */
 
                for (int channel = 0; channel < 16; ++channel) {
-                       input_port->parser()->channel_bank_change[channel].connect_same_thread (incoming_connections, boost::bind (&MIDISceneChanger::bank_change_input, this, _1, _2, channel));
-                       input_port->parser()->channel_program_change[channel].connect_same_thread (incoming_connections, boost::bind (&MIDISceneChanger::program_change_input, this, _1, _2, channel));
+                       async->parser()->channel_bank_change[channel].connect_same_thread (incoming_connections, boost::bind (&MIDISceneChanger::bank_change_input, this, _1, _2, channel));
+                       async->parser()->channel_program_change[channel].connect_same_thread (incoming_connections, boost::bind (&MIDISceneChanger::program_change_input, this, _1, _2, channel));
                }
        }
 }
@@ -177,19 +262,18 @@ MIDISceneChanger::set_recording (bool yn)
 }
 
 bool
-MIDISceneChanger::recording() const 
+MIDISceneChanger::recording() const
 {
        return _session.transport_rolling() && _session.get_record_enabled();
 }
 
 void
-MIDISceneChanger::bank_change_input (MIDI::Parser& parser, unsigned short, int)
+  MIDISceneChanger::bank_change_input (MIDI::Parser& /*parser*/, unsigned short, int)
 {
-       if (!recording()) {
-               return;
+       if (recording()) {
+               have_seen_bank_changes = true;
        }
-
-       last_bank_message_time = parser.get_timestamp ();
+       MIDIInputActivity (); /* EMIT SIGNAL */
 }
 
 void
@@ -200,39 +284,62 @@ MIDISceneChanger::program_change_input (MIDI::Parser& parser, MIDI::byte program
        last_program_message_time = time;
 
        if (!recording()) {
-               jump_to (input_port->channel (channel)->bank(), program);
+
+               MIDIInputActivity (); /* EMIT SIGNAL */
+
+               int bank = -1;
+               if (have_seen_bank_changes) {
+                       bank = boost::dynamic_pointer_cast<AsyncMIDIPort>(input_port)->channel (channel)->bank();
+               }
+
+               jump_to (bank, program);
                return;
        }
 
        Locations* locations (_session.locations ());
        Location* loc;
        bool new_mark = false;
-       framecnt_t slop = (framecnt_t) floor ((Config->get_inter_scene_gap_msecs() / 1000.0) * _session.frame_rate());
 
        /* check for marker at current location */
 
-       loc = locations->mark_at (time, slop);
+       loc = locations->mark_at (time, Config->get_inter_scene_gap_frames());
 
        if (!loc) {
                /* create a new marker at the desired position */
-               
+
                std::string new_name;
 
                if (!locations->next_available_name (new_name, _("Scene "))) {
                        std::cerr << "No new marker name available\n";
                        return;
                }
-               
+
                loc = new Location (_session, time, time, new_name, Location::IsMark);
                new_mark = true;
        }
 
-       unsigned short bank = input_port->channel (channel)->bank();
+       int bank = -1;
+       if (have_seen_bank_changes) {
+               bank = boost::dynamic_pointer_cast<AsyncMIDIPort>(input_port)->channel (channel)->bank();
+       }
+
+       MIDISceneChange* msc =new MIDISceneChange (channel, bank, program & 0x7f);
 
-       MIDISceneChange* msc =new MIDISceneChange (loc->start(), channel, bank, program & 0x7f);
+       /* check for identical scene change so we can re-use color, if any */
+
+       Locations::LocationList copy (locations->list());
+
+       for (Locations::LocationList::const_iterator l = copy.begin(); l != copy.end(); ++l) {
+               boost::shared_ptr<MIDISceneChange> sc = boost::dynamic_pointer_cast<MIDISceneChange>((*l)->scene_change());
+
+               if (sc && (*sc.get()) == *msc) {
+                       msc->set_color (sc->color ());
+                       break;
+               }
+       }
 
        loc->set_scene_change (boost::shared_ptr<MIDISceneChange> (msc));
-       
+
        /* this will generate a "changed" signal to be emitted by locations,
           and we will call ::gather() to update our list of MIDI events.
        */
@@ -240,6 +347,8 @@ MIDISceneChanger::program_change_input (MIDI::Parser& parser, MIDI::byte program
        if (new_mark) {
                locations->add (loc);
        }
+
+       MIDIInputActivity (); /* EMIT SIGNAL */
 }
 
 void