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

#include "mess.h"
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <sys/mman.h>
#include <pthread.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>
#include <cmath>

#include "ladspa.h"

extern void initMess();

#ifdef RTCAP
//---------------------------------------------------------
//   getCapabilities
//---------------------------------------------------------

void getCapabilities()
      {
#ifdef __linux__
      const char* napp = getenv("GIVERTCAP");
      system(napp ? napp : "givertcap");
#endif // __linux__
      }
#endif

//---------------------------------------------------------
//   doSetuid
//    Restore the effective UID to its original value.
//---------------------------------------------------------

#ifdef _POSIX_SAVED_IDS
static void doSetuid(uid_t euid, uid_t)
#else
static void doSetuid(uid_t euid, uid_t ruid)
#endif
      {
#ifdef RTCAP
      extern void getCapabilities();
      getCapabilities();
#else
      int status;

#ifdef _POSIX_SAVED_IDS
      status = seteuid (euid);
#else
      status = setreuid (ruid, euid);
#endif
      if (status < 0) {
            perror("doSetuid: Couldn't set uid.\n");
            }
#endif
      }

//---------------------------------------------------------
//   undoSetuid
//    Set the effective UID to the real UID.
//---------------------------------------------------------

static void undoSetuid(uid_t euid, uid_t ruid)
      {
#ifndef RTCAP
      int status;

#ifdef _POSIX_SAVED_IDS
      status = seteuid (ruid);
#else
      status = setreuid (euid, ruid);
#endif
      if (status < 0) {
            fprintf(stderr, "undoSetuid: Couldn't set uid (eff:%d,real:%d): %s\n",
               euid, ruid, strerror(errno));
            exit (status);
            }
#endif
      }

//---------------------------------------------------------
//   curTime
//---------------------------------------------------------

static double curTime()
      {
      struct timeval t;
      gettimeofday(&t, 0);
      return (double)((double)t.tv_sec + (t.tv_usec / 1000000.0));
      }

//---------------------------------------------------------
//   midi_run
//---------------------------------------------------------

static void* midi_run(void* d)
      {
      Mess* s = (Mess*) d;
      s->midiRun();
      pthread_exit(0);
      }

//---------------------------------------------------------
//   midiRun
//---------------------------------------------------------

void Mess::midiRun()
      {
#define BIG_ENOUGH_STACK (1024*1024*1)
      char buf[BIG_ENOUGH_STACK];
      for (int i = 0; i < BIG_ENOUGH_STACK; i++)
            buf[i] = i;
#undef BIG_ENOUGH_STACK

      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, 0);
      pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, 0);

      int policy;
      if ((policy = sched_getscheduler (0)) < 0) {
            printf("Cannot get current client scheduler: %s\n", strerror(errno));
            }

      printf("Mess: MidiThread set to %s priority 60\n",
         policy == SCHED_FIFO ? "SCHED_FIFO" : "SCHED_OTHER");

      pthread_mutex_lock(&messLock);
      pthread_cond_signal(&messReady);
      pthread_mutex_unlock(&messLock);

      MEvent* ev;
      for (;;) {
            int n = poll(pfd, npfd, -1);  // wait infinite for event
            if (n < 0) {
                  perror("MusE Mess: poll failed");
                  break;
                  }
            if (n == 0) {
                  fprintf(stderr, "MusE: Mess: poll return zero\n");
                  sleep(1);
                  continue;
                  }
            for(;;) {
                  snd_seq_event_t* event;
                  int rv = snd_seq_event_input(alsaSeq, &event);
                  if (rv < 0)
                        break;

                  pthread_mutex_lock(&lock);
                  double frame = startFrame;
                  pthread_mutex_unlock(&lock);

                  if (event->type == SND_SEQ_EVENT_PORT_START)
                        continue;

                  unsigned pos = lrint((curTime() - frame) * _sampleRate);

                  ev = 0;
                  switch(event->type) {
//                        case SND_SEQ_EVENT_PORT_START:
                        case SND_SEQ_EVENT_PORT_EXIT:
                        case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                              break;

                        case SND_SEQ_EVENT_NOTEON:
                        case SND_SEQ_EVENT_KEYPRESS:
                        case SND_SEQ_EVENT_NOTEOFF:
                              ev = allocMEvent();
                              ev->setSamplePos(pos);
                              ev->setType(event->type);
                              ev->setChan(event->data.note.channel);
                              ev->setDataA(event->data.note.note);
                              ev->setDataB(event->data.note.velocity);
                              break;

                        case SND_SEQ_EVENT_CONTROL14:
                        case SND_SEQ_EVENT_NONREGPARAM:
                        case SND_SEQ_EVENT_REGPARAM:
                        case SND_SEQ_EVENT_CONTROLLER:
                        case SND_SEQ_EVENT_PGMCHANGE:
                        case SND_SEQ_EVENT_CHANPRESS:
                              ev = allocMEvent();
                              ev->setSamplePos(pos);
                              ev->setType(event->type);
                              ev->setChan(event->data.control.channel);
                              ev->setDataA(event->data.control.param);
                              ev->setDataB(event->data.control.value);
                              break;


                        case SND_SEQ_EVENT_PITCHBEND:
                              ev = allocMEvent();
                              ev->setSamplePos(pos);
                              ev->setType(event->type);
                              ev->setChan(event->data.control.channel);
                              ev->setDataA((event->data.control.value >> 7) & 0x7f);
                              ev->setDataB(event->data.control.value & 0x7f);
                              break;

                        case SND_SEQ_EVENT_SYSEX:
                              {
                              int len = event->data.ext.len;
                              unsigned char* data = new unsigned char[len];
                              memcpy(data, event->data.ext.ptr, len);
                              ev = allocMEvent();
                              ev->setSamplePos(pos);
                              ev->setType(event->type);
                              ev->setData(data);
                              ev->setDataLen(len);
                              }
                              break;

                        default:
                              printf("mess: ALSA Seq input: type %d not handled\n", event->type);
                              break;
                        }
                  snd_seq_free_event(event);
                  if (ev) {
                        if (frame == 0.0) {
                              // audio not running
                              // allow for initialize events
                              processEvent(ev);
                              }
                        else {
                              pthread_mutex_lock(&lock);
                              events.push_back(ev);
                              pthread_mutex_unlock(&lock);
                              }
                        }
                  }
            }
      }

