diff options
Diffstat (limited to 'libs/surfaces/generic_midi')
-rw-r--r-- | libs/surfaces/generic_midi/SConscript | 1 | ||||
-rw-r--r-- | libs/surfaces/generic_midi/generic_midi_control_protocol.cc | 145 | ||||
-rw-r--r-- | libs/surfaces/generic_midi/generic_midi_control_protocol.h | 41 | ||||
-rw-r--r-- | libs/surfaces/generic_midi/midicontrollable.cc | 367 | ||||
-rw-r--r-- | libs/surfaces/generic_midi/midicontrollable.h | 100 |
5 files changed, 618 insertions, 36 deletions
diff --git a/libs/surfaces/generic_midi/SConscript b/libs/surfaces/generic_midi/SConscript index 213a81a99d..f9c2de08f8 100644 --- a/libs/surfaces/generic_midi/SConscript +++ b/libs/surfaces/generic_midi/SConscript @@ -23,6 +23,7 @@ genericmidi.Append(POTFILE = domain + '.pot') genericmidi_files=Split(""" interface.cc generic_midi_control_protocol.cc +midicontrollable.cc """) genericmidi.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE") diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc index 95b9d22393..dec891703e 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.cc +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.cc @@ -1,20 +1,54 @@ +/* + Copyright (C) 2006 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. + + $Id$ +*/ + +#include <algorithm> + #include <midi++/port.h> +#include <midi++/manager.h> +#include <midi++/port_request.h> #include <ardour/route.h> #include <ardour/session.h> #include "generic_midi_control_protocol.h" +#include "midicontrollable.h" using namespace ARDOUR; +using namespace PBD; #include "i18n.h" GenericMidiControlProtocol::GenericMidiControlProtocol (Session& s) : ControlProtocol (s, _("GenericMIDI")) { - _port = s.midi_port(); - s.MIDI_PortChanged.connect (mem_fun (*this, &GenericMidiControlProtocol::port_change)); + MIDI::Manager* mm = MIDI::Manager::instance(); + MIDI::PortRequest pr ("ardour:MIDI control", "ardour:MIDI control", "duplex", "alsa/seq"); + _port = mm->add_port (pr); + + _feedback_interval = 10000; // microseconds + last_feedback_time = 0; + + Controllable::StartLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::start_learning)); + Controllable::StopLearning.connect (mem_fun (*this, &GenericMidiControlProtocol::stop_learning)); + Session::SendFeedback.connect (mem_fun (*this, &GenericMidiControlProtocol::send_feedback)); } GenericMidiControlProtocol::~GenericMidiControlProtocol () @@ -24,42 +58,101 @@ GenericMidiControlProtocol::~GenericMidiControlProtocol () int GenericMidiControlProtocol::set_active (bool yn) { - /* start delivery/outbound thread */ + /* start/stop delivery/outbound thread */ return 0; } void -GenericMidiControlProtocol::port_change () +GenericMidiControlProtocol::set_feedback_interval (microseconds_t ms) { - _port = session->midi_port (); + _feedback_interval = ms; } -void -GenericMidiControlProtocol::set_port (MIDI::Port* p) +void +GenericMidiControlProtocol::send_feedback () { - _port = p; + microseconds_t now = get_microseconds (); + + if (last_feedback_time != 0) { + if ((now - last_feedback_time) < _feedback_interval) { + return; + } + } + + _send_feedback (); + + last_feedback_time = now; } void -GenericMidiControlProtocol::send_route_feedback (list<Route*>& routes) +GenericMidiControlProtocol::_send_feedback () { - if (_port != 0) { - - const int32_t bufsize = 16 * 1024; - MIDI::byte buf[bufsize]; - int32_t bsize = bufsize; - MIDI::byte* end = buf; - - for (list<Route*>::iterator r = routes.begin(); r != routes.end(); ++r) { - end = (*r)->write_midi_feedback (end, bsize); - } - - if (end == buf) { - return; - } - - _port->write (buf, (int32_t) (end - buf)); - //cerr << "MIDI feedback: wrote " << (int32_t) (end - buf) << " to midi port\n"; + const int32_t bufsize = 16 * 1024; + MIDI::byte buf[bufsize]; + int32_t bsize = bufsize; + MIDI::byte* end = buf; + + for (MIDIControllables::iterator r = controllables.begin(); r != controllables.end(); ++r) { + end = (*r)->write_feedback (end, bsize); + } + + if (end == buf) { + return; + } + + _port->write (buf, (int32_t) (end - buf)); +} + +bool +GenericMidiControlProtocol::start_learning (Controllable* c) +{ + if (c == 0) { + return false; + } + + MIDIControllable* mc = new MIDIControllable (*_port, *c); + + + { + Glib::Mutex::Lock lm (pending_lock); + pending_controllables.push_back (mc); + mc->learning_stopped.connect (bind (mem_fun (*this, &GenericMidiControlProtocol::learning_stopped), mc)); + } + + mc->learn_about_external_control (); + return true; +} + +void +GenericMidiControlProtocol::learning_stopped (MIDIControllable* mc) +{ + Glib::Mutex::Lock lm (pending_lock); + Glib::Mutex::Lock lm2 (controllables_lock); + + MIDIControllables::iterator i = find (pending_controllables.begin(), pending_controllables.end(), mc); + + if (i != pending_controllables.end()) { + pending_controllables.erase (i); } + + controllables.push_back (mc); } +void +GenericMidiControlProtocol::stop_learning (Controllable* c) +{ + Glib::Mutex::Lock lm (pending_lock); + + /* learning timed out, and we've been told to consider this attempt to learn to be cancelled. find the + relevant MIDIControllable and remove it from the pending list. + */ + + for (MIDIControllables::iterator i = pending_controllables.begin(); i != pending_controllables.end(); ++i) { + if (&(*i)->get_controllable() == c) { + (*i)->stop_learning (); + delete (*i); + pending_controllables.erase (i); + break; + } + } +} diff --git a/libs/surfaces/generic_midi/generic_midi_control_protocol.h b/libs/surfaces/generic_midi/generic_midi_control_protocol.h index 70cbd181c8..f86f5f6434 100644 --- a/libs/surfaces/generic_midi/generic_midi_control_protocol.h +++ b/libs/surfaces/generic_midi/generic_midi_control_protocol.h @@ -1,34 +1,55 @@ #ifndef ardour_generic_midi_control_protocol_h #define ardour_generic_midi_control_protocol_h +#include <vector> +#include <glibmm/thread.h> +#include <ardour/types.h> + #include <control_protocol/control_protocol.h> namespace MIDI { class Port; } +namespace PBD { + class Controllable; +} + namespace ARDOUR { + class Session; +} -class GenericMidiControlProtocol : public ControlProtocol { +class MIDIControllable; + +class GenericMidiControlProtocol : public ARDOUR::ControlProtocol { public: - GenericMidiControlProtocol (Session&); + GenericMidiControlProtocol (ARDOUR::Session&); virtual ~GenericMidiControlProtocol(); int set_active (bool yn); static bool probe() { return true; } - void set_port (MIDI::Port*); MIDI::Port* port () const { return _port; } + void set_feedback_interval (ARDOUR::microseconds_t); - void send_route_feedback (std::list<Route*>&); - private: - void route_feedback (ARDOUR::Route&, bool); MIDI::Port* _port; + ARDOUR::microseconds_t _feedback_interval; + ARDOUR::microseconds_t last_feedback_time; - void port_change (); -}; + void _send_feedback (); + void send_feedback (); -} + typedef std::vector<MIDIControllable*> MIDIControllables; + MIDIControllables controllables; + MIDIControllables pending_controllables; + Glib::Mutex controllables_lock; + Glib::Mutex pending_lock; + + bool start_learning (PBD::Controllable*); + void stop_learning (PBD::Controllable*); + + void learning_stopped (MIDIControllable*); +}; -#endif // ardour_generic_midi_control_protocol_h +#endif /* ardour_generic_midi_control_protocol_h */ diff --git a/libs/surfaces/generic_midi/midicontrollable.cc b/libs/surfaces/generic_midi/midicontrollable.cc new file mode 100644 index 0000000000..f4b7ef665b --- /dev/null +++ b/libs/surfaces/generic_midi/midicontrollable.cc @@ -0,0 +1,367 @@ +/* + Copyright (C) 1998-2006 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. + + $Id: midicontrollable.cc 629 2006-06-21 23:01:03Z paul $ +*/ + +#include <cstdio> /* for sprintf, sigh */ +#include <climits> +#include <pbd/error.h> +#include <pbd/xml++.h> +#include <midi++/port.h> +#include <midi++/channel.h> + +#include "midicontrollable.h" + +using namespace sigc; +using namespace MIDI; +using namespace PBD; +using namespace ARDOUR; + +bool MIDIControllable::_send_feedback = false; + +MIDIControllable::MIDIControllable (Port& p, Controllable& c, bool is_bistate) + : controllable (c), _port (p), bistate (is_bistate) +{ + setting = false; + last_written = 0; // got a better idea ? + control_type = none; + _control_description = "MIDI Control: none"; + control_additional = (byte) -1; + connections = 0; + feedback = true; // for now + + /* use channel 0 ("1") as the initial channel */ + + midi_rebind (0); +} + +MIDIControllable::~MIDIControllable () +{ + drop_external_control (); +} + +void +MIDIControllable::midi_forget () +{ + /* stop listening for incoming messages, but retain + our existing event + type information. + */ + + if (connections > 0) { + midi_sense_connection[0].disconnect (); + } + + if (connections > 1) { + midi_sense_connection[1].disconnect (); + } + + connections = 0; + midi_learn_connection.disconnect (); + +} + +void +MIDIControllable::midi_rebind (channel_t c) +{ + if (c >= 0) { + bind_midi (c, control_type, control_additional); + } else { + midi_forget (); + } +} + +void +MIDIControllable::learn_about_external_control () +{ + drop_external_control (); + midi_learn_connection = _port.input()->any.connect (mem_fun (*this, &MIDIControllable::midi_receiver)); + learning_started (); +} + +void +MIDIControllable::stop_learning () +{ + midi_learn_connection.disconnect (); +} + +void +MIDIControllable::drop_external_control () +{ + if (connections > 0) { + midi_sense_connection[0].disconnect (); + } + if (connections > 1) { + midi_sense_connection[1].disconnect (); + } + + connections = 0; + midi_learn_connection.disconnect (); + + control_type = none; + control_additional = (byte) -1; +} + +void +MIDIControllable::midi_sense_note_on (Parser &p, EventTwoBytes *tb) +{ + midi_sense_note (p, tb, true); +} + +void +MIDIControllable::midi_sense_note_off (Parser &p, EventTwoBytes *tb) +{ + midi_sense_note (p, tb, false); +} + +void +MIDIControllable::midi_sense_note (Parser &p, EventTwoBytes *msg, bool is_on) +{ + if (!bistate) { + controllable.set_value (msg->note_number/127.0); + } else { + + /* Note: parser handles the use of zero velocity to + mean note off. if we get called with is_on=true, then we + got a *real* note on. + */ + + if (msg->note_number == control_additional) { + controllable.set_value (is_on ? 1 : 0); + } + } +} + +void +MIDIControllable::midi_sense_controller (Parser &, EventTwoBytes *msg) +{ + if (control_additional == msg->controller_number) { + if (!bistate) { + controllable.set_value (msg->value/127.0); + } else { + if (msg->value > 64.0) { + controllable.set_value (1); + } else { + controllable.set_value (0); + } + } + } +} + +void +MIDIControllable::midi_sense_program_change (Parser &p, byte msg) +{ + /* XXX program change messages make no sense for bistates */ + + if (!bistate) { + controllable.set_value (msg/127.0); + } +} + +void +MIDIControllable::midi_sense_pitchbend (Parser &p, pitchbend_t pb) +{ + /* pitchbend messages make no sense for bistates */ + + /* XXX gack - get rid of assumption about typeof pitchbend_t */ + + controllable.set_value ((pb/(float) SHRT_MAX)); +} + +void +MIDIControllable::midi_receiver (Parser &p, byte *msg, size_t len) +{ + /* we only respond to channel messages */ + + if ((msg[0] & 0xF0) < 0x80 || (msg[0] & 0xF0) > 0xE0) { + return; + } + + /* if the our port doesn't do input anymore, forget it ... */ + + if (!_port.input()) { + return; + } + + bind_midi ((channel_t) (msg[0] & 0xf), eventType (msg[0] & 0xF0), msg[1]); + + learning_stopped (); +} + +void +MIDIControllable::bind_midi (channel_t chn, eventType ev, MIDI::byte additional) +{ + char buf[64]; + + drop_external_control (); + + control_type = ev; + control_channel = chn; + control_additional = additional; + + if (_port.input() == 0) { + return; + } + + Parser& p = *_port.input(); + + int chn_i = chn; + switch (ev) { + case MIDI::off: + midi_sense_connection[0] = p.channel_note_off[chn_i].connect + (mem_fun (*this, &MIDIControllable::midi_sense_note_off)); + + /* if this is a bistate, connect to noteOn as well, + and we'll toggle back and forth between the two. + */ + + if (bistate) { + midi_sense_connection[1] = p.channel_note_on[chn_i].connect + (mem_fun (*this, &MIDIControllable::midi_sense_note_on)); + connections = 2; + } else { + connections = 1; + } + _control_description = "MIDI control: NoteOff"; + break; + + case MIDI::on: + midi_sense_connection[0] = p.channel_note_on[chn_i].connect + (mem_fun (*this, &MIDIControllable::midi_sense_note_on)); + if (bistate) { + midi_sense_connection[1] = p.channel_note_off[chn_i].connect + (mem_fun (*this, &MIDIControllable::midi_sense_note_off)); + connections = 2; + } else { + connections = 1; + } + _control_description = "MIDI control: NoteOn"; + break; + + case MIDI::controller: + midi_sense_connection[0] = p.channel_controller[chn_i].connect + (mem_fun (*this, &MIDIControllable::midi_sense_controller)); + connections = 1; + snprintf (buf, sizeof (buf), "MIDI control: Controller %d", control_additional); + _control_description = buf; + break; + + case MIDI::program: + if (!bistate) { + midi_sense_connection[0] = p.channel_program_change[chn_i].connect + (mem_fun (*this, + &MIDIControllable::midi_sense_program_change)); + connections = 1; + _control_description = "MIDI control: ProgramChange"; + } + break; + + case MIDI::pitchbend: + if (!bistate) { + midi_sense_connection[0] = p.channel_pitchbend[chn_i].connect + (mem_fun (*this, &MIDIControllable::midi_sense_pitchbend)); + connections = 1; + _control_description = "MIDI control: Pitchbend"; + } + break; + + default: + break; + } +} + +void +MIDIControllable::send_feedback () +{ + byte msg[3]; + + if (setting || !_send_feedback || control_type == none) { + return; + } + + msg[0] = (control_type & 0xF0) | (control_channel & 0xF); + msg[1] = control_additional; + msg[2] = (byte) (controllable.get_value() * 127.0f); + + _port.write (msg, 3); +} + +MIDI::byte* +MIDIControllable::write_feedback (MIDI::byte* buf, int32_t& bufsize, bool force) +{ + if (control_type != none &&_send_feedback && bufsize > 2) { + + MIDI::byte gm = (MIDI::byte) (controllable.get_value() * 127.0); + + if (gm != last_written) { + *buf++ = (0xF0 & control_type) | (0xF & control_channel); + *buf++ = control_additional; /* controller number */ + *buf++ = gm; + last_written = gm; + bufsize -= 3; + } + } + + return buf; +} + +int +MIDIControllable::set_state (const XMLNode& node) +{ + const XMLProperty* prop; + int xx; + + if ((prop = node.property ("event")) != 0) { + sscanf (prop->value().c_str(), "0x%x", &xx); + control_type = (MIDI::eventType) xx; + } else { + return -1; + } + + if ((prop = node.property ("channel")) != 0) { + sscanf (prop->value().c_str(), "%d", &xx); + control_channel = (MIDI::channel_t) xx; + } else { + return -1; + } + + if ((prop = node.property ("additional")) != 0) { + sscanf (prop->value().c_str(), "0x%x", &xx); + control_additional = (MIDI::byte) xx; + } else { + return -1; + } + + return 0; +} + +XMLNode& +MIDIControllable::get_state () +{ + char buf[32]; + XMLNode& node (controllable.get_state ()); + + snprintf (buf, sizeof(buf), "0x%x", (int) control_type); + node.add_property ("event", buf); + snprintf (buf, sizeof(buf), "%d", (int) control_channel); + node.add_property ("channel", buf); + snprintf (buf, sizeof(buf), "0x%x", (int) control_additional); + node.add_property ("additional", buf); + + return node; +} + diff --git a/libs/surfaces/generic_midi/midicontrollable.h b/libs/surfaces/generic_midi/midicontrollable.h new file mode 100644 index 0000000000..7dd0be1d87 --- /dev/null +++ b/libs/surfaces/generic_midi/midicontrollable.h @@ -0,0 +1,100 @@ +/* + Copyright (C) 1998-2006 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. + + $Id: controllable.h 4 2005-05-13 20:47:18Z taybin $ +*/ + +#ifndef __gm_midicontrollable_h__ +#define __gm_midicontrollable_h__ + +#include <string> + +#include <sigc++/sigc++.h> + +#include <midi++/types.h> +#include <pbd/controllable.h> +#include <pbd/stateful.h> +#include <ardour/types.h> + +namespace MIDI { + +class Channel; +class Port; +class Parser; + +} + +class MIDIControllable : public Stateful +{ + public: + MIDIControllable (MIDI::Port&, PBD::Controllable&, bool bistate = false); + virtual ~MIDIControllable (); + + void send_feedback (); + MIDI::byte* write_feedback (MIDI::byte* buf, int32_t& bufsize, bool force = false); + + void midi_rebind (MIDI::channel_t channel=-1); + void midi_forget (); + void learn_about_external_control (); + void stop_learning (); + void drop_external_control (); + + sigc::signal<void> learning_started; + sigc::signal<void> learning_stopped; + + bool get_midi_feedback () { return feedback; } + void set_midi_feedback (bool val) { feedback = val; } + + MIDI::Port& get_port() const { return _port; } + PBD::Controllable& get_controllable() const { return controllable; } + + std::string control_description() const { return _control_description; } + + XMLNode& get_state (void); + int set_state (const XMLNode&); + + private: + PBD::Controllable& controllable; + MIDI::Port& _port; + bool setting; + MIDI::byte last_written; + bool bistate; + int midi_msg_id; /* controller ID or note number */ + sigc::connection midi_sense_connection[2]; + sigc::connection midi_learn_connection; + size_t connections; + MIDI::eventType control_type; + MIDI::byte control_additional; + MIDI::channel_t control_channel; + std::string _control_description; + bool feedback; + + static bool _send_feedback; + + void midi_receiver (MIDI::Parser &p, MIDI::byte *, size_t); + void midi_sense_note (MIDI::Parser &, MIDI::EventTwoBytes *, bool is_on); + void midi_sense_note_on (MIDI::Parser &p, MIDI::EventTwoBytes *tb); + void midi_sense_note_off (MIDI::Parser &p, MIDI::EventTwoBytes *tb); + void midi_sense_controller (MIDI::Parser &, MIDI::EventTwoBytes *); + void midi_sense_program_change (MIDI::Parser &, MIDI::byte); + void midi_sense_pitchbend (MIDI::Parser &, MIDI::pitchbend_t); + + void bind_midi (MIDI::channel_t, MIDI::eventType, MIDI::byte); +}; + +#endif // __gm_midicontrollable_h__ + |