merge (squash) with scenechange topic branch to provide MIDI-driven scene change...
authorPaul Davis <paul@linuxaudiosystems.com>
Mon, 28 Apr 2014 23:58:24 +0000 (19:58 -0400)
committerPaul Davis <paul@linuxaudiosystems.com>
Mon, 28 Apr 2014 23:58:24 +0000 (19:58 -0400)
22 files changed:
libs/ardour/ardour/async_midi_port.h
libs/ardour/ardour/location.h
libs/ardour/ardour/midi_scene_change.h [new file with mode: 0644]
libs/ardour/ardour/midi_scene_changer.h [new file with mode: 0644]
libs/ardour/ardour/midiport_manager.h
libs/ardour/ardour/rc_configuration_vars.h
libs/ardour/ardour/scene_changer.h [new file with mode: 0644]
libs/ardour/ardour/session.h
libs/ardour/async_midi_port.cc
libs/ardour/location.cc
libs/ardour/midi_scene_change.cc [new file with mode: 0644]
libs/ardour/midi_scene_changer.cc [new file with mode: 0644]
libs/ardour/midi_ui.cc
libs/ardour/midiport_manager.cc
libs/ardour/session.cc
libs/ardour/session_midi.cc
libs/ardour/session_process.cc
libs/ardour/session_state.cc
libs/ardour/wscript
libs/midi++2/channel.cc
libs/midi++2/midi++/channel.h
libs/midi++2/midi++/parser.h

index c5babf6135c3bbda22b9310d980bede82a2e3120..26946e30167d9932914547c861d869b27fde2ce1 100644 (file)
@@ -22,6 +22,8 @@
 #include <string>
 #include <iostream>
 
+#include <boost/function.hpp>
+
 #include "pbd/xml++.h"
 #include "pbd/crossthread.h"
 #include "pbd/signals.h"
@@ -64,6 +66,8 @@ class LIBARDOUR_API AsyncMIDIPort : public ARDOUR::MidiPort, public MIDI::Port {
 #endif
        }
 
+       void set_timer (boost::function<framecnt_t (void)>&);
+
        static void set_process_thread (pthread_t);
        static pthread_t get_process_thread () { return _process_thread; }
        static bool is_process_thread();
