//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: organgui.cpp,v 1.1.1.1 2003/10/29 10:06:06 wschweer Exp $
//
//    This is a simple GUI implemented with QT for
//    organ software synthesizer.
//
//  (C) Copyright 2001 Werner Schweer (ws@seh.de)
//=========================================================

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

#include <list>

#include "organgui.h"

#include <qapplication.h>
#include <qslider.h>
#include <qcheckbox.h>
#include <qsocketnotifier.h>
#include <qlistbox.h>
#include <qtoolbutton.h>
#include <qlineedit.h>
#include <qfiledialog.h>
#include <qlcdnumber.h>
#include <qsignalmapper.h>

#include "xml.h"
#include "filedialog.h"

//---------------------------------------------------------
//   Preset
//---------------------------------------------------------

struct Preset {
      QString name;
      int ctrl[NUM_CONTROLLER];
      void readConfiguration(Xml& xml);
      void readControl(Xml& xml);
      void writeConfiguration(Xml& xml, int level);
      };

std::list<Preset> presets;
typedef std::list<Preset>::iterator iPreset;

QString museProject;
QString museGlobalShare;
QString museUser;

QString instanceName;
static const char* presetFileTypes[] = {
   "Presets (*.pre)",
   0
   };

//---------------------------------------------------------
//   readControl
//---------------------------------------------------------