//---------------------------------------------------------
//   Mess
//---------------------------------------------------------

Mess::Mess(const char* cn, int chn)
   : _className(strdup(cn))
      {
      realTimePriority = 60;
      head             = 0;
      chunks           = 0;
      _channels        = chn;
      outputPorts      = new float*[_channels];
      _name            = 0;
      alsaSeq          = 0;
      pfd              = 0;
      npfd             = 0;
      startFrame       = 0.0;
      _sampleRate      = 44100;          // default
      pthread_mutex_init(&lock, 0);
      pthread_mutex_init(&messLock, 0);
      pthread_cond_init(&messReady, 0);
      }

//---------------------------------------------------------
//   Mess
//---------------------------------------------------------

Mess::~Mess()
      {
      pthread_cancel(midiThread);
      pthread_join(midiThread, 0);
      pthread_mutex_destroy(&lock);
      pthread_mutex_destroy(&messLock);

      Chunk* n = chunks;
      while (n) {
            Chunk* p = n;
            n = n->next;
            delete p;
            }

      delete[] outputPorts;
      if (_name)
            delete _name;
      if (_className)
            delete _className;
      if (!alsaSeq) {
            printf("~Mess(): no seq!\n");
            return;
            }
      int error = snd_seq_delete_port(alsaSeq, _alsaPort.port);
      if (error) {
            fprintf(stderr, "ALSA: cannot delete port: %s\n",
               snd_strerror(error));
            }
      // why does this not remove client from client table?:
      error = snd_seq_close(alsaSeq);
      if (error) {
            fprintf(stderr, "ALSA: cannot close seq: %s\n",
               snd_strerror(error));
            }
      }

//---------------------------------------------------------
//   grow
//---------------------------------------------------------

void Mess::grow()
      {
      Chunk* n = new Chunk;
      n->next  = chunks;
      chunks   = n;

      char* start = n->mem;
      char* last  = &start[(nelem-1)*sizeof(MEvent)];

      for (char* p = start; p < last; p += sizeof(MEvent)) {
            reinterpret_cast<Verweis*>(p)->next
              = reinterpret_cast<Verweis*>(p + sizeof(MEvent));
            }
      reinterpret_cast<Verweis*>(last)->next = 0;
      head = reinterpret_cast<Verweis*>(start);
      }

//---------------------------------------------------------
//   registerAlsa
//---------------------------------------------------------

