diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 5d29f35301..372581f4e6 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -34,6 +34,7 @@ #include #include "midi++/port.h" +#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" #include "midi++/manager.h" @@ -133,7 +134,7 @@ _thread_init_callback (void * /*arg*/) SessionEvent::create_per_thread_pool (X_("Audioengine"), 512); - MIDI::Port::set_process_thread (pthread_self()); + MIDI::JackMIDIPort::set_process_thread (pthread_self()); } static void @@ -233,7 +234,7 @@ AudioEngine::stop (bool forever) } else { jack_deactivate (_priv_jack); Stopped(); /* EMIT SIGNAL */ - MIDI::Port::JackHalted (); /* EMIT SIGNAL */ + MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ } } @@ -1106,7 +1107,7 @@ AudioEngine::halted (void *arg) if (was_running) { ae->Halted(""); /* EMIT SIGNAL */ - MIDI::Port::JackHalted (); /* EMIT SIGNAL */ + MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ } } @@ -1356,7 +1357,7 @@ AudioEngine::disconnect_from_jack () if (_running) { _running = false; Stopped(); /* EMIT SIGNAL */ - MIDI::Port::JackHalted (); /* EMIT SIGNAL */ + MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */ } return 0; diff --git a/libs/ardour/midi_ui.cc b/libs/ardour/midi_ui.cc index 770a371457..302dce86ba 100644 --- a/libs/ardour/midi_ui.cc +++ b/libs/ardour/midi_ui.cc @@ -143,6 +143,8 @@ MidiControlUI::reset_ports () if ((fd = (*i)->selectable ()) >= 0) { Glib::RefPtr psrc = IOSource::create (fd, IO_IN|IO_HUP|IO_ERR); + cerr << "MIDI UI listening to " << (*i)->name() << endl; + psrc->connect (sigc::bind (sigc::mem_fun (this, &MidiControlUI::midi_input_handler), *i)); psrc->attach (_main_loop->get_context()); diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 5744b55c65..80dbd98963 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -106,6 +106,7 @@ #include "ardour/operations.h" #include "midi++/port.h" +#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" #include "midi++/manager.h" @@ -837,7 +838,7 @@ Session::hookup_io () /* Tell all IO objects to connect themselves together */ IO::enable_connecting (); - MIDI::Port::MakeConnections (); + MIDI::JackMIDIPort::MakeConnections (); /* Now reset all panners */ diff --git a/libs/ardour/ticker.cc b/libs/ardour/ticker.cc index 734b401356..5d078952a1 100644 --- a/libs/ardour/ticker.cc +++ b/libs/ardour/ticker.cc @@ -20,6 +20,7 @@ #include "pbd/stacktrace.h" #include "midi++/port.h" +#include "midi++/jack_midi_port.h" #include "midi++/manager.h" #include "evoral/midi_events.h" @@ -152,13 +153,13 @@ void MidiClockTicker::tick (const framepos_t& transport_frame) double next_tick = _last_tick + one_ppqn_in_frames (transport_frame); frameoffset_t next_tick_offset = llrint (next_tick) - transport_frame; + MIDI::JackMIDIPort* mp = dynamic_cast (_midi_port); + DEBUG_TRACE (PBD::DEBUG::MidiClock, string_compose ("Transport: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n", - transport_frame, _last_tick, next_tick, next_tick_offset, _midi_port->nframes_this_cycle() - ) - ); + transport_frame, _last_tick, next_tick, next_tick_offset, mp ? mp->nframes_this_cycle() : 0)); - if (next_tick_offset >= _midi_port->nframes_this_cycle()) { + if (!mp || (next_tick_offset >= mp->nframes_this_cycle())) { break; } diff --git a/libs/midi++2/channel.cc b/libs/midi++2/channel.cc index 0c9fbe39d1..66ce5ed71c 100644 --- a/libs/midi++2/channel.cc +++ b/libs/midi++2/channel.cc @@ -25,7 +25,7 @@ using namespace MIDI; -Channel::Channel (byte channelnum, PortBase &p) +Channel::Channel (byte channelnum, Port &p) : _port (p) { _channel_number = channelnum; diff --git a/libs/midi++2/ipmidi_port.cc b/libs/midi++2/ipmidi_port.cc new file mode 100644 index 0000000000..0ef0a9952a --- /dev/null +++ b/libs/midi++2/ipmidi_port.cc @@ -0,0 +1,290 @@ +/* + Copyright (C) 2012 Paul Davie + + 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: port.cc 12065 2012-04-23 16:23:48Z paul $ +*/ +#include +#include +#include +#include +#include +#include +#include + +#if defined(WIN32) +static WSADATA g_wsaData; +typedef int socklen_t; +#else +#include +#include +inline void closesocket(int s) { ::close(s); } +#endif + +#include +#include + +#include "pbd/xml++.h" +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/convert.h" +#include "pbd/compose.h" + +#include "midi++/types.h" +#include "midi++/ipmidi_port.h" +#include "midi++/channel.h" + +using namespace MIDI; +using namespace std; +using namespace PBD; + +IPMIDIPort::IPMIDIPort (int base_port, const string& iface) + : Port (string_compose ("IPmidi@%1", base_port), Port::Flags (Port::IsInput|Port::IsOutput)) + , sockin (-1) + , sockout (-1) +{ + if (!open_sockets (base_port, iface)) { + throw (failed_constructor ()); + } +} + +IPMIDIPort::IPMIDIPort (const XMLNode& node) + : Port (node) +{ + /* base class does not class set_state() */ + set_state (node); +} + +IPMIDIPort::~IPMIDIPort () +{ + close_sockets (); +} + +int +IPMIDIPort::selectable () const +{ + return sockin; +} + +XMLNode& +IPMIDIPort::get_state () const +{ + return Port::get_state (); +} + +void +IPMIDIPort::set_state (const XMLNode& node) +{ + Port::set_state (node); +} + +void +IPMIDIPort::close_sockets () +{ + if (sockin >= 0) { + ::closesocket (sockin); + sockin = -1; + } + + if (sockout >= 0) { + ::closesocket (sockout); + sockout = -1; + } +} + +static bool +get_address (int sock, struct in_addr *inaddr, const string& ifname ) +{ + // Get interface address from supplied name. + +#if !defined(WIN32) + struct ifreq ifr; + ::strncpy(ifr.ifr_name, ifname.c_str(), sizeof(ifr.ifr_name)); + + if (::ioctl(sock, SIOCGIFFLAGS, (char *) &ifr)) { + ::perror("ioctl(SIOCGIFFLAGS)"); + return false; + } + + if ((ifr.ifr_flags & IFF_UP) == 0) { + error << string_compose ("interface %1 is down", ifname) << endmsg; + return false; + } + + if (::ioctl(sock, SIOCGIFADDR, (char *) &ifr)) { + ::perror("ioctl(SIOCGIFADDR)"); + return false; + } + + struct sockaddr_in sa; + ::memcpy(&sa, &ifr.ifr_addr, sizeof(struct sockaddr_in)); + inaddr->s_addr = sa.sin_addr.s_addr; + + return true; + +#else + + return false; + +#endif // !WIN32 +} + +bool +IPMIDIPort::open_sockets (int base_port, const string& ifname) +{ + int protonum = 0; + struct protoent *proto = ::getprotobyname("IP"); + + if (proto) { + protonum = proto->p_proto; + } + + sockin = ::socket (PF_INET, SOCK_DGRAM, protonum); + if (sockin < 0) { + ::perror("socket(in)"); + return false; + } + + struct sockaddr_in addrin; + ::memset(&addrin, 0, sizeof(addrin)); + addrin.sin_family = AF_INET; + addrin.sin_addr.s_addr = htonl(INADDR_ANY); + addrin.sin_port = htons(base_port); + + if (::bind(sockin, (struct sockaddr *) (&addrin), sizeof(addrin)) < 0) { + ::perror("bind"); + return false; + } + + // Will Hall, 2007 + // INADDR_ANY will bind to default interface, + // specify alternate interface nameon which to bind... + struct in_addr if_addr_in; + if (!ifname.empty()) { + if (!get_address(sockin, &if_addr_in, ifname)) { + error << string_compose ("socket(in): could not find interface address for %1", ifname) << endmsg; + return false; + } + if (::setsockopt(sockin, IPPROTO_IP, IP_MULTICAST_IF, + (char *) &if_addr_in, sizeof(if_addr_in))) { + ::perror("setsockopt(IP_MULTICAST_IF)"); + return false; + } + } else { + if_addr_in.s_addr = htonl (INADDR_ANY); + } + + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = ::inet_addr("225.0.0.37"); + mreq.imr_interface.s_addr = if_addr_in.s_addr; + if(::setsockopt (sockin, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) { + ::perror("setsockopt(IP_ADD_MEMBERSHIP)"); + fprintf(stderr, "socket(in): your kernel is probably missing multicast support.\n"); + return false; + } + + // Output socket... + + sockout = ::socket (AF_INET, SOCK_DGRAM, protonum); + + if (sockout < 0) { + ::perror("socket(out)"); + return false; + } + + // Will Hall, Oct 2007 + if (!ifname.empty()) { + struct in_addr if_addr_out; + if (!get_address(sockout, &if_addr_out, ifname)) { + error << string_compose ("socket(out): could not find interface address for %1", ifname) << endmsg; + return false; + } + if (::setsockopt(sockout, IPPROTO_IP, IP_MULTICAST_IF, (char *) &if_addr_out, sizeof(if_addr_out))) { + ::perror("setsockopt(IP_MULTICAST_IF)"); + return false; + } + } + + ::memset(&addrout, 0, sizeof(struct sockaddr_in)); + addrout.sin_family = AF_INET; + addrout.sin_addr.s_addr = ::inet_addr("225.0.0.37"); + addrout.sin_port = htons (base_port); + + // Turn off loopback... + int loop = 0; + if (::setsockopt(sockout, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop, sizeof (loop)) < 0) { + ::perror("setsockopt(IP_MULTICAST_LOOP)"); + return false; + } + + if (fcntl (sockin, F_SETFL, O_NONBLOCK)) { + error << "cannot set non-blocking mode for IP MIDI input socket (" << ::strerror (errno) << ')' << endmsg; + return false; + } + + if (fcntl (sockout, F_SETFL, O_NONBLOCK)) { + error << "cannot set non-blocking mode for IP MIDI output socket (" << ::strerror (errno) << ')' << endmsg; + return false; + } + + return true; +} + +int +IPMIDIPort::write (byte* msg, size_t msglen, timestamp_t /* ignored */) { + + if (sockout) { + if (::sendto (sockout, (char *) msg, msglen, 0, (struct sockaddr *) &addrout, sizeof(struct sockaddr_in)) < 0) { + ::perror("sendto"); + return -1; + } + return msglen; + } + return 0; +} + +int +IPMIDIPort::read (byte* buf, size_t bufsize) +{ + /* nothing to do here - all handled by parse() */ + return 0; +} + +void +IPMIDIPort::parse (framecnt_t timestamp) +{ + /* input was detected on the socket, so go get it and hand it to the + * parser. This will emit appropriate signals that will be handled + * by anyone who cares. + */ + + unsigned char buf[1024]; + struct sockaddr_in sender; + socklen_t slen = sizeof(sender); + int r = ::recvfrom (sockin, (char *) buf, sizeof(buf), 0, (struct sockaddr *) &sender, &slen); + + if (r >= 0) { + + _parser->set_timestamp (timestamp); + + for (int i = 0; i < r; ++i) { + _parser->scanner (buf[i]); + } + } else { + ::perror ("failed to recv from socket"); + } +} + diff --git a/libs/midi++2/jack_midi_port.cc b/libs/midi++2/jack_midi_port.cc new file mode 100644 index 0000000000..b00aff8d8d --- /dev/null +++ b/libs/midi++2/jack_midi_port.cc @@ -0,0 +1,466 @@ +/* + Copyright (C) 1998 Paul Barton-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 +#include +#include +#include + +#include +#include + +#include "pbd/xml++.h" +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/convert.h" +#include "pbd/strsplit.h" +#include "pbd/stacktrace.h" + +#include "midi++/types.h" +#include "midi++/jack_midi_port.h" +#include "midi++/channel.h" + +using namespace MIDI; +using namespace std; +using namespace PBD; + +pthread_t JackMIDIPort::_process_thread; +Signal0 JackMIDIPort::JackHalted; +Signal0 JackMIDIPort::MakeConnections; + +JackMIDIPort::JackMIDIPort (string const & name, Flags flags, jack_client_t* jack_client) + : Port (name, flags) + , _currently_in_cycle (false) + , _nframes_this_cycle (0) + , _jack_client (jack_client) + , _jack_port (0) + , output_fifo (512) + , input_fifo (1024) + , xthread (true) +{ + assert (jack_client); + init (name, flags); +} + +JackMIDIPort::JackMIDIPort (const XMLNode& node, jack_client_t* jack_client) + : Port (node) + , _currently_in_cycle (false) + , _nframes_this_cycle (0) + , _jack_client (jack_client) + , _jack_port (0) + , output_fifo (512) + , input_fifo (1024) + , xthread (true) +{ + assert (jack_client); + + Descriptor desc (node); + init (desc.tag, desc.flags); + set_state (node); +} + +void +JackMIDIPort::init (string const & name, Flags flags) +{ + if (!create_port ()) { + _ok = true; + } + + MakeConnections.connect_same_thread (connect_connection, boost::bind (&JackMIDIPort::make_connections, this)); + JackHalted.connect_same_thread (halt_connection, boost::bind (&JackMIDIPort::jack_halted, this)); +} + + +JackMIDIPort::~JackMIDIPort () +{ + for (int i = 0; i < 16; i++) { + delete _channel[i]; + } + + if (_jack_port) { + if (_jack_client) { + jack_port_unregister (_jack_client, _jack_port); + _jack_port = 0; + } + } +} + +void +JackMIDIPort::parse (framecnt_t timestamp) +{ + byte buf[512]; + + /* NOTE: parsing is done (if at all) by initiating a read from + the port. Each port implementation calls on the parser + once it has data ready. + */ + + _parser->set_timestamp (timestamp); + + while (1) { + + // cerr << "+++ READ ON " << name() << endl; + + int nread = read (buf, sizeof (buf)); + + // cerr << "-- READ (" << nread << " ON " << name() << endl; + + if (nread > 0) { + if ((size_t) nread < sizeof (buf)) { + break; + } else { + continue; + } + } else if (nread == 0) { + break; + } else if (errno == EAGAIN) { + break; + } else { + fatal << "Error reading from MIDI port " << name() << endmsg; + /*NOTREACHED*/ + } + } +} + +void +JackMIDIPort::cycle_start (pframes_t nframes) +{ + assert (_jack_port); + + _currently_in_cycle = true; + _nframes_this_cycle = nframes; + + assert(_nframes_this_cycle == nframes); + + if (sends_output()) { + void *buffer = jack_port_get_buffer (_jack_port, nframes); + jack_midi_clear_buffer (buffer); + flush (buffer); + } + + if (receives_input()) { + void* jack_buffer = jack_port_get_buffer(_jack_port, nframes); + const pframes_t event_count = jack_midi_get_event_count(jack_buffer); + + jack_midi_event_t ev; + timestamp_t cycle_start_frame = jack_last_frame_time (_jack_client); + + for (pframes_t i = 0; i < event_count; ++i) { + jack_midi_event_get (&ev, jack_buffer, i); + input_fifo.write (cycle_start_frame + ev.time, (Evoral::EventType) 0, ev.size, ev.buffer); + } + + if (event_count) { + xthread.wakeup (); + } + } +} + +void +JackMIDIPort::cycle_end () +{ + if (sends_output()) { + flush (jack_port_get_buffer (_jack_port, _nframes_this_cycle)); + } + + _currently_in_cycle = false; + _nframes_this_cycle = 0; +} + +void +JackMIDIPort::jack_halted () +{ + _jack_client = 0; + _jack_port = 0; +} + +void +JackMIDIPort::drain (int check_interval_usecs) +{ + RingBuffer< Evoral::Event >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + if (is_process_thread()) { + error << "Process thread called MIDI::JackMIDIPort::drain() - this cannot work" << endmsg; + return; + } + + while (1) { + output_fifo.get_write_vector (&vec); + if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) { + break; + } + usleep (check_interval_usecs); + } +} + +int +JackMIDIPort::write(byte * msg, size_t msglen, timestamp_t timestamp) +{ + int ret = 0; + + if (!sends_output()) { + return ret; + } + + if (!is_process_thread()) { + + Glib::Mutex::Lock lm (output_fifo_lock); + RingBuffer< Evoral::Event >::rw_vector vec = { { 0, 0 }, { 0, 0} }; + + output_fifo.get_write_vector (&vec); + + if (vec.len[0] + vec.len[1] < 1) { + error << "no space in FIFO for non-process thread MIDI write" << endmsg; + return 0; + } + + if (vec.len[0]) { + if (!vec.buf[0]->owns_buffer()) { + vec.buf[0]->set_buffer (0, 0, true); + } + vec.buf[0]->set (msg, msglen, timestamp); + } else { + if (!vec.buf[1]->owns_buffer()) { + vec.buf[1]->set_buffer (0, 0, true); + } + vec.buf[1]->set (msg, msglen, timestamp); + } + + output_fifo.increment_write_idx (1); + + ret = msglen; + + } else { + + // XXX This had to be temporarily commented out to make export work again + if (!(timestamp < _nframes_this_cycle)) { + std::cerr << "attempting to write MIDI event of " << msglen << " bytes at time " + << timestamp << " of " << _nframes_this_cycle + << " (this will not work - needs a code fix)" + << std::endl; + } + + if (_currently_in_cycle) { + if (timestamp == 0) { + timestamp = _last_write_timestamp; + } + + if (jack_midi_event_write (jack_port_get_buffer (_jack_port, _nframes_this_cycle), + timestamp, msg, msglen) == 0) { + ret = msglen; + _last_write_timestamp = timestamp; + + } else { + ret = 0; + cerr << "write of " << msglen << " failed, port holds " + << jack_midi_get_event_count (jack_port_get_buffer (_jack_port, _nframes_this_cycle)) + << endl; + } + } else { + cerr << "write to JACK midi port failed: not currently in a process cycle." << endl; + PBD::stacktrace (cerr, 20); + } + } + + if (ret > 0 && _parser) { + // ardour doesn't care about this and neither should your app, probably + // output_parser->raw_preparse (*output_parser, msg, ret); + for (int i = 0; i < ret; i++) { + _parser->scanner (msg[i]); + } + // ardour doesn't care about this and neither should your app, probably + // output_parser->raw_postparse (*output_parser, msg, ret); + } + + return ret; +} + +void +JackMIDIPort::flush (void* jack_port_buffer) +{ + RingBuffer< Evoral::Event >::rw_vector vec = { { 0, 0 }, { 0, 0 } }; + size_t written; + + output_fifo.get_read_vector (&vec); + + if (vec.len[0] + vec.len[1]) { + // cerr << "Flush " << vec.len[0] + vec.len[1] << " events from non-process FIFO\n"; + } + + if (vec.len[0]) { + Evoral::Event* evp = vec.buf[0]; + + for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { + jack_midi_event_write (jack_port_buffer, + (timestamp_t) evp->time(), evp->buffer(), evp->size()); + } + } + + if (vec.len[1]) { + Evoral::Event* evp = vec.buf[1]; + + for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { + jack_midi_event_write (jack_port_buffer, + (timestamp_t) evp->time(), evp->buffer(), evp->size()); + } + } + + if ((written = vec.len[0] + vec.len[1]) != 0) { + output_fifo.increment_read_idx (written); + } +} + +int +JackMIDIPort::read (byte *, size_t) +{ + if (!receives_input()) { + return 0; + } + + timestamp_t time; + Evoral::EventType type; + uint32_t size; + byte buffer[input_fifo.capacity()]; + + while (input_fifo.read (&time, &type, &size, buffer)) { + _parser->set_timestamp (time); + for (uint32_t i = 0; i < size; ++i) { + _parser->scanner (buffer[i]); + } + } + + return 0; +} + +int +JackMIDIPort::create_port () +{ + _jack_port = jack_port_register(_jack_client, _tagname.c_str(), JACK_DEFAULT_MIDI_TYPE, _flags, 0); + return _jack_port == 0 ? -1 : 0; +} + +XMLNode& +JackMIDIPort::get_state () const +{ + XMLNode& root = Port::get_state (); + +#if 0 + byte device_inquiry[6]; + + device_inquiry[0] = 0xf0; + device_inquiry[0] = 0x7e; + device_inquiry[0] = 0x7f; + device_inquiry[0] = 0x06; + device_inquiry[0] = 0x02; + device_inquiry[0] = 0xf7; + + write (device_inquiry, sizeof (device_inquiry), 0); +#endif + + if (_jack_port) { + + const char** jc = jack_port_get_connections (_jack_port); + string connection_string; + if (jc) { + for (int i = 0; jc[i]; ++i) { + if (i > 0) { + connection_string += ','; + } + connection_string += jc[i]; + } + free (jc); + } + + if (!connection_string.empty()) { + root.add_property ("connections", connection_string); + } + } else { + if (!_connections.empty()) { + root.add_property ("connections", _connections); + } + } + + return root; +} + +void +JackMIDIPort::set_state (const XMLNode& node) +{ + const XMLProperty* prop; + + if ((prop = node.property ("tag")) == 0 || prop->value() != _tagname) { + return; + } + + Port::set_state (node); + + if ((prop = node.property ("connections")) != 0) { + _connections = prop->value (); + } +} + +void +JackMIDIPort::make_connections () +{ + if (!_connections.empty()) { + vector ports; + split (_connections, ports, ','); + for (vector::iterator x = ports.begin(); x != ports.end(); ++x) { + if (_jack_client) { + if (receives_input()) { + jack_connect (_jack_client, (*x).c_str(), jack_port_name (_jack_port)); + } else { + jack_connect (_jack_client, jack_port_name (_jack_port), (*x).c_str()); + } + /* ignore failures */ + } + } + } + + connect_connection.disconnect (); +} + +void +JackMIDIPort::set_process_thread (pthread_t thr) +{ + _process_thread = thr; +} + +bool +JackMIDIPort::is_process_thread() +{ + return (pthread_self() == _process_thread); +} + +void +JackMIDIPort::reestablish (jack_client_t* jack) +{ + _jack_client = jack; + int const r = create_port (); + + if (r) { + PBD::error << "could not reregister ports for " << name() << endmsg; + } +} + +void +JackMIDIPort::reconnect () +{ + make_connections (); +} diff --git a/libs/midi++2/manager.cc b/libs/midi++2/manager.cc index 61d4c4c363..822c74e125 100644 --- a/libs/midi++2/manager.cc +++ b/libs/midi++2/manager.cc @@ -27,6 +27,7 @@ #include "midi++/manager.h" #include "midi++/channel.h" #include "midi++/port.h" +#include "midi++/jack_midi_port.h" #include "midi++/mmc.h" using namespace std; @@ -40,12 +41,12 @@ Manager::Manager (jack_client_t* jack) { _mmc = new MachineControl (this, jack); - _mtc_input_port = add_port (new MIDI::Port ("MTC in", Port::IsInput, jack)); - _mtc_output_port = add_port (new MIDI::Port ("MTC out", Port::IsOutput, jack)); - _midi_input_port = add_port (new MIDI::Port ("MIDI control in", Port::IsInput, jack)); - _midi_output_port = add_port (new MIDI::Port ("MIDI control out", Port::IsOutput, jack)); - _midi_clock_input_port = add_port (new MIDI::Port ("MIDI clock in", Port::IsInput, jack)); - _midi_clock_output_port = add_port (new MIDI::Port ("MIDI clock out", Port::IsOutput, jack)); + _mtc_input_port = add_port (new MIDI::JackMIDIPort ("MTC in", Port::IsInput, jack)); + _mtc_output_port = add_port (new MIDI::JackMIDIPort ("MTC out", Port::IsOutput, jack)); + _midi_input_port = add_port (new MIDI::JackMIDIPort ("MIDI control in", Port::IsInput, jack)); + _midi_output_port = add_port (new MIDI::JackMIDIPort ("MIDI control out", Port::IsOutput, jack)); + _midi_clock_input_port = add_port (new MIDI::JackMIDIPort ("MIDI clock in", Port::IsInput, jack)); + _midi_clock_output_port = add_port (new MIDI::JackMIDIPort ("MIDI clock out", Port::IsOutput, jack)); } Manager::~Manager () @@ -117,7 +118,10 @@ Manager::reestablish (jack_client_t* jack) boost::shared_ptr pr = _ports.reader (); for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) { - (*p)->reestablish (jack); + JackMIDIPort* pp = dynamic_cast (*p); + if (pp) { + pp->reestablish (jack); + } } } @@ -128,7 +132,10 @@ Manager::reconnect () boost::shared_ptr pr = _ports.reader (); for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) { - (*p)->reconnect (); + JackMIDIPort* pp = dynamic_cast (*p); + if (pp) { + pp->reconnect (); + } } } diff --git a/libs/midi++2/midi++/channel.h b/libs/midi++2/midi++/channel.h index 370a569156..d00ce700c5 100644 --- a/libs/midi++2/midi++/channel.h +++ b/libs/midi++2/midi++/channel.h @@ -39,9 +39,9 @@ class Port; class Channel : public PBD::ScopedConnectionList { public: - Channel (byte channel_number, PortBase &); + Channel (byte channel_number, Port &); - PortBase &midi_port() { return _port; } + Port &midi_port() { return _port; } byte channel() { return _channel_number; } byte program() { return _program_number; } byte bank() { return _bank_number; } @@ -111,11 +111,11 @@ class Channel : public PBD::ScopedConnectionList { } protected: - friend class PortBase; + friend class Port; void connect_signals (); private: - PortBase& _port; + Port& _port; /* Current channel values */ byte _channel_number; diff --git a/libs/midi++2/midi++/ipmidi_port.h b/libs/midi++2/midi++/ipmidi_port.h new file mode 100644 index 0000000000..bf949bd601 --- /dev/null +++ b/libs/midi++2/midi++/ipmidi_port.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 1998-2010 Paul Barton-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 __libmidi_ipmidi_port_h__ +#define __libmidi_ipmidi_port_h__ + +#include +#include +#if defined(WIN32) +#include +#else +#include +#include +#endif + +#include + +#include "pbd/xml++.h" +#include "pbd/crossthread.h" +#include "pbd/signals.h" +#include "pbd/ringbuffer.h" + +#include "evoral/Event.hpp" +#include "evoral/EventRingBuffer.hpp" + +#include "midi++/types.h" +#include "midi++/parser.h" +#include "midi++/port.h" + +namespace MIDI { + +class IPMIDIPort : public Port { + public: + IPMIDIPort (int base_port = lowest_ipmidi_port_default, const std::string& ifname = std::string()); + IPMIDIPort (const XMLNode&); + ~IPMIDIPort (); + + XMLNode& get_state () const; + void set_state (const XMLNode&); + + int write (byte *msg, size_t msglen, timestamp_t timestamp); + int read (byte *buf, size_t bufsize); + void parse (framecnt_t timestamp); + int selectable () const; + + static const int lowest_ipmidi_port_default = 21928; + +private: + int sockin; + int sockout; + struct sockaddr_in addrout; + + bool open_sockets (int base_port, const std::string& ifname); + void close_sockets (); + + void init (std::string const &, Flags); +}; + +} // namespace MIDI + +#endif // __libmidi_ipmidi_port_h__ diff --git a/libs/midi++2/midi++/jack_midi_port.h b/libs/midi++2/midi++/jack_midi_port.h new file mode 100644 index 0000000000..e381120a99 --- /dev/null +++ b/libs/midi++2/midi++/jack_midi_port.h @@ -0,0 +1,103 @@ +/* + Copyright (C) 1998-2010 Paul Barton-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 __libmidi_port_h__ +#define __libmidi_port_h__ + +#include +#include + +#include + +#include "pbd/xml++.h" +#include "pbd/crossthread.h" +#include "pbd/signals.h" +#include "pbd/ringbuffer.h" + +#include "evoral/Event.hpp" +#include "evoral/EventRingBuffer.hpp" + +#include "midi++/types.h" +#include "midi++/parser.h" +#include "midi++/port.h" + +namespace MIDI { + +class Channel; +class PortRequest; + +class JackMIDIPort : public Port { + public: + JackMIDIPort (std::string const &, Port::Flags, jack_client_t *); + JackMIDIPort (const XMLNode&, jack_client_t *); + ~JackMIDIPort (); + + XMLNode& get_state () const; + void set_state (const XMLNode&); + + void cycle_start (pframes_t nframes); + void cycle_end (); + + void parse (framecnt_t timestamp); + int write (byte *msg, size_t msglen, timestamp_t timestamp); + int read (byte *buf, size_t bufsize); + void drain (int check_interval_usecs); + int selectable () const { return xthread.selectable(); } + + pframes_t nframes_this_cycle() const { return _nframes_this_cycle; } + + void reestablish (jack_client_t *); + void reconnect (); + + static void set_process_thread (pthread_t); + static pthread_t get_process_thread () { return _process_thread; } + static bool is_process_thread(); + + static PBD::Signal0 MakeConnections; + static PBD::Signal0 JackHalted; + +private: + bool _currently_in_cycle; + pframes_t _nframes_this_cycle; + jack_client_t* _jack_client; + jack_port_t* _jack_port; + timestamp_t _last_write_timestamp; + RingBuffer< Evoral::Event > output_fifo; + Evoral::EventRingBuffer input_fifo; + Glib::Mutex output_fifo_lock; + CrossThreadChannel xthread; + + int create_port (); + + /** Channel used to signal to the MidiControlUI that input has arrived */ + + std::string _connections; + PBD::ScopedConnection connect_connection; + PBD::ScopedConnection halt_connection; + void flush (void* jack_port_buffer); + void jack_halted (); + void make_connections (); + void init (std::string const &, Flags); + + static pthread_t _process_thread; + +}; + +} // namespace MIDI + +#endif // __libmidi_port_h__ diff --git a/libs/midi++2/midi++/manager.h b/libs/midi++2/midi++/manager.h index bab4a18dd2..c37ba392b4 100644 --- a/libs/midi++2/midi++/manager.h +++ b/libs/midi++2/midi++/manager.h @@ -88,12 +88,12 @@ class Manager { static Manager *theManager; MIDI::MachineControl* _mmc; - MIDI::Port* _mtc_input_port; - MIDI::Port* _mtc_output_port; - MIDI::Port* _midi_input_port; - MIDI::Port* _midi_output_port; - MIDI::Port* _midi_clock_input_port; - MIDI::Port* _midi_clock_output_port; + MIDI::Port* _mtc_input_port; + MIDI::Port* _mtc_output_port; + MIDI::Port* _midi_input_port; + MIDI::Port* _midi_output_port; + MIDI::Port* _midi_clock_input_port; + MIDI::Port* _midi_clock_output_port; SerializedRCUManager _ports; }; diff --git a/libs/midi++2/midi++/parser.h b/libs/midi++2/midi++/parser.h index ac452798cc..f5f343e952 100644 --- a/libs/midi++2/midi++/parser.h +++ b/libs/midi++2/midi++/parser.h @@ -29,7 +29,7 @@ namespace MIDI { -class PortBase; +class Port; class Parser; typedef PBD::Signal1 ZeroByteSignal; @@ -41,7 +41,7 @@ typedef PBD::Signal3 Signal; class Parser { public: - Parser (PortBase &p); + Parser (Port &p); ~Parser (); /* sets the time that will be reported for any MTC or MIDI Clock @@ -105,7 +105,7 @@ class Parser { const char *midi_event_type_name (MIDI::eventType); void trace (bool onoff, std::ostream *o, const std::string &prefix = ""); bool tracing() { return trace_stream != 0; } - PortBase &port() { return _port; } + Port &port() { return _port; } void set_offline (bool); bool offline() const { return _offline; } @@ -136,7 +136,7 @@ class Parser { void reset_mtc_state (); private: - PortBase&_port; + Port&_port; /* tracing */ std::ostream *trace_stream; diff --git a/libs/midi++2/midi++/port.h b/libs/midi++2/midi++/port.h index e7b532013e..a2315f7284 100644 --- a/libs/midi++2/midi++/port.h +++ b/libs/midi++2/midi++/port.h @@ -16,8 +16,8 @@ */ -#ifndef __libmidi_port_h__ -#define __libmidi_port_h__ +#ifndef __libmidi_port_base_h__ +#define __libmidi_port_base_h__ #include #include @@ -34,70 +34,127 @@ #include "midi++/types.h" #include "midi++/parser.h" -#include "midi++/port_base.h" namespace MIDI { class Channel; class PortRequest; -class Port : public PortBase { +class Port { public: - Port (std::string const &, PortBase::Flags, jack_client_t *); - Port (const XMLNode&, jack_client_t *); - ~Port (); + enum Flags { + IsInput = JackPortIsInput, + IsOutput = JackPortIsOutput, + }; + + Port (std::string const &, Flags); + Port (const XMLNode&); + virtual ~Port (); XMLNode& get_state () const; void set_state (const XMLNode&); - void cycle_start (pframes_t nframes); - void cycle_end (); + // FIXME: make Manager a friend of port so these can be hidden? - void parse (framecnt_t timestamp); - int write (byte *msg, size_t msglen, timestamp_t timestamp); - int read (byte *buf, size_t bufsize); - void drain (int check_interval_usecs); - int selectable () const { return xthread.selectable(); } + /* Only for use by MidiManager. Don't ever call this. */ + virtual void cycle_start (pframes_t nframes) {} + /* Only for use by MidiManager. Don't ever call this. */ + virtual void cycle_end () {} - pframes_t nframes_this_cycle() const { return _nframes_this_cycle; } + /** Write a message to port. + * @param msg Raw MIDI message to send + * @param msglen Size of @a msg + * @param timestamp Time stamp in frames of this message (relative to cycle start) + * @return number of bytes successfully written + */ + virtual int write (byte *msg, size_t msglen, timestamp_t timestamp) = 0; - void reestablish (jack_client_t *); - void reconnect (); + /** Read raw bytes from a port. + * @param buf memory to store read data in + * @param bufsize size of @a buf + * @return number of bytes successfully read, negative if error + */ + virtual int read (byte *buf, size_t bufsize) = 0; - static void set_process_thread (pthread_t); - static pthread_t get_process_thread () { return _process_thread; } - static bool is_process_thread(); + /** block until the output FIFO used by non-process threads + * is empty, checking every @a check_interval_usecs usecs + * for current status. Not to be called by a thread that + * executes any part of a JACK process callback (will + * simply return immediately in that situation). + */ + virtual void drain (int check_interval_usecs) {} - static PBD::Signal0 MakeConnections; - static PBD::Signal0 JackHalted; + /** Write a message to port. + * @return true on success. + * FIXME: describe semantics here + */ + int midimsg (byte *msg, size_t len, timestamp_t timestamp) { + return !(write (msg, len, timestamp) == (int) len); + } -private: - bool _currently_in_cycle; - pframes_t _nframes_this_cycle; - jack_client_t* _jack_client; - jack_port_t* _jack_port; - timestamp_t _last_write_timestamp; - RingBuffer< Evoral::Event > output_fifo; - Evoral::EventRingBuffer input_fifo; - Glib::Mutex output_fifo_lock; - CrossThreadChannel xthread; + virtual void parse (framecnt_t timestamp) = 0; - int create_port (); + bool clock (timestamp_t timestamp); - /** Channel used to signal to the MidiControlUI that input has arrived */ + /* select(2)/poll(2)-based I/O */ + + /** Get the file descriptor for port. + * @return File descriptor, or -1 if not selectable. + */ + virtual int selectable () const = 0; + + Channel *channel (channel_t chn) { + return _channel[chn&0x7F]; + } - std::string _connections; - PBD::ScopedConnection connect_connection; - PBD::ScopedConnection halt_connection; - void flush (void* jack_port_buffer); - void jack_halted (); - void make_connections (); + Parser* parser () { + return _parser; + } + + const char *name () const { return _tagname.c_str(); } + bool ok () const { return _ok; } + + virtual bool centrally_parsed() const; + void set_centrally_parsed (bool yn) { _centrally_parsed = yn; } + + bool receives_input () const { + return _flags == IsInput; + } + + bool sends_output () const { + return _flags == IsOutput; + } + + struct Descriptor { + std::string tag; + Flags flags; + + Descriptor (const XMLNode&); + XMLNode& get_state(); + }; + + static std::string state_node_name; + + protected: + bool _ok; + std::string _tagname; + Channel* _channel[16]; + Parser* _parser; + Flags _flags; + bool _centrally_parsed; + void init (std::string const &, Flags); - - static pthread_t _process_thread; - }; +struct PortSet { + PortSet (std::string str) : owner (str) { } + + std::string owner; + std::list ports; +}; + +std::ostream & operator << (std::ostream& os, const Port& port); + } // namespace MIDI -#endif // __libmidi_port_h__ +#endif // __libmidi_port_base_h__ diff --git a/libs/midi++2/midi++/port_base.h b/libs/midi++2/midi++/port_base.h deleted file mode 100644 index f1649d84ba..0000000000 --- a/libs/midi++2/midi++/port_base.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - Copyright (C) 1998-2010 Paul Barton-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 __libmidi_port_base_h__ -#define __libmidi_port_base_h__ - -#include -#include - -#include - -#include "pbd/xml++.h" -#include "pbd/crossthread.h" -#include "pbd/signals.h" -#include "pbd/ringbuffer.h" - -#include "evoral/Event.hpp" -#include "evoral/EventRingBuffer.hpp" - -#include "midi++/types.h" -#include "midi++/parser.h" - -namespace MIDI { - -class Channel; -class PortRequest; - -class PortBase { - public: - enum Flags { - IsInput = JackPortIsInput, - IsOutput = JackPortIsOutput, - }; - - PortBase (std::string const &, Flags); - PortBase (const XMLNode&); - virtual ~PortBase (); - - XMLNode& get_state () const; - void set_state (const XMLNode&); - - // FIXME: make Manager a friend of port so these can be hidden? - - /* Only for use by MidiManager. Don't ever call this. */ - virtual void cycle_start (pframes_t nframes) {} - /* Only for use by MidiManager. Don't ever call this. */ - virtual void cycle_end () {} - - /** Write a message to port. - * @param msg Raw MIDI message to send - * @param msglen Size of @a msg - * @param timestamp Time stamp in frames of this message (relative to cycle start) - * @return number of bytes successfully written - */ - virtual int write (byte *msg, size_t msglen, timestamp_t timestamp) = 0; - - /** Read raw bytes from a port. - * @param buf memory to store read data in - * @param bufsize size of @a buf - * @return number of bytes successfully read, negative if error - */ - virtual int read (byte *buf, size_t bufsize) = 0; - - /** block until the output FIFO used by non-process threads - * is empty, checking every @a check_interval_usecs usecs - * for current status. Not to be called by a thread that - * executes any part of a JACK process callback (will - * simply return immediately in that situation). - */ - virtual void drain (int check_interval_usecs) {} - - /** Write a message to port. - * @return true on success. - * FIXME: describe semantics here - */ - int midimsg (byte *msg, size_t len, timestamp_t timestamp) { - return !(write (msg, len, timestamp) == (int) len); - } - - bool clock (timestamp_t timestamp); - - /* select(2)/poll(2)-based I/O */ - - /** Get the file descriptor for port. - * @return File descriptor, or -1 if not selectable. - */ - virtual int selectable () const = 0; - - Channel *channel (channel_t chn) { - return _channel[chn&0x7F]; - } - - Parser* parser () { - return _parser; - } - - const char *name () const { return _tagname.c_str(); } - bool ok () const { return _ok; } - - virtual bool centrally_parsed() const; - void set_centrally_parsed (bool yn) { _centrally_parsed = yn; } - - bool receives_input () const { - return _flags == IsInput; - } - - bool sends_output () const { - return _flags == IsOutput; - } - - struct Descriptor { - std::string tag; - Flags flags; - - Descriptor (const XMLNode&); - XMLNode& get_state(); - }; - - static std::string state_node_name; - - protected: - bool _ok; - std::string _tagname; - Channel* _channel[16]; - Parser* _parser; - Flags _flags; - bool _centrally_parsed; - - void init (std::string const &, Flags); -}; - -struct PortSet { - PortSet (std::string str) : owner (str) { } - - std::string owner; - std::list ports; -}; - -std::ostream & operator << (std::ostream& os, const PortBase& port); - -} // namespace MIDI - -#endif // __libmidi_port_base_h__ diff --git a/libs/midi++2/mmc.cc b/libs/midi++2/mmc.cc index e02598def2..06eadb5b34 100644 --- a/libs/midi++2/mmc.cc +++ b/libs/midi++2/mmc.cc @@ -25,6 +25,7 @@ #include "pbd/error.h" #include "midi++/mmc.h" #include "midi++/port.h" +#include "midi++/jack_midi_port.h" #include "midi++/parser.h" #include "midi++/manager.h" @@ -202,8 +203,8 @@ MachineControl::MachineControl (Manager* m, jack_client_t* jack) _receive_device_id = 0x7f; _send_device_id = 0x7f; - _input_port = m->add_port (new Port ("MMC in", Port::IsInput, jack)); - _output_port = m->add_port (new Port ("MMC out", Port::IsOutput, jack)); + _input_port = m->add_port (new JackMIDIPort ("MMC in", Port::IsInput, jack)); + _output_port = m->add_port (new JackMIDIPort ("MMC out", Port::IsOutput, jack)); _input_port->parser()->mmc.connect_same_thread (port_connections, boost::bind (&MachineControl::process_mmc_message, this, _1, _2, _3)); _input_port->parser()->start.connect_same_thread (port_connections, boost::bind (&MachineControl::spp_start, this)); diff --git a/libs/midi++2/parser.cc b/libs/midi++2/parser.cc index a56e3f82e4..8e3af64504 100644 --- a/libs/midi++2/parser.cc +++ b/libs/midi++2/parser.cc @@ -30,7 +30,7 @@ #include "midi++/types.h" #include "midi++/parser.h" -#include "midi++/port_base.h" +#include "midi++/port.h" #include "midi++/mmc.h" #include "pbd/transmitter.h" @@ -104,7 +104,7 @@ Parser::midi_event_type_name (eventType t) } } -Parser::Parser (PortBase &p) +Parser::Parser (Port &p) : _port(p) { trace_stream = 0; diff --git a/libs/midi++2/port.cc b/libs/midi++2/port.cc index ef1704857d..3e7896631a 100644 --- a/libs/midi++2/port.cc +++ b/libs/midi++2/port.cc @@ -15,7 +15,7 @@ along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - $Id$ + $Id: port.cc 11871 2012-04-10 16:27:01Z paul $ */ #include #include @@ -40,325 +40,117 @@ using namespace MIDI; using namespace std; using namespace PBD; -pthread_t Port::_process_thread; -Signal0 Port::JackHalted; -Signal0 Port::MakeConnections; +string Port::state_node_name = "MIDI-port"; -Port::Port (string const & name, Flags flags, jack_client_t* jack_client) - : PortBase (name, flags) - , _currently_in_cycle (false) - , _nframes_this_cycle (0) - , _jack_client (jack_client) - , _jack_port (0) - , output_fifo (512) - , input_fifo (1024) - , xthread (true) +Port::Port (string const & name, Flags flags) + : _flags (flags) + , _centrally_parsed (true) { - assert (jack_client); init (name, flags); } -Port::Port (const XMLNode& node, jack_client_t* jack_client) - : PortBase (node) - , _currently_in_cycle (false) - , _nframes_this_cycle (0) - , _jack_client (jack_client) - , _jack_port (0) - , output_fifo (512) - , input_fifo (1024) - , xthread (true) +Port::Port (const XMLNode& node) + : _centrally_parsed (true) { - assert (jack_client); - Descriptor desc (node); + init (desc.tag, desc.flags); - set_state (node); + + /* derived class must call ::set_state() */ } void Port::init (string const & name, Flags flags) { - if (!create_port ()) { - _ok = true; + _ok = false; /* derived class must set to true if constructor + succeeds. + */ + + _parser = 0; + + _tagname = name; + _flags = flags; + + _parser = new Parser (*this); + + for (int i = 0; i < 16; i++) { + _channel[i] = new Channel (i, *this); + _channel[i]->connect_signals (); } - - MakeConnections.connect_same_thread (connect_connection, boost::bind (&Port::make_connections, this)); - JackHalted.connect_same_thread (halt_connection, boost::bind (&Port::jack_halted, this)); } - Port::~Port () { for (int i = 0; i < 16; i++) { delete _channel[i]; } - - if (_jack_port) { - if (_jack_client) { - jack_port_unregister (_jack_client, _jack_port); - _jack_port = 0; - } - } } -void -Port::parse (framecnt_t timestamp) +/** Send a clock tick message. + * \return true on success. + */ +bool +Port::clock (timestamp_t timestamp) { - byte buf[512]; - - /* NOTE: parsing is done (if at all) by initiating a read from - the port. Each port implementation calls on the parser - once it has data ready. - */ + static byte clockmsg = 0xf8; - _parser->set_timestamp (timestamp); - - while (1) { - - // cerr << "+++ READ ON " << name() << endl; - - int nread = read (buf, sizeof (buf)); - - // cerr << "-- READ (" << nread << " ON " << name() << endl; - - if (nread > 0) { - if ((size_t) nread < sizeof (buf)) { - break; - } else { - continue; - } - } else if (nread == 0) { - break; - } else if (errno == EAGAIN) { - break; - } else { - fatal << "Error reading from MIDI port " << name() << endmsg; - /*NOTREACHED*/ - } - } -} - -void -Port::cycle_start (pframes_t nframes) -{ - assert (_jack_port); - - _currently_in_cycle = true; - _nframes_this_cycle = nframes; - - assert(_nframes_this_cycle == nframes); - if (sends_output()) { - void *buffer = jack_port_get_buffer (_jack_port, nframes); - jack_midi_clear_buffer (buffer); - flush (buffer); + return midimsg (&clockmsg, 1, timestamp); } - if (receives_input()) { - void* jack_buffer = jack_port_get_buffer(_jack_port, nframes); - const pframes_t event_count = jack_midi_get_event_count(jack_buffer); - - jack_midi_event_t ev; - timestamp_t cycle_start_frame = jack_last_frame_time (_jack_client); - - for (pframes_t i = 0; i < event_count; ++i) { - jack_midi_event_get (&ev, jack_buffer, i); - input_fifo.write (cycle_start_frame + ev.time, (Evoral::EventType) 0, ev.size, ev.buffer); - } - - if (event_count) { - xthread.wakeup (); - } - } + return false; } -void -Port::cycle_end () +std::ostream & MIDI::operator << ( std::ostream & os, const MIDI::Port & port ) { - if (sends_output()) { - flush (jack_port_get_buffer (_jack_port, _nframes_this_cycle)); - } - - _currently_in_cycle = false; - _nframes_this_cycle = 0; + using namespace std; + os << "MIDI::Port { "; + os << "name: " << port.name(); + os << "; "; + os << "ok: " << port.ok(); + os << "; "; + os << " }"; + return os; } -void -Port::jack_halted () +Port::Descriptor::Descriptor (const XMLNode& node) { - _jack_client = 0; - _jack_port = 0; -} + const XMLProperty *prop; + bool have_tag = false; + bool have_mode = false; -void -Port::drain (int check_interval_usecs) -{ - RingBuffer< Evoral::Event >::rw_vector vec = { { 0, 0 }, { 0, 0} }; - - if (is_process_thread()) { - error << "Process thread called MIDI::Port::drain() - this cannot work" << endmsg; - return; + if ((prop = node.property ("tag")) != 0) { + tag = prop->value(); + have_tag = true; } - while (1) { - output_fifo.get_write_vector (&vec); - if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) { - break; - } - usleep (check_interval_usecs); - } -} + if ((prop = node.property ("mode")) != 0) { -int -Port::write(byte * msg, size_t msglen, timestamp_t timestamp) -{ - int ret = 0; - - if (!sends_output()) { - return ret; - } - - if (!is_process_thread()) { - - Glib::Mutex::Lock lm (output_fifo_lock); - RingBuffer< Evoral::Event >::rw_vector vec = { { 0, 0 }, { 0, 0} }; - - output_fifo.get_write_vector (&vec); - - if (vec.len[0] + vec.len[1] < 1) { - error << "no space in FIFO for non-process thread MIDI write" << endmsg; - return 0; + if (strings_equal_ignore_case (prop->value(), "output") || strings_equal_ignore_case (prop->value(), "out")) { + flags = IsOutput; + } else if (strings_equal_ignore_case (prop->value(), "input") || strings_equal_ignore_case (prop->value(), "in")) { + flags = IsInput; } - if (vec.len[0]) { - if (!vec.buf[0]->owns_buffer()) { - vec.buf[0]->set_buffer (0, 0, true); - } - vec.buf[0]->set (msg, msglen, timestamp); - } else { - if (!vec.buf[1]->owns_buffer()) { - vec.buf[1]->set_buffer (0, 0, true); - } - vec.buf[1]->set (msg, msglen, timestamp); - } - - output_fifo.increment_write_idx (1); - - ret = msglen; - - } else { - - // XXX This had to be temporarily commented out to make export work again - if (!(timestamp < _nframes_this_cycle)) { - std::cerr << "attempting to write MIDI event of " << msglen << " bytes at time " - << timestamp << " of " << _nframes_this_cycle - << " (this will not work - needs a code fix)" - << std::endl; - } - - if (_currently_in_cycle) { - if (timestamp == 0) { - timestamp = _last_write_timestamp; - } - - if (jack_midi_event_write (jack_port_get_buffer (_jack_port, _nframes_this_cycle), - timestamp, msg, msglen) == 0) { - ret = msglen; - _last_write_timestamp = timestamp; - - } else { - ret = 0; - cerr << "write of " << msglen << " failed, port holds " - << jack_midi_get_event_count (jack_port_get_buffer (_jack_port, _nframes_this_cycle)) - << endl; - } - } else { - cerr << "write to JACK midi port failed: not currently in a process cycle." << endl; - PBD::stacktrace (cerr, 20); - } + have_mode = true; } - if (ret > 0 && _parser) { - // ardour doesn't care about this and neither should your app, probably - // output_parser->raw_preparse (*output_parser, msg, ret); - for (int i = 0; i < ret; i++) { - _parser->scanner (msg[i]); - } - // ardour doesn't care about this and neither should your app, probably - // output_parser->raw_postparse (*output_parser, msg, ret); - } - - return ret; -} - -void -Port::flush (void* jack_port_buffer) -{ - RingBuffer< Evoral::Event >::rw_vector vec = { { 0, 0 }, { 0, 0 } }; - size_t written; - - output_fifo.get_read_vector (&vec); - - if (vec.len[0] + vec.len[1]) { - // cerr << "Flush " << vec.len[0] + vec.len[1] << " events from non-process FIFO\n"; + if (!have_tag || !have_mode) { + throw failed_constructor(); } - - if (vec.len[0]) { - Evoral::Event* evp = vec.buf[0]; - - for (size_t n = 0; n < vec.len[0]; ++n, ++evp) { - jack_midi_event_write (jack_port_buffer, - (timestamp_t) evp->time(), evp->buffer(), evp->size()); - } - } - - if (vec.len[1]) { - Evoral::Event* evp = vec.buf[1]; - - for (size_t n = 0; n < vec.len[1]; ++n, ++evp) { - jack_midi_event_write (jack_port_buffer, - (timestamp_t) evp->time(), evp->buffer(), evp->size()); - } - } - - if ((written = vec.len[0] + vec.len[1]) != 0) { - output_fifo.increment_read_idx (written); - } -} - -int -Port::read (byte *, size_t) -{ - if (!receives_input()) { - return 0; - } - - timestamp_t time; - Evoral::EventType type; - uint32_t size; - byte buffer[input_fifo.capacity()]; - - while (input_fifo.read (&time, &type, &size, buffer)) { - _parser->set_timestamp (time); - for (uint32_t i = 0; i < size; ++i) { - _parser->scanner (buffer[i]); - } - } - - return 0; -} - -int -Port::create_port () -{ - _jack_port = jack_port_register(_jack_client, _tagname.c_str(), JACK_DEFAULT_MIDI_TYPE, _flags, 0); - return _jack_port == 0 ? -1 : 0; } XMLNode& Port::get_state () const { - XMLNode& root = PortBase::get_state (); + XMLNode* root = new XMLNode (state_node_name); + root->add_property ("tag", _tagname); + + if (_flags == IsInput) { + root->add_property ("mode", "input"); + } else { + root->add_property ("mode", "output"); + } #if 0 byte device_inquiry[6]; @@ -373,30 +165,7 @@ Port::get_state () const write (device_inquiry, sizeof (device_inquiry), 0); #endif - if (_jack_port) { - - const char** jc = jack_port_get_connections (_jack_port); - string connection_string; - if (jc) { - for (int i = 0; jc[i]; ++i) { - if (i > 0) { - connection_string += ','; - } - connection_string += jc[i]; - } - free (jc); - } - - if (!connection_string.empty()) { - root.add_property ("connections", connection_string); - } - } else { - if (!_connections.empty()) { - root.add_property ("connections", _connections); - } - } - - return root; + return *root; } void @@ -407,60 +176,10 @@ Port::set_state (const XMLNode& node) if ((prop = node.property ("tag")) == 0 || prop->value() != _tagname) { return; } - - PortBase::set_state (node); - - if ((prop = node.property ("connections")) != 0) { - _connections = prop->value (); - } -} - -void -Port::make_connections () -{ - if (!_connections.empty()) { - vector ports; - split (_connections, ports, ','); - for (vector::iterator x = ports.begin(); x != ports.end(); ++x) { - if (_jack_client) { - if (receives_input()) { - jack_connect (_jack_client, (*x).c_str(), jack_port_name (_jack_port)); - } else { - jack_connect (_jack_client, jack_port_name (_jack_port), (*x).c_str()); - } - /* ignore failures */ - } - } - } - - connect_connection.disconnect (); -} - -void -Port::set_process_thread (pthread_t thr) -{ - _process_thread = thr; } bool -Port::is_process_thread() +Port::centrally_parsed() const { - return (pthread_self() == _process_thread); -} - -void -Port::reestablish (jack_client_t* jack) -{ - _jack_client = jack; - int const r = create_port (); - - if (r) { - PBD::error << "could not reregister ports for " << name() << endmsg; - } -} - -void -Port::reconnect () -{ - make_connections (); + return _centrally_parsed; } diff --git a/libs/midi++2/port_base.cc b/libs/midi++2/port_base.cc deleted file mode 100644 index 2910ace2a9..0000000000 --- a/libs/midi++2/port_base.cc +++ /dev/null @@ -1,185 +0,0 @@ -/* - Copyright (C) 1998 Paul Barton-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: port.cc 11871 2012-04-10 16:27:01Z paul $ -*/ -#include -#include -#include -#include - -#include -#include - -#include "pbd/xml++.h" -#include "pbd/error.h" -#include "pbd/failed_constructor.h" -#include "pbd/convert.h" -#include "pbd/strsplit.h" -#include "pbd/stacktrace.h" - -#include "midi++/types.h" -#include "midi++/port_base.h" -#include "midi++/channel.h" - -using namespace MIDI; -using namespace std; -using namespace PBD; - -string PortBase::state_node_name = "MIDI-port"; - -PortBase::PortBase (string const & name, Flags flags) - : _flags (flags) - , _centrally_parsed (true) -{ - init (name, flags); -} - -PortBase::PortBase (const XMLNode& node) - : _centrally_parsed (true) -{ - Descriptor desc (node); - - init (desc.tag, desc.flags); - - /* derived class must call ::set_state() */ -} - -void -PortBase::init (string const & name, Flags flags) -{ - _ok = false; /* derived class must set to true if constructor - succeeds. - */ - - _parser = 0; - - _tagname = name; - _flags = flags; - - _parser = new Parser (*this); - - for (int i = 0; i < 16; i++) { - _channel[i] = new Channel (i, *this); - _channel[i]->connect_signals (); - } -} - -PortBase::~PortBase () -{ - for (int i = 0; i < 16; i++) { - delete _channel[i]; - } -} - -/** Send a clock tick message. - * \return true on success. - */ -bool -PortBase::clock (timestamp_t timestamp) -{ - static byte clockmsg = 0xf8; - - if (sends_output()) { - return midimsg (&clockmsg, 1, timestamp); - } - - return false; -} - -std::ostream & MIDI::operator << ( std::ostream & os, const MIDI::PortBase & port ) -{ - using namespace std; - os << "MIDI::Port { "; - os << "name: " << port.name(); - os << "; "; - os << "ok: " << port.ok(); - os << "; "; - os << " }"; - return os; -} - -PortBase::Descriptor::Descriptor (const XMLNode& node) -{ - const XMLProperty *prop; - bool have_tag = false; - bool have_mode = false; - - if ((prop = node.property ("tag")) != 0) { - tag = prop->value(); - have_tag = true; - } - - if ((prop = node.property ("mode")) != 0) { - - if (strings_equal_ignore_case (prop->value(), "output") || strings_equal_ignore_case (prop->value(), "out")) { - flags = IsOutput; - } else if (strings_equal_ignore_case (prop->value(), "input") || strings_equal_ignore_case (prop->value(), "in")) { - flags = IsInput; - } - - have_mode = true; - } - - if (!have_tag || !have_mode) { - throw failed_constructor(); - } -} - -XMLNode& -PortBase::get_state () const -{ - XMLNode* root = new XMLNode (state_node_name); - root->add_property ("tag", _tagname); - - if (_flags == IsInput) { - root->add_property ("mode", "input"); - } else { - root->add_property ("mode", "output"); - } - -#if 0 - byte device_inquiry[6]; - - device_inquiry[0] = 0xf0; - device_inquiry[0] = 0x7e; - device_inquiry[0] = 0x7f; - device_inquiry[0] = 0x06; - device_inquiry[0] = 0x02; - device_inquiry[0] = 0xf7; - - write (device_inquiry, sizeof (device_inquiry), 0); -#endif - - return *root; -} - -void -PortBase::set_state (const XMLNode& node) -{ - const XMLProperty* prop; - - if ((prop = node.property ("tag")) == 0 || prop->value() != _tagname) { - return; - } -} - -bool -PortBase::centrally_parsed() const -{ - return _centrally_parsed; -} diff --git a/libs/midi++2/wscript b/libs/midi++2/wscript index eccefe695d..d4f71124aa 100644 --- a/libs/midi++2/wscript +++ b/libs/midi++2/wscript @@ -47,9 +47,10 @@ def build(bld): obj.source = ''' midi.cc channel.cc + ipmidi_port.cc + jack_midi_port.cc manager.cc parser.cc - port_base.cc port.cc midnam_patch.cc mmc.cc diff --git a/libs/pbd/base_ui.cc b/libs/pbd/base_ui.cc index d56e4a31a4..e3b34a6df2 100644 --- a/libs/pbd/base_ui.cc +++ b/libs/pbd/base_ui.cc @@ -74,7 +74,9 @@ BaseUI::main_thread () { set_event_loop_for_thread (this); thread_init (); + std::cerr << pthread_self() << ' ' << _name << " running event loop\n"; _main_loop->run (); + std::cerr << pthread_self() << ' ' << _name << " event loop finished\n"; } void @@ -104,7 +106,7 @@ BaseUI::quit () bool BaseUI::request_handler (Glib::IOCondition ioc) { - /* check the transport request pipe */ + /* check the request pipe */ if (ioc & ~IO_IN) { _main_loop->quit (); diff --git a/libs/pbd/crossthread.cc b/libs/pbd/crossthread.cc index 553c8d52f3..07a732954b 100644 --- a/libs/pbd/crossthread.cc +++ b/libs/pbd/crossthread.cc @@ -81,6 +81,7 @@ RefPtr CrossThreadChannel::ios () { if (!_ios) { + std::cerr << "New x-channel fd " << fds[0] << std::endl; _ios = new RefPtr (IOSource::create (fds[0], IOCondition(IO_IN|IO_PRI|IO_ERR|IO_HUP|IO_NVAL))); } return *_ios; diff --git a/libs/surfaces/mackie/device_info.cc b/libs/surfaces/mackie/device_info.cc index 4df56d3a7a..93800aa43b 100644 --- a/libs/surfaces/mackie/device_info.cc +++ b/libs/surfaces/mackie/device_info.cc @@ -50,6 +50,7 @@ DeviceInfo::DeviceInfo() , _has_jog_wheel (true) , _has_touch_sense_faders (true) , _uses_logic_control_buttons (false) + , _uses_ipmidi (false) , _name (X_("Mackie Control Universal Pro")) { mackie_control_buttons (); @@ -267,6 +268,12 @@ DeviceInfo::set_state (const XMLNode& node, int /* version */) } } + if ((child = node.child ("UsesIPMIDI")) != 0) { + if ((prop = child->property ("value")) != 0) { + _uses_ipmidi = string_is_affirmative (prop->value()); + } + } + if ((child = node.child ("LogicControlButtons")) != 0) { if ((prop = child->property ("value")) != 0) { _uses_logic_control_buttons = string_is_affirmative (prop->value()); @@ -369,6 +376,12 @@ DeviceInfo::has_timecode_display () const return _has_timecode_display; } +bool +DeviceInfo::uses_ipmidi () const +{ + return _uses_ipmidi; +} + bool DeviceInfo::has_global_controls () const { diff --git a/libs/surfaces/mackie/device_info.h b/libs/surfaces/mackie/device_info.h index b08616db45..4071fcffc7 100644 --- a/libs/surfaces/mackie/device_info.h +++ b/libs/surfaces/mackie/device_info.h @@ -67,6 +67,7 @@ class DeviceInfo bool has_global_controls() const; bool has_jog_wheel () const; bool has_touch_sense_faders() const; + bool uses_ipmidi() const; const std::string& name() const; static std::map device_info; @@ -86,6 +87,7 @@ class DeviceInfo bool _has_jog_wheel; bool _has_touch_sense_faders; bool _uses_logic_control_buttons; + bool _uses_ipmidi; std::string _name; std::map _global_buttons; diff --git a/libs/surfaces/mackie/mackie_control_protocol.cc b/libs/surfaces/mackie/mackie_control_protocol.cc index 2608ec4879..83c4399943 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.cc +++ b/libs/surfaces/mackie/mackie_control_protocol.cc @@ -114,7 +114,7 @@ MackieControlProtocol::MackieControlProtocol (Session& session) set_device (Config->get_mackie_device_name()); set_profile (Config->get_mackie_device_profile()); - + TrackSelectionChanged.connect (gui_connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::gui_track_selection_changed, this, _1), this); _instance = this; @@ -128,6 +128,10 @@ MackieControlProtocol::~MackieControlProtocol() _active = false; + /* stop event loop */ + + BaseUI::quit (); + try { close(); } @@ -584,21 +588,23 @@ MackieControlProtocol::create_surfaces () } stype = ext; - _input_bundle->add_channel ( - surface->port().input_port().name(), - ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (surface->port().input_port().name()) - ); - - _output_bundle->add_channel ( - surface->port().output_port().name(), - ARDOUR::DataType::MIDI, - session->engine().make_port_name_non_relative (surface->port().output_port().name()) - ); + if (!_device_info.uses_ipmidi()) { + _input_bundle->add_channel ( + surface->port().input_port().name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (surface->port().input_port().name()) + ); + + _output_bundle->add_channel ( + surface->port().output_port().name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (surface->port().output_port().name()) + ); + } int fd; MIDI::Port& input_port (surface->port().input_port()); - + if ((fd = input_port.selectable ()) >= 0) { Glib::RefPtr psrc = IOSource::create (fd, IO_IN|IO_HUP|IO_ERR); @@ -1083,8 +1089,6 @@ MackieControlProtocol::midi_input_handler (IOCondition ioc, MIDI::Port* port) if (ioc & IO_IN) { - CrossThreadChannel::drain (port->selectable()); - DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", port->name())); framepos_t now = session->engine().frame_time(); port->parse (now); diff --git a/libs/surfaces/mackie/surface.cc b/libs/surfaces/mackie/surface.cc index 5c6eb6d559..e08ba24a9e 100644 --- a/libs/surfaces/mackie/surface.cc +++ b/libs/surfaces/mackie/surface.cc @@ -109,8 +109,7 @@ Surface::~Surface () } delete _jog_wheel; - - /* don't delete the port, because we want its output to remain queued */ + delete _port; } const MidiByteArray& diff --git a/libs/surfaces/mackie/surface_port.cc b/libs/surfaces/mackie/surface_port.cc index be080fa44b..645da67b43 100644 --- a/libs/surfaces/mackie/surface_port.cc +++ b/libs/surfaces/mackie/surface_port.cc @@ -25,6 +25,8 @@ #include "midi++/types.h" #include "midi++/port.h" +#include "midi++/jack_midi_port.h" +#include "midi++/ipmidi_port.h" #include "midi++/manager.h" #include "ardour/debug.h" @@ -51,38 +53,49 @@ using namespace PBD; SurfacePort::SurfacePort (Surface& s) : _surface (&s) { - jack_client_t* jack = MackieControlProtocol::instance()->get_session().engine().jack(); - - _input_port = new MIDI::Port (string_compose (_("%1 in"), _surface->name()), MIDI::Port::IsInput, jack); - _output_port =new MIDI::Port (string_compose (_("%1 out"), _surface->name()), MIDI::Port::IsOutput, jack); - - /* MackieControl has its own thread for handling input from the input - * port, and we don't want anything handling output from the output - * port. This stops the Generic MIDI UI event loop in ardour from - * attempting to handle these ports. - */ - - _input_port->set_centrally_parsed (false); - _output_port->set_centrally_parsed (false); - - MIDI::Manager * mm = MIDI::Manager::instance(); - - mm->add_port (_input_port); - mm->add_port (_output_port); + if (_surface->mcp().device_info().uses_ipmidi()) { + _input_port = new MIDI::IPMIDIPort (MIDI::IPMIDIPort::lowest_ipmidi_port_default+_surface->number()); + _output_port = _input_port; + } else { + jack_client_t* jack = MackieControlProtocol::instance()->get_session().engine().jack(); + + _input_port = new MIDI::JackMIDIPort (string_compose (_("%1 in"), _surface->name()), MIDI::Port::IsInput, jack); + _output_port =new MIDI::JackMIDIPort (string_compose (_("%1 out"), _surface->name()), MIDI::Port::IsOutput, jack); + + /* MackieControl has its own thread for handling input from the input + * port, and we don't want anything handling output from the output + * port. This stops the Generic MIDI UI event loop in ardour from + * attempting to handle these ports. + */ + + _input_port->set_centrally_parsed (false); + _output_port->set_centrally_parsed (false); + + MIDI::Manager * mm = MIDI::Manager::instance(); + + mm->add_port (_input_port); + mm->add_port (_output_port); + } } SurfacePort::~SurfacePort() { - MIDI::Manager* mm = MIDI::Manager::instance (); - - if (_input_port) { - mm->remove_port (_input_port); + if (_surface->mcp().device_info().uses_ipmidi()) { delete _input_port; - } + } else { - if (_output_port) { - mm->remove_port (_output_port); - delete _output_port; + MIDI::Manager* mm = MIDI::Manager::instance (); + + if (_input_port) { + mm->remove_port (_input_port); + delete _input_port; + } + + if (_output_port) { + _output_port->drain (10000); + mm->remove_port (_output_port); + delete _output_port; + } } } diff --git a/libs/surfaces/mackie/surface_port.h b/libs/surfaces/mackie/surface_port.h index 1a65d8dbcf..70b1df4580 100644 --- a/libs/surfaces/mackie/surface_port.h +++ b/libs/surfaces/mackie/surface_port.h @@ -59,8 +59,8 @@ protected: private: Mackie::Surface* _surface; - MIDI::Port* _input_port; - MIDI::Port* _output_port; + MIDI::Port* _input_port; + MIDI::Port* _output_port; }; std::ostream& operator << (std::ostream& , const SurfacePort& port); diff --git a/mcp/nucleus.device b/mcp/nucleus.device index ce9a546d6b..1456d4e8a7 100644 --- a/mcp/nucleus.device +++ b/mcp/nucleus.device @@ -11,4 +11,5 @@ +