//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: alsamidi.cpp,v 1.1.1.1 2003/10/29 10:06:12 wschweer Exp $
//  (C) Copyright 2000-2001 Werner Schweer (ws@seh.de)
//=========================================================

#include "alsamidi.h"
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <qstring.h>
#include <glob.h>
#include "globals.h"
#include "mididev.h"
#include "midictrl.h"
#include "../midiport.h"
#include "../midithread.h"
#include <list>
#include "mpevent.h"

static int alsaSeqFdi = -1;
static int alsaSeqFdo = -1;

snd_seq_t* alsaSeq;
int alsaSeqFd = -1;
snd_seq_addr_t musePort;

//---------------------------------------------------------
//   MidiAlsaDevice
//---------------------------------------------------------

MidiAlsaDevice::MidiAlsaDevice(const snd_seq_addr_t& a, const QString& n)
   : MidiDevice(n)
      {
      adr = a;
      init();
      }

int MidiAlsaDevice::selectWfd()
      {
      return alsaSeqFdo;
      }

//---------------------------------------------------------
//   open
//---------------------------------------------------------

QString MidiAlsaDevice::open(int rw)
      {
      rwFlag = rw;
      snd_seq_port_subscribe_t* subs;

      snd_seq_port_subscribe_alloca(&subs);

      // subscribe for writing
      if (rwFlag & 1) {
            snd_seq_port_subscribe_set_sender(subs, &musePort);
            snd_seq_port_subscribe_set_dest(subs, &adr);
            int error = snd_seq_subscribe_port(alsaSeq, subs);
            if (error < 0)
                  return QString("Play: ")+QString(snd_strerror(error));
            }

      // subscribe for reading
      if (rwFlag & 2) {
            snd_seq_port_subscribe_set_dest(subs, &musePort);
	      snd_seq_port_subscribe_set_sender(subs, &adr);
            int error = snd_seq_subscribe_port(alsaSeq, subs);
            if (error < 0)
                  return QString("Rec: ") + QString(snd_strerror(error));
            }
      return QString("OK");
      }

//---------------------------------------------------------
//   close
//---------------------------------------------------------

void MidiAlsaDevice::close()
      {
      snd_seq_port_subscribe_t* subs;

      snd_seq_port_subscribe_alloca(&subs);
      if (rwFlag & 1) {
            snd_seq_port_subscribe_set_sender(subs, &musePort);
            snd_seq_port_subscribe_set_dest(subs, &adr);
            snd_seq_unsubscribe_port(alsaSeq, subs);
            }

      if (rwFlag & 2) {
	      snd_seq_port_subscribe_set_dest(subs, &musePort);
	      snd_seq_port_subscribe_set_sender(subs, &adr);
            snd_seq_unsubscribe_port(alsaSeq, subs);
            }
      }

//---------------------------------------------------------
//   putEvent
//---------------------------------------------------------

void MidiAlsaDevice::putEvent(const MidiPlayEvent* e)
      {
      if (midiOutputTrace) {
            printf("MidiOut: midiAlsa: ");
            e->dump();
            }
      int chn = e->channel();
      int a = e->dataA();
      int b = e->dataB();

      snd_seq_event_t event;
      prepareEvent(&event);
      switch(e->type()) {
            case 0x90:
                  snd_seq_ev_set_noteon(&event, chn, a, b);
                  break;
            case 0x80:
                  snd_seq_ev_set_noteoff(&event, chn, a, 0);
                  break;
            case 0xc0:
                  snd_seq_ev_set_pgmchange(&event, chn, a);
                  break;
            case 0xb0:
                  snd_seq_ev_set_controller(&event, chn, a, b);
                  break;
            case 0xe0:
                  snd_seq_ev_set_pitchbend(&event, chn, (a + (b <<7)) - 8192);
                  break;
            case 0xa0:
                  // chnEvent2(chn, 0xa0, a, b);
                  break;
            case 0xd0:
                  snd_seq_ev_set_chanpress(&event, chn, a);
                  break;
            case 0xf0:
                  {
                  const unsigned char* p = e->data();
                  int n                  = e->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);
                  }
                  return;

            default:
                  printf("MidiAlsaDevice::putEvent(): event type %d not implemented\n",
                     e->type());
                  return;
            }
      putEvent(&event);
      }

