add support for IP MIDI (multicast MIDI over IP UDP sockets) to ardour and use it if requested inside MCP code. required renaming the pre-existing MIDI::Port as MIDI:JackMIDIPort - MIDI::Port becomes the base type for both JackMIDIPort and IPMIDIPort

git-svn-id: svn://localhost/ardour2/branches/3.0@12069 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis 2012-04-24 02:28:51 +00:00
parent 10d37fecc1
commit 33140f3267
29 changed files with 1235 additions and 818 deletions

View File

@ -34,6 +34,7 @@
#include <jack/weakjack.h>
#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;

View File

@ -143,6 +143,8 @@ MidiControlUI::reset_ports ()
if ((fd = (*i)->selectable ()) >= 0) {
Glib::RefPtr<IOSource> 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());

View File

@ -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 */

View File

@ -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::JackMIDIPort*> (_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;
}

View File

@ -25,7 +25,7 @@
using namespace MIDI;
Channel::Channel (byte channelnum, PortBase &p)
Channel::Channel (byte channelnum, Port &p)
: _port (p)
{
_channel_number = channelnum;

290
libs/midi++2/ipmidi_port.cc Normal file
View File

@ -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 <iostream>
#include <cstdio>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#if defined(WIN32)
static WSADATA g_wsaData;
typedef int socklen_t;
#else
#include <unistd.h>
#include <sys/ioctl.h>
inline void closesocket(int s) { ::close(s); }
#endif
#include <jack/jack.h>
#include <jack/midiport.h>
#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");
}
}

View File

@ -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 <iostream>
#include <cstdio>
#include <fcntl.h>
#include <errno.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#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<void> JackMIDIPort::JackHalted;
Signal0<void> 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<double> >::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<double> >::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<double> >::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<double>* 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<double>* 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<string> ports;
split (_connections, ports, ',');
for (vector<string>::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 ();
}

View File

@ -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<PortList> pr = _ports.reader ();
for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) {
(*p)->reestablish (jack);
JackMIDIPort* pp = dynamic_cast<JackMIDIPort*> (*p);
if (pp) {
pp->reestablish (jack);
}
}
}
@ -128,7 +132,10 @@ Manager::reconnect ()
boost::shared_ptr<PortList> pr = _ports.reader ();
for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) {
(*p)->reconnect ();
JackMIDIPort* pp = dynamic_cast<JackMIDIPort*> (*p);
if (pp) {
pp->reconnect ();
}
}
}

View File

@ -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;

View File

@ -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 <string>
#include <iostream>
#if defined(WIN32)
#include <winsock.h>
#else
#include <arpa/inet.h>
#include <net/if.h>
#endif
#include <jack/types.h>
#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__

View File

@ -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 <string>
#include <iostream>
#include <jack/types.h>
#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<void> MakeConnections;
static PBD::Signal0<void> 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<double> > output_fifo;
Evoral::EventRingBuffer<timestamp_t> 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__

View File

@ -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<PortList> _ports;
};

View File

@ -29,7 +29,7 @@
namespace MIDI {
class PortBase;
class Port;
class Parser;
typedef PBD::Signal1<void,Parser&> ZeroByteSignal;
@ -41,7 +41,7 @@ typedef PBD::Signal3<void,Parser &, byte *, size_t> 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;

View File

@ -16,8 +16,8 @@
*/
#ifndef __libmidi_port_h__
#define __libmidi_port_h__
#ifndef __libmidi_port_base_h__
#define __libmidi_port_base_h__
#include <string>
#include <iostream>
@ -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<void> MakeConnections;
static PBD::Signal0<void> 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<double> > output_fifo;
Evoral::EventRingBuffer<timestamp_t> 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<XMLNode> ports;
};
std::ostream & operator << (std::ostream& os, const Port& port);
} // namespace MIDI
#endif // __libmidi_port_h__
#endif // __libmidi_port_base_h__

View File

@ -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 <string>
#include <iostream>
#include <jack/types.h>
#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<XMLNode> ports;
};
std::ostream & operator << (std::ostream& os, const PortBase& port);
} // namespace MIDI
#endif // __libmidi_port_base_h__

View File

@ -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));

View File

@ -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;

View File

@ -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 <iostream>
#include <cstdio>
@ -40,325 +40,117 @@ using namespace MIDI;
using namespace std;
using namespace PBD;
pthread_t Port::_process_thread;
Signal0<void> Port::JackHalted;
Signal0<void> 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<double> >::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<double> >::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<double> >::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<double>* 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<double>* 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<string> ports;
split (_connections, ports, ',');
for (vector<string>::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;
}

View File

@ -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 <iostream>
#include <cstdio>
#include <fcntl.h>
#include <errno.h>
#include <jack/jack.h>
#include <jack/midiport.h>
#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;
}

View File

@ -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

View File

@ -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 ();

View File

@ -81,6 +81,7 @@ RefPtr<IOSource>
CrossThreadChannel::ios ()
{
if (!_ios) {
std::cerr << "New x-channel fd " << fds[0] << std::endl;
_ios = new RefPtr<IOSource> (IOSource::create (fds[0], IOCondition(IO_IN|IO_PRI|IO_ERR|IO_HUP|IO_NVAL)));
}
return *_ios;

View File

@ -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
{

View File

@ -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<std::string,DeviceInfo> 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<Button::ID,GlobalButtonInfo> _global_buttons;

View File

@ -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<IOSource> 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);

View File

@ -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&

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -11,4 +11,5 @@
<JogWheel value="yes"/>
<TouchSenseFaders value="yes"/>
<LogicControlButtons value="yes"/>
<UsesIPMIDI value="yes"/>
</MackieProtocolDevice>