From: Paul Davis Date: Mon, 28 Apr 2014 23:58:24 +0000 (-0400) Subject: merge (squash) with scenechange topic branch to provide MIDI-driven scene change... X-Git-Tag: 4.0-rc1~1601^2~1292^2~10 X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=commitdiff_plain;h=2cf411e4be0b10e6ecf47d2070963299b6a810e7 merge (squash) with scenechange topic branch to provide MIDI-driven scene change markers --- diff --git a/libs/ardour/ardour/async_midi_port.h b/libs/ardour/ardour/async_midi_port.h index c5babf6135..26946e3016 100644 --- a/libs/ardour/ardour/async_midi_port.h +++ b/libs/ardour/ardour/async_midi_port.h @@ -22,6 +22,8 @@ #include #include +#include + #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&); + 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 timer; RingBuffer< Evoral::Event > output_fifo; Evoral::EventRingBuffer input_fifo; Glib::Threads::Mutex output_fifo_lock; diff --git a/libs/ardour/ardour/location.h b/libs/ardour/ardour/location.h index b0956eea36..6cea208f05 100644 --- a/libs/ardour/ardour/location.h +++ b/libs/ardour/ardour/location.h @@ -34,10 +34,13 @@ #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 scene_change() const { return _scene_change; } + void set_scene_change (boost::shared_ptr); + PBD::Signal1 name_changed; PBD::Signal1 end_changed; PBD::Signal1 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 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 _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 index 0000000000..86793c57fb --- /dev/null +++ b/libs/ardour/ardour/midi_scene_change.h @@ -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 index 0000000000..2cc0464bec --- /dev/null +++ b/libs/ardour/ardour/midi_scene_changer.h @@ -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); + + 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 > Scenes; + + MIDI::Port* input_port; + boost::shared_ptr 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); + + 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__ */ diff --git a/libs/ardour/ardour/midiport_manager.h b/libs/ardour/ardour/midiport_manager.h index b5b46e8510..5e87238c22 100644 --- a/libs/ardour/ardour/midiport_manager.h +++ b/libs/ardour/ardour/midiport_manager.h @@ -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 scene_in() const { return boost::dynamic_pointer_cast(_scene_in); } + boost::shared_ptr scene_out() const { return boost::dynamic_pointer_cast(_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 _midi_in; boost::shared_ptr _midi_out; boost::shared_ptr _mmc_in; boost::shared_ptr _mmc_out; + boost::shared_ptr _scene_in; + boost::shared_ptr _scene_out; /* synchronously handled ports: ARDOUR::MidiPort */ boost::shared_ptr _mtc_input_port; diff --git a/libs/ardour/ardour/rc_configuration_vars.h b/libs/ardour/ardour/rc_configuration_vars.h index d290d8ebc8..4401b1f74c 100644 --- a/libs/ardour/ardour/rc_configuration_vars.h +++ b/libs/ardour/ardour/rc_configuration_vars.h @@ -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 index 0000000000..d5ba984e92 --- /dev/null +++ b/libs/ardour/ardour/scene_changer.h @@ -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 + +#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__ */ diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 15af67ada7..40ada138a6 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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 VersionMismatch; + SceneChanger* scene_changer() const { return _scene_changer; } + boost::shared_ptr ltc_input_port() const; boost::shared_ptr ltc_output_port() const; boost::shared_ptr ltc_input_io() { return _ltc_input; } boost::shared_ptr 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 midi_clock_output_port () const; - boost::shared_ptr midi_clock_input_port () const; - boost::shared_ptr mtc_output_port () const; - boost::shared_ptr mtc_input_port () const; + MIDI::Port* scene_input_port () const; + MIDI::Port* scene_output_port () const; - MIDI::MachineControl& mmc() { return *_mmc; } + boost::shared_ptr scene_in () const; + boost::shared_ptr scene_out () const; + + boost::shared_ptr midi_clock_output_port () const; + boost::shared_ptr midi_clock_input_port () const; + boost::shared_ptr mtc_output_port () const; + boost::shared_ptr 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 diff --git a/libs/ardour/async_midi_port.cc b/libs/ardour/async_midi_port.cc index bd583328c3..21b59dec00 100644 --- a/libs/ardour/async_midi_port.cc +++ b/libs/ardour/async_midi_port.cc @@ -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& 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()); } diff --git a/libs/ardour/location.cc b/libs/ardour/location.cc index 2a27fc318a..90265af4e4 100644 --- a/libs/ardour/location.cc +++ b/libs/ardour/location.cc @@ -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 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 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 index 0000000000..81a74911a9 --- /dev/null +++ b/libs/ardour/midi_scene_change.cc @@ -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 index 0000000000..49e835ca80 --- /dev/null +++ b/libs/ardour/midi_scene_changer.cc @@ -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 sc; + + scenes.clear (); + + for (Locations::LocationList::const_iterator l = locations.begin(); l != locations.end(); ++l) { + + if ((sc = (*l)->scene_change()) != 0) { + + boost::shared_ptr msc = boost::dynamic_pointer_cast (sc); + + if (msc) { + scenes.insert (std::make_pair (msc->time(), msc)); + } + } + } +} + +void +MIDISceneChanger::deliver (MidiBuffer& mbuf, framepos_t when, boost::shared_ptr 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 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 (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 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 msc = boost::dynamic_pointer_cast (sc); + + if (msc->bank() == bank && msc->program() == program && (*l)->start() < where) { + where = (*l)->start(); + } + } + } + + if (where != max_framepos) { + _session.request_locate (where); + } +} diff --git a/libs/ardour/midi_ui.cc b/libs/ardour/midi_ui.cc index 97dfdce6bf..e00ec587ec 100644 --- a/libs/ardour/midi_ui.cc +++ b/libs/ardour/midi_ui.cc @@ -122,6 +122,10 @@ MidiControlUI::reset_ports () if ((p = dynamic_cast (_session.mmc_input_port()))) { ports.push_back (p); } + + if ((p = dynamic_cast (_session.scene_input_port()))) { + ports.push_back (p); + } if (ports.empty()) { return; diff --git a/libs/ardour/midiport_manager.cc b/libs/ardour/midiport_manager.cc index 6de0436586..b1699ca5ab 100644 --- a/libs/ardour/midiport_manager.cc +++ b/libs/ardour/midiport_manager.cc @@ -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(_mmc_in).get(); _mmc_output_port = boost::dynamic_pointer_cast(_mmc_out).get(); + _scene_input_port = boost::dynamic_pointer_cast(_scene_in).get(); + _scene_output_port = boost::dynamic_pointer_cast(_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()); diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 2aef745961..05fa883a9d 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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; diff --git a/libs/ardour/session_midi.cc b/libs/ardour/session_midi.cc index ea6dfe81cf..93df1fc946 100644 --- a/libs/ardour/session_midi.cc +++ b/libs/ardour/session_midi.cc @@ -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 +Session::scene_in () const +{ + return _midi_ports->scene_in (); +} + +boost::shared_ptr +Session::scene_out () const +{ + return _midi_ports->scene_out (); +} diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index eb5c0de294..680f2861de 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -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 */ } diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 07f10e9bc1..6a06863e9e 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -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 timer_func (boost::bind (&Session::audible_frame, this)); + boost::dynamic_pointer_cast(scene_in())->set_timer (timer_func); + setup_midi_machine_control (); if (_butler->start_thread()) { diff --git a/libs/ardour/wscript b/libs/ardour/wscript index c12aec4ef8..0046e2eaec 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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', diff --git a/libs/midi++2/channel.cc b/libs/midi++2/channel.cc index ed8f4da5bc..190ea18568 100644 --- a/libs/midi++2/channel.cc +++ b/libs/midi++2/channel.cc @@ -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 diff --git a/libs/midi++2/midi++/channel.h b/libs/midi++2/midi++/channel.h index 02c16e6729..f3ec434ca5 100644 --- a/libs/midi++2/midi++/channel.h +++ b/libs/midi++2/midi++/channel.h @@ -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; diff --git a/libs/midi++2/midi++/parser.h b/libs/midi++2/midi++/parser.h index e4126b210b..420e7fcb7b 100644 --- a/libs/midi++2/midi++/parser.h +++ b/libs/midi++2/midi++/parser.h @@ -34,6 +34,7 @@ class Port; class Parser; typedef PBD::Signal1 ZeroByteSignal; +typedef PBD::Signal2 BankSignal; typedef PBD::Signal2 TimestampedSignal; typedef PBD::Signal2 OneByteSignal; typedef PBD::Signal2 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];