//---------------------------------------------------------
//   putEvent
//---------------------------------------------------------

void MidiAlsaDevice::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;
            printf("MidiAlsaDevice::putEvent(): midi write error, returns %d expected %d: %s\n",
               error, len, snd_strerror(error));
            if (error == 12)
                  sleep(1);
            } while (error == -12);
      }

//---------------------------------------------------------
//   putClock
//---------------------------------------------------------

void MidiAlsaDevice::putClock()
      {
      if (midiOutputTrace)
            printf("midiAlsa: clock\n");
      snd_seq_event_t event;
      prepareEvent(&event);
      event.type    = SND_SEQ_EVENT_CLOCK;
      int error = snd_seq_event_output_direct(alsaSeq, &event);
      if (error < 0)
            printf("MidiAlsaDevice::putClock(): alsa midi write error: %s\n", snd_strerror(error));
      }

//---------------------------------------------------------
//   putStart
//---------------------------------------------------------

void MidiAlsaDevice::putStart()
      {
      if (midiOutputTrace)
            printf("midiAlsa: start\n");
      snd_seq_event_t event;
      prepareEvent(&event);
      event.type    = SND_SEQ_EVENT_START;
      int error = snd_seq_event_output_direct(alsaSeq, &event);
      if (error < 0)
            printf("putStart: alsa midi write error: %s\n", snd_strerror(error));
      }

//---------------------------------------------------------
//   putStop
//---------------------------------------------------------

void MidiAlsaDevice::putStop()
      {
      if (midiOutputTrace)
            printf("midiAlsa: stop\n");
      snd_seq_event_t event;
      prepareEvent(&event);
      event.type    = SND_SEQ_EVENT_STOP;
      int error = snd_seq_event_output_direct(alsaSeq, &event);
      if (error < 0)
            printf("putStop: alsa midi write error: %s\n", snd_strerror(error));
      }

//---------------------------------------------------------
//   putContinue
//---------------------------------------------------------

void MidiAlsaDevice::putContinue()
      {
      if (midiOutputTrace)
            printf("midiAlsa: continue\n");
      snd_seq_event_t event;
      prepareEvent(&event);
      event.type    = SND_SEQ_EVENT_CONTINUE;
      int error = snd_seq_event_output_direct(alsaSeq, &event);
      if (error < 0)
            printf("putContinue: alsa midi write error: %s\n", snd_strerror(error));
      }

//---------------------------------------------------------
//   putSongpos
//---------------------------------------------------------

void MidiAlsaDevice::putSongpos(int beat)
      {
      if (midiOutputTrace)
            printf("midiAlsa: songpos %d\n", beat);
      snd_seq_event_t event;
      prepareEvent(&event);
      event.data.control.value = beat;
      event.type    = SND_SEQ_EVENT_SONGPOS;
      int error = snd_seq_event_output_direct(alsaSeq, &event);
      if (error < 0)
            printf("putContinue: alsa midi write error: %s\n", snd_strerror(error));
      }

//---------------------------------------------------------
//   initMidiAlsa
//    return true on error
//---------------------------------------------------------

bool initMidiAlsa()
      {
      int error = snd_seq_open(&alsaSeq, "hw", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK);
      if (error < 0) {
            fprintf(stderr, "Could not open ALSA sequencer: %s\n",
               snd_strerror(error));
            alsaFound = false;
            return true;
            }
      const int inCap  = SND_SEQ_PORT_CAP_SUBS_READ;
      const int outCap = SND_SEQ_PORT_CAP_SUBS_WRITE;

      alsaFound = true;
      snd_seq_client_info_t *cinfo;
      snd_seq_client_info_alloca(&cinfo);
      snd_seq_client_info_set_client(cinfo, -1);

      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) == 0)
                        continue;
                  snd_seq_addr_t adr = *snd_seq_port_info_get_addr(pinfo);
                  MidiAlsaDevice* dev = new MidiAlsaDevice(adr, QString(snd_seq_port_info_get_name(pinfo)));
                  int flags = 0;
                  if (capability & outCap)
                        flags |= 1;
                  if (capability & inCap)
                        flags |= 2;
                  dev->setrwFlags(flags);
                  if (debugMsg)
                        printf("ALSA port add: <%s>, %d:%d flags %d 0x%0x\n",
                           snd_seq_port_info_get_name(pinfo),
                           adr.client, adr.port,
                           flags, capability);
                  midiDevices.add(dev);
                  }
            }
      snd_seq_set_client_name(alsaSeq, "MusE Sequencer");
      int ci = snd_seq_poll_descriptors_count(alsaSeq, POLLIN);
      int co = snd_seq_poll_descriptors_count(alsaSeq, POLLOUT);

      if (ci > 1 || co > 1) {
            printf("ALSA midi: cannot handle more than one poll fd\n");
            abort();
            }

      struct pollfd pfdi[ci];
      struct pollfd pfdo[co];
      snd_seq_poll_descriptors(alsaSeq, pfdi, ci, POLLIN);
      snd_seq_poll_descriptors(alsaSeq, pfdo, co, POLLOUT);
      alsaSeqFdo = pfdo[0].fd;
      alsaSeqFdi = pfdi[0].fd;

      int port  = snd_seq_create_simple_port(alsaSeq, "MusE Port 0",
         inCap | outCap | SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE,
         SND_SEQ_PORT_TYPE_APPLICATION);
      if (port < 0) {
            perror("create port");
            exit(1);
            }
      musePort.port   = port;
      musePort.client = snd_seq_client_id(alsaSeq);

      //-----------------------------------------
      //    subscribe to "Announce"
      //    this enables callbacks for any
      //    alsa port changes
      //-----------------------------------------

      snd_seq_addr_t aadr;
      aadr.client = SND_SEQ_CLIENT_SYSTEM;
      aadr.port   = SND_SEQ_PORT_SYSTEM_ANNOUNCE;

      snd_seq_port_subscribe_t* subs;
      snd_seq_port_subscribe_alloca(&subs);
      snd_seq_port_subscribe_set_dest(subs, &musePort);
      snd_seq_port_subscribe_set_sender(subs, &aadr);
      error = snd_seq_subscribe_port(alsaSeq, subs);
      if (error < 0) {
            printf("Alsa: Subscribe System failed: %s", snd_strerror(error));
            return true;
            }
      return false;
      }

struct AlsaPort {
      snd_seq_addr_t adr;
      char* name;
      int flags;
      AlsaPort(snd_seq_addr_t a, const char* s, int f) {
            adr = a;
            name = strdup(s);
            flags = f;
            }
      };

static std::list<AlsaPort> portList;

//---------------------------------------------------------
//   alsaScanMidiPorts
//---------------------------------------------------------

void alsaScanMidiPorts()
      {
//    printf("alsa scan midi ports\n");
      if (!alsaFound)
            return;
      const int inCap  = SND_SEQ_PORT_CAP_SUBS_READ;
      const int outCap = SND_SEQ_PORT_CAP_SUBS_WRITE;

      portList.clear();

      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) == 0)
                     && ((capability & inCap) == 0))
                        continue;
                  snd_seq_addr_t adr;
                  const char* name;
                  adr  = *snd_seq_port_info_get_addr(pinfo);
                  name = snd_seq_port_info_get_name(pinfo);
                  if (adr.client == musePort.client && adr.port == musePort.port)
                        continue;
                  int flags = 0;
                  if (capability & outCap)
                        flags |= 1;
                  if (capability & inCap)
                        flags |= 2;
// printf("ALSA port add: <%s>, flags %d\n", name, flags);
                  portList.push_back(AlsaPort(adr, name, flags));
                  }
            }
      //
      //  check for devices to delete
      //
      for (iMidiDevice i = midiDevices.begin(); i != midiDevices.end();) {
            MidiAlsaDevice* d = dynamic_cast<MidiAlsaDevice*>(*i);
            if (d == 0) {
                  ++i;
                  continue;
                  }
            std::list<AlsaPort>::iterator k = portList.begin();
            for (; k != portList.end(); ++k) {
                  if (k->adr.client == d->adr.client
                     && k->adr.port == d->adr.port) {
                        break;
                        }
                  }
            if (k == portList.end()) {
                  if (d->port() != -1)
                        midiPorts[d->port()].setMidiDevice(0);
                  iMidiDevice k = i;
// printf("erase device\n");
                  ++i;
                  midiDevices.erase(k);
                  }
            else {
                  ++i;
                  }
            }
      //
      //  check for devices to add
      //
      for (std::list<AlsaPort>::iterator k = portList.begin(); k != portList.end(); ++k) {
            iMidiDevice i = midiDevices.begin();
// printf("ALSA port: <%s>\n", k->name);
            for (;i != midiDevices.end(); ++i) {
                  MidiAlsaDevice* d = dynamic_cast<MidiAlsaDevice*>(*i);
                  if (d == 0)
                        continue;
                  if ((k->adr.client == d->adr.client) && (k->adr.port == d->adr.port)) {
                        break;
                        }
                  }
            if (i == midiDevices.end()) {
                  // add device
                  MidiAlsaDevice* dev = new MidiAlsaDevice(k->adr,
                     QString(k->name));
                  dev->setrwFlags(k->flags);
                  midiDevices.add(dev);
// printf("add device\n");
                  }
            }
      }

//---------------------------------------------------------
//   alsaSelectRfd
//---------------------------------------------------------

int alsaSelectRfd()
      {
      return alsaSeqFdi;
      }

//---------------------------------------------------------
//   alsaSelectWfd
//---------------------------------------------------------

int alsaSelectWfd()
      {
      return alsaSeqFdo;
      }

//---------------------------------------------------------
//   processInput
//---------------------------------------------------------

