//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: synth.cpp,v 1.1.1.1 2003/10/29 10:06:05 wschweer Exp $
//
//  This file is derived from fluid Synth and modified
//    for MusE.
//  Parts of fluid are derived from Smurf Sound Font Editor.
//  Parts of Smurf Sound Font Editor are derived from
//    awesfx utilities
//  Smurf:  Copyright (C) 1999-2000 Josh Green
//  fluid:   Copyright (C) 2001 Peter Hanappe
//  MusE:   Copyright (C) 2001 Werner Schweer
//  awesfx: Copyright (C) 1996-1999 Takashi Iwai
//=========================================================

#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <cmath>
#include <sys/mman.h>
#include <poll.h>
#include <sys/time.h>

#include "midictrl.h"
#include "synth.h"

//---------------------------------------------------------
//   ISynth
//---------------------------------------------------------

ISynth::ISynth(const char* cn)
   : Mess(cn, 2)
      {
      _busy      = false;
      sfont      = 0;
      _gmMode    = false;     // General Midi Mode
      _fluidsynth = 0;
      }

//---------------------------------------------------------
//   processEvent
//---------------------------------------------------------

void ISynth::processEvent(MEvent* ev)
      {
      int ch = ev->chan();
      int a  = ev->dataA();
      int b  = ev->dataB();

      switch(ev->type()) {
            case SND_SEQ_EVENT_NOTEON:
            case SND_SEQ_EVENT_KEYPRESS:
                  fluid_synth_noteon(_fluidsynth, ch, a, b);
                  break;

            case SND_SEQ_EVENT_NOTEOFF:
                  fluid_synth_noteoff(_fluidsynth, ch, a);
                  break;

            case SND_SEQ_EVENT_PGMCHANGE:
                  fluid_synth_program_change(_fluidsynth, ch, b);
                  break;

            case SND_SEQ_EVENT_PITCHBEND:
                  fluid_synth_pitch_bend (_fluidsynth, ch, (a << 7) | b);
                  break;

            case SND_SEQ_EVENT_CONTROL14:
            case SND_SEQ_EVENT_NONREGPARAM:
            case SND_SEQ_EVENT_REGPARAM:
            case SND_SEQ_EVENT_CONTROLLER:
                  {
                  static int ctrlLo, ctrlHi, dataLo, dataHi;
                  switch(ev->dataA()) {
                        case CTRL_LNRPN:  ctrlLo = ev->dataB(); break;
                        case CTRL_HNRPN:  ctrlHi = ev->dataB(); break;
                        case CTRL_LDATA:  dataLo = ev->dataB(); break;
                        case CTRL_HDATA:  dataHi = ev->dataB(); break;
                        }
                  if (ev->dataA() == CTRL_HDATA) {
                        int ctrl = ctrlLo+ctrlHi*128;
                        int data = dataLo+dataHi*128;
                        fluid_synth_cc(_fluidsynth, ch, ctrl, data);
                        }
                  }
                  break;

            case SND_SEQ_EVENT_SYSEX:
                  sysex(ev->data(), ev->dataLen());
                  break;

            case SND_SEQ_EVENT_CHANPRESS:
                  break;

            default:
                  printf("processEvent: unknown event\n");
            }
      }

//---------------------------------------------------------
//   sysex
//
//    f0 7e 7f 09 01 f7         GM on
//    f0 7e 7f 09 02 f7         GM off
//    f0 7f 7f 04 01 ll hh f7   Master Volume (ll-low byte, hh-high byte)
//    f0 7c 00 01 nn ... f7     replace Soundfont (nn-ascii char of path
//    f0 7c 00 02 nn ... f7     add Soundfont
//    f0 7c 00 03 nn ... f7     remove Soundfont
//---------------------------------------------------------

void ISynth::sysex(const unsigned char* data, int len)
      {
      if (len >= 6 && data[0] == 0xf0 && data[len-1] == 0xf7) {

            //---------------------------------------------
            //  Universal Non Realtime
            //---------------------------------------------

            if (data[1] == 0x7e) {
                  if (data[2] == 0x7f) {  // device Id
                        if (data[3] == 0x9) {   // GM
                              if (data[4] == 0x1) {
                                    gmOn(true);
                                    return;
                                    }
                              else if (data[4] == 0x2) {
                                    gmOn(false);
                                    return;
                                    }
                              }
                        }
                  }

            //---------------------------------------------
            //  Universal Realtime
            //---------------------------------------------

            else if (data[1] == 0x7f) {
                  if (data[2] == 0x7f) {  // device Id
                        if ((data[3] == 0x4) && (data[4] == 0x1)) {
                              float v = (data[6]*128 + data[5])/32767.0;
                              fluid_synth_set_gain(_fluidsynth, v);
                              return;
                              }
                        }
                  }

            //---------------------------------------------
            //  MusE Soft Synth
            //---------------------------------------------

            else if (data[1] == 0x7c) {
                  int n = len - 5;
                  if (n < 1) {
                        printf("fluid: bad sysEx:\n");
                        return;
                        }
                  char buffer[n+1];
                  memcpy(buffer, (char*)data+4, n);
                  buffer[n] = 0;
                  if (data[2] == 0) {     // fluid
                        if (data[3] == 1) {  // load sound font
                              sysexSoundFont(SF_REPLACE, buffer);
                              return;
                              }
                        else if (data[3] == 2) {  // load sound font
                              sysexSoundFont(SF_ADD, buffer);
                              return;
                              }
                        else if (data[3] == 3) {  // load sound font
                              sysexSoundFont(SF_REMOVE, buffer);
                              return;
                              }
                        }
                  }
            else if (data[1] == 0x41) {   // roland
                  if (data[2] == 0x10 && data[3] == 0x42 && data[4] == 0x12
                     && data[5] == 0x40 && data[6] == 00 && data[7] == 0x7f
                     && data[8] == 0x41) {
                        // gs on
                        gmOn(true);
                        }
                  }
            }
      printf("fluid: unknown sysex received, len %d:\n", len);
      for (int i = 0; i < len; ++i)
            printf("%02x ", data[i]);
      printf("\n");
      }

//---------------------------------------------------------
//   gmOn
//---------------------------------------------------------

void ISynth::gmOn(bool flag)
      {
      _gmMode = flag;
      allNotesOff();
      }

//---------------------------------------------------------
//   allNotesOff
//    stop all notes
//---------------------------------------------------------

void ISynth::allNotesOff()
      {
      for (int ch = 0; ch < 16; ++ch) {
            fluid_synth_cc(_fluidsynth, ch, 0x7b, 0);  // all notes off
            }
      }

//---------------------------------------------------------
//   init
//    return true on error
//---------------------------------------------------------

bool ISynth::init()
      {
      fluid_settings_t* settings;
//      settings.flags &= ~(fluid_AUDIO | fluid_MIDI | fluid_REVERB | fluid_CHORUS);
//      settings.sample_rate   = sampleRate();
//      settings.sample_format = fluid_FLOAT_FORMAT;
      settings = new_fluid_settings();
//    fluid_settings_setstr(settings, char* name, char* str);
      fluid_settings_setnum(settings, "synth.sample-rate", float(sampleRate()));

      _fluidsynth = new_fluid_synth(settings);
      //
      //  get some default sound font:
      //
      char* museLib = INSTDIR;
      char* sfont = getenv("DEFAULT_SOUNDFONT");
      if (sfont == 0)
            sfont = "MiniPiano.SF2";
      char* sfontPath;
      char buffer[strlen(museLib) + strlen(sfont) + strlen("soundfonts") + 2];
      if (*sfont == '/')
            sfontPath = sfont;
      else {
            sprintf(buffer, "%s/soundfonts/%s", museLib, sfont);
            sfontPath = buffer;
            }
      fluid_synth_sfload(_fluidsynth, sfontPath, 1);
      fluid_synth_set_gain(_fluidsynth, 1.0);
      return false;
      }

//---------------------------------------------------------
//   ~ISynth
//---------------------------------------------------------

ISynth::~ISynth()
      {
      // TODO delete settings
      if (_fluidsynth)
            delete_fluid_synth(_fluidsynth);
      }

//---------------------------------------------------------
//   write
//---------------------------------------------------------

void ISynth::write(int n, float** ports, int offset)
      {
      if (_busy)
            return;

      fluid_synth_write_float(_fluidsynth, n, ports[0],
         offset, 1, ports[1], offset, 1);
      }

//=========================================================
//    MESSS interface
//=========================================================

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