void Mess::registerAlsa()
      {
      //-----------------------------------------
      //  connect to ALSA and get poll
      //  descriptors
      //-----------------------------------------

      if (alsaSeq == 0) {
            int error = snd_seq_open(&alsaSeq, "hw", SND_SEQ_OPEN_DUPLEX|SND_SEQ_NONBLOCK, 0);
            if (error < 0) {
                  fprintf(stderr, "Mess: Could not open ALSA sequencer: %s\n",
                     snd_strerror(error));
                  alsaSeq = 0;
                  return;
                  }
            }

      snd_seq_set_client_name(alsaSeq, _className);
      npfd = snd_seq_poll_descriptors_count(alsaSeq, POLLIN);
      pfd  = new pollfd[npfd];
      snd_seq_poll_descriptors(alsaSeq, pfd, npfd, POLLIN);
      for (int i = 0; i < npfd; ++i) {
            pfd[i].events  = POLLIN;
            pfd[i].revents = 0;
            }

      //-----------------------------------------
      //  find an unused alsa port name
      //  find muse port
      //-----------------------------------------

      bool museFound = false;
      char buffer[256];
      for (int i = 1;; ++i) {
            bool found = false;
            sprintf(buffer, "%s-%d", _className, i);

            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) {
                        const char* portname;
                        portname = snd_seq_port_info_get_name(pinfo);
                        if (strcmp(portname, buffer) == 0) {
                              found = true;
                              break;
                              }
                        if (strcmp(portname, "MusE Port 0") == 0) {
                              museFound = true;
                              snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
                              _musePort.port   = snd_seq_port_info_get_port(pinfo);
                              _musePort.client = snd_seq_client_info_get_client(cinfo);
                              }
                        }
                  if (found)
                        break;
                  }
            if (!found)
                  break;
            }
      if (!museFound) {
            printf("Mess: muse port not found!\n");
            return;
            }
      _name = strdup(buffer);

      //-----------------------------------------
      //    create port to alsa sequencer
      //-----------------------------------------

      int port  = snd_seq_create_simple_port(alsaSeq, _name,
         SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
         SND_SEQ_PORT_TYPE_APPLICATION);
      if (port < 0) {
            perror("create port");
            return;
            }
      _alsaPort.port   = port;
      _alsaPort.client = snd_seq_client_id(alsaSeq);

      //----------------------------------------------
      // create the midi thread
      // with SCHED_FIFO and priority 60

      pthread_attr_t* attributes = 0;
      doSetuid(euid, ruid);
      if (realTimePriority) {
            struct sched_param rt_param;
            memset(&rt_param, 0, sizeof(rt_param));
            rt_param.sched_priority = 60;

            attributes = new pthread_attr_t;
            pthread_attr_init(attributes);

            if (pthread_attr_setschedpolicy(attributes, SCHED_FIFO)) {
                  printf("Mess: cannot set FIFO scheduling class for RT thread\n");
                  }
            if (pthread_attr_setschedparam(attributes, &rt_param)) {
                  printf("Mess: Cannot set scheduling priority for RT thread (%s)\n", strerror(errno));
                  }
            if (pthread_attr_setscope(attributes, PTHREAD_SCOPE_SYSTEM)) {
                  printf("Mess: Cannot set scheduling scope for RT thread\n");
                  }
            }
      pthread_mutex_lock(&messLock);

      int err = pthread_create(&midiThread, attributes, midi_run, this);

      if (err) {
            printf("Mess: Couldn't create midi thread: %s\n",
               strerror(errno));
      } else
	      pthread_cond_wait(&messReady, &messLock);
      pthread_mutex_unlock(&messLock);

      if (attributes) {
            pthread_attr_destroy(attributes);
            delete attributes;
            }
      undoSetuid(euid, ruid);
      }

//---------------------------------------------------------
//   getPatchName
//---------------------------------------------------------

const char* Mess::getPatchName(int /*channel*/, int /*hbank*/, int /*lbank*/,
   int /*prog*/, MType /*type*/)
      {
      return "???";
      }

//---------------------------------------------------------
//    sysex
//---------------------------------------------------------