void alsaProcessMidiInput()
      {
      snd_seq_event_t* ev;
      for (;;) {
            int rv = snd_seq_event_input(alsaSeq, &ev);
            if (rv < 0) {
                  break;
                  }
// printf("alsa midi input type %d\n", ev->type);
            switch(ev->type) {
                  case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                  case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
                        return;
                  case SND_SEQ_EVENT_CLIENT_START:
                  case SND_SEQ_EVENT_CLIENT_EXIT:
                        // return;
                        // on first start of a software synthesizer we only
                        // get CLIENT_START event and no PORT_START, why?

                  case SND_SEQ_EVENT_PORT_START:
                  case SND_SEQ_EVENT_PORT_EXIT:
                        alsaScanMidiPorts();
                        midiThread->midiPortsChanged();  // signal gui
                        snd_seq_free_event(ev);
                        return;
                  }

            int curPort = -1;
            //
            // find real source device
            //
            for (iMidiDevice i = midiDevices.begin(); i != midiDevices.end(); ++i) {
                  MidiAlsaDevice* d = dynamic_cast<MidiAlsaDevice*>(*i);
                  if (d  && d->adr.client == ev->source.client
                     && d->adr.port == ev->source.port) {
                        curPort = d->port();
                        }
                  }
// printf("port <%s> %d %d %d\n", name().latin1(), rwFlag, rwFlags(), port());
// printf("type %d\n", ev->type);

            if (curPort == -1) {
                  if (debugMsg) {
                        fprintf(stderr, "no port %d:%d found for received alsa event\n",
                           ev->source.client, ev->source.port);
                        }
                  snd_seq_free_event(ev);
                  return;
                  }

            MidiPlayEvent* event = 0;
            switch(ev->type) {
                  case SND_SEQ_EVENT_NOTEON:
                  case SND_SEQ_EVENT_KEYPRESS:
                        event = new MidiPlayEvent(
                           curPort, ev->data.note.channel,
                           0x90,
                           ev->data.note.note,
                           ev->data.note.velocity);
                        break;

                  case SND_SEQ_EVENT_NOTEOFF:
                        event = new MidiPlayEvent(
                           curPort,
                           ev->data.note.channel,
                           0x80,
                           ev->data.note.note,
                           ev->data.note.velocity);
                        break;

                  case SND_SEQ_EVENT_CHANPRESS:
                        event = new MidiPlayEvent(
                           curPort,
                           ev->data.control.channel,
                           0xd0,
                           ev->data.control.value,
                           0);
                        break;

                  case SND_SEQ_EVENT_PGMCHANGE:
                        event = new MidiPlayEvent(
                           curPort,
                           ev->data.control.channel,
                           0xc0,
                           ev->data.control.value,
                           0);
                        break;

                  case SND_SEQ_EVENT_PITCHBEND:
                        {
                        int val = ev->data.control.value - 8192;
                        event = new MidiPlayEvent(
                           curPort,
                           ev->data.control.channel,
                           0xe0,
                           val & 0x7f,
                           (val >> 7) & 0x7f
                           );
                        }
                        break;

                  case SND_SEQ_EVENT_CONTROLLER:
                        event = new MidiPlayEvent(
                           curPort,
                           ev->data.control.channel,
                           0xb0,
                           ev->data.control.param,
                           ev->data.control.value);
                        break;

                  case SND_SEQ_EVENT_CLOCK:
                        midiThread->realtimeSystemInput(curPort, 0xf8);
                        break;

                  case SND_SEQ_EVENT_START:
                        midiThread->realtimeSystemInput(curPort, 0xfa);
                        break;

                  case SND_SEQ_EVENT_CONTINUE:
                        midiThread->realtimeSystemInput(curPort, 0xfb);
                        break;

                  case SND_SEQ_EVENT_STOP:
                        midiThread->realtimeSystemInput(curPort, 0xfc);
                        break;

                  case SND_SEQ_EVENT_TICK:
                        midiThread->realtimeSystemInput(curPort, 0xf9);
                        break;

                  case SND_SEQ_EVENT_SYSEX:
                        event = new MidiPlayEvent(curPort,
                           (unsigned char*)(ev->data.ext.ptr)+1,
                           ev->data.ext.len-2);
                        break;
                  case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                  case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:  // write port is released
                        break;
                  case SND_SEQ_EVENT_SONGPOS:
                        midiThread->setSongPosition(curPort, ev->data.control.value);
                        break;
                  case SND_SEQ_EVENT_SENSING:
                        break;
                  case SND_SEQ_EVENT_QFRAME:
                        midiThread->mtcInputQuarter(curPort, ev->data.control.value);
                        break;
                  case SND_SEQ_EVENT_CLIENT_START:
                  case SND_SEQ_EVENT_CLIENT_EXIT:
                  case SND_SEQ_EVENT_CLIENT_CHANGE:
                  case SND_SEQ_EVENT_PORT_CHANGE:
                  case SND_SEQ_EVENT_SONGSEL:
                  case SND_SEQ_EVENT_TIMESIGN:
                  case SND_SEQ_EVENT_KEYSIGN:
                  case SND_SEQ_EVENT_SETPOS_TICK:
                  case SND_SEQ_EVENT_SETPOS_TIME:
                  case SND_SEQ_EVENT_TEMPO:
                  case SND_SEQ_EVENT_TUNE_REQUEST:
                  case SND_SEQ_EVENT_RESET:

                  case SND_SEQ_EVENT_NOTE:
                  case SND_SEQ_EVENT_CONTROL14:
                  case SND_SEQ_EVENT_NONREGPARAM:
                  case SND_SEQ_EVENT_REGPARAM:
printf("alsa midi input type %d received\n", ev->type);
                        break;
                  default:
                        printf("ALSA Midi input: type %d not handled\n", ev->type);
                        break;
                  }
            if (event)
                  midiThread->eventReceived(event);
            snd_seq_free_event(ev);
            if (rv == 0)
                  break;
            }
      }

