2008-06-02 17:41:35 -04:00
|
|
|
/*
|
|
|
|
Copyright (C) 2004 Paul Davis
|
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
|
|
|
$Id$
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <cerrno>
|
|
|
|
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "pbd/failed_constructor.h"
|
|
|
|
#include "pbd/error.h"
|
|
|
|
#include "pbd/xml++.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
2009-02-25 13:26:51 -05:00
|
|
|
#include "midi++/types.h"
|
|
|
|
#include "midi++/alsa_sequencer.h"
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
#include "i18n.h"
|
|
|
|
|
|
|
|
//#define DOTRACE 1
|
|
|
|
|
|
|
|
#ifdef DOTRACE
|
|
|
|
#define TR_FN() (cerr << __FUNCTION__ << endl)
|
|
|
|
#define TR_VAL(v) (cerr << __FILE__ " " << __LINE__ << " " #v "=" << v << endl)
|
|
|
|
#else
|
|
|
|
#define TR_FN()
|
|
|
|
#define TR_VAL(v)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace MIDI;
|
|
|
|
using namespace PBD;
|
|
|
|
|
|
|
|
snd_seq_t* ALSA_SequencerMidiPort::seq = 0;
|
|
|
|
|
|
|
|
ALSA_SequencerMidiPort::ALSA_SequencerMidiPort (const XMLNode& node)
|
|
|
|
: Port (node)
|
|
|
|
, decoder (0)
|
|
|
|
, encoder (0)
|
|
|
|
, port_id (-1)
|
|
|
|
{
|
|
|
|
TR_FN();
|
|
|
|
int err;
|
|
|
|
Descriptor desc (node);
|
|
|
|
|
|
|
|
if (!seq && init_client (desc.device) < 0) {
|
|
|
|
_ok = false;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (0 <= (err = create_ports (desc)) &&
|
|
|
|
0 <= (err = snd_midi_event_new (1024, &decoder)) && // Length taken from ARDOUR::Session::midi_read ()
|
|
|
|
0 <= (err = snd_midi_event_new (64, &encoder))) { // Length taken from ARDOUR::Session::mmc_buffer
|
|
|
|
snd_midi_event_init (decoder);
|
|
|
|
snd_midi_event_init (encoder);
|
|
|
|
_ok = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
set_state (node);
|
|
|
|
}
|
|
|
|
|
|
|
|
ALSA_SequencerMidiPort::~ALSA_SequencerMidiPort ()
|
|
|
|
{
|
|
|
|
if (decoder) {
|
|
|
|
snd_midi_event_free (decoder);
|
|
|
|
}
|
|
|
|
if (encoder) {
|
|
|
|
snd_midi_event_free (encoder);
|
|
|
|
}
|
|
|
|
if (port_id >= 0) {
|
|
|
|
snd_seq_delete_port (seq, port_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ALSA_SequencerMidiPort::selectable () const
|
|
|
|
{
|
|
|
|
struct pollfd pfd[1];
|
|
|
|
if (0 <= snd_seq_poll_descriptors (seq, pfd, 1, POLLIN | POLLOUT)) {
|
|
|
|
return pfd[0].fd;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ALSA_SequencerMidiPort::write (byte *msg, size_t msglen, timestamp_t ignored)
|
|
|
|
{
|
|
|
|
TR_FN ();
|
|
|
|
int R;
|
|
|
|
int totwritten = 0;
|
|
|
|
snd_midi_event_reset_encode (encoder);
|
|
|
|
int nwritten = snd_midi_event_encode (encoder, msg, msglen, &SEv);
|
|
|
|
TR_VAL (nwritten);
|
|
|
|
while (0 < nwritten) {
|
|
|
|
if (0 <= (R = snd_seq_event_output (seq, &SEv)) &&
|
|
|
|
0 <= (R = snd_seq_drain_output (seq))) {
|
|
|
|
bytes_written += nwritten;
|
|
|
|
totwritten += nwritten;
|
|
|
|
if (output_parser) {
|
|
|
|
output_parser->raw_preparse (*output_parser, msg, nwritten);
|
|
|
|
for (int i = 0; i < nwritten; i++) {
|
|
|
|
output_parser->scanner (msg[i]);
|
|
|
|
}
|
|
|
|
output_parser->raw_postparse (*output_parser, msg, nwritten);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
TR_VAL(R);
|
|
|
|
return R;
|
|
|
|
}
|
|
|
|
|
|
|
|
msglen -= nwritten;
|
|
|
|
msg += nwritten;
|
|
|
|
if (msglen > 0) {
|
|
|
|
nwritten = snd_midi_event_encode (encoder, msg, msglen, &SEv);
|
|
|
|
TR_VAL(nwritten);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return totwritten;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ALSA_SequencerMidiPort::read (byte *buf, size_t max)
|
|
|
|
{
|
|
|
|
TR_FN();
|
|
|
|
int err;
|
|
|
|
snd_seq_event_t *ev;
|
|
|
|
if (0 <= (err = snd_seq_event_input (seq, &ev))) {
|
|
|
|
TR_VAL(err);
|
|
|
|
err = snd_midi_event_decode (decoder, buf, max, ev);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err > 0) {
|
|
|
|
bytes_read += err;
|
|
|
|
|
|
|
|
if (input_parser) {
|
|
|
|
input_parser->raw_preparse (*input_parser, buf, err);
|
|
|
|
for (int i = 0; i < err; i++) {
|
|
|
|
input_parser->scanner (buf[i]);
|
|
|
|
}
|
|
|
|
input_parser->raw_postparse (*input_parser, buf, err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -ENOENT == err ? 0 : err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ALSA_SequencerMidiPort::create_ports (const Port::Descriptor& desc)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
unsigned int caps = 0;
|
|
|
|
|
|
|
|
if (desc.mode == O_WRONLY || desc.mode == O_RDWR)
|
|
|
|
caps |= SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
|
|
|
|
if (desc.mode == O_RDONLY || desc.mode == O_RDWR)
|
|
|
|
caps |= SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ;
|
2008-12-12 09:43:24 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
if (0 <= (err = snd_seq_create_simple_port (seq, desc.tag.c_str(), caps,
|
|
|
|
(SND_SEQ_PORT_TYPE_MIDI_GENERIC|
|
|
|
|
SND_SEQ_PORT_TYPE_SOFTWARE|
|
|
|
|
SND_SEQ_PORT_TYPE_APPLICATION)))) {
|
|
|
|
|
|
|
|
port_id = err;
|
|
|
|
|
|
|
|
snd_seq_ev_clear (&SEv);
|
|
|
|
snd_seq_ev_set_source (&SEv, port_id);
|
|
|
|
snd_seq_ev_set_subs (&SEv);
|
|
|
|
snd_seq_ev_set_direct (&SEv);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ALSA_SequencerMidiPort::init_client (std::string name)
|
|
|
|
{
|
|
|
|
static bool called = false;
|
|
|
|
|
|
|
|
if (called) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
called = true;
|
|
|
|
|
|
|
|
if (snd_seq_open (&seq, "default", SND_SEQ_OPEN_DUPLEX, 0) >= 0) {
|
|
|
|
snd_seq_set_client_name (seq, name.c_str());
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
warning << "The ALSA MIDI system is not available. No ports based on it will be created"
|
|
|
|
<< endmsg;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
ALSA_SequencerMidiPort::discover (vector<PortSet>& ports)
|
|
|
|
{
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
snd_seq_client_info_t *client_info;
|
|
|
|
snd_seq_port_info_t *port_info;
|
|
|
|
|
|
|
|
snd_seq_client_info_alloca (&client_info);
|
|
|
|
snd_seq_port_info_alloca (&port_info);
|
|
|
|
snd_seq_client_info_set_client (client_info, -1);
|
|
|
|
|
|
|
|
while (snd_seq_query_next_client(seq, client_info) >= 0) {
|
|
|
|
|
|
|
|
int alsa_client;
|
|
|
|
|
|
|
|
if ((alsa_client = snd_seq_client_info_get_client(client_info)) <= 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_seq_port_info_set_client(port_info, alsa_client);
|
|
|
|
snd_seq_port_info_set_port(port_info, -1);
|
|
|
|
|
|
|
|
char client[256];
|
|
|
|
snprintf (client, sizeof (client), "%d:%s", alsa_client, snd_seq_client_info_get_name(client_info));
|
|
|
|
|
|
|
|
ports.push_back (PortSet (client));
|
|
|
|
|
|
|
|
while (snd_seq_query_next_port(seq, port_info) >= 0) {
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
int type = snd_seq_port_info_get_type(pinfo);
|
|
|
|
if (!(type & SND_SEQ_PORT_TYPE_PORT)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
unsigned int port_capability = snd_seq_port_info_get_capability(port_info);
|
|
|
|
|
|
|
|
if ((port_capability & SND_SEQ_PORT_CAP_NO_EXPORT) == 0) {
|
|
|
|
|
|
|
|
int alsa_port = snd_seq_port_info_get_port(port_info);
|
|
|
|
|
|
|
|
char port[256];
|
|
|
|
snprintf (port, sizeof (port), "%d:%s", alsa_port, snd_seq_port_info_get_name(port_info));
|
|
|
|
|
|
|
|
std::string mode;
|
|
|
|
|
|
|
|
if (port_capability & SND_SEQ_PORT_CAP_READ) {
|
|
|
|
if (port_capability & SND_SEQ_PORT_CAP_WRITE) {
|
|
|
|
mode = "duplex";
|
|
|
|
} else {
|
|
|
|
mode = "output";
|
|
|
|
}
|
|
|
|
} else if (port_capability & SND_SEQ_PORT_CAP_WRITE) {
|
|
|
|
if (port_capability & SND_SEQ_PORT_CAP_READ) {
|
|
|
|
mode = "duplex";
|
|
|
|
} else {
|
|
|
|
mode = "input";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode node (X_("MIDI-port"));
|
|
|
|
node.add_property ("device", client);
|
|
|
|
node.add_property ("tag", port);
|
|
|
|
node.add_property ("mode", mode);
|
|
|
|
node.add_property ("type", "alsa/sequencer");
|
|
|
|
|
|
|
|
ports.back().ports.push_back (node);
|
|
|
|
++n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ALSA_SequencerMidiPort::get_connections (vector<SequencerPortAddress>& connections, int dir) const
|
|
|
|
{
|
|
|
|
snd_seq_query_subscribe_t *subs;
|
|
|
|
snd_seq_addr_t seq_addr;
|
|
|
|
|
|
|
|
snd_seq_query_subscribe_alloca (&subs);
|
|
|
|
|
|
|
|
// Get port connections...
|
|
|
|
|
|
|
|
if (dir) {
|
|
|
|
snd_seq_query_subscribe_set_type(subs, SND_SEQ_QUERY_SUBS_WRITE);
|
|
|
|
} else {
|
|
|
|
snd_seq_query_subscribe_set_type(subs, SND_SEQ_QUERY_SUBS_READ);
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_seq_query_subscribe_set_index(subs, 0);
|
|
|
|
seq_addr.client = snd_seq_client_id (seq);
|
|
|
|
seq_addr.port = port_id;
|
|
|
|
snd_seq_query_subscribe_set_root(subs, &seq_addr);
|
2008-12-12 09:43:24 -05:00
|
|
|
|
2008-06-02 17:41:35 -04:00
|
|
|
while (snd_seq_query_port_subscribers(seq, subs) >= 0) {
|
|
|
|
|
2008-12-12 09:43:24 -05:00
|
|
|
if (snd_seq_query_subscribe_get_time_real (subs)) {
|
|
|
|
/* interesting connection */
|
|
|
|
|
|
|
|
seq_addr = *snd_seq_query_subscribe_get_addr (subs);
|
|
|
|
|
|
|
|
connections.push_back (SequencerPortAddress (seq_addr.client,
|
|
|
|
seq_addr.port));
|
|
|
|
}
|
2008-06-02 17:41:35 -04:00
|
|
|
|
|
|
|
snd_seq_query_subscribe_set_index(subs, snd_seq_query_subscribe_get_index(subs) + 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode&
|
|
|
|
ALSA_SequencerMidiPort::get_state () const
|
|
|
|
{
|
|
|
|
XMLNode& root (Port::get_state ());
|
|
|
|
vector<SequencerPortAddress> connections;
|
|
|
|
XMLNode* sub = 0;
|
|
|
|
char buf[256];
|
|
|
|
|
|
|
|
get_connections (connections, 1);
|
|
|
|
|
|
|
|
if (!connections.empty()) {
|
|
|
|
if (!sub) {
|
|
|
|
sub = new XMLNode (X_("connections"));
|
|
|
|
}
|
|
|
|
for (vector<SequencerPortAddress>::iterator i = connections.begin(); i != connections.end(); ++i) {
|
|
|
|
XMLNode* cnode = new XMLNode (X_("read"));
|
|
|
|
snprintf (buf, sizeof (buf), "%d:%d", i->first, i->second);
|
|
|
|
cnode->add_property ("dest", buf);
|
|
|
|
sub->add_child_nocopy (*cnode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
connections.clear ();
|
|
|
|
get_connections (connections, 0);
|
|
|
|
|
|
|
|
if (!connections.empty()) {
|
|
|
|
if (!sub) {
|
|
|
|
sub = new XMLNode (X_("connections"));
|
|
|
|
}
|
|
|
|
for (vector<SequencerPortAddress>::iterator i = connections.begin(); i != connections.end(); ++i) {
|
|
|
|
XMLNode* cnode = new XMLNode (X_("write"));
|
|
|
|
snprintf (buf, sizeof (buf), "%d:%d", i->first, i->second);
|
|
|
|
cnode->add_property ("dest", buf);
|
|
|
|
sub->add_child_nocopy (*cnode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sub) {
|
|
|
|
root.add_child_nocopy (*sub);
|
|
|
|
}
|
|
|
|
|
|
|
|
return root;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
ALSA_SequencerMidiPort::set_state (const XMLNode& node)
|
|
|
|
{
|
|
|
|
Port::set_state (node);
|
|
|
|
|
|
|
|
XMLNodeList children (node.children());
|
|
|
|
XMLNodeIterator iter;
|
|
|
|
|
|
|
|
for (iter = children.begin(); iter != children.end(); ++iter) {
|
|
|
|
|
|
|
|
if ((*iter)->name() == X_("connections")) {
|
|
|
|
|
|
|
|
XMLNodeList gchildren ((*iter)->children());
|
|
|
|
XMLNodeIterator gciter;
|
|
|
|
|
|
|
|
for (gciter = gchildren.begin(); gciter != gchildren.end(); ++gciter) {
|
|
|
|
XMLProperty* prop;
|
|
|
|
|
|
|
|
if ((prop = (*gciter)->property ("dest")) != 0) {
|
|
|
|
int client;
|
|
|
|
int port;
|
|
|
|
|
|
|
|
if (sscanf (prop->value().c_str(), "%d:%d", &client, &port) == 2) {
|
|
|
|
|
|
|
|
snd_seq_port_subscribe_t *sub;
|
|
|
|
snd_seq_addr_t seq_addr;
|
|
|
|
|
|
|
|
snd_seq_port_subscribe_alloca(&sub);
|
|
|
|
|
|
|
|
if ((*gciter)->name() == X_("write")) {
|
|
|
|
|
|
|
|
seq_addr.client = snd_seq_client_id (seq);
|
|
|
|
seq_addr.port = port_id;
|
|
|
|
snd_seq_port_subscribe_set_sender(sub, &seq_addr);
|
|
|
|
|
|
|
|
seq_addr.client = client;
|
|
|
|
seq_addr.port = port;
|
|
|
|
snd_seq_port_subscribe_set_dest(sub, &seq_addr);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
seq_addr.client = snd_seq_client_id (seq);
|
|
|
|
seq_addr.port = port_id;
|
|
|
|
snd_seq_port_subscribe_set_dest(sub, &seq_addr);
|
|
|
|
|
|
|
|
seq_addr.client = client;
|
|
|
|
seq_addr.port = port;
|
|
|
|
snd_seq_port_subscribe_set_sender(sub, &seq_addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_seq_subscribe_port (seq, sub);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|