void Mess::sendSysex(const unsigned char* p, int n)
      {
      snd_seq_event_t event;
      int len             = n + sizeof(event) + 2;
      char* buf           = new char[len];
      memset(&event, 0, sizeof(event));
      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));
      event.tag           = 0;
      event.queue         = SND_SEQ_QUEUE_DIRECT;
      event.dest          = _musePort;
      event.source        = _alsaPort;

      memcpy(buf, &event, sizeof(event));
      char* pp = buf + sizeof(event);
      *pp++ = 0xf0;
      memcpy(pp, p, n);
      pp += n;
      *pp = 0xf7;

      int error = snd_seq_event_output_direct(alsaSeq, &event);
      if (error < 0)
            fprintf(stderr, "sysex: src:%d:%d-dst:%d:%d midi write error (n=%d): %s\n",
                _alsaPort.port, _alsaPort.client, _musePort.port, _musePort.client,
                n, snd_strerror(error));
      delete[] buf;
      }

//---------------------------------------------------------
//   processreplace
//---------------------------------------------------------

void Mess::processreplace(int len)
      {
      pthread_mutex_lock(&lock);
      startFrame = curTime();
      pthread_mutex_unlock(&lock);

      for (int channel = 0; channel < _channels; ++channel)
            memset(outputPorts[channel], 0, len * sizeof(float));

      int offset = 0;

      //---------------------------------------------------
      //  process all midi events for this buffer
      //---------------------------------------------------

      for (;;) {
            //---------------------------------------------
            //  get next midi event
            //---------------------------------------------
            pthread_mutex_lock(&lock);

            if (events.empty()) {
                  pthread_mutex_unlock(&lock);
                  break;
                  }
            MEvent* e = events.front();
            int samplePos = e->samplePos();
            if (samplePos < 0)                  // ???
                  samplePos = 0;
            if (samplePos >= len) {
                  samplePos %= len;
                  for (iMEvent i = events.begin(); i != events.end(); ++i) {
                        (*i)->adjustSamplePos(len);
                        }
                  pthread_mutex_unlock(&lock);
                  break;
                  }
            events.erase(events.begin());
            pthread_mutex_unlock(&lock);

            //---------------------------------------------
            //  generate samples up to this midi event
            //---------------------------------------------

            int samplesToSynthesize = samplePos - offset;

// printf("write %d %d  %d\n", samplesToSynthesize, offset, samplePos);
            write(samplesToSynthesize, outputPorts, offset);
            offset += samplesToSynthesize;
            processEvent(e);
            freeMEvent(e);
            }

      //---------------------------------------------------
      //  any samples left?
      //---------------------------------------------------

      int samplesToSynthesize = len - offset;
      if (samplesToSynthesize > 0) {
// printf("write %d %d\n", samplesToSynthesize, offset);
            write(samplesToSynthesize, outputPorts, offset);
            }
      }

//---------------------------------------------------------
//   MessMono
//---------------------------------------------------------

MessMono::MessMono(const char* name, int channels)
   : Mess(name, channels)
      {
      }

MessMono::~MessMono()
      {
      }

//---------------------------------------------------------
//   midiNoteOn
//---------------------------------------------------------

void MessMono::midiNoteOn(int channel, int pitch, int velo)
      {
      if (velo == 0) {
            midiNoteOff(channel, pitch);
            return;
            }
      pitchStack.push_back(PitchVelo(channel, pitch, velo));
      noteon(channel, pitch, velo);
      }

//---------------------------------------------------------
//   midiNoteOff
//---------------------------------------------------------

void MessMono::midiNoteOff(int channel, int pitch)
      {
      if (pitchStack.empty())
            return;
      if (pitchStack.back().pitch == pitch) {
            pitchStack.pop_back();
            if (pitchStack.empty()) {
                  noteoff(channel, pitch);
                  return;
                  }
            PitchVelo pv = pitchStack.back();
            noteon(pv.channel, pv.pitch, pv.velo);  // change pitch
            return;
            }
      for (std::list<PitchVelo>::iterator i = pitchStack.begin();
         i != pitchStack.end(); ++i) {
            if ((*i).pitch == pitch) {
                  pitchStack.erase(i);
                  return;
                  }
            }
      // no noteon found
      // emergency stop:
      noteoff(channel, pitch);
      }

//---------------------------------------------------------
//   processEvents
//    process received midi event
//---------------------------------------------------------

