mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-09 08:14:58 +01:00
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
29 changed files with 1235 additions and 818 deletions
|
|
@ -34,6 +34,7 @@
|
||||||
#include <jack/weakjack.h>
|
#include <jack/weakjack.h>
|
||||||
|
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/jack_midi_port.h"
|
||||||
#include "midi++/mmc.h"
|
#include "midi++/mmc.h"
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
|
|
||||||
|
|
@ -133,7 +134,7 @@ _thread_init_callback (void * /*arg*/)
|
||||||
|
|
||||||
SessionEvent::create_per_thread_pool (X_("Audioengine"), 512);
|
SessionEvent::create_per_thread_pool (X_("Audioengine"), 512);
|
||||||
|
|
||||||
MIDI::Port::set_process_thread (pthread_self());
|
MIDI::JackMIDIPort::set_process_thread (pthread_self());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
@ -233,7 +234,7 @@ AudioEngine::stop (bool forever)
|
||||||
} else {
|
} else {
|
||||||
jack_deactivate (_priv_jack);
|
jack_deactivate (_priv_jack);
|
||||||
Stopped(); /* EMIT SIGNAL */
|
Stopped(); /* EMIT SIGNAL */
|
||||||
MIDI::Port::JackHalted (); /* EMIT SIGNAL */
|
MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1106,7 +1107,7 @@ AudioEngine::halted (void *arg)
|
||||||
|
|
||||||
if (was_running) {
|
if (was_running) {
|
||||||
ae->Halted(""); /* EMIT SIGNAL */
|
ae->Halted(""); /* EMIT SIGNAL */
|
||||||
MIDI::Port::JackHalted (); /* EMIT SIGNAL */
|
MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1356,7 +1357,7 @@ AudioEngine::disconnect_from_jack ()
|
||||||
if (_running) {
|
if (_running) {
|
||||||
_running = false;
|
_running = false;
|
||||||
Stopped(); /* EMIT SIGNAL */
|
Stopped(); /* EMIT SIGNAL */
|
||||||
MIDI::Port::JackHalted (); /* EMIT SIGNAL */
|
MIDI::JackMIDIPort::JackHalted (); /* EMIT SIGNAL */
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,8 @@ MidiControlUI::reset_ports ()
|
||||||
if ((fd = (*i)->selectable ()) >= 0) {
|
if ((fd = (*i)->selectable ()) >= 0) {
|
||||||
Glib::RefPtr<IOSource> psrc = IOSource::create (fd, IO_IN|IO_HUP|IO_ERR);
|
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->connect (sigc::bind (sigc::mem_fun (this, &MidiControlUI::midi_input_handler), *i));
|
||||||
psrc->attach (_main_loop->get_context());
|
psrc->attach (_main_loop->get_context());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,7 @@
|
||||||
#include "ardour/operations.h"
|
#include "ardour/operations.h"
|
||||||
|
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/jack_midi_port.h"
|
||||||
#include "midi++/mmc.h"
|
#include "midi++/mmc.h"
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
|
|
||||||
|
|
@ -837,7 +838,7 @@ Session::hookup_io ()
|
||||||
/* Tell all IO objects to connect themselves together */
|
/* Tell all IO objects to connect themselves together */
|
||||||
|
|
||||||
IO::enable_connecting ();
|
IO::enable_connecting ();
|
||||||
MIDI::Port::MakeConnections ();
|
MIDI::JackMIDIPort::MakeConnections ();
|
||||||
|
|
||||||
/* Now reset all panners */
|
/* Now reset all panners */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include "pbd/stacktrace.h"
|
#include "pbd/stacktrace.h"
|
||||||
|
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/jack_midi_port.h"
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
|
|
||||||
#include "evoral/midi_events.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);
|
double next_tick = _last_tick + one_ppqn_in_frames (transport_frame);
|
||||||
frameoffset_t next_tick_offset = llrint (next_tick) - 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,
|
DEBUG_TRACE (PBD::DEBUG::MidiClock,
|
||||||
string_compose ("Transport: %1, last tick time: %2, next tick time: %3, offset: %4, cycle length: %5\n",
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
using namespace MIDI;
|
using namespace MIDI;
|
||||||
|
|
||||||
Channel::Channel (byte channelnum, PortBase &p)
|
Channel::Channel (byte channelnum, Port &p)
|
||||||
: _port (p)
|
: _port (p)
|
||||||
{
|
{
|
||||||
_channel_number = channelnum;
|
_channel_number = channelnum;
|
||||||
|
|
|
||||||
290
libs/midi++2/ipmidi_port.cc
Normal file
290
libs/midi++2/ipmidi_port.cc
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
466
libs/midi++2/jack_midi_port.cc
Normal file
466
libs/midi++2/jack_midi_port.cc
Normal 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 ();
|
||||||
|
}
|
||||||
|
|
@ -27,6 +27,7 @@
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
#include "midi++/channel.h"
|
#include "midi++/channel.h"
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/jack_midi_port.h"
|
||||||
#include "midi++/mmc.h"
|
#include "midi++/mmc.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
@ -40,12 +41,12 @@ Manager::Manager (jack_client_t* jack)
|
||||||
{
|
{
|
||||||
_mmc = new MachineControl (this, jack);
|
_mmc = new MachineControl (this, jack);
|
||||||
|
|
||||||
_mtc_input_port = add_port (new MIDI::Port ("MTC in", Port::IsInput, jack));
|
_mtc_input_port = add_port (new MIDI::JackMIDIPort ("MTC in", Port::IsInput, jack));
|
||||||
_mtc_output_port = add_port (new MIDI::Port ("MTC out", Port::IsOutput, jack));
|
_mtc_output_port = add_port (new MIDI::JackMIDIPort ("MTC out", Port::IsOutput, jack));
|
||||||
_midi_input_port = add_port (new MIDI::Port ("MIDI control in", Port::IsInput, jack));
|
_midi_input_port = add_port (new MIDI::JackMIDIPort ("MIDI control in", Port::IsInput, jack));
|
||||||
_midi_output_port = add_port (new MIDI::Port ("MIDI control out", Port::IsOutput, jack));
|
_midi_output_port = add_port (new MIDI::JackMIDIPort ("MIDI control out", Port::IsOutput, jack));
|
||||||
_midi_clock_input_port = add_port (new MIDI::Port ("MIDI clock in", Port::IsInput, jack));
|
_midi_clock_input_port = add_port (new MIDI::JackMIDIPort ("MIDI clock in", Port::IsInput, jack));
|
||||||
_midi_clock_output_port = add_port (new MIDI::Port ("MIDI clock out", Port::IsOutput, jack));
|
_midi_clock_output_port = add_port (new MIDI::JackMIDIPort ("MIDI clock out", Port::IsOutput, jack));
|
||||||
}
|
}
|
||||||
|
|
||||||
Manager::~Manager ()
|
Manager::~Manager ()
|
||||||
|
|
@ -117,7 +118,10 @@ Manager::reestablish (jack_client_t* jack)
|
||||||
boost::shared_ptr<PortList> pr = _ports.reader ();
|
boost::shared_ptr<PortList> pr = _ports.reader ();
|
||||||
|
|
||||||
for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) {
|
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 ();
|
boost::shared_ptr<PortList> pr = _ports.reader ();
|
||||||
|
|
||||||
for (PortList::const_iterator p = pr->begin(); p != pr->end(); ++p) {
|
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 {
|
class Channel : public PBD::ScopedConnectionList {
|
||||||
|
|
||||||
public:
|
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 channel() { return _channel_number; }
|
||||||
byte program() { return _program_number; }
|
byte program() { return _program_number; }
|
||||||
byte bank() { return _bank_number; }
|
byte bank() { return _bank_number; }
|
||||||
|
|
@ -111,11 +111,11 @@ class Channel : public PBD::ScopedConnectionList {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class PortBase;
|
friend class Port;
|
||||||
void connect_signals ();
|
void connect_signals ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PortBase& _port;
|
Port& _port;
|
||||||
|
|
||||||
/* Current channel values */
|
/* Current channel values */
|
||||||
byte _channel_number;
|
byte _channel_number;
|
||||||
|
|
|
||||||
76
libs/midi++2/midi++/ipmidi_port.h
Normal file
76
libs/midi++2/midi++/ipmidi_port.h
Normal 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__
|
||||||
103
libs/midi++2/midi++/jack_midi_port.h
Normal file
103
libs/midi++2/midi++/jack_midi_port.h
Normal 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__
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
namespace MIDI {
|
namespace MIDI {
|
||||||
|
|
||||||
class PortBase;
|
class Port;
|
||||||
class Parser;
|
class Parser;
|
||||||
|
|
||||||
typedef PBD::Signal1<void,Parser&> ZeroByteSignal;
|
typedef PBD::Signal1<void,Parser&> ZeroByteSignal;
|
||||||
|
|
@ -41,7 +41,7 @@ typedef PBD::Signal3<void,Parser &, byte *, size_t> Signal;
|
||||||
|
|
||||||
class Parser {
|
class Parser {
|
||||||
public:
|
public:
|
||||||
Parser (PortBase &p);
|
Parser (Port &p);
|
||||||
~Parser ();
|
~Parser ();
|
||||||
|
|
||||||
/* sets the time that will be reported for any MTC or MIDI Clock
|
/* 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);
|
const char *midi_event_type_name (MIDI::eventType);
|
||||||
void trace (bool onoff, std::ostream *o, const std::string &prefix = "");
|
void trace (bool onoff, std::ostream *o, const std::string &prefix = "");
|
||||||
bool tracing() { return trace_stream != 0; }
|
bool tracing() { return trace_stream != 0; }
|
||||||
PortBase &port() { return _port; }
|
Port &port() { return _port; }
|
||||||
|
|
||||||
void set_offline (bool);
|
void set_offline (bool);
|
||||||
bool offline() const { return _offline; }
|
bool offline() const { return _offline; }
|
||||||
|
|
@ -136,7 +136,7 @@ class Parser {
|
||||||
void reset_mtc_state ();
|
void reset_mtc_state ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PortBase&_port;
|
Port&_port;
|
||||||
/* tracing */
|
/* tracing */
|
||||||
|
|
||||||
std::ostream *trace_stream;
|
std::ostream *trace_stream;
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef __libmidi_port_h__
|
#ifndef __libmidi_port_base_h__
|
||||||
#define __libmidi_port_h__
|
#define __libmidi_port_base_h__
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
@ -34,70 +34,127 @@
|
||||||
|
|
||||||
#include "midi++/types.h"
|
#include "midi++/types.h"
|
||||||
#include "midi++/parser.h"
|
#include "midi++/parser.h"
|
||||||
#include "midi++/port_base.h"
|
|
||||||
|
|
||||||
namespace MIDI {
|
namespace MIDI {
|
||||||
|
|
||||||
class Channel;
|
class Channel;
|
||||||
class PortRequest;
|
class PortRequest;
|
||||||
|
|
||||||
class Port : public PortBase {
|
class Port {
|
||||||
public:
|
public:
|
||||||
Port (std::string const &, PortBase::Flags, jack_client_t *);
|
enum Flags {
|
||||||
Port (const XMLNode&, jack_client_t *);
|
IsInput = JackPortIsInput,
|
||||||
~Port ();
|
IsOutput = JackPortIsOutput,
|
||||||
|
};
|
||||||
|
|
||||||
|
Port (std::string const &, Flags);
|
||||||
|
Port (const XMLNode&);
|
||||||
|
virtual ~Port ();
|
||||||
|
|
||||||
XMLNode& get_state () const;
|
XMLNode& get_state () const;
|
||||||
void set_state (const XMLNode&);
|
void set_state (const XMLNode&);
|
||||||
|
|
||||||
void cycle_start (pframes_t nframes);
|
// FIXME: make Manager a friend of port so these can be hidden?
|
||||||
void cycle_end ();
|
|
||||||
|
|
||||||
void parse (framecnt_t timestamp);
|
/* Only for use by MidiManager. Don't ever call this. */
|
||||||
int write (byte *msg, size_t msglen, timestamp_t timestamp);
|
virtual void cycle_start (pframes_t nframes) {}
|
||||||
int read (byte *buf, size_t bufsize);
|
/* Only for use by MidiManager. Don't ever call this. */
|
||||||
void drain (int check_interval_usecs);
|
virtual void cycle_end () {}
|
||||||
int selectable () const { return xthread.selectable(); }
|
|
||||||
|
|
||||||
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 *);
|
/** Read raw bytes from a port.
|
||||||
void reconnect ();
|
* @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);
|
/** block until the output FIFO used by non-process threads
|
||||||
static pthread_t get_process_thread () { return _process_thread; }
|
* is empty, checking every @a check_interval_usecs usecs
|
||||||
static bool is_process_thread();
|
* 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;
|
/** Write a message to port.
|
||||||
static PBD::Signal0<void> JackHalted;
|
* @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:
|
virtual void parse (framecnt_t timestamp) = 0;
|
||||||
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 ();
|
bool clock (timestamp_t timestamp);
|
||||||
|
|
||||||
/** Channel used to signal to the MidiControlUI that input has arrived */
|
/* select(2)/poll(2)-based I/O */
|
||||||
|
|
||||||
std::string _connections;
|
/** Get the file descriptor for port.
|
||||||
PBD::ScopedConnection connect_connection;
|
* @return File descriptor, or -1 if not selectable.
|
||||||
PBD::ScopedConnection halt_connection;
|
*/
|
||||||
void flush (void* jack_port_buffer);
|
virtual int selectable () const = 0;
|
||||||
void jack_halted ();
|
|
||||||
void make_connections ();
|
|
||||||
void init (std::string const &, Flags);
|
|
||||||
|
|
||||||
static pthread_t _process_thread;
|
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 Port& port);
|
||||||
|
|
||||||
} // namespace MIDI
|
} // 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 "pbd/error.h"
|
||||||
#include "midi++/mmc.h"
|
#include "midi++/mmc.h"
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/jack_midi_port.h"
|
||||||
#include "midi++/parser.h"
|
#include "midi++/parser.h"
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
|
|
||||||
|
|
@ -202,8 +203,8 @@ MachineControl::MachineControl (Manager* m, jack_client_t* jack)
|
||||||
_receive_device_id = 0x7f;
|
_receive_device_id = 0x7f;
|
||||||
_send_device_id = 0x7f;
|
_send_device_id = 0x7f;
|
||||||
|
|
||||||
_input_port = m->add_port (new Port ("MMC in", Port::IsInput, jack));
|
_input_port = m->add_port (new JackMIDIPort ("MMC in", Port::IsInput, jack));
|
||||||
_output_port = m->add_port (new Port ("MMC out", Port::IsOutput, 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()->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));
|
_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++/types.h"
|
||||||
#include "midi++/parser.h"
|
#include "midi++/parser.h"
|
||||||
#include "midi++/port_base.h"
|
#include "midi++/port.h"
|
||||||
#include "midi++/mmc.h"
|
#include "midi++/mmc.h"
|
||||||
#include "pbd/transmitter.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)
|
: _port(p)
|
||||||
{
|
{
|
||||||
trace_stream = 0;
|
trace_stream = 0;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
along with this program; if not, write to the Free Software
|
along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
|
||||||
$Id$
|
$Id: port.cc 11871 2012-04-10 16:27:01Z paul $
|
||||||
*/
|
*/
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
|
@ -40,325 +40,117 @@ using namespace MIDI;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
|
|
||||||
pthread_t Port::_process_thread;
|
string Port::state_node_name = "MIDI-port";
|
||||||
Signal0<void> Port::JackHalted;
|
|
||||||
Signal0<void> Port::MakeConnections;
|
|
||||||
|
|
||||||
Port::Port (string const & name, Flags flags, jack_client_t* jack_client)
|
Port::Port (string const & name, Flags flags)
|
||||||
: PortBase (name, flags)
|
: _flags (flags)
|
||||||
, _currently_in_cycle (false)
|
, _centrally_parsed (true)
|
||||||
, _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);
|
init (name, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
Port::Port (const XMLNode& node, jack_client_t* jack_client)
|
Port::Port (const XMLNode& node)
|
||||||
: PortBase (node)
|
: _centrally_parsed (true)
|
||||||
, _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);
|
Descriptor desc (node);
|
||||||
|
|
||||||
init (desc.tag, desc.flags);
|
init (desc.tag, desc.flags);
|
||||||
set_state (node);
|
|
||||||
|
/* derived class must call ::set_state() */
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Port::init (string const & name, Flags flags)
|
Port::init (string const & name, Flags flags)
|
||||||
{
|
{
|
||||||
if (!create_port ()) {
|
_ok = false; /* derived class must set to true if constructor
|
||||||
_ok = true;
|
succeeds.
|
||||||
}
|
*/
|
||||||
|
|
||||||
MakeConnections.connect_same_thread (connect_connection, boost::bind (&Port::make_connections, this));
|
_parser = 0;
|
||||||
JackHalted.connect_same_thread (halt_connection, boost::bind (&Port::jack_halted, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
_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 ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Port::~Port ()
|
Port::~Port ()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
delete _channel[i];
|
delete _channel[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_jack_port) {
|
|
||||||
if (_jack_client) {
|
|
||||||
jack_port_unregister (_jack_client, _jack_port);
|
|
||||||
_jack_port = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
/** Send a clock tick message.
|
||||||
Port::parse (framecnt_t timestamp)
|
* \return true on success.
|
||||||
{
|
|
||||||
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.
|
|
||||||
*/
|
*/
|
||||||
|
bool
|
||||||
_parser->set_timestamp (timestamp);
|
Port::clock (timestamp_t 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);
|
static byte clockmsg = 0xf8;
|
||||||
|
|
||||||
_currently_in_cycle = true;
|
|
||||||
_nframes_this_cycle = nframes;
|
|
||||||
|
|
||||||
assert(_nframes_this_cycle == nframes);
|
|
||||||
|
|
||||||
if (sends_output()) {
|
if (sends_output()) {
|
||||||
void *buffer = jack_port_get_buffer (_jack_port, nframes);
|
return midimsg (&clockmsg, 1, timestamp);
|
||||||
jack_midi_clear_buffer (buffer);
|
|
||||||
flush (buffer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receives_input()) {
|
return false;
|
||||||
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) {
|
std::ostream & MIDI::operator << ( std::ostream & os, const MIDI::Port & port )
|
||||||
xthread.wakeup ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Port::cycle_end ()
|
|
||||||
{
|
{
|
||||||
if (sends_output()) {
|
using namespace std;
|
||||||
flush (jack_port_get_buffer (_jack_port, _nframes_this_cycle));
|
os << "MIDI::Port { ";
|
||||||
|
os << "name: " << port.name();
|
||||||
|
os << "; ";
|
||||||
|
os << "ok: " << port.ok();
|
||||||
|
os << "; ";
|
||||||
|
os << " }";
|
||||||
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currently_in_cycle = false;
|
Port::Descriptor::Descriptor (const XMLNode& node)
|
||||||
_nframes_this_cycle = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Port::jack_halted ()
|
|
||||||
{
|
{
|
||||||
_jack_client = 0;
|
const XMLProperty *prop;
|
||||||
_jack_port = 0;
|
bool have_tag = false;
|
||||||
|
bool have_mode = false;
|
||||||
|
|
||||||
|
if ((prop = node.property ("tag")) != 0) {
|
||||||
|
tag = prop->value();
|
||||||
|
have_tag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
if ((prop = node.property ("mode")) != 0) {
|
||||||
Port::drain (int check_interval_usecs)
|
|
||||||
{
|
|
||||||
RingBuffer< Evoral::Event<double> >::rw_vector vec = { { 0, 0 }, { 0, 0} };
|
|
||||||
|
|
||||||
if (is_process_thread()) {
|
if (strings_equal_ignore_case (prop->value(), "output") || strings_equal_ignore_case (prop->value(), "out")) {
|
||||||
error << "Process thread called MIDI::Port::drain() - this cannot work" << endmsg;
|
flags = IsOutput;
|
||||||
return;
|
} else if (strings_equal_ignore_case (prop->value(), "input") || strings_equal_ignore_case (prop->value(), "in")) {
|
||||||
|
flags = IsInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (1) {
|
have_mode = true;
|
||||||
output_fifo.get_write_vector (&vec);
|
|
||||||
if (vec.len[0] + vec.len[1] >= output_fifo.bufsize() - 1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
usleep (check_interval_usecs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
if (!have_tag || !have_mode) {
|
||||||
Port::write(byte * msg, size_t msglen, timestamp_t timestamp)
|
throw failed_constructor();
|
||||||
{
|
|
||||||
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
|
|
||||||
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 (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&
|
XMLNode&
|
||||||
Port::get_state () const
|
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
|
#if 0
|
||||||
byte device_inquiry[6];
|
byte device_inquiry[6];
|
||||||
|
|
@ -373,30 +165,7 @@ Port::get_state () const
|
||||||
write (device_inquiry, sizeof (device_inquiry), 0);
|
write (device_inquiry, sizeof (device_inquiry), 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (_jack_port) {
|
return *root;
|
||||||
|
|
||||||
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
|
void
|
||||||
|
|
@ -407,60 +176,10 @@ Port::set_state (const XMLNode& node)
|
||||||
if ((prop = node.property ("tag")) == 0 || prop->value() != _tagname) {
|
if ((prop = node.property ("tag")) == 0 || prop->value() != _tagname) {
|
||||||
return;
|
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
|
bool
|
||||||
Port::is_process_thread()
|
Port::centrally_parsed() const
|
||||||
{
|
{
|
||||||
return (pthread_self() == _process_thread);
|
return _centrally_parsed;
|
||||||
}
|
|
||||||
|
|
||||||
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 ();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = '''
|
obj.source = '''
|
||||||
midi.cc
|
midi.cc
|
||||||
channel.cc
|
channel.cc
|
||||||
|
ipmidi_port.cc
|
||||||
|
jack_midi_port.cc
|
||||||
manager.cc
|
manager.cc
|
||||||
parser.cc
|
parser.cc
|
||||||
port_base.cc
|
|
||||||
port.cc
|
port.cc
|
||||||
midnam_patch.cc
|
midnam_patch.cc
|
||||||
mmc.cc
|
mmc.cc
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,9 @@ BaseUI::main_thread ()
|
||||||
{
|
{
|
||||||
set_event_loop_for_thread (this);
|
set_event_loop_for_thread (this);
|
||||||
thread_init ();
|
thread_init ();
|
||||||
|
std::cerr << pthread_self() << ' ' << _name << " running event loop\n";
|
||||||
_main_loop->run ();
|
_main_loop->run ();
|
||||||
|
std::cerr << pthread_self() << ' ' << _name << " event loop finished\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -104,7 +106,7 @@ BaseUI::quit ()
|
||||||
bool
|
bool
|
||||||
BaseUI::request_handler (Glib::IOCondition ioc)
|
BaseUI::request_handler (Glib::IOCondition ioc)
|
||||||
{
|
{
|
||||||
/* check the transport request pipe */
|
/* check the request pipe */
|
||||||
|
|
||||||
if (ioc & ~IO_IN) {
|
if (ioc & ~IO_IN) {
|
||||||
_main_loop->quit ();
|
_main_loop->quit ();
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ RefPtr<IOSource>
|
||||||
CrossThreadChannel::ios ()
|
CrossThreadChannel::ios ()
|
||||||
{
|
{
|
||||||
if (!_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)));
|
_ios = new RefPtr<IOSource> (IOSource::create (fds[0], IOCondition(IO_IN|IO_PRI|IO_ERR|IO_HUP|IO_NVAL)));
|
||||||
}
|
}
|
||||||
return *_ios;
|
return *_ios;
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ DeviceInfo::DeviceInfo()
|
||||||
, _has_jog_wheel (true)
|
, _has_jog_wheel (true)
|
||||||
, _has_touch_sense_faders (true)
|
, _has_touch_sense_faders (true)
|
||||||
, _uses_logic_control_buttons (false)
|
, _uses_logic_control_buttons (false)
|
||||||
|
, _uses_ipmidi (false)
|
||||||
, _name (X_("Mackie Control Universal Pro"))
|
, _name (X_("Mackie Control Universal Pro"))
|
||||||
{
|
{
|
||||||
mackie_control_buttons ();
|
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 ((child = node.child ("LogicControlButtons")) != 0) {
|
||||||
if ((prop = child->property ("value")) != 0) {
|
if ((prop = child->property ("value")) != 0) {
|
||||||
_uses_logic_control_buttons = string_is_affirmative (prop->value());
|
_uses_logic_control_buttons = string_is_affirmative (prop->value());
|
||||||
|
|
@ -369,6 +376,12 @@ DeviceInfo::has_timecode_display () const
|
||||||
return _has_timecode_display;
|
return _has_timecode_display;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
DeviceInfo::uses_ipmidi () const
|
||||||
|
{
|
||||||
|
return _uses_ipmidi;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
DeviceInfo::has_global_controls () const
|
DeviceInfo::has_global_controls () const
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ class DeviceInfo
|
||||||
bool has_global_controls() const;
|
bool has_global_controls() const;
|
||||||
bool has_jog_wheel () const;
|
bool has_jog_wheel () const;
|
||||||
bool has_touch_sense_faders() const;
|
bool has_touch_sense_faders() const;
|
||||||
|
bool uses_ipmidi() const;
|
||||||
const std::string& name() const;
|
const std::string& name() const;
|
||||||
|
|
||||||
static std::map<std::string,DeviceInfo> device_info;
|
static std::map<std::string,DeviceInfo> device_info;
|
||||||
|
|
@ -86,6 +87,7 @@ class DeviceInfo
|
||||||
bool _has_jog_wheel;
|
bool _has_jog_wheel;
|
||||||
bool _has_touch_sense_faders;
|
bool _has_touch_sense_faders;
|
||||||
bool _uses_logic_control_buttons;
|
bool _uses_logic_control_buttons;
|
||||||
|
bool _uses_ipmidi;
|
||||||
std::string _name;
|
std::string _name;
|
||||||
|
|
||||||
std::map<Button::ID,GlobalButtonInfo> _global_buttons;
|
std::map<Button::ID,GlobalButtonInfo> _global_buttons;
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,10 @@ MackieControlProtocol::~MackieControlProtocol()
|
||||||
|
|
||||||
_active = false;
|
_active = false;
|
||||||
|
|
||||||
|
/* stop event loop */
|
||||||
|
|
||||||
|
BaseUI::quit ();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
@ -584,6 +588,7 @@ MackieControlProtocol::create_surfaces ()
|
||||||
}
|
}
|
||||||
stype = ext;
|
stype = ext;
|
||||||
|
|
||||||
|
if (!_device_info.uses_ipmidi()) {
|
||||||
_input_bundle->add_channel (
|
_input_bundle->add_channel (
|
||||||
surface->port().input_port().name(),
|
surface->port().input_port().name(),
|
||||||
ARDOUR::DataType::MIDI,
|
ARDOUR::DataType::MIDI,
|
||||||
|
|
@ -595,6 +600,7 @@ MackieControlProtocol::create_surfaces ()
|
||||||
ARDOUR::DataType::MIDI,
|
ARDOUR::DataType::MIDI,
|
||||||
session->engine().make_port_name_non_relative (surface->port().output_port().name())
|
session->engine().make_port_name_non_relative (surface->port().output_port().name())
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
int fd;
|
int fd;
|
||||||
MIDI::Port& input_port (surface->port().input_port());
|
MIDI::Port& input_port (surface->port().input_port());
|
||||||
|
|
@ -1083,8 +1089,6 @@ MackieControlProtocol::midi_input_handler (IOCondition ioc, MIDI::Port* port)
|
||||||
|
|
||||||
if (ioc & IO_IN) {
|
if (ioc & IO_IN) {
|
||||||
|
|
||||||
CrossThreadChannel::drain (port->selectable());
|
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", port->name()));
|
DEBUG_TRACE (DEBUG::MidiIO, string_compose ("data available on %1\n", port->name()));
|
||||||
framepos_t now = session->engine().frame_time();
|
framepos_t now = session->engine().frame_time();
|
||||||
port->parse (now);
|
port->parse (now);
|
||||||
|
|
|
||||||
|
|
@ -109,8 +109,7 @@ Surface::~Surface ()
|
||||||
}
|
}
|
||||||
|
|
||||||
delete _jog_wheel;
|
delete _jog_wheel;
|
||||||
|
delete _port;
|
||||||
/* don't delete the port, because we want its output to remain queued */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MidiByteArray&
|
const MidiByteArray&
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
#include "midi++/types.h"
|
#include "midi++/types.h"
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/jack_midi_port.h"
|
||||||
|
#include "midi++/ipmidi_port.h"
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
|
|
||||||
#include "ardour/debug.h"
|
#include "ardour/debug.h"
|
||||||
|
|
@ -51,10 +53,14 @@ using namespace PBD;
|
||||||
SurfacePort::SurfacePort (Surface& s)
|
SurfacePort::SurfacePort (Surface& s)
|
||||||
: _surface (&s)
|
: _surface (&s)
|
||||||
{
|
{
|
||||||
|
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();
|
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);
|
_input_port = new MIDI::JackMIDIPort (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);
|
_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
|
/* MackieControl has its own thread for handling input from the input
|
||||||
* port, and we don't want anything handling output from the output
|
* port, and we don't want anything handling output from the output
|
||||||
|
|
@ -70,9 +76,14 @@ SurfacePort::SurfacePort (Surface& s)
|
||||||
mm->add_port (_input_port);
|
mm->add_port (_input_port);
|
||||||
mm->add_port (_output_port);
|
mm->add_port (_output_port);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SurfacePort::~SurfacePort()
|
SurfacePort::~SurfacePort()
|
||||||
{
|
{
|
||||||
|
if (_surface->mcp().device_info().uses_ipmidi()) {
|
||||||
|
delete _input_port;
|
||||||
|
} else {
|
||||||
|
|
||||||
MIDI::Manager* mm = MIDI::Manager::instance ();
|
MIDI::Manager* mm = MIDI::Manager::instance ();
|
||||||
|
|
||||||
if (_input_port) {
|
if (_input_port) {
|
||||||
|
|
@ -81,10 +92,12 @@ SurfacePort::~SurfacePort()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_output_port) {
|
if (_output_port) {
|
||||||
|
_output_port->drain (10000);
|
||||||
mm->remove_port (_output_port);
|
mm->remove_port (_output_port);
|
||||||
delete _output_port;
|
delete _output_port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// wrapper for one day when strerror_r is working properly
|
// wrapper for one day when strerror_r is working properly
|
||||||
string fetch_errmsg (int error_number)
|
string fetch_errmsg (int error_number)
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,5 @@
|
||||||
<JogWheel value="yes"/>
|
<JogWheel value="yes"/>
|
||||||
<TouchSenseFaders value="yes"/>
|
<TouchSenseFaders value="yes"/>
|
||||||
<LogicControlButtons value="yes"/>
|
<LogicControlButtons value="yes"/>
|
||||||
|
<UsesIPMIDI value="yes"/>
|
||||||
</MackieProtocolDevice>
|
</MackieProtocolDevice>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue