//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: jack.cpp,v 1.30.2.4 2005/01/08 22:27:55 spamatica Exp $
//  (C) Copyright 2002 Werner Schweer (ws@seh.de)
//=========================================================

#include "config.h"
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>

#include "audio.h"
#include "globals.h"
#include "song.h"
#include "jackaudio.h"
#include "track.h"

#ifndef RTCAP
extern void doSetuid();
extern void undoSetuid();
#endif

#ifdef VST_SUPPORT
#include <fst.h>
#endif

JackAudioDevice* jackAudio;

//---------------------------------------------------------
//   jack_thread_init
//---------------------------------------------------------

static void jack_thread_init (void* /*data*/)
      {
      doSetuid();
      if (jackAudio->isRealtime()) {
            struct sched_param rt_param;
            int rv;
            memset(&rt_param, 0, sizeof(sched_param));
            int type;
            rv = pthread_getschedparam(pthread_self(), &type, &rt_param);
            if (rv != 0)
                  perror("get scheduler parameter");
            if (type != SCHED_FIFO) {
                  fprintf(stderr, "JACK thread not running SCHED_FIFO, try to set...\n");

                  memset(&rt_param, 0, sizeof(sched_param));
                  rt_param.sched_priority = 1;
                  rv = pthread_setschedparam(pthread_self(), SCHED_FIFO, &rt_param);
                  if (rv != 0)
                        perror("2set realtime scheduler");
                  memset(&rt_param, 0, sizeof(sched_param));
                  rv = pthread_getschedparam(pthread_self(), &type, &rt_param);
                  if (rv != 0)
                        perror("get scheduler parameter");
                  if (type != SCHED_FIFO)
                        fprintf(stderr, "JACK still not running FIFO !?!\n"
                        "======reliable RT operation not possible!!======\n");
                  else
                        fprintf(stderr, "JACK thread succesfully set to SCHED_FIFO\n");
                  }
            }
#ifdef VST_SUPPORT
      if (loadVST)
            fst_adopt_thread();
#endif
      undoSetuid();
      }

//---------------------------------------------------------
//   processAudio
//    JACK callback
//---------------------------------------------------------

static int processAudio(jack_nframes_t frames, void*)
      {
      segmentSize = frames;
      audio->process((unsigned long)frames);
      return 0;
      }

//---------------------------------------------------------
//   processSync
//    return TRUE (non-zero) when ready to roll.
//---------------------------------------------------------

static int processSync(jack_transport_state_t state, jack_position_t* pos, void*)
      {
      int audioState = Audio::STOP;
      switch (state) {
            case JackTransportStopped:   audioState = Audio::STOP;
            case JackTransportLooping:
            case JackTransportRolling:   audioState = Audio::PLAY;
            case JackTransportStarting:  audioState = Audio::START_PLAY;
            }
      unsigned frame = pos->frame;
      return audio->sync(audioState, frame);
      }

//---------------------------------------------------------
//   processShutdown
//---------------------------------------------------------

static void processShutdown(void*)
      {
      audio->shutdown();
      }

//---------------------------------------------------------
//   jackError
//---------------------------------------------------------

static void jackError(const char *s)
      {
      fprintf(stderr, "JACK ERROR: %s\n", s);
      }

//---------------------------------------------------------
//   noJackError
//---------------------------------------------------------

static void noJackError(const char* /* s */)
      {
      }

//---------------------------------------------------------
//   JackAudioDevice
//---------------------------------------------------------

JackAudioDevice::JackAudioDevice(jack_client_t* cl, char * name)
   : AudioDevice()
      {
      strcpy(jackRegisteredName, name);
      client = cl;
      }

//---------------------------------------------------------
//   ~JackAudioDevice
//---------------------------------------------------------

JackAudioDevice::~JackAudioDevice()
      {
      if (jack_client_close(client)) {
            fprintf(stderr, "jack_client_close() failed: %s\n",
               strerror(errno));
            }
      }

//---------------------------------------------------------
//   getJackName()
//---------------------------------------------------------

char* JackAudioDevice::getJackName()
      {
      return jackRegisteredName;
      }

//---------------------------------------------------------
//   initJackAudio
//    return true if JACK not found
//---------------------------------------------------------

bool initJackAudio()
      {
      if (debugMsg) {
            fprintf(stderr, "init Jack Audio\n");
            jack_set_error_function(jackError);
            }
      else
            jack_set_error_function(noJackError);
      doSetuid();

      jack_client_t* client = 0;
      int i = 0;
      char jackIdString[8];
      for (i = 0; i < 5; ++i) {
            sprintf(jackIdString, "MusE-%d", i+1);
            client = jack_client_new(jackIdString);
            if (client)
                  break;
            }

      if (i == 5)
            return true;

      if (debugMsg)
            fprintf(stderr, "init Jack Audio: register device\n");
      if (client) {
            jack_set_error_function(jackError);
            if (debugMsg)
                  fprintf(stderr, "init Jack Audio: register device\n");

            jackAudio = new JackAudioDevice(client, jackIdString);
            if (debugMsg)
                  fprintf(stderr, "init Jack Audio: register client\n");
            jackAudio->registerClient();
            sampleRate  = jack_get_sample_rate(client);
            segmentSize = jack_get_buffer_size(client);
            jack_set_thread_init_callback(client, (JackThreadInitCallback) jack_thread_init, 0);
            }
      undoSetuid();
      if (client) {
            audioDevice = jackAudio;
            return false;
            }
      return true;
      }

static int bufsize_callback(jack_nframes_t n, void*)
      {
      printf("JACK: buffersize changed %d\n", n);
      return 0;
      }

//---------------------------------------------------------
//   freewheel_callback
//---------------------------------------------------------

static void freewheel_callback(int starting, void*)
      {
      audio->setFreewheel(starting);
      }

static int srate_callback(jack_nframes_t n, void*)
      {
      if (debugMsg)
            printf("JACK: sample rate changed: %d\n", n);
      return 0;
      }

//---------------------------------------------------------
//   registration_callback
//---------------------------------------------------------

static void registration_callback(jack_port_id_t, int, void*)
      {
      if (debugMsg)
            printf("JACK: registration changed\n");
      }

//---------------------------------------------------------
//   graph_callback
//    this is called from jack when the connections
//    changed
//---------------------------------------------------------

static int graph_callback(void*)
      {
      // we cannot call JackAudioDevice::graphChanged() from this
      // context, so we send a message to the gui thread which in turn
      // calls graphChanged()
      audio->sendMsgToGui('C');
      if (debugMsg)
            printf("JACK: graph changed\n");
      return 0;
      }

//---------------------------------------------------------
//   JackAudioDevice::graphChanged
//    this is called from song in gui context triggered
//    by graph_callback()
//---------------------------------------------------------

void JackAudioDevice::graphChanged()
      {
      InputList* il = song->inputs();
      for (iAudioInput ii = il->begin(); ii != il->end(); ++ii) {
            AudioInput* it = *ii;
            int channels = it->channels();
            for (int channel = 0; channel < channels; ++channel) {
                  jack_port_t* port = (jack_port_t*)(it->jackPort(channel));
                  if (port == 0)
                        continue;
                  const char** ports = jack_port_get_all_connections(client, port);
                  RouteList* rl      = it->inRoutes();

                  //---------------------------------------
                  // check for disconnects
                  //---------------------------------------

                  bool erased;
                  // limit set to 20 iterations for disconnects, don't know how to make it go
                  // the "right" amount
                  for (int i = 0;i < 20;i++) {
                        erased = false;
                        for (iRoute irl = rl->begin(); irl != rl->end(); ++irl) {
                              if (irl->channel != channel)
                                    continue;
                              QString name = irl->name();
                              const char* portName = name.latin1();
                              //printf("portname=%s\n", portName);
                              bool found = false;
                              const char** pn = ports;
                              while (pn && *pn) {
                                    if (strcmp(*pn, portName) == 0) {
                                          found = true;
                                          break;
                                          }
                                    ++pn;
                                    }
                              if (!found) {
                                    audio->msgRemoveRoute1(
                                       Route(portName, false, channel),
                                       Route(it, channel)
                                       );
                                    erased = true;
                                    break;
                                    }
                              }
                        if (!erased)
                              break;
                        }

                  //---------------------------------------
                  // check for connects
                  //---------------------------------------

                  if (ports) {
                        const char** pn = ports;
                        while (*pn) {
                              bool found = false;
                              for (iRoute irl = rl->begin(); irl != rl->end(); ++irl) {
                                    if (irl->channel != channel)
                                          continue;
                                    QString name = irl->name();
                                    const char* portName = name.latin1();
                                    if (strcmp(*pn, portName) == 0) {
                                          found = true;
                                          break;
                                          }
                                    }
                              if (!found) {
                                    audio->msgAddRoute1(
                                       Route(*pn, false, channel),
                                       Route(it, channel)
                                       );
                                    }
                              ++pn;
                              }

                        delete ports;
                        }
                  }
            }
      OutputList* ol = song->outputs();
      for (iAudioOutput ii = ol->begin(); ii != ol->end(); ++ii) {
            AudioOutput* it = *ii;
            int channels = it->channels();
            for (int channel = 0; channel < channels; ++channel) {
                  jack_port_t* port = (jack_port_t*)(it->jackPort(channel));
                  if (port == 0)
                        continue;
                  const char** ports = jack_port_get_all_connections(client, port);
                  RouteList* rl      = it->outRoutes();

                  //---------------------------------------
                  // check for disconnects
                  //---------------------------------------

                  bool erased;
                  // limit set to 20 iterations for disconnects, don't know how to make it go
                  // the "right" amount
                  for (int i = 0; i < 20 ; i++) {
                        erased = false;
                        for (iRoute irl = rl->begin(); irl != rl->end(); ++irl) {
                              if (irl->channel != channel)
                                    continue;
                              QString name = irl->name();
                              const char* portName = name.latin1();
                              bool found = false;
                              const char** pn = ports;
                              while (pn && *pn) {
                                    if (strcmp(*pn, portName) == 0) {
                                          found = true;
                                          break;
                                          }
                                    ++pn;
                                    }
                              if (!found) {
                                    audio->msgRemoveRoute1(
                                       Route(it, channel),
                                       Route(portName, false, channel)
                                       );
                                    erased = true;
                                    break;
                                    }
                              }
                        if (!erased)
                              break;
                        }

                  //---------------------------------------
                  // check for connects
                  //---------------------------------------

                  if (ports) {
                        const char** pn = ports;
                        while (*pn) {
                              bool found = false;
                              for (iRoute irl = rl->begin(); irl != rl->end(); ++irl) {
                                    if (irl->channel != channel)
                                          continue;
                                    QString name = irl->name();
                                    const char* portName = name.latin1();
                                    if (strcmp(*pn, portName) == 0) {
                                          found = true;
                                          break;
                                          }
                                    }
                              if (!found) {
                                    audio->msgAddRoute1(
                                       Route(it, channel),
                                       Route(*pn, false, channel)
                                       );
                                    }
                              ++pn;
                              }

                        delete ports;
                        }
                  }
            }
      }

//static int xrun_callback(void*)
//      {
//      printf("JACK: xrun\n");
//      return 0;
//      }

//---------------------------------------------------------
//   register
//---------------------------------------------------------

void JackAudioDevice::registerClient()
      {
      jack_set_process_callback(client, processAudio, 0);
      jack_set_sync_callback(client, processSync, 0);
      jack_on_shutdown(client, processShutdown, 0);
      jack_set_buffer_size_callback(client, bufsize_callback, 0);
      jack_set_sample_rate_callback(client, srate_callback, 0);
      jack_set_port_registration_callback(client, registration_callback, 0);
      jack_set_graph_order_callback(client, graph_callback, 0);
//      jack_set_xrun_callback(client, xrun_callback, 0);
      jack_set_freewheel_callback (client, freewheel_callback, 0);
      }

//---------------------------------------------------------
//   registerInPort
//---------------------------------------------------------

void* JackAudioDevice::registerInPort(const char* name)
      {
      void* p = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
// printf("JACK: registerInPort: <%s> %p\n", name, p);
      return p;
      }

//---------------------------------------------------------
//   registerOutPort
//---------------------------------------------------------

void* JackAudioDevice::registerOutPort(const char* name)
      {
      void* p = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
// printf("JACK: registerOutPort: <%s> %p\n", name, p);
      return p;
      }

//---------------------------------------------------------
//   exitJackAudio
//---------------------------------------------------------

void exitJackAudio()
      {
      if (jackAudio)
            delete jackAudio;
      }

//---------------------------------------------------------
//   connect
//---------------------------------------------------------

void JackAudioDevice::connect(void* src, void* dst)
      {
      const char* sn = jack_port_name((jack_port_t*) src);
      const char* dn = jack_port_name((jack_port_t*) dst);
      if (sn == 0 || dn == 0) {
            fprintf(stderr, "JackAudio::connect: unknown jack ports\n");
            return;
            }
      if (jack_connect(client, sn, dn)) {
            fprintf(stderr, "jack connect <%s>%p - <%s>%p failed\n",
               sn, src, dn, dst);
            }
      }

//---------------------------------------------------------
//   disconnect
//---------------------------------------------------------

void JackAudioDevice::disconnect(void* src, void* dst)
      {
      const char* sn = jack_port_name((jack_port_t*) src);
      const char* dn = jack_port_name((jack_port_t*) dst);
      if (sn == 0 || dn == 0) {
            fprintf(stderr, "JackAudio::disconnect: unknown jack ports\n");
            return;
            }
      if (jack_disconnect(client, sn, dn)) {
            fprintf(stderr, "jack disconnect <%s> - <%s> failed\n",
               sn, dn);
            }
      }

//---------------------------------------------------------
//   start
//---------------------------------------------------------

void JackAudioDevice::start()
      {
      doSetuid();

      if (jack_activate(client)) {
            fprintf (stderr, "JACK: cannot activate client\n");
            exit(-1);
            }
      /* connect the ports. Note: you can't do this before
         the client is activated, because we can't allow
         connections to be made to clients that aren't
         running.
       */

      InputList* il = song->inputs();
      for (iAudioInput i = il->begin(); i != il->end(); ++i) {
            AudioInput* ai = *i;
            int channel = ai->channels();
            for (int ch = 0; ch < channel; ++ch) {
                  RouteList* rl = ai->inRoutes();
                  void* port = ai->jackPort(ch);
                  for (iRoute ir = rl->begin(); ir != rl->end(); ++ir) {
                        if (ir->channel == ch)
                              connect(ir->jackPort, port);
                        }
                  }
            }
      OutputList* ol = song->outputs();
      for (iAudioOutput i = ol->begin(); i != ol->end(); ++i) {
            AudioOutput* ai = *i;
            int channel = ai->channels();
            for (int ch = 0; ch < channel; ++ch) {
                  RouteList* rl = ai->outRoutes();
                  void* port = ai->jackPort(ch);
                  for (iRoute r = rl->begin(); r != rl->end(); ++r) {
                        if (r->channel == ch) {
                              connect(port, r->jackPort);
                              }
                        }
                  }
            }
      undoSetuid();
      }

//---------------------------------------------------------
//   stop
//---------------------------------------------------------

void JackAudioDevice::stop()
      {
      if (jack_deactivate(client)) {
            fprintf (stderr, "cannot deactivate client");
            }
      }

//---------------------------------------------------------
//   framePos
//---------------------------------------------------------

int JackAudioDevice::framePos() const
      {
      jack_nframes_t n = jack_frame_time(client);
      return (int)n;
      }

#if 0
//---------------------------------------------------------
//   framesSinceCycleStart
//---------------------------------------------------------