const char* ISynth::getPatchName(int ch, int /*hbank*/, int lbank, int prog, MType /*type*/)
      {
      char* name = "---";
      fluid_font = fluid_synth_get_sfont((fluid_synth_t*)synth(), 0);
      if (ch == 9)
            lbank = 128;
      fluid_preset_t* preset = (*fluid_font->get_preset)(fluid_font, lbank, prog);
      if (preset)
            name = (*preset->get_name)(preset);
      return name;
      }

//---------------------------------------------------------
//   getNextPatch
//---------------------------------------------------------

const MidiPatch* ISynth::getNextPatch(int ch, const MidiPatch* p) const
      {
      if (p == 0) {
            fluid_font = fluid_synth_get_sfont((fluid_synth_t*)synth(), 0);
            (*fluid_font->iteration_start)(fluid_font);
            }
      fluid_preset_t preset;

      while ((*fluid_font->iteration_next)(fluid_font, &preset)) {
            int bank    = (*preset.get_banknum)(&preset);
//            if ((bank == -1) ^ (ch != 9))
//                  continue;
            if ((bank == 128) && (ch != 9))
                  continue;
            if ((bank != 128) && (ch == 9))
                  continue;
            patch.typ   = 0;
            patch.hbank = -1;
            patch.name  = (*preset.get_name)(&preset);
            patch.lbank = -1;
            patch.prog  = (*preset.get_num)(&preset);
            return &patch;
            }
      return 0;
      }

//---------------------------------------------------------
//   getFirstParameter
//---------------------------------------------------------

bool ISynth::getFirstParameter(const char*&, const char*& ) const
      {
      return false;
      }

//---------------------------------------------------------
//   getNextParameter
//---------------------------------------------------------

bool ISynth::getNextParameter(const char*&, const char*&) const
      {
      return false;
      }

//---------------------------------------------------------
//   setParameter
//---------------------------------------------------------

void ISynth::setParameter(const char* p, const char* value)
      {
      if (strcmp(p, "soundfont") == 0) {
            sysexSoundFont(SF_ADD, value);
            }
      else if (strcmp(p, "fsoundfont") == 0) {
            sysexSoundFont(SF_REPLACE, value);
            }
      else
            fprintf(stderr, "fluid: setParameter(%s,%s): unknown param\n",
               p, value);
      }

//---------------------------------------------------------
//   getMidiInitEvent
//---------------------------------------------------------

int ISynth::getMidiInitEvent(int id, RawMidiEvent* ev)
      {
      if (id > 0)
            return 0;
      if (sfont == 0) {
            printf("ISynth:: no sound font\n");
            return 0;
            }
      ev->setType(SND_SEQ_EVENT_SYSEX);
      int n = 4 + strlen(sfont);
      unsigned char* buffer = new unsigned char[n];
      buffer[0] = 0x7c;
      buffer[1] = 0x00;
      buffer[2] = SF_REPLACE;
      strcpy((char*)(buffer+3), sfont);
      ev->setData(buffer);
      ev->setDataLen(n);
      return ++id;
      }

//---------------------------------------------------------
//   fontLoad
//    helper thread to load soundfont in the
//    background
//---------------------------------------------------------

static void* fontLoad(void* t)
      {
      ISynth* is = (ISynth*) t;

      int rv = fluid_synth_sfload(is->synth(), is->getFont(), 1);
      is->setBusy(false);
      printf("soundfont %s loaded, rv %d\n", is->getFont(), rv);
      pthread_exit(0);
      }

//---------------------------------------------------------
//   sysexSoftfont
//---------------------------------------------------------

void ISynth::sysexSoundFont(SfOp op, const char* data)
      {
      allNotesOff();

      switch(op) {
            case SF_REMOVE:
                  break;
            case SF_REPLACE:
            case SF_ADD:
                  _busy = true;
                  if (sfont)
                        delete[] sfont;
                  sfont = new char[strlen(data)+1];
                  strcpy(sfont, data);
      printf("load soundfont %s\n", sfont);
                  {
                  pthread_attr_t* attributes = (pthread_attr_t*) malloc(sizeof(pthread_attr_t));
                  pthread_attr_init(attributes);
                  pthread_attr_setdetachstate(attributes, PTHREAD_CREATE_DETACHED);
                  if (pthread_create(&fontThread, attributes, ::fontLoad, this))
                        perror("creating thread failed:");
                  pthread_attr_destroy(attributes);
                  }
                  break;
            }
      }

