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

V547 Expression 'error == 2' is always false.

V505 The 'alloca' function is used inside the loop. This can quickly overflow stack.

V505 The 'alloca' function is used inside the loop. This can quickly overflow stack.

V684 A value of the variable '(& event)->flags' is not modified. Consider inspecting the expression. It is possible that '1' should be present instead of '0'.

V684 A value of the variable '(& event)->flags' is not modified. Consider inspecting the expression. It is possible that '1' should be present instead of '0'.

V684 A value of the variable '(& event)->flags' is not modified. Consider inspecting the expression. It is possible that '1' should be present instead of '0'.

V684 A value of the variable '(& event)->flags' is not modified. Consider inspecting the expression. It is possible that '1' should be present instead of '0'.

V684 A value of the variable '(& event)->flags' is not modified. Consider inspecting the expression. It is possible that '1' should be present instead of '0'.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: _alsaPort, _alsaClient.

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: alsaSeq.