563 lines
20 KiB
C++
563 lines
20 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Linux Music Score Editor
|
|
// $Id: mididriver.cpp 5568 2012-04-22 10:08:43Z wschweer $
|
|
//
|
|
// Copyright (C) 2008 Werner Schweer and others
|
|
//
|
|
// AlsaDriver based on code from Fons Adriaensen (clalsadr.cc)
|
|
// Copyright (C) 2003 Fons Adriaensen
|
|
// partly based on original work from Paul Davis
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License version 2.
|
|
//
|
|
// 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.
|
|
//=============================================================================
|
|
|
|
#include <time.h>
|
|
|
|
#include "config.h"
|
|
#include "mididriver.h"
|
|
#include "preferences.h"
|
|
#include "musescore.h"
|
|
#include "midi/midifile.h"
|
|
#include "globals.h"
|
|
#include "seq.h"
|
|
#include "libmscore/utils.h"
|
|
#include "libmscore/score.h"
|
|
|
|
namespace Ms {
|
|
|
|
//---------------------------------------------------------
|
|
// Port
|
|
//---------------------------------------------------------
|
|
|
|
Port::Port()
|
|
{
|
|
type = ZERO_TYPE;
|
|
}
|
|
|
|
Port::Port(unsigned char client, unsigned char port)
|
|
{
|
|
_alsaPort = port;
|
|
_alsaClient = client;
|
|
type = ALSA_TYPE;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setZero
|
|
//---------------------------------------------------------
|
|
|
|
void Port::setZero()
|
|
{
|
|
type = ZERO_TYPE;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// isZero
|
|
//---------------------------------------------------------
|
|
|
|
bool Port::isZero() const
|
|
{
|
|
return type == ZERO_TYPE;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// operator==
|
|
//---------------------------------------------------------
|
|
|
|
bool Port::operator==(const Port& p) const
|
|
{
|
|
if (type == ALSA_TYPE)
|
|
return _alsaPort == p._alsaPort && _alsaClient == p._alsaClient;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// operator<
|
|
//---------------------------------------------------------
|
|
|
|
bool Port::operator<(const Port& p) const
|
|
{
|
|
if (type == ALSA_TYPE) {
|
|
if (_alsaPort != p._alsaPort)
|
|
return _alsaPort < p._alsaPort;
|
|
return _alsaClient < p._alsaClient;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_ALSA
|
|
#include "alsa.h"
|
|
#include "alsamidi.h"
|
|
|
|
namespace Ms {
|
|
static const unsigned int inCap = SND_SEQ_PORT_CAP_SUBS_READ;
|
|
static const unsigned int outCap = SND_SEQ_PORT_CAP_SUBS_WRITE;
|
|
|
|
//---------------------------------------------------------
|
|
// AlsaMidiDriver
|
|
//---------------------------------------------------------
|
|
|
|
AlsaMidiDriver::AlsaMidiDriver(Seq* s)
|
|
: MidiDriver(s)
|
|
{
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// init
|
|
// return false on error
|
|
//---------------------------------------------------------
|
|
|
|
bool AlsaMidiDriver::init()
|
|
{
|
|
int error = snd_seq_open(&alsaSeq, "hw", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
|
|
if (error < 0) {
|
|
if (error == ENOENT)
|
|
qDebug("open ALSA sequencer failed: %s", snd_strerror(error));
|
|
return false;
|
|
}
|
|
|
|
snd_seq_set_client_name(alsaSeq, "MuseScore");
|
|
|
|
//-----------------------------------------
|
|
// subscribe to "Announce"
|
|
// this enables callbacks for any
|
|
// alsa port changes
|
|
//-----------------------------------------
|
|
|
|
snd_seq_addr_t src, dst;
|
|
int rv = snd_seq_create_simple_port(alsaSeq, "MuseScore Port 0",
|
|
inCap | outCap | SND_SEQ_PORT_CAP_READ
|
|
| SND_SEQ_PORT_CAP_WRITE
|
|
| SND_SEQ_PORT_CAP_NO_EXPORT,
|
|
SND_SEQ_PORT_TYPE_APPLICATION);
|
|
if (rv < 0) {
|
|
qDebug("Alsa: create MuseScore port failed: %s", snd_strerror(error));
|
|
return false;
|
|
}
|
|
dst.port = rv;
|
|
dst.client = snd_seq_client_id(alsaSeq);
|
|
src.port = SND_SEQ_PORT_SYSTEM_ANNOUNCE;
|
|
src.client = SND_SEQ_CLIENT_SYSTEM;
|
|
|
|
snd_seq_port_subscribe_t* subs;
|
|
snd_seq_port_subscribe_alloca(&subs);
|
|
snd_seq_port_subscribe_set_dest(subs, &dst);
|
|
snd_seq_port_subscribe_set_sender(subs, &src);
|
|
error = snd_seq_subscribe_port(alsaSeq, subs);
|
|
if (error < 0) {
|
|
qDebug("Alsa: Subscribe System failed: %s", snd_strerror(error));
|
|
return false;
|
|
}
|
|
midiInPort = registerOutPort("MuseScore Port-0");
|
|
midiOutPorts.append(registerInPort("MuseScore Port-0"));
|
|
|
|
struct pollfd* pfd;
|
|
int npfd;
|
|
getInputPollFd(&pfd, &npfd);
|
|
for (int i = 0; i < npfd; ++i) {
|
|
int fd = pfd[i].fd;
|
|
if (fd != -1) {
|
|
QSocketNotifier* s = new QSocketNotifier(fd, QSocketNotifier::Read, mscore);
|
|
s->connect(s, SIGNAL(activated(int)), seq, SLOT(midiInputReady()));
|
|
}
|
|
}
|
|
#if 0
|
|
// TODO: autoconnect all output ports
|
|
QList<PortName> ol = outputPorts();
|
|
foreach(PortName pn, ol) {
|
|
if (MScore::debugMode)
|
|
qDebug("connect to midi output <%s>", qPrintable(pn.name));
|
|
qDebug("Output <%s>", qPrintable(pn.name));
|
|
}
|
|
#endif
|
|
|
|
// connect all midi sources to mscore
|
|
QList<PortName> il = inputPorts();
|
|
foreach(PortName pn, il) {
|
|
if (MScore::debugMode)
|
|
qDebug("connect to midi input <%s>", qPrintable(pn.name));
|
|
connect(pn.port, midiInPort);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// outputPorts
|
|
//---------------------------------------------------------
|
|
|
|
QList<PortName> AlsaMidiDriver::outputPorts()
|
|
{
|
|
QList<PortName> clientList;
|
|
snd_seq_client_info_t* cinfo;
|
|
snd_seq_client_info_alloca(&cinfo);
|
|
snd_seq_client_info_set_client(cinfo, 0);
|
|
|
|
while (snd_seq_query_next_client(alsaSeq, cinfo) >= 0) {
|
|
snd_seq_port_info_t *pinfo;
|
|
snd_seq_port_info_alloca(&pinfo);
|
|
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
|
snd_seq_port_info_set_port(pinfo, -1);
|
|
while (snd_seq_query_next_port(alsaSeq, pinfo) >= 0) {
|
|
unsigned int capability = snd_seq_port_info_get_capability(pinfo);
|
|
if (((capability & outCap) == outCap)
|
|
&& !(capability & SND_SEQ_PORT_CAP_NO_EXPORT)) {
|
|
int client = snd_seq_port_info_get_client(pinfo);
|
|
if (client != snd_seq_client_id(alsaSeq)) {
|
|
PortName pn;
|
|
pn.name = QString(snd_seq_port_info_get_name(pinfo));
|
|
pn.port = Port(client, snd_seq_port_info_get_port(pinfo));
|
|
clientList.append(pn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return clientList;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// inputPorts
|
|
//---------------------------------------------------------
|
|
|
|
QList<PortName> AlsaMidiDriver::inputPorts()
|
|
{
|
|
QList<PortName> clientList;
|
|
|
|
snd_seq_client_info_t* cinfo;
|
|
snd_seq_client_info_alloca(&cinfo);
|
|
snd_seq_client_info_set_client(cinfo, 0);
|
|
|
|
while (snd_seq_query_next_client(alsaSeq, cinfo) >= 0) {
|
|
snd_seq_port_info_t *pinfo;
|
|
snd_seq_port_info_alloca(&pinfo);
|
|
snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
|
|
snd_seq_port_info_set_port(pinfo, -1);
|
|
while (snd_seq_query_next_port(alsaSeq, pinfo) >= 0) {
|
|
unsigned int capability = snd_seq_port_info_get_capability(pinfo);
|
|
if (((capability & inCap) == inCap)
|
|
&& !(capability & SND_SEQ_PORT_CAP_NO_EXPORT)) {
|
|
int client = snd_seq_port_info_get_client(pinfo);
|
|
if (client != snd_seq_client_id(alsaSeq)) {
|
|
PortName pn;
|
|
pn.name = QString(snd_seq_port_info_get_name(pinfo));
|
|
pn.port = Port(client, snd_seq_port_info_get_port(pinfo));
|
|
clientList.append(pn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return clientList;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// connect
|
|
// return false if connect fails
|
|
//---------------------------------------------------------
|
|
|
|
bool AlsaMidiDriver::connect(Port src, Port dst)
|
|
{
|
|
snd_seq_port_subscribe_t* sub;
|
|
snd_seq_port_subscribe_alloca(&sub);
|
|
|
|
snd_seq_addr_t s, d;
|
|
s.port = src.alsaPort();
|
|
s.client = src.alsaClient();
|
|
d.port = dst.alsaPort();
|
|
d.client = dst.alsaClient();
|
|
snd_seq_port_subscribe_set_sender(sub, &s);
|
|
snd_seq_port_subscribe_set_dest(sub, &d);
|
|
|
|
int rv = snd_seq_subscribe_port(alsaSeq, sub);
|
|
if (rv < 0) {
|
|
qDebug("AlsaMidi::connect(%d:%d, %d:%d) failed: %s",
|
|
src.alsaClient(), src.alsaPort(),
|
|
dst.alsaClient(), dst.alsaPort(),
|
|
snd_strerror(rv));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// registerOutPort
|
|
//---------------------------------------------------------
|
|
|
|
Port AlsaMidiDriver::registerOutPort(const QString& name)
|
|
{
|
|
int alsaPort = snd_seq_create_simple_port(alsaSeq, name.toLatin1().data(),
|
|
outCap | SND_SEQ_PORT_CAP_WRITE, SND_SEQ_PORT_TYPE_APPLICATION);
|
|
if (alsaPort < 0) {
|
|
perror("cannot create alsa out port");
|
|
return Port();
|
|
}
|
|
return Port(snd_seq_client_id(alsaSeq), alsaPort);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// registerInPort
|
|
//---------------------------------------------------------
|
|
|
|
Port AlsaMidiDriver::registerInPort(const QString& name)
|
|
{
|
|
int alsaPort = snd_seq_create_simple_port(alsaSeq, name.toLatin1().data(),
|
|
inCap | SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_APPLICATION);
|
|
if (alsaPort < 0) {
|
|
perror("cannot create alsa in port");
|
|
return Port();
|
|
}
|
|
return Port(snd_seq_client_id(alsaSeq), alsaPort);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// updateInPortCount
|
|
//---------------------------------------------------------
|
|
|
|
void AlsaMidiDriver::updateInPortCount(int maxport)
|
|
{
|
|
int ports = midiOutPorts.size();
|
|
if (maxport == ports)
|
|
return;
|
|
if (MScore::debugMode)
|
|
qDebug()<<"ALSA number of ports:"<<ports<<", change to:"<<maxport;
|
|
if (maxport > ports) {
|
|
for (int i = ports; i < maxport; ++i)
|
|
midiOutPorts.append(registerInPort(QString("MuseScore Port-%1").arg(i)));
|
|
}
|
|
else if (maxport < ports) {
|
|
for(int i = ports - 1; i >= maxport; --i) {
|
|
if (snd_seq_delete_simple_port(alsaSeq, midiOutPorts[i].alsaPort()) < 0)
|
|
qDebug("Can not delete ALSA port");
|
|
else
|
|
midiOutPorts.removeAt(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getInputPollFd
|
|
//---------------------------------------------------------
|
|
|
|
void AlsaMidiDriver::getInputPollFd(struct pollfd** p, int* n)
|
|
{
|
|
int npfdi = snd_seq_poll_descriptors_count(alsaSeq, POLLIN);
|
|
struct pollfd* pfdi = new struct pollfd[npfdi];
|
|
snd_seq_poll_descriptors(alsaSeq, pfdi, npfdi, POLLIN);
|
|
*p = pfdi;
|
|
*n = npfdi;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getOutputPollFd
|
|
//---------------------------------------------------------
|
|
|
|
void AlsaMidiDriver::getOutputPollFd(struct pollfd** p, int* n)
|
|
{
|
|
int npfdo = snd_seq_poll_descriptors_count(alsaSeq, POLLOUT);
|
|
struct pollfd* pfdo = new struct pollfd[npfdo];
|
|
snd_seq_poll_descriptors(alsaSeq, pfdo, npfdo, POLLOUT);
|
|
*p = pfdo;
|
|
*n = npfdo;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// read
|
|
//---------------------------------------------------------
|
|
|
|
void AlsaMidiDriver::read()
|
|
{
|
|
snd_seq_event_t* ev;
|
|
for (;;) {
|
|
int rv = snd_seq_event_input(alsaSeq, &ev);
|
|
if (rv < 0)
|
|
return;
|
|
|
|
if (!mscore || !mscore->midiinEnabled()) {
|
|
snd_seq_free_event(ev);
|
|
return;
|
|
}
|
|
|
|
if (ev->type == SND_SEQ_EVENT_NOTEON) {
|
|
int pitch = ev->data.note.note;
|
|
int velo = ev->data.note.velocity;
|
|
mscore->midiNoteReceived(ev->data.note.channel, pitch, velo);
|
|
}
|
|
else if (ev->type == SND_SEQ_EVENT_NOTEOFF) { // "Virtual Keyboard" sends this
|
|
int pitch = ev->data.note.note;
|
|
mscore->midiNoteReceived(ev->data.note.channel, pitch, 0);
|
|
}
|
|
else if (ev->type == SND_SEQ_EVENT_CONTROLLER) {
|
|
mscore->midiCtrlReceived(ev->data.control.param,
|
|
ev->data.control.value);
|
|
}
|
|
|
|
if (midiInputTrace) {
|
|
qDebug("MidiIn: ");
|
|
switch(ev->type) {
|
|
case SND_SEQ_EVENT_NOTEON:
|
|
qDebug("noteOn ch:%2d 0x%02x 0x%02x",
|
|
ev->data.note.channel,
|
|
ev->data.note.note,
|
|
ev->data.note.velocity);
|
|
break;
|
|
case SND_SEQ_EVENT_SYSEX:
|
|
qDebug("sysEx len %d", ev->data.ext.len);
|
|
break;
|
|
case SND_SEQ_EVENT_CONTROLLER:
|
|
qDebug("ctrl 0x%02x 0x%02x",
|
|
ev->data.control.param,
|
|
ev->data.control.value);
|
|
break;
|
|
case SND_SEQ_EVENT_PITCHBEND:
|
|
qDebug("pitchbend 0x%04x", ev->data.control.value);
|
|
break;
|
|
case SND_SEQ_EVENT_PGMCHANGE:
|
|
qDebug("pgmChange 0x%02x", ev->data.control.value);
|
|
break;
|
|
case SND_SEQ_EVENT_CHANPRESS:
|
|
qDebug("channelPress 0x%02x", ev->data.control.value);
|
|
break;
|
|
case SND_SEQ_EVENT_START:
|
|
qDebug("start");
|
|
break;
|
|
case SND_SEQ_EVENT_CONTINUE:
|
|
qDebug("continue");
|
|
break;
|
|
case SND_SEQ_EVENT_STOP:
|
|
qDebug("stop");
|
|
break;
|
|
case SND_SEQ_EVENT_SONGPOS:
|
|
qDebug("songpos");
|
|
break;
|
|
default:
|
|
qDebug("type 0x%02x", ev->type);
|
|
break;
|
|
case SND_SEQ_EVENT_PORT_SUBSCRIBED:
|
|
case SND_SEQ_EVENT_SENSING:
|
|
break;
|
|
}
|
|
}
|
|
snd_seq_free_event(ev);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void AlsaMidiDriver::write(const Event& e)
|
|
{
|
|
MasterScore* cs = mscore->currentScore()->masterScore();
|
|
int port = cs->midiPort(e.channel());
|
|
int chn = cs->midiChannel(e.channel());
|
|
int a = e.dataA();
|
|
int b = e.dataB();
|
|
|
|
if (midiOutputTrace)
|
|
qDebug("midiOut: %2d %02x %02x %02x", port, e.type(), a, b);
|
|
|
|
snd_seq_event_t event;
|
|
memset(&event, 0, sizeof(event));
|
|
snd_seq_ev_set_direct(&event);
|
|
if (port >= midiOutPorts.size())
|
|
port = 0;
|
|
snd_seq_ev_set_source(&event, midiOutPorts[port].alsaPort());
|
|
snd_seq_ev_set_dest(&event, SND_SEQ_ADDRESS_SUBSCRIBERS, 0);
|
|
|
|
switch(e.type()) {
|
|
case ME_NOTEON:
|
|
snd_seq_ev_set_noteon(&event, chn, a, b);
|
|
break;
|
|
case ME_NOTEOFF:
|
|
snd_seq_ev_set_noteoff(&event, chn, a, 0);
|
|
break;
|
|
case ME_PROGRAM:
|
|
snd_seq_ev_set_pgmchange(&event, chn, a);
|
|
break;
|
|
case ME_CONTROLLER:
|
|
snd_seq_ev_set_controller(&event, chn, a, b);
|
|
break;
|
|
case ME_PITCHBEND:
|
|
snd_seq_ev_set_pitchbend(&event, chn, a);
|
|
break;
|
|
case ME_POLYAFTER:
|
|
// chnEvent2(chn, 0xa0, a, b);
|
|
break;
|
|
case ME_AFTERTOUCH:
|
|
snd_seq_ev_set_chanpress(&event, chn, a);
|
|
break;
|
|
case ME_SYSEX:
|
|
{
|
|
#if 0
|
|
SysexEvent* se = (SysexEvent*) e;
|
|
const unsigned char* p = se->data();
|
|
int n = se->len();
|
|
int len = n + sizeof(event) + 2;
|
|
char buf[len];
|
|
event.type = SND_SEQ_EVENT_SYSEX;
|
|
event.flags = SND_SEQ_EVENT_LENGTH_VARIABLE;
|
|
event.data.ext.len = n + 2;
|
|
event.data.ext.ptr = (void*)(buf + sizeof(event));
|
|
memcpy(buf, &event, sizeof(event));
|
|
char* pp = buf + sizeof(event);
|
|
*pp++ = 0xf0;
|
|
memcpy(pp, p, n);
|
|
pp += n;
|
|
*pp = 0xf7;
|
|
putEvent(&event);
|
|
#endif
|
|
return;
|
|
}
|
|
default:
|
|
qDebug("MidiAlsaDriver::putEvent(): event type 0x%02x not implemented", e.type());
|
|
return;
|
|
}
|
|
putEvent(&event);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// putEvent
|
|
// return false if event is delivered
|
|
//---------------------------------------------------------
|
|
|
|
bool AlsaMidiDriver::putEvent(snd_seq_event_t* event)
|
|
{
|
|
int error;
|
|
|
|
do {
|
|
error = snd_seq_event_output_direct(alsaSeq, event);
|
|
int len = snd_seq_event_length(event);
|
|
if (error == len) {
|
|
return false;
|
|
}
|
|
if (error < 0) {
|
|
if (error == -12) {
|
|
return true;
|
|
}
|
|
else {
|
|
qDebug("MidiAlsaDevice::%p putEvent(): midi write error: %s",
|
|
this, snd_strerror(error));
|
|
//exit(-1);
|
|
}
|
|
}
|
|
else
|
|
qDebug("MidiAlsaDevice::putEvent(): midi write returns %d, expected %d: %s",
|
|
error, len, snd_strerror(error));
|
|
} while (error == -12);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
#endif /* USE_ALSA */
|
|
|
|
|