int JackAudioDevice::framesSinceCycleStart() const
      {
      jack_nframes_t n = jack_frames_since_cycle_start(client);
      return (int)n;
      }

//---------------------------------------------------------
//   framesDelay
//    TODO
//---------------------------------------------------------

int JackAudioDevice::frameDelay() const
      {
      jack_nframes_t n = (segmentSize * (segmentCount-1)) - jack_frames_since_cycle_start(client);
      return (int)n;
      }
#endif

//---------------------------------------------------------
//   outputPorts
//---------------------------------------------------------

std::list<QString> JackAudioDevice::outputPorts()
      {
      const char** ports = jack_get_ports(client, 0, 0, 0);
      std::list<QString> clientList;
      for (const char** p = ports; p && *p; ++p) {
            jack_port_t* port = jack_port_by_name(client, *p);
            int flags = jack_port_flags(port);
            if (!(flags & JackPortIsOutput))
                  continue;
            char buffer[128];
            strncpy(buffer, *p, 128);
            if (strncmp(buffer, "MusE", 4) == 0)
                  continue;
            clientList.push_back(QString(buffer));
            }
      return clientList;
      }

//---------------------------------------------------------
//   inputPorts
//---------------------------------------------------------

std::list<QString> JackAudioDevice::inputPorts()
      {
      const char** ports = jack_get_ports(client, 0, 0, 0);
      std::list<QString> clientList;
      for (const char** p = ports; p && *p; ++p) {
            jack_port_t* port = jack_port_by_name(client, *p);
            int flags = jack_port_flags(port);
            if (!(flags & JackPortIsInput))
                  continue;
            char buffer[128];
            strncpy(buffer, *p, 128);
            if (strncmp(buffer, "MusE", 4) == 0)
                  continue;
            clientList.push_back(QString(buffer));
            }
      return clientList;
      }

//---------------------------------------------------------
//   portName
//---------------------------------------------------------

QString JackAudioDevice::portName(void* port)
      {
      if (!port) 
            return "";
      
      QString s(jack_port_name((jack_port_t*)port));
// printf("Jack::portName %p %s\n", port, s.latin1());
      return s;
      }

//---------------------------------------------------------
//   unregisterPort
//---------------------------------------------------------

void JackAudioDevice::unregisterPort(void* p)
      {
//      printf("JACK: unregister Port\n");
      jack_port_unregister(client, (jack_port_t*)p);
      }

//---------------------------------------------------------
//   getState
//---------------------------------------------------------

int JackAudioDevice::getState()
      {
      transportState = jack_transport_query(client, &pos);
      switch (transportState) {
            case JackTransportStopped:  return Audio::STOP;
            case JackTransportLooping:
            case JackTransportRolling:  return Audio::PLAY;
            case JackTransportStarting:  return Audio::START_PLAY;
            default:
                  return Audio::STOP;
            }
      }

//---------------------------------------------------------
//   setFreewheel
//---------------------------------------------------------

void JackAudioDevice::setFreewheel(bool f)
      {
//      printf("JACK: setFreewheel %d\n", f);
      jack_set_freewheel(client, f);
      }

//---------------------------------------------------------
//   startTransport
//---------------------------------------------------------

void JackAudioDevice::startTransport()
      {
//      printf("JACK: startTransport\n");
      jack_transport_start(client);
      }

//---------------------------------------------------------
//   stopTransport
//---------------------------------------------------------

void JackAudioDevice::stopTransport()
      {
//      printf("JACK: stopTransport\n");
      jack_transport_stop(client);
      }

//---------------------------------------------------------
//   seekTransport
//---------------------------------------------------------

void JackAudioDevice::seekTransport(unsigned frame)
      {
//      printf("JACK: seekTransport %d\n", frame);
      jack_transport_locate(client, frame);
      }

//---------------------------------------------------------
//   findPort
//---------------------------------------------------------

void* JackAudioDevice::findPort(const char* name)
      {
      void* p = jack_port_by_name(client, name);
// printf("Jack::findPort <%s>, %p\n", name, p);
      return p;
      }