@@ -71,6 +75,8 @@ class LIBARDOUR_API AsyncMIDIPort : public ARDOUR::MidiPort, public MIDI::Port {
   private:     
        bool                    _currently_in_cycle;
         MIDI::timestamp_t       _last_write_timestamp;
+       bool                    have_timer;
+       boost::function<framecnt_t (void)> timer;
        RingBuffer< Evoral::Event<double> > output_fifo;
         Evoral::EventRingBuffer<MIDI::timestamp_t> input_fifo;
         Glib::Threads::Mutex output_fifo_lock;
index b0956eea36d2e32e1c257509c29d13b5cee33c26..6cea208f05014e7af62c40147d758ed7c747aa9a 100644 (file)
 #include "pbd/statefuldestructible.h"
 
 #include "ardour/ardour.h"
+#include "ardour/scene_change.h"
 #include "ardour/session_handle.h"
 
 namespace ARDOUR {
 
+class SceneChange;
+
 class LIBARDOUR_API Location : public SessionHandleRef, public PBD::StatefulDestructible
 {
   public:
@@ -93,6 +96,9 @@ class LIBARDOUR_API Location : public SessionHandleRef, public PBD::StatefulDest
 
        Flags flags () const { return _flags; }
 
+       boost::shared_ptr<SceneChange> scene_change() const { return _scene_change; }
+       void set_scene_change (boost::shared_ptr<SceneChange>);
+
        PBD::Signal1<void,Location*> name_changed;
        PBD::Signal1<void,Location*> end_changed;
        PBD::Signal1<void,Location*> start_changed;
@@ -116,6 +122,8 @@ class LIBARDOUR_API Location : public SessionHandleRef, public PBD::StatefulDest
        void set_position_lock_style (PositionLockStyle ps);
        void recompute_frames_from_bbt ();
 
+       static PBD::Signal0<void> scene_changed;
+
   private:
        std::string        _name;
        framepos_t         _start;
@@ -125,6 +133,7 @@ class LIBARDOUR_API Location : public SessionHandleRef, public PBD::StatefulDest
        Flags              _flags;
        bool               _locked;
        PositionLockStyle  _position_lock_style;
+       boost::shared_ptr<SceneChange> _scene_change;
 
        void set_mark (bool yn);
        bool set_flag_internal (bool yn, Flags flag);
@@ -161,6 +170,8 @@ class LIBARDOUR_API Locations : public SessionHandleRef, public PBD::StatefulDes
        int set_current (Location *, bool want_lock = true);
        Location *current () const { return current_location; }
 
+       Location* mark_at (framepos_t, framecnt_t slop = 0) const;
+
         framepos_t first_mark_before (framepos_t, bool include_special_ranges = false);
        framepos_t first_mark_after (framepos_t, bool include_special_ranges = false);
 
diff --git a/libs/ardour/ardour/midi_scene_change.h b/libs/ardour/ardour/midi_scene_change.h
new file mode 100644 (file)
index 0000000..86793c5
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+    Copyright (C) 2014 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
+    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 __libardour_midi_scene_change_h__
+#define __libardour_midi_scene_change_h__
+
+#include "evoral/PatchChange.hpp"
+
+#include "ardour/scene_change.h"
+
+namespace ARDOUR
+{
+
+class MidiPort;
+
+class MIDISceneChange : public SceneChange
+{
+  public:
+       MIDISceneChange (framepos_t time, int channel, int bank = -1, int program = -1);
+       MIDISceneChange (const XMLNode&, int version);
+       ~MIDISceneChange ();
+
+       void set_channel (int channel);
+       void set_program (int program);
+       void set_bank (int bank);
+
+       int channel () const { return _channel; }
+       int program () const { return _program; }
+       int bank () const { return _bank; }
+
+       size_t get_bank_msb_message (uint8_t* buf, size_t size) const;
+       size_t get_bank_lsb_message (uint8_t* buf, size_t size) const;
+       size_t get_program_message (uint8_t* buf, size_t size) const;
+
+       XMLNode& get_state();
+       int set_state (const XMLNode&, int version);
+
+  private:
+       int _bank;
+       int _program;
+       uint8_t _channel;
+};
+
+} /* namespace */
+       
+
+#endif /* __libardour_scene_change_h__ */
diff --git a/libs/ardour/ardour/midi_scene_changer.h b/libs/ardour/ardour/midi_scene_changer.h
new file mode 100644 (file)
index 0000000..2cc0464
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+    Copyright (C) 2014 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
+    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 __libardour_midi_scene_changer_h__
+#define __libardour_midi_scene_changer_h__
+
+#include "ardour/scene_changer.h"
+
+namespace ARDOUR
+{
+
+class MIDISceneChanger : public SceneChanger
+{
+    public:
+       MIDISceneChanger (Session&);
+       ~MIDISceneChanger ();
+
+       void run (framepos_t start, framepos_t end);
+       void set_input_port (MIDI::Port*);
+       void set_output_port (boost::shared_ptr<MidiPort>);
+
+       uint8_t bank_at (framepos_t, uint8_t channel);
+       uint8_t program_at (framepos_t, uint8_t channel);
+
+       void set_recording (bool);
+       void locate (framepos_t);
+
+    private:
+       typedef std::multimap<framepos_t,boost::shared_ptr<MIDISceneChange> > Scenes;
+
+       MIDI::Port* input_port;
+       boost::shared_ptr<MidiPort> output_port;
+       Scenes scenes;
+       bool _recording;
+       framepos_t last_bank_message_time;
+       framepos_t last_program_message_time;
+       unsigned short current_bank;
+       int last_delivered_program;
+       int last_delivered_bank;
+
+       void gather ();
+       bool recording () const;
+       void jump_to (int bank, int program);
+       void deliver (MidiBuffer&, framepos_t, boost::shared_ptr<MIDISceneChange>);
+
+       void bank_change_input (MIDI::Parser&, unsigned short);
+       void program_change_input (MIDI::Parser&, MIDI::byte);
+       void locations_changed (Locations::Change);
+
+       PBD::ScopedConnection incoming_bank_change_connection;
+       PBD::ScopedConnection incoming_program_change_connection;
+};
+
+} // namespace
+
+#endif /* __libardour_midi_scene_changer_h__ */
index b5b46e85101df22d4859ba422632a3f32370dadd..5e87238c22f7021fb519d895660f8d1c0e250282 100644 (file)
@@ -30,6 +30,7 @@
 #include "midi++/port.h"
 
 #include "ardour/libardour_visibility.h"
+#include "ardour/midi_port.h"
 #include "ardour/types.h"
 
 namespace ARDOUR {
@@ -56,7 +57,12 @@ class LIBARDOUR_API MidiPortManager {
     MIDI::Port* midi_output_port () const { return _midi_output_port; }
     MIDI::Port* mmc_input_port () const { return _mmc_input_port; }
     MIDI::Port* mmc_output_port () const { return _mmc_output_port; }
+    MIDI::Port* scene_input_port () const { return _scene_input_port; }
+    MIDI::Port* scene_output_port () const { return _scene_output_port; }
     
+    boost::shared_ptr<MidiPort> scene_in() const { return boost::dynamic_pointer_cast<MidiPort>(_scene_in); }
+    boost::shared_ptr<MidiPort> scene_out() const { return boost::dynamic_pointer_cast<MidiPort>(_scene_out); }
+
     /* Ports used for synchronization. These have their I/O handled inside the
      * process callback.
      */
@@ -77,13 +83,17 @@ class LIBARDOUR_API MidiPortManager {
     MIDI::Port* _midi_output_port;
     MIDI::Port* _mmc_input_port;
     MIDI::Port* _mmc_output_port;
-    /* these point to the same objects as the 4 members above,
+    MIDI::Port* _scene_input_port;
+    MIDI::Port* _scene_output_port;
+    /* these point to the same objects as the members above,
        but cast to their ARDOUR::Port base class
     */
     boost::shared_ptr<Port> _midi_in;
     boost::shared_ptr<Port> _midi_out;
     boost::shared_ptr<Port> _mmc_in;
     boost::shared_ptr<Port> _mmc_out;
+    boost::shared_ptr<Port> _scene_in;
+    boost::shared_ptr<Port> _scene_out;
 
     /* synchronously handled ports: ARDOUR::MidiPort */
     boost::shared_ptr<MidiPort> _mtc_input_port;
index d290d8ebc844f541a1208cf6b671eb04842a47b7..4401b1f74ca952f9cdd31fb622fdfe289467c990 100644 (file)
@@ -44,6 +44,7 @@ CONFIG_VARIABLE (int32_t, mmc_receive_device_id, "mmc-receive-device-id", 0x7f)
 CONFIG_VARIABLE (int32_t, mmc_send_device_id, "mmc-send-device-id", 0)
 CONFIG_VARIABLE (int32_t, initial_program_change, "initial-program-change", -1)
 CONFIG_VARIABLE (bool, first_midi_bank_is_zero, "display-first-midi-bank-as-zero", false)
+CONFIG_VARIABLE (int32_t, inter_scene_gap_msecs, "inter-scene-gap-msecs", 1)
 
 /* Timecode and related */
 
diff --git a/libs/ardour/ardour/scene_changer.h b/libs/ardour/ardour/scene_changer.h
new file mode 100644 (file)
index 0000000..d5ba984
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+    Copyright (C) 2014 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
+    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 __libardour_scene_changer_h__
+#define __libardour_scene_changer_h__
+
+#include <map>
+
+#include "pbd/signals.h"
+
+#include "ardour/location.h"
+#include "ardour/midi_scene_change.h"
+#include "ardour/session_handle.h"
+#include "ardour/types.h"
+
+namespace MIDI
+{
+class Parser;
+class Port;
+}
+
+namespace ARDOUR 
+{
+
+class Session;
+class AsyncMidiPort;
+
+class SceneChanger : public SessionHandleRef
+{
+    public:
+        SceneChanger (Session& s) : SessionHandleRef (s) {}
+        virtual ~SceneChanger () {};
+       
+       virtual void run (framepos_t start, framepos_t end) = 0;
+};
+
+} /* namespace */
+       
+
+#endif /* __libardour_scene_change_h__ */
index 15af67ada7421e452be4b83f593093428f888f37..40ada138a6dac0f8027d0536da423afbba66110a 100644 (file)
@@ -129,6 +129,7 @@ class Route;
 class RouteGroup;
 class SMFSource;
 class Send;
+class SceneChanger;
 class SessionDirectory;
 class SessionMetadata;
 class SessionPlaylists;
@@ -868,23 +869,31 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
         */
         static PBD::Signal2<void,std::string,std::string> VersionMismatch;
 
+       SceneChanger* scene_changer() const { return _scene_changer; }
+
         boost::shared_ptr<Port> ltc_input_port() const;
         boost::shared_ptr<Port> ltc_output_port() const;
 
        boost::shared_ptr<IO> ltc_input_io() { return _ltc_input; }
        boost::shared_ptr<IO> ltc_output_io() { return _ltc_output; }
 
-    MIDI::Port* midi_input_port () const;
-    MIDI::Port* midi_output_port () const;
-    MIDI::Port* mmc_output_port () const;
-    MIDI::Port* mmc_input_port () const;
+       MIDI::Port* midi_input_port () const;
+       MIDI::Port* midi_output_port () const;
+       MIDI::Port* mmc_output_port () const;
+       MIDI::Port* mmc_input_port () const;
 
-    boost::shared_ptr<MidiPort> midi_clock_output_port () const;
-    boost::shared_ptr<MidiPort> midi_clock_input_port () const;
-    boost::shared_ptr<MidiPort> mtc_output_port () const;
-    boost::shared_ptr<MidiPort> mtc_input_port () const;
+       MIDI::Port* scene_input_port () const;
+       MIDI::Port* scene_output_port () const;
 
-    MIDI::MachineControl& mmc() { return *_mmc; }
+       boost::shared_ptr<MidiPort> scene_in () const;
+       boost::shared_ptr<MidiPort> scene_out () const;
+       
+       boost::shared_ptr<MidiPort> midi_clock_output_port () const;
+       boost::shared_ptr<MidiPort> midi_clock_input_port () const;
+       boost::shared_ptr<MidiPort> mtc_output_port () const;
+       boost::shared_ptr<MidiPort> mtc_input_port () const;
+    
+       MIDI::MachineControl& mmc() { return *_mmc; }
 
   protected:
        friend class AudioEngine;
@@ -1607,16 +1616,19 @@ class LIBARDOUR_API Session : public PBD::StatefulDestructible, public PBD::Scop
         void reconnect_ltc_input ();
         void reconnect_ltc_output ();
 
-    /* persistent, non-track related MIDI ports */
-    MidiPortManager* _midi_ports;
-    MIDI::MachineControl* _mmc;
-
-    void setup_ltc ();
-    void setup_click ();
-    void setup_click_state (const XMLNode*);
-    void setup_bundles ();
-
-    static int get_session_info_from_path (XMLTree& state_tree, const std::string& xmlpath);
+       /* Scene Changing */
+       SceneChanger* _scene_changer;
+       
+       /* persistent, non-track related MIDI ports */
+       MidiPortManager* _midi_ports;
+       MIDI::MachineControl* _mmc;
+       
+       void setup_ltc ();
+       void setup_click ();
+       void setup_click_state (const XMLNode*);
+       void setup_bundles ();
+       
+       static int get_session_info_from_path (XMLTree& state_tree, const std::string& xmlpath);
 };
 
 } // namespace ARDOUR
index bd583328c30f1781a768d3268f27c1053bb64265..21b59dec00241bacfaa3a9b11acba2fb19fb932d 100644 (file)
@@ -50,6 +50,7 @@ AsyncMIDIPort::AsyncMIDIPort (string const & name, PortFlags flags)
        , MIDI::Port (name, MIDI::Port::Flags (0))
        , _currently_in_cycle (false)
        , _last_write_timestamp (0)
+       , have_timer (false)
        , output_fifo (512)
        , input_fifo (1024)
 #ifndef PLATFORM_WINDOWS
@@ -62,6 +63,13 @@ AsyncMIDIPort::~AsyncMIDIPort ()
 {
 }
 
+void
+AsyncMIDIPort::set_timer (boost::function<framecnt_t (void)>& f)
+{
+       timer = f;
+       have_timer = true;
+}
+
 void
 AsyncMIDIPort::flush_output_fifo (MIDI::pframes_t nframes)
 {
@@ -113,9 +121,18 @@ AsyncMIDIPort::cycle_start (MIDI::pframes_t nframes)
 
        if (ARDOUR::Port::receives_input()) {
                MidiBuffer& mb (get_midi_buffer (nframes));
-               pframes_t when = AudioEngine::instance()->sample_time_at_cycle_start();
+               framecnt_t when;
+               
+               if (have_timer) {
+                       when = timer ();
+               } else {
+                       when = AudioEngine::instance()->sample_time_at_cycle_start();
+               }
 
                for (MidiBuffer::iterator b = mb.begin(); b != mb.end(); ++b) {
+                       if (!have_timer) {
+                               when += (*b).time();
+                       }
                        input_fifo.write (when, (Evoral::EventType) 0, (*b).size(), (*b).buffer());
                }
 
index 2a27fc318a9d986a3afc00fdda2f5082fee1d98d..90265af4e4262410fa4bfd37b09d5b117eee89a7 100644 (file)
@@ -30,6 +30,7 @@
 #include "pbd/enumwriter.h"
 
 #include "ardour/location.h"
+#include "ardour/midi_scene_change.h"
 #include "ardour/session.h"
 #include "ardour/audiofilesource.h"
 #include "ardour/tempo.h"
@@ -42,6 +43,8 @@ using namespace std;
 using namespace ARDOUR;
 using namespace PBD;
 
+PBD::Signal0<void> Location::scene_changed;
+
 Location::Location (Session& s)
        : SessionHandleRef (s)
        , _start (0)
@@ -87,6 +90,8 @@ Location::Location (const Location& other)
 
        assert (_start >= 0);
        assert (_end >= 0);
+
+       /* scene change is NOT COPIED */
 }
 
 Location::Location (Session& s, const XMLNode& node)
@@ -134,6 +139,8 @@ Location::operator= (const Location& other)
        _bbt_end = other._bbt_end;
        _flags = other._flags;
        _position_lock_style = other._position_lock_style;
+       
+       /* XXX need to copy scene change */
 
        /* copy is not locked even if original was */
 
@@ -431,11 +438,15 @@ Location::get_state ()
        node->add_property ("locked", (_locked ? "yes" : "no"));
        node->add_property ("position-lock-style", enum_2_string (_position_lock_style));
 
+       if (_scene_change) {
+               node->add_child_nocopy (_scene_change->get_state());
+       }
+
        return *node;
 }
 
 int
-Location::set_state (const XMLNode& node, int /*version*/)
+Location::set_state (const XMLNode& node, int version)
 {
        const XMLProperty *prop;
 
@@ -521,6 +532,16 @@ Location::set_state (const XMLNode& node, int /*version*/)
                _position_lock_style = PositionLockStyle (string_2_enum (prop->value(), _position_lock_style));
        }
 
+       XMLNode* scene_child = find_named_node (node, SceneChange::xml_node_name);
+       
+       if (scene_child) {
+               _scene_change = SceneChange::factory (*scene_child, version);
+
+               if (_scene_change) {
+                       _scene_change->set_time (_start);
+               }
+       }
+
        recompute_bbt_from_frames ();
 
        changed (this); /* EMIT SIGNAL */
@@ -581,6 +602,14 @@ Location::unlock ()
        LockChanged (this);
 }
 
+void
+Location::set_scene_change (boost::shared_ptr<SceneChange>  sc)
+{
+       _scene_change = sc;
+
+       scene_changed (); /* EMIT SIGNAL */
+}
+
 /*---------------------------------------------------------------------- */
 
 Locations::Locations (Session& s)
@@ -675,6 +704,7 @@ Locations::clear ()
                        ++tmp;
 
                        if (!(*i)->is_session_range()) {
+                               delete *i;
                                locations.erase (i);
                        }
 
@@ -700,6 +730,7 @@ Locations::clear_markers ()
                        ++tmp;
 
                        if ((*i)->is_mark() && !(*i)->is_session_range()) {
+                               delete *i;
                                locations.erase (i);
                        }
 
@@ -723,6 +754,7 @@ Locations::clear_ranges ()
                        ++tmp;
 
                        if (!(*i)->is_mark()) {
+                               delete *i;
                                locations.erase (i);
 
                        }
@@ -779,6 +811,7 @@ Locations::remove (Location *loc)
 
                for (i = locations.begin(); i != locations.end(); ++i) {
                        if ((*i) == loc) {
+                               delete *i;
                                locations.erase (i);
                                was_removed = true;
                                if (current_location == loc) {
@@ -972,6 +1005,44 @@ Locations::first_mark_before (framepos_t frame, bool include_special_ranges)
        return -1;
 }
 
+Location*
+Locations::mark_at (framepos_t pos, framecnt_t slop) const
+{
+       Glib::Threads::Mutex::Lock lm (lock);
+       Location* closest = 0;
+       frameoffset_t mindelta = max_framepos;
+       frameoffset_t delta;
+
+       /* locations are not necessarily stored in linear time order so we have
+        * to iterate across all of them to find the one closest to a give point.
+        */
+
+       for (LocationList::const_iterator i = locations.begin(); i != locations.end(); ++i) {
+
+               if ((*i)->is_mark()) {
+                       if (pos > (*i)->start()) { 
+                               delta = pos - (*i)->start();
+                       } else {
+                               delta = (*i)->start() - pos;
+                       }
+                       
+                       if (slop == 0 && delta == 0) {
+                               /* special case: no slop, and direct hit for position */
+                               return *i;
+                       }
+
+                       if (delta <= slop) {
+                               if (delta < mindelta) {
+                                       closest = *i;
+                                       mindelta = delta;
+                               }
+                       }
+               }
+       }
+
+       return closest;
+}
+
 framepos_t
 Locations::first_mark_after (framepos_t frame, bool include_special_ranges)
 {
@@ -1146,3 +1217,4 @@ Locations::find_all_between (framepos_t start, framepos_t end, LocationList& ll,
                }
        }
 }
+
diff --git a/libs/ardour/midi_scene_change.cc b/libs/ardour/midi_scene_change.cc
new file mode 100644 (file)
index 0000000..81a7491
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+    Copyright (C) 2014 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
+    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 "pbd/error.h"
+#include "pbd/compose.h"
+
+#include "ardour/midi_port.h"
+#include "ardour/midi_scene_change.h"
+
+#include "i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+
+MIDISceneChange::MIDISceneChange (framepos_t time, int c, int b, int p)
+       : SceneChange (time)
+       , _bank (b)
+       , _program (p)
+       , _channel (c & 0xf)
+{
+       if (_bank > 16384) {
+               _bank = -1;
+       }
+
+       if (_program > 128) {
+               _program = -1;
+       }
+}
+
+MIDISceneChange::MIDISceneChange (const XMLNode& node, int version)
+       : SceneChange (0)
+       , _bank (-1)
+       , _program (-1)
+       , _channel (-1)
+{
+       set_state (node, version);
+}
+
+MIDISceneChange::~MIDISceneChange ()
+{
+}
+
+size_t
+MIDISceneChange::get_bank_msb_message (uint8_t* buf, size_t size) const
+{
+       if (size < 3 || _bank < 0) {
+               return 0;
+       }
+
+       buf[0] = 0xB0 | (_channel & 0xf);
+       buf[1] = 0x0;
+       buf[2] = (_bank & 0xf700) >> 8;
+
+       return 3;
+}
+
+size_t
+MIDISceneChange::get_bank_lsb_message (uint8_t* buf, size_t size) const
+{
+       if (size < 3 || _bank < 0) {
+               return 0;
+       }
+
+       buf[0] = 0xB0 | (_channel & 0xf);
+       buf[1] = 0x20;
+       buf[2] = (_bank & 0xf7);        
+
+       return 3;
+}
+
+size_t
+MIDISceneChange::get_program_message (uint8_t* buf, size_t size) const
+{
+       if (size < 2 || _program < 0) {
+               return 0;
+       }
+
+       buf[0] = 0xC0 | (_channel & 0xf);
+       buf[1] = _program & 0xf7;
+
+       return 2;
+}
+
+XMLNode&
+MIDISceneChange::get_state ()
+{
+       char buf[32];
+       XMLNode* node = new XMLNode (SceneChange::xml_node_name);
+
+       node->add_property (X_("type"), X_("MIDI"));
+       snprintf (buf, sizeof (buf), "%d", (int) _program);
+       node->add_property (X_("id"), id().to_s());
+       snprintf (buf, sizeof (buf), "%d", (int) _program);
+       node->add_property (X_("program"), buf);
+       snprintf (buf, sizeof (buf), "%d", (int) _bank);
+       node->add_property (X_("bank"), buf);
+       snprintf (buf, sizeof (buf), "%d", (int) _channel);
+       node->add_property (X_("channel"), buf);
+
+       return *node;
+}
+
+int
+MIDISceneChange::set_state (const XMLNode& node, int /* version-ignored */)
+{
+       if (!set_id (node)) {
+               return -1;
+       }
+
+       const XMLProperty* prop;
+
+       if ((prop = node.property (X_("program"))) == 0) {
+               return -1;
+       }
+       _program = atoi (prop->value());
+
+       if ((prop = node.property (X_("bank"))) == 0) {
+               return -1;
+       }
+       _bank = atoi (prop->value());
+
+       if ((prop = node.property (X_("channel"))) == 0) {
+               return -1;
+       }
+       _channel = atoi (prop->value());
+
+       return 0;
+}
diff --git a/libs/ardour/midi_scene_changer.cc b/libs/ardour/midi_scene_changer.cc
new file mode 100644 (file)
index 0000000..49e835c
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+    Copyright (C) 2014 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
+    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 "evoral/MIDIEvent.hpp"
+#include "midi++/parser.h"
+#include "midi++/port.h"
+
+#include "ardour/event_type_map.h"
+#include "ardour/midi_port.h"
+#include "ardour/midi_scene_change.h"
+#include "ardour/midi_scene_changer.h"
+#include "ardour/session.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+
+MIDISceneChanger::MIDISceneChanger (Session& s)
+       : SceneChanger (s)
+       , _recording (true)
+       , last_bank_message_time (-1)
+       , 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));
+}
+
+MIDISceneChanger::~MIDISceneChanger ()
+{
+}
+
+void
+MIDISceneChanger::locations_changed (Locations::Change)
+{
+       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 ()
+{
+       const Locations::LocationList& locations (_session.locations()->list());
+       boost::shared_ptr<SceneChange> sc;
+
+       scenes.clear ();
+
+       for (Locations::LocationList::const_iterator l = locations.begin(); l != locations.end(); ++l) {
+
+               if ((sc = (*l)->scene_change()) != 0) {
+
+                       boost::shared_ptr<MIDISceneChange> msc = boost::dynamic_pointer_cast<MIDISceneChange> (sc);
+
+                       if (msc) {
+                               scenes.insert (std::make_pair (msc->time(), msc));
+                       }
+               }
+       }
+}
+
+void
+MIDISceneChanger::deliver (MidiBuffer& mbuf, framepos_t when, boost::shared_ptr<MIDISceneChange> msc)
+{
+       uint8_t buf[4];
+       size_t cnt;
+
+       if ((cnt = msc->get_bank_msb_message (buf, sizeof (buf))) > 0) {
+               mbuf.push_back (when, cnt, buf);
+
+               if ((cnt = msc->get_bank_lsb_message (buf, sizeof (buf))) > 0) {
+                       mbuf.push_back (when, cnt, buf);
+               }
+
+               last_delivered_bank = msc->bank();
+       }
+
+       if ((cnt = msc->get_program_message (buf, sizeof (buf))) > 0) {
+               mbuf.push_back (when, cnt, buf);
+
+               last_delivered_program = msc->program();
+       }
+}
+                       
+
+void
+MIDISceneChanger::run (framepos_t start, framepos_t end)
+{
+       if (!output_port || recording()) {
+               return;
+       }
+
+       /* get lower bound of events to consider */
+
+       Scenes::const_iterator i = scenes.lower_bound (start);
+       MidiBuffer& mbuf (output_port->get_midi_buffer (end-start));
+
+       while (i != scenes.end()) {
+
+               if (i->first >= end) {
+                       break;
+               }
+               
+               deliver (mbuf, i->first - start, i->second);
+
+               ++i;
+       }
+}
+
+void
+MIDISceneChanger::locate (framepos_t pos)
+{
+       Scenes::const_iterator i = scenes.upper_bound (pos);
+
+       if (i == scenes.end()) {
+               return;
+       }
+
+       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);
+       }
+}              
+
+void
+MIDISceneChanger::set_input_port (MIDI::Port* mp)
+{
+       input_port = mp;
+
+       incoming_bank_change_connection.disconnect ();
+       incoming_program_change_connection.disconnect ();
+       
+       if (input_port) {
+               
+               /* 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.
+                */
+
+               input_port->parser()->bank_change.connect_same_thread (incoming_bank_change_connection, boost::bind (&MIDISceneChanger::bank_change_input, this, _1, _2));
+               input_port->parser()->program_change.connect_same_thread (incoming_program_change_connection, boost::bind (&MIDISceneChanger::program_change_input, this, _1, _2));
+       }
+}
+
+void
+MIDISceneChanger::set_output_port (boost::shared_ptr<MidiPort> mp)
+{
+       output_port = mp;
+}
+
+void
+MIDISceneChanger::set_recording (bool yn)
+{
+       _recording = yn;
+}
+
+bool
+MIDISceneChanger::recording() const 
+{
+       return _session.transport_rolling() && _session.get_record_enabled();
+}
+
+void
+MIDISceneChanger::bank_change_input (MIDI::Parser& parser, unsigned short bank)
+{
+       if (!recording()) {
+               return;
+       }
+
+       last_bank_message_time = parser.get_timestamp ();
+       current_bank = bank;
+}
+
+void
+MIDISceneChanger::program_change_input (MIDI::Parser& parser, MIDI::byte program)
+{
+       framecnt_t time = parser.get_timestamp ();
+       frameoffset_t delta = time - last_program_message_time;
+
+       last_program_message_time = time;
+
+       if (!recording()) {
+               jump_to (current_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);
+
+       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;
+       }
+
+       uint8_t bank;
+       uint8_t channel = (program & 0xf0) >> 8;
+
+       /* if we received a bank change message within the last 2 msec, use the
+        * current bank value, otherwise lookup the current bank number and use
+        * that.
+        */
+
+       if (time - last_bank_message_time < (2 * _session.frame_rate() / 1000.0)) {
+               bank = current_bank;
+       } else {
+               bank = -1;
+       }
+
+       MIDISceneChange* msc =new MIDISceneChange (loc->start(), channel, bank, program & 0x7f);
+
+       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.
+       */
+
+       if (new_mark) {
+               locations->add (loc);
+       }
+}
+
+void
+MIDISceneChanger::jump_to (int bank, int program)
+{
+       const Locations::LocationList& locations (_session.locations()->list());
+       boost::shared_ptr<SceneChange> sc;
+       framepos_t where = max_framepos;
+
+       for (Locations::LocationList::const_iterator l = locations.begin(); l != locations.end(); ++l) {
+
+               if ((sc = (*l)->scene_change()) != 0) {
+
+                       boost::shared_ptr<MIDISceneChange> msc = boost::dynamic_pointer_cast<MIDISceneChange> (sc);
+
+                       if (msc->bank() == bank && msc->program() == program && (*l)->start() < where) {
+                               where = (*l)->start();
+                       }
+               }
+       }
+
+       if (where != max_framepos) {
+               _session.request_locate (where);
+       }
+}
index 97dfdce6bf0ce1fc18d8f7d0d59a6f3adad8ffd4..e00ec587ec4da2f6180b397ee7c3ecb09ce0b92e 100644 (file)
@@ -122,6 +122,10 @@ MidiControlUI::reset_ports ()
        if ((p = dynamic_cast<AsyncMIDIPort*> (_session.mmc_input_port()))) {
                ports.push_back (p);
        }