void MessMono::processEvent(MEvent* ev)
      {
      switch(ev->type()) {
            case SND_SEQ_EVENT_NOTEON:
            case SND_SEQ_EVENT_KEYPRESS:
                  midiNoteOn(ev->chan(), ev->dataA(), ev->dataB());
                  break;

            case SND_SEQ_EVENT_NOTEOFF:
                  midiNoteOff(ev->chan(), ev->dataA());
                  break;

		case SND_SEQ_EVENT_CONTROL14:
		case SND_SEQ_EVENT_NONREGPARAM:
		case SND_SEQ_EVENT_REGPARAM:
		case SND_SEQ_EVENT_CONTROLLER:
			switch(ev->dataA()) {
				case 0x62:	ctrlLo = ev->dataB(); break;
				case 0x63:	ctrlHi = ev->dataB(); break;
				case 0x06:	dataHi = ev->dataB(); break;
				case 0x026:	dataLo = ev->dataB(); break;
			}
			if(ev->dataA() == 0x06)
				setController(ev->chan(), ctrlLo+ctrlHi*128, dataLo+dataHi*128);
			break;

		case SND_SEQ_EVENT_SYSEX:
			sysex(ev->data(), ev->dataLen());
			break;
            case SND_SEQ_EVENT_CHANPRESS:
            case SND_SEQ_EVENT_PITCHBEND:
            case SND_SEQ_EVENT_PGMCHANGE:
                  break;
            default:
                  printf("MessMono: event type %d not processed\n", ev->type());
                  break;
            }
      }

//=========================================================
//    LADSPA
//=========================================================

static Mess* (*xinstantiate)(const char* name);

//---------------------------------------------------------
//   instantiate
//    construct a new synthesizer instance
//---------------------------------------------------------

static LADSPA_Handle instantiate(const LADSPA_Descriptor* descr, unsigned long sr)
      {
      Mess* synth = xinstantiate(descr->Label);
      synth->setSampleRate(sr);
      if (synth->init()) {
            delete synth;
            synth = 0;
            }
      return synth;
      }

//---------------------------------------------------------
//   run
//---------------------------------------------------------

static void run(LADSPA_Handle instance, unsigned long sampleCount)
      {
      ((Mess *)instance)->processreplace(sampleCount);
      }

//---------------------------------------------------------
//   connectPort
//    Connect a port to a data location.
//---------------------------------------------------------

static void connect(LADSPA_Handle Instance, unsigned long port, float* data)
      {
      ((Mess *)Instance)->setPort(port, data);
      }

//---------------------------------------------------------
//   activate
//---------------------------------------------------------

static void activate(LADSPA_Handle instance)
      {
      ((Mess *)instance)->registerAlsa();
      }

//---------------------------------------------------------
//   cleanup
//---------------------------------------------------------

static void cleanup(LADSPA_Handle instance)
      {
      delete (Mess *)instance;
      }

//---------------------------------------------------------
//   portNames
//---------------------------------------------------------

static const char* portNames[] = {
      "Output",
      "Output",
      };

//---------------------------------------------------------
//   portDescriptors
//---------------------------------------------------------

static LADSPA_PortDescriptor portDescriptors[] = {
      LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO,
      LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO,
      };

//---------------------------------------------------------
//   portRangeHints
//---------------------------------------------------------

static LADSPA_PortRangeHint portRangeHints[] = {
      { 0, 0.0, 0.0 },
      { 0, 0.0, 0.0 },
      };

static LADSPA_Descriptor descriptor = {
      1223,
      "label",
      LADSPA_PROPERTY_HARD_RT_CAPABLE,
      "name",
      "maker",
      "copyright",
      1,
      portDescriptors,
      portNames,
      portRangeHints,
      0,                // impl. data
      instantiate,
      connect,
      activate,
      run,
      0,
      0,
      0,
      cleanup
      };

//---------------------------------------------------------
//   ladspa_descriptor
//    Return a descriptor of the requested plugin type.
//---------------------------------------------------------

const LADSPA_Descriptor* ladspa_descriptor(unsigned long i)
      {
      initMess();         // create ladspa descriptor
      return (i == 0) ? &descriptor : 0;
      }

//---------------------------------------------------------
//   initMess
//    ports  -  1-Mono  2-Stereo
//---------------------------------------------------------

void Mess::initMess(unsigned long id, const char* label, const char* maker,
   const char* name, const char* copyright, unsigned long ports,
   Mess* (inst)(const char*))
      {
      descriptor.UniqueID  = id;
      descriptor.Label     = label;
      descriptor.Name      = name;
      descriptor.Maker     = maker;
      descriptor.Copyright = copyright;
      descriptor.PortCount = ports;
      xinstantiate         = inst;
      }