void Preset::readControl(Xml& xml)
      {
      int idx = 0;
      int val = 0;
      for (;;) {
            Xml::Token token(xml.parse());
            const QString& tag(xml.s1());
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        xml.unknown("control");
                        break;
                  case Xml::Attribut:
                        if (tag == "idx") {
                              idx = xml.s2().toInt();
                              if (idx >= NUM_CONTROLLER)
                                    idx = 0;
                              }
                        else if (tag == "val")
                              val = xml.s2().toInt();
                        break;
                  case Xml::TagEnd:
                        if (tag == "control") {
                              ctrl[idx] = val;
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   readConfiguration
//---------------------------------------------------------

void Preset::readConfiguration(Xml& xml)
      {
      for (;;) {
            Xml::Token token(xml.parse());
            const QString& tag(xml.s1());
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (tag == "control")
                              readControl(xml);
                        else
                              xml.unknown("preset");
                        break;
                  case Xml::Attribut:
                        if (tag == "name")
                              name = xml.s2();
                        break;
                  case Xml::TagEnd:
                        if (tag == "preset")
                              return;
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   writeConfiguration
//---------------------------------------------------------

void Preset::writeConfiguration(Xml& xml, int level)
      {
      xml.tag(level++, "preset name=\"%s\"", name.latin1());
      for (int i = 0; i < NUM_CONTROLLER; ++i) {
            xml.tag(level, "control idx=\"%d\" val=\"%d\" /", i, ctrl[i]);
            }
      xml.tag(level--, "/preset");
      }

//---------------------------------------------------------
//   initParameter
//    set initial parameters as in organ.cpp
//---------------------------------------------------------

void OrganGui::initParameter()
      {
      int maxdata = 128*128-1;

      setParam(HARM0, maxdata);
      setParam(HARM1, maxdata);
      setParam(HARM2, maxdata);
      setParam(HARM3, maxdata);
      setParam(HARM4, maxdata);
      setParam(HARM5, maxdata);
      setParam(ATTACK_LO,  maxdata/100);
      setParam(DECAY_LO, 0);
      setParam(SUSTAIN_LO, maxdata);
      setParam(RELEASE_LO, maxdata/2);
      setParam(ATTACK_HI,  maxdata/100);
      setParam(DECAY_HI, 0);
      setParam(SUSTAIN_HI, maxdata);
      setParam(RELEASE_HI, maxdata/2);

      setParam(BRASS, 1);
      setParam(FLUTE, 1);
      setParam(REED,  1);
      }

//---------------------------------------------------------
//   OrganGui
//---------------------------------------------------------

OrganGui::OrganGui()
   : OrganGuiBase(0, "organgui", WType_TopLevel | WDestructiveClose),
     MidiRawIn()
      {
      QSocketNotifier* s = new QSocketNotifier(0, QSocketNotifier::Read);
      connect(s, SIGNAL(activated(int)), SLOT(readStdin(int)));

      dctrl[0]  = SynthGuiCtrl(p1,  lcd1,  SynthGuiCtrl::SLIDER);
      dctrl[1]  = SynthGuiCtrl(p2,  lcd2,  SynthGuiCtrl::SLIDER);
      dctrl[2]  = SynthGuiCtrl(p3,  lcd3,  SynthGuiCtrl::SLIDER);
      dctrl[3]  = SynthGuiCtrl(p4,  lcd4,  SynthGuiCtrl::SLIDER);
      dctrl[4]  = SynthGuiCtrl(p5,  lcd5,  SynthGuiCtrl::SLIDER);
      dctrl[5]  = SynthGuiCtrl(p6,  lcd6,  SynthGuiCtrl::SLIDER);
      dctrl[6]  = SynthGuiCtrl(p7,  lcd7,  SynthGuiCtrl::SLIDER);
      dctrl[7]  = SynthGuiCtrl(p8,  lcd8,  SynthGuiCtrl::SLIDER);
      dctrl[8]  = SynthGuiCtrl(p9,  lcd9,  SynthGuiCtrl::SLIDER);
      dctrl[9]  = SynthGuiCtrl(p10, lcd10, SynthGuiCtrl::SLIDER);
      dctrl[10] = SynthGuiCtrl(p11, lcd11, SynthGuiCtrl::SLIDER);
      dctrl[11] = SynthGuiCtrl(p12, lcd12, SynthGuiCtrl::SLIDER);
      dctrl[12] = SynthGuiCtrl(p13, lcd13, SynthGuiCtrl::SLIDER);
      dctrl[13] = SynthGuiCtrl(p14, lcd14, SynthGuiCtrl::SLIDER);
      dctrl[14] = SynthGuiCtrl(sw1,    0,  SynthGuiCtrl::SWITCH);
      dctrl[15] = SynthGuiCtrl(sw2,    0,  SynthGuiCtrl::SWITCH);
      dctrl[16] = SynthGuiCtrl(sw3,    0,  SynthGuiCtrl::SWITCH);

      map = new QSignalMapper(this);
      for (int i = 0; i < 17; ++i) {
            map->setMapping(dctrl[i].editor, i);
            if (dctrl[i].type == SynthGuiCtrl::SLIDER)
                  connect((QSlider*)(dctrl[i].editor), SIGNAL(valueChanged(int)), map, SLOT(map()));
            else if (dctrl[i].type == SynthGuiCtrl::SWITCH)
                  connect((QCheckBox*)(dctrl[i].editor), SIGNAL(toggled(bool)), map, SLOT(map()));
            }
      connect(map, SIGNAL(mapped(int)), this, SLOT(ctrlChanged(int)));

      connect(presetList, SIGNAL(clicked(QListBoxItem*)),
        this, SLOT(presetClicked(QListBoxItem*)));
      // presetNameEdit
      connect(presetSet,   SIGNAL(clicked()), this, SLOT(setPreset()));
      connect(savePresets, SIGNAL(clicked()), this, SLOT(savePresetsPressed()));
      connect(loadPresets, SIGNAL(clicked()), this, SLOT(loadPresetsPressed()));

      ctrlHi = 0;
      ctrlLo = 0;
      dataHi = 0;
      dataLo = 0;

      initParameter();
      }

//---------------------------------------------------------
//   ctrlChanged
//---------------------------------------------------------

void OrganGui::ctrlChanged(int idx)
      {
      SynthGuiCtrl* ctrl = &dctrl[idx];
      int val = 0;
      if (ctrl->type == SynthGuiCtrl::SLIDER) {
            QSlider* slider = (QSlider*)(ctrl->editor);
            int max = slider->maxValue();
            val = (slider->value() * 16383 + max/2) / max;
            }
      else if (ctrl->type == SynthGuiCtrl::SWITCH) {
            val = ((QCheckBox*)(ctrl->editor))->isOn();
            }
      sendControllerChange(idx, val);
      }

//---------------------------------------------------------
//   presetClicked
//---------------------------------------------------------

void OrganGui::presetClicked(QListBoxItem* item)
      {
      if (item == 0)
            return;
      presetNameEdit->setText(item->text());
      Preset* preset = 0;
      for (iPreset i = presets.begin(); i != presets.end(); ++i) {
            if (i->name == item->text()) {
                  preset = &*i;
                  break;
                  }
            }
      activatePreset(preset);
      }

//---------------------------------------------------------
//   setPreset
//---------------------------------------------------------

void OrganGui::activatePreset(Preset* preset)
      {
      if (preset == 0) {
            fprintf(stderr, "internal error 1\n");
            exit(-1);
            }
      for (unsigned int i = 0; i < sizeof(dctrl)/sizeof(*dctrl); ++i) {
            setParam(i, preset->ctrl[i]);
            ctrlChanged(i);
            }
      }

//---------------------------------------------------------
//   setPreset
//---------------------------------------------------------

void OrganGui::setPreset()
      {
      if (presetNameEdit->text().isEmpty())
            return;
      for (iPreset i = presets.begin(); i != presets.end(); ++i) {
            if (i->name == presetNameEdit->text()) {
                  setPreset(&*i);
                  return;
                  }
            }
      addNewPreset(presetNameEdit->text());
      }

//---------------------------------------------------------
//   addNewPreset
//---------------------------------------------------------

void OrganGui::addNewPreset(const QString& name)
      {
      Preset p;
      p.name = name;
      setPreset(&p);
      presets.push_back(p);
      presetList->insertItem(name);
      }

//---------------------------------------------------------
//   setPreset
//---------------------------------------------------------

void OrganGui::setPreset(Preset* preset)
      {
      for (unsigned int i = 0; i < NUM_CONTROLLER; ++i) {
            int val = 0;
            SynthGuiCtrl* ctrl = &dctrl[i];
            if (ctrl->type == SynthGuiCtrl::SLIDER) {
                  QSlider* slider = (QSlider*)(ctrl->editor);
                  int max = slider->maxValue();
                  val = (slider->value() * 16383 + max/2) / max;
                  }
            else if (ctrl->type == SynthGuiCtrl::SWITCH) {
                  val = ((QCheckBox*)(ctrl->editor))->isOn();
                  }
            preset->ctrl[i] = val;
            }
      }

//---------------------------------------------------------
//   setParam
//    set param in gui
//    val -- midi value 0 - 16383
//---------------------------------------------------------

void OrganGui::setParam(int param, int val)
      {
      if (param >= int(sizeof(dctrl)/sizeof(*dctrl))) {
            fprintf(stderr, "organ: set unknown parameter %d to %d\n", param, val);
            return;
            }
      SynthGuiCtrl* ctrl = &dctrl[param];
      ctrl->editor->blockSignals(true);
      if (ctrl->type == SynthGuiCtrl::SLIDER) {
            QSlider* slider = (QSlider*)(ctrl->editor);
            int max = slider->maxValue();
            val = (val * max + 8191) / 16383;
            slider->setValue(val);
            if (ctrl->label)
                  ((QLCDNumber*)(ctrl->label))->display(val);
            }
      else if (ctrl->type == SynthGuiCtrl::SWITCH) {
            ((QCheckBox*)(ctrl->editor))->setChecked(val);
            }
      ctrl->editor->blockSignals(false);
      }

//---------------------------------------------------------
//   sendControllerChange
//    send controller change to synti
//---------------------------------------------------------

void OrganGui::sendControllerChange(int ctrl, int val)
      {
// fprintf(stderr, "sendControllerChange %d %d\n", ctrl, val);
      putchar(0xb0);
      putchar(0x62);
      putchar(ctrl & 0x7f);
      putchar(0x63);
      putchar(ctrl >> 7);
      putchar(0x26);
      putchar(val & 0x7f);
      putchar(0x6);
      putchar(val >> 7);
      fflush(stdout);
      }

//---------------------------------------------------------
//   readStdin
//---------------------------------------------------------

void OrganGui::readStdin(int fd)
      {
      unsigned char buffer[128];
      int n = ::read(fd, buffer, 128);
//      fprintf(stderr, "organgui: read stdin %d\n", n);
      dataInput(buffer, n);
      }

//---------------------------------------------------------
//   closeEvent
//---------------------------------------------------------

void OrganGui::closeEvent(QCloseEvent* ce)
      {
      ce->accept();
      exit(0);
      }

//---------------------------------------------------------
//   sysexReceived
//---------------------------------------------------------

void OrganGui::sysexReceived(const unsigned char* data, int len)
      {
      if (len >= 4) {
            //---------------------------------------------
            //  MusE Soft Synth
            //---------------------------------------------

            if (data[0] == 0x7c) {
                  if (data[1] == 1) {     // organ
                        if (data[2] == 2) {        // parameter response
                              if (len != 6) {
                                    fprintf(stderr, "organ gui: bad sysEx len\n");
                                    return;
                                    }
                              setParam(data[3], data[4] + (data[5]<<7));
                              return;
                              }
                        else if (data[2] == 1) {  // param request
                              return;
                              }
                        }
                  }
            }
      fprintf(stderr, "organ gui: unknown sysex received, len %d:\n", len);
      for (int i = 0; i < len; ++i)
            fprintf(stderr, "%02x ", data[i]);
      fprintf(stderr, "\n");
      }

//---------------------------------------------------------
//   eventReceived
//---------------------------------------------------------

void OrganGui::eventReceived(int a, int b, int c)
      {
      switch(a & 0xf0) {
            case 0xb0:
                  switch(b) {
                        case 0x62:  ctrlLo = c; break;
                        case 0x63:  ctrlHi = c; break;
                        case 0x06:  dataHi = c; break;
                        case 0x026: dataLo = c; break;
                        }
                  // controller events are always transmitted
                  // ctrlLo - ctrlHi - dataLo - dataHi
                  if (b == 0x06)
                        setParam(ctrlLo + ctrlHi*128, dataLo + dataHi*128);
                  break;
            case 0x80:
            case 0x90:
                  break;
            default:
                  fprintf(stderr, "OrganGUi: unknown event typ 0x%02x received\n",
                     a);
                  break;
            }
      }

//---------------------------------------------------------
//   loadPresetsPressed
//---------------------------------------------------------

void OrganGui::loadPresetsPressed()
      {
      QString s("presets/synthi/organ/");
      QString fn = getOpenFileName(s, presetFileTypes, this,
         tr("MusE: Load Organ Presets"));
      if (fn.isEmpty())
            return;
      bool popenFlag;
      FILE* f = fileOpen(this, fn, QString(".pre"), "r", popenFlag, true);
      if (f == 0)
            return;
      presets.clear();
      presetList->clear();

      Xml xml(f);
      int mode = 0;
      for (;;) {
            Xml::Token token = xml.parse();
            QString tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::TagStart:
                        if (mode == 0 && tag == "muse")
                              mode = 1;
                        else if (mode == 1 && tag == "preset") {
                              Preset preset;
                              preset.readConfiguration(xml);
                              presets.push_back(preset);
                              presetList->insertItem(preset.name);
                              }
                        else
                              xml.unknown("SynthPreset");
                        break;
                  case Xml::Attribut:
                        break;
                  case Xml::TagEnd:
                        if (tag == "muse")
                              goto ende;
                  default:
                        break;
                  }
            }
ende:
      if (popenFlag)
            pclose(f);
      else
            fclose(f);
      if (presets.empty())
            return;
      Preset preset = presets.front();
      activatePreset(&preset);
      }

//---------------------------------------------------------
//   savePresetsPressed
//---------------------------------------------------------

void OrganGui::savePresetsPressed()
      {
      QString s("presets/synthi/organ/");
      QString fn = getSaveFileName(s, presetFileTypes, this,
         tr("MusE: Save Organ Presets"));
      if (fn.isEmpty())
            return;
      bool popenFlag;
      FILE* f = fileOpen(this, fn, QString(".pre"), "w", popenFlag, false, true);
      if (f == 0)
            return;
      Xml xml(f);
      xml.header();
      xml.tag(0, "muse version=\"1.0\"");

      for (iPreset i = presets.begin(); i != presets.end(); ++i)
            i->writeConfiguration(xml, 1);

      xml.tag(1, "/muse");

      if (popenFlag)
            pclose(f);
      else
            fclose(f);
      }

//---------------------------------------------------------
//   catchSignal
//---------------------------------------------------------

static void catchSignal(int sig)
      {
      fprintf(stderr, "organ: signal %d catched\n", sig);
      if (sig == SIGSEGV)
            abort();
      if (sig == SIGINT)
            exit(0);
      exit(0);
      }

//---------------------------------------------------------
//   main
//---------------------------------------------------------

int main(int argc, char* argv[])
      {
      museUser = getenv("MUSEHOME");
      if (museUser == 0)
            museUser = getenv("HOME");
      museGlobalShare = getenv("MUSE");
      if (museGlobalShare == 0) {
            museGlobalShare = "/usr/muse";
            if (access(museGlobalShare.latin1(), R_OK) != 0) {
                  museGlobalShare = "/usr/local/muse";
                  if (access(museGlobalShare.latin1(), R_OK) != 0)
                        museGlobalShare = museUser;
                  }
            }
      instanceName = argv[1];
      if (argc >= 3)
            museProject  = argv[2];

      for (int i = 0; i < _NSIG; ++i) {
            if (i != SIGABRT)
                  signal(i, catchSignal);
            }

      QApplication app(argc, argv, true);
      QWidget* w = new OrganGui;
      if (argc > 1)
            w->setCaption(instanceName);
      w->show();
      app.connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
      qApp->exec();
      }

