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:
parent
10d37fecc1
commit
33140f3267
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
using namespace MIDI;
|
||||
|
||||
Channel::Channel (byte channelnum, PortBase &p)
|
||||
Channel::Channel (byte channelnum, Port &p)
|
||||
: _port (p)
|
||||
{
|
||||
_channel_number = channelnum;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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 ();
|
||||
}
|
|
@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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__
|
|
@ -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__
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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__
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 ();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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&
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -11,4 +11,5 @@
|
|||
<JogWheel value="yes"/>
|
||||
<TouchSenseFaders value="yes"/>
|
||||
<LogicControlButtons value="yes"/>
|
||||
<UsesIPMIDI value="yes"/>
|
||||
</MackieProtocolDevice>
|
||||
|
|
Loading…
Reference in New Issue