+
+       if ((p = dynamic_cast<AsyncMIDIPort*> (_session.scene_input_port()))) {
+               ports.push_back (p);
+       }
        
        if (ports.empty()) {
                return;
index 6de043658690187d0af697e02e29c9fdb8f8951b..b1699ca5abc5a548abc6fc2bddf64e0d450af5fe 100644 (file)
@@ -40,8 +40,14 @@ MidiPortManager::~MidiPortManager ()
        if (_midi_in) {
                AudioEngine::instance()->unregister_port (_midi_in);
        }
-       if (_midi_in) {
-               AudioEngine::instance()->unregister_port (_midi_in);
+       if (_midi_out) {
+               AudioEngine::instance()->unregister_port (_midi_out);
+       }
+       if (_scene_in) {
+               AudioEngine::instance()->unregister_port (_scene_in);
+       }
+       if (_scene_out) {
+               AudioEngine::instance()->unregister_port (_scene_out);
        }
        if (_mtc_input_port) {
                AudioEngine::instance()->unregister_port (_mtc_input_port);
@@ -73,7 +79,10 @@ MidiPortManager::create_ports ()
 
        _mmc_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("MMC in"), true);
        _mmc_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("MMC out"), true);
-       
+
+       _scene_in  = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Scene in"), true);
+       _scene_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Scene out"), true);
+
        /* XXX nasty type conversion needed because of the mixed inheritance
         * required to integrate MIDI::IPMidiPort and ARDOUR::AsyncMIDIPort.
         *
@@ -88,6 +97,9 @@ MidiPortManager::create_ports ()
        _mmc_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_mmc_in).get();
        _mmc_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_mmc_out).get();
 
+       _scene_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_scene_in).get();
+       _scene_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_scene_out).get();
+
        /* Now register ports used for sync (MTC and MIDI Clock)
         */
 
@@ -129,6 +141,8 @@ MidiPortManager::set_midi_port_states (const XMLNodeList&nodes)
        ports.insert (make_pair (_midi_output_port->name(), _midi_out));
        ports.insert (make_pair (_mmc_input_port->name(), _mmc_in));
        ports.insert (make_pair (_mmc_output_port->name(), _mmc_out));
+       ports.insert (make_pair (_scene_output_port->name(), _scene_out));
+       ports.insert (make_pair (_scene_input_port->name(), _scene_in));
        
        for (XMLNodeList::const_iterator n = nodes.begin(); n != nodes.end(); ++n) {
                if ((prop = (*n)->property (X_("name"))) == 0) {
@@ -159,6 +173,8 @@ MidiPortManager::get_midi_port_states () const
        ports.insert (make_pair (_midi_output_port->name(), _midi_out));
        ports.insert (make_pair (_mmc_input_port->name(), _mmc_in));
        ports.insert (make_pair (_mmc_output_port->name(), _mmc_out));
+       ports.insert (make_pair (_scene_output_port->name(), _scene_out));
+       ports.insert (make_pair (_scene_input_port->name(), _scene_in));
 
        for (PortMap::const_iterator p = ports.begin(); p != ports.end(); ++p) {
                s.push_back (&p->second->get_state());
index 2aef7459615de38000acfcfe8c0090fbcd5e3dc1..05fa883a9d3b02343d8cec6553ad1f1893021e8d 100644 (file)
@@ -68,6 +68,7 @@
 #include "ardour/filename_extensions.h"
 #include "ardour/graph.h"
 #include "ardour/midiport_manager.h"
+#include "ardour/scene_changer.h"
 #include "ardour/midi_track.h"
 #include "ardour/midi_ui.h"
 #include "ardour/operations.h"
@@ -260,6 +261,7 @@ Session::Session (AudioEngine &eng,
        ,  _speakers (new Speakers)
        , _order_hint (0)
        , ignore_route_processor_changes (false)
+       , _scene_changer (0)
        , _midi_ports (0)
        , _mmc (0)
 {
@@ -547,6 +549,8 @@ Session::destroy ()
        /* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
        playlists.reset ();
 
+       delete _scene_changer; _scene_changer = 0;
+
        delete _mmc; _mmc = 0;
        delete _midi_ports; _midi_ports = 0;
        delete _locations; _locations = 0;
index ea6dfe81cf17f6bd5f9bb5542103af04f39a90b5..93df1fc9466e16ed98c348c4d5f1da4855fee560 100644 (file)
@@ -644,3 +644,27 @@ Session::mmc_input_port () const
 {
        return _midi_ports->mmc_input_port ();
 }
+
+MIDI::Port*
+Session::scene_output_port () const
+{
+       return _midi_ports->scene_output_port ();
+}
+
+MIDI::Port*
+Session::scene_input_port () const
+{
+       return _midi_ports->scene_input_port ();
+}
+
+boost::shared_ptr<MidiPort>
+Session::scene_in () const
+{
+       return _midi_ports->scene_in ();
+}
+
+boost::shared_ptr<MidiPort>
+Session::scene_out () const
+{
+       return _midi_ports->scene_out ();
+}
index eb5c0de294f00cfbfe4c81b66949683930a13524..680f2861de2a2bdae8e90515ea89c96ef1fdd0d8 100644 (file)
@@ -35,6 +35,7 @@
 #include "ardour/graph.h"
 #include "ardour/port.h"
 #include "ardour/process_thread.h"
+#include "ardour/scene_changer.h"
 #include "ardour/session.h"
 #include "ardour/slave.h"
 #include "ardour/ticker.h"
@@ -86,6 +87,9 @@ Session::process (pframes_t nframes)
                if (!_silent && !_engine.freewheeling() && Config->get_send_midi_clock() && (transport_speed() == 1.0f || transport_speed() == 0.0f) && midi_clock->has_midi_port()) {
                        midi_clock->tick (transport_at_start, nframes);
                }
+
+               _scene_changer->run (transport_at_start, transport_at_start + nframes);
+
        } catch (...) {
                /* don't bother with a message */
        }
index 07f10e9bc10236538e2207dd3532d2b9b9c9a3ef..6a06863e9e16b1ca347cd261df2fe3e374d861ff 100644 (file)
@@ -77,6 +77,7 @@
 #include "pbd/localtime_r.h"
 
 #include "ardour/amp.h"
+#include "ardour/async_midi_port.h"
 #include "ardour/audio_diskstream.h"
 #include "ardour/audio_track.h"
 #include "ardour/audioengine.h"
@@ -92,6 +93,7 @@
 #include "ardour/midi_model.h"
 #include "ardour/midi_patch_manager.h"
 #include "ardour/midi_region.h"
+#include "ardour/midi_scene_changer.h"
 #include "ardour/midi_source.h"
 #include "ardour/midi_track.h"
 #include "ardour/pannable.h"
@@ -207,6 +209,16 @@ Session::post_engine_init ()
        BootMessage (_("Using configuration"));
 
        _midi_ports = new MidiPortManager;
+       
+       MIDISceneChanger* msc;
+
+       _scene_changer = msc = new MIDISceneChanger (*this);
+       msc->set_input_port (scene_input_port());
+       msc->set_output_port (scene_out());
+
+       boost::function<framecnt_t(void)> timer_func (boost::bind (&Session::audible_frame, this));
+       boost::dynamic_pointer_cast<AsyncMIDIPort>(scene_in())->set_timer (timer_func);
+
        setup_midi_machine_control ();
        
        if (_butler->start_thread()) {
index c12aec4ef83aa959d643c04f8646862717948783..0046e2eaecf16c70c249aeec79e2194ef389ee94 100644 (file)
@@ -119,6 +119,8 @@ libardour_sources = [
         'midi_port.cc',
         'midi_region.cc',
         'midi_ring_buffer.cc',
+        'midi_scene_change.cc',
+        'midi_scene_changer.cc',
         'midi_source.cc',
         'midi_state_tracker.cc',
         'midi_stretch.cc',
@@ -164,6 +166,7 @@ libardour_sources = [
         'route_group.cc',
         'route_group_member.cc',
         'rb_effect.cc',
+        'scene_change.cc',
         'search_paths.cc',
         'send.cc',
         'session.cc',
index ed8f4da5bc2b56c7910fd263f5aa7140a4d306ae..190ea185680fa48555acdbbef5d2f825477d71f1 100644 (file)
@@ -115,7 +115,7 @@ Channel::process_controller (Parser & /*parser*/, EventTwoBytes *tb)
           all changes *are* atomic.
        */
 
-       if (tb->controller_number <= 31) { /* unsigned: no test for >= 0 */
+       if (tb->controller_number < 32) { /* unsigned: no test for >= 0 */
 
                /* if this controller is already known to use 14 bits,
                   then treat this value as the MSB, and combine it 
@@ -128,7 +128,7 @@ Channel::process_controller (Parser & /*parser*/, EventTwoBytes *tb)
                cv = (unsigned short) _controller_val[tb->controller_number];
 
                if (_controller_14bit[tb->controller_number]) {
-                       cv = ((tb->value << 7) | (cv & 0x7f));
+                       cv = ((tb->value & 0x7f) << 7) | (cv & 0x7f);
                } else {
                        cv = tb->value;
                }
@@ -160,8 +160,14 @@ Channel::process_controller (Parser & /*parser*/, EventTwoBytes *tb)
                        cv = (cv & 0x3f80) | (tb->value & 0x7f);
                }
 
-               _controller_val[tb->controller_number] = 
-                       (controller_value_t) cv;
+               /* update the 14 bit value */
+               _controller_val[cn] = (controller_value_t) cv;
+
+               /* also store the "raw" 7 bit value in the incoming controller
+                  value store
+               */
+               _controller_val[tb->controller_number] = (controller_value_t) tb->value;
+
        } else {
 
                /* controller can only take 7 bit values */
@@ -173,12 +179,11 @@ Channel::process_controller (Parser & /*parser*/, EventTwoBytes *tb)
        /* bank numbers are special, in that they have their own signal
         */
 
-       if (tb->controller_number == 0) {
-               _bank_number = (unsigned short) _controller_val[0];
+       if (tb->controller_number == 0 || tb->controller_number == 0x20) {
+               _bank_number = _controller_val[0];
                _port.parser()->bank_change (*_port.parser(), _bank_number);
                _port.parser()->channel_bank_change[_channel_number] (*_port.parser(), _bank_number);
        }
-
 }
 
 void
index 02c16e67297cf2f2198a3b8fea06121689264379..f3ec434ca5008d0338ca2188619626d3e2921a66 100644 (file)
@@ -42,7 +42,7 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList {
        Port &midi_port()           { return _port; }
        byte channel()                  { return _channel_number; }
        byte program()                  { return _program_number; }
-       byte bank()                     { return _bank_number; }
+       unsigned short bank()           { return _bank_number; }
        byte pressure ()                { return _chanpress; }
        byte poly_pressure (byte n)     { return _polypress[n]; }
 
@@ -117,7 +117,7 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList {
 
        /* Current channel values */
        byte               _channel_number;
-       byte               _bank_number;
+       unsigned short     _bank_number;
        byte               _program_number;
        byte               _rpn_msb;
        byte               _rpn_lsb;
index e4126b210bac34db57ae7ee4b8bdcc897679a2b3..420e7fcb7bb89a71115c8b0d76bf81d4ebb89945 100644 (file)
@@ -34,6 +34,7 @@ class Port;
 class Parser;
 
 typedef PBD::Signal1<void,Parser&>                   ZeroByteSignal;
+typedef PBD::Signal2<void,Parser&,unsigned short>    BankSignal;
 typedef PBD::Signal2<void,Parser&,framecnt_t>        TimestampedSignal;
 typedef PBD::Signal2<void,Parser&, byte>             OneByteSignal;
 typedef PBD::Signal2<void,Parser &, EventTwoBytes *> TwoByteSignal;
@@ -55,7 +56,7 @@ class LIBMIDIPP_API Parser {
 
        /* signals that anyone can connect to */
        
-       OneByteSignal         bank_change;
+       BankSignal            bank_change;
        TwoByteSignal         note_on;
        TwoByteSignal         note_off;
        TwoByteSignal         poly_pressure;
@@ -64,7 +65,7 @@ class LIBMIDIPP_API Parser {
        PitchBendSignal       pitchbend;
        TwoByteSignal         controller;
 
-       OneByteSignal         channel_bank_change[16];
+       BankSignal            channel_bank_change[16];
        TwoByteSignal         channel_note_on[16];
        TwoByteSignal         channel_note_off[16];
        TwoByteSignal         channel_poly_pressure[16];