//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midictrl.cpp,v 1.17 2004/06/23 12:07:16 wschweer Exp $
//
//  (C) Copyright 2001-2004 Werner Schweer (ws@seh.de)
//=========================================================

#include <assert.h>
#include <stdio.h>

#include <qobject.h>
#include "midictrl.h"
#include "xml.h"

static const char* ctrlName[] = {
       "BankSelMSB",  "Modulation",  "BreathCtrl",  "Control 3",  "Foot Ctrl",
       "Porta Time",  "DataEntMSB",  "MainVolume",  "Balance",    "Control 9",
/*10*/ "Pan",         "Expression",  "Control 12",  "Control 13", "Control 14",
       "Control 15",  "Gen.Purp.1",  "Gen.Purp.2",  "Gen.Purp.3", "Gen.Purp.4",
/*20*/ "Control 20",  "Control 21",  "Control 22",  "Control 23", "Control 24",
       "Control 25",  "Control 26",  "Control 27",  "Control 28", "Control 29",
/*30*/ "Control 30",  "Control 31",  "BankSelLSB",  "Modul. LSB", "BrthCt.LSB",
       "Control 35", "FootCt.LSB",   "Port.T LSB",  "DataEntLSB", "MainVolLSB",
       "BalanceLSB",  "Control 41",  "Pan LSB",     "Expr. LSB",  "Control 44",
       "Control 45",  "Control 46",  "Control 47",  "Gen.P.1LSB", "Gen.P.2LSB",
/*50*/ "Gen.P.3LSB",  "Gen.P.4LSB", "Control 52",   "Control 53",  "Control 54",
       "Control 55", "Control 56",  "Control 57",   "Control 58",  "Control 59",
       "Control 60",  "Control 61",  "Control 62",  "Control 63", "Sustain",
       "Porta Ped",   "Sostenuto",  "Soft Pedal", "Control 68",  "Hold 2",
       "Control 70",  "HarmonicCo", "ReleaseTime", "Attack Time", "Brightness",
       "Control 75", "Control 76",  "Control 77",  "Control 78",  "Control 79",
       "Gen.Purp.5",  "Gen.Purp.6",  "Gen.Purp.7", "Gen.Purp.8", "Porta Ctrl",
       "Control 85",  "Control 86",  "Control 87", "Control 88",  "Control 89",
       "Control 90",  "Effect1Dep", "Effect2Dep",  "Effect3Dep",  "Effect4Dep",
       "Phaser Dep", "Data Incr",   "Data Decr",   "NRPN LSB",    "NRPN MSB",
/*100*/ "RPN LSB",     "RPN MSB",     "Control102", "Control103", "Control104",
       "Control105",  "Control106",  "Control107", "Control108",  "Control109",
       "Control110",  "Control111", "Control112",  "Control113",  "Control114",
       "Control115", "Control116",  "Control117",  "Control118",  "Control119",
       "AllSndOff",   "Reset Ctrl",  "Local Ctrl", "AllNoteOff", "OmniModOff",
       "OmniModeOn",  "MonoModeOn",  "PolyModeOn"
      };

#if 0
static const char* ctrl14Name[] = {
     "BankSel",    "Modulation",  "BreathCtrl",
     "Control 3",  "Foot Ctrl",   "Porta Time",  "DataEntry",
     "MainVolume", "Balance",     "Control 9",   "Pan",
     "Expression", "Control 12",  "Control 13",  "Control 14",
     "Control 15", "Gen.Purp.1",  "Gen.Purp.2",  "Gen.Purp.3",
     "Gen.Purp.4", "Control 20",  "Control 21",  "Control 22",
     "Control 23", "Control 24",  "Control 25",  "Control 26",
     "Control 27", "Control 28",  "Control 29",  "Control 30",
     "Control 31",
     };
#endif

enum {
      COL_NAME = 0, COL_TYPE,
      COL_HNUM, COL_LNUM, COL_MIN, COL_MAX
      };

MidiControllerList defaultMidiController;
//
// some global controller which are always available:
//
MidiController veloCtrl("Velocity",                 CTRL_VELOCITY,      0, 127,   0);
static MidiController pitchCtrl("PitchBend",        CTRL_PITCH,     -8192, +8191, 0);
static MidiController programCtrl("Program",        CTRL_PROGRAM,       0, 0xffffff, 0);
static MidiController mastervolCtrl("MasterVolume", CTRL_MASTER_VOLUME, 0, 0x3fff, 0x3000);
static MidiController volumeCtrl("MainVolume",      CTRL_VOLUME,        0, 127, 100);
static MidiController panCtrl("Pan",                CTRL_PANPOT,      -64, 63,    0);

//---------------------------------------------------------
//   ctrlType2Int
//   int2ctrlType
//---------------------------------------------------------

static struct {
      MidiController::ControllerType type;
      QString name;
      }  ctrlTypes[] = {
      { MidiController::Controller7, QString("Control7") },
      { MidiController::Controller14, QString("Control14") },
      { MidiController::RPN, QString("RPN") },
      { MidiController::NRPN, QString("NRPN") },
      { MidiController::Pitch, QString("Pitch") },
      { MidiController::Program, QString("Program") },
      { MidiController::RPN, QString("RPN14") },
      { MidiController::NRPN, QString("NRPN14") },
      { MidiController::Controller7, QString("Control") },    // alias
      };

//---------------------------------------------------------
//   ctrlType2Int
//---------------------------------------------------------

MidiController::ControllerType ctrlType2Int(const QString& s)
      {
      int n = sizeof(ctrlTypes)/sizeof(*ctrlTypes);
      for (int i = 0; i < n; ++i) {
            if (ctrlTypes[i].name == s)
                  return ctrlTypes[i].type;
            }
      return MidiController::ControllerType(0);
      }

//---------------------------------------------------------
//   int2ctrlType
//---------------------------------------------------------

const QString& int2ctrlType(int n)
      {
      static QString dontKnow("?T?");
      int size = sizeof(ctrlTypes)/sizeof(*ctrlTypes);
      for (int i = 0; i < size; ++i) {
            if (ctrlTypes[i].type == n)
                  return ctrlTypes[i].name;
            }
      return dontKnow;
      }

//---------------------------------------------------------
//   initMidiController
//---------------------------------------------------------

void initMidiController()
      {
      defaultMidiController.push_back(&veloCtrl);
      defaultMidiController.push_back(&pitchCtrl);
      defaultMidiController.push_back(&programCtrl);
      defaultMidiController.push_back(&mastervolCtrl);
      defaultMidiController.push_back(&volumeCtrl);
      defaultMidiController.push_back(&panCtrl);
      }

//---------------------------------------------------------
//   midiCtrlName
//---------------------------------------------------------

QString midiCtrlName(int ctrl)
      {
      if (ctrl < 0x10000)
            return QString(ctrlName[ctrl]);
      return QString("?N?");
      }

//---------------------------------------------------------
//   MidiController
//---------------------------------------------------------

MidiController::MidiController()
   : _name(QString(QT_TR_NOOP("Velocity")))
      {
      _num     = CTRL_VELOCITY;
      _minVal  = 0;
      _maxVal  = 127;
      _initVal = 0;
      }

MidiController::MidiController(const QString& s, int n, int min, int max, int init)
   : _name(s), _num(n), _minVal(min), _maxVal(max), _initVal(init)
      {
      }

//---------------------------------------------------------
//   type
//---------------------------------------------------------

MidiController::ControllerType midiControllerType(int num)
      {
      if (num < 0x10000)
            return MidiController::Controller7;
      if (num < 0x20000)
            return MidiController::Controller14;
      if (num < 0x30000)
            return MidiController::RPN;
      if (num < 0x40000)
            return MidiController::NRPN;
      if (num == CTRL_PITCH)
            return MidiController::Pitch;
      if (num == CTRL_PROGRAM)
            return MidiController::Program;
      if (num == CTRL_VELOCITY)
            return MidiController::Velo;
      if (num < 0x60000)
            return MidiController::RPN14;
      if (num < 0x70000)
            return MidiController::NRPN14;
      return MidiController::Controller7;
      }

//---------------------------------------------------------
//   MidiController::write
//---------------------------------------------------------

void MidiController::write(int level, Xml& xml) const
      {
      ControllerType t = midiControllerType(_num);
      QString type(int2ctrlType(t));

      int h = (_num >> 8) & 0x7f;
      int l = _num & 0x7f;

      QString sl;
      if (_num & 0xff == 0xff)
            sl = "Pitch";
      else
            sl.setNum(l);

      xml.tag(level, "Controller name=\"%s\" type=\"%s\" h=\"%d\" l=\"%s\" min=\"%d\" max\"%d\" init=\"%d\" /",
         _name.latin1(), type.latin1(), h, sl.latin1(), _minVal, _maxVal, _initVal);
      }

//---------------------------------------------------------
//   MidiController::read
//---------------------------------------------------------

void MidiController::read(Xml& xml)
      {
      ControllerType t = Controller7;
      _initVal = CTRL_VAL_UNKNOWN;
      static const int NOT_SET = 0x100000;
      _minVal  = NOT_SET;
      _maxVal  = NOT_SET;
      int h    = 0;
      int l    = 0;
      bool     ok;
      int base = 10;

      for (;;) {
            Xml::Token token = xml.parse();
            const QString& tag = xml.s1();
            switch (token) {
                  case Xml::Error:
                  case Xml::End:
                        return;
                  case Xml::Attribut:
                        {
                        QString s = xml.s2();
                        if (s.left(2) == "0x")
                              base = 16;
                        if (tag == "name")
                              _name = xml.s2();
                        else if (tag == "type")
                              t = ctrlType2Int(xml.s2());
                        else if (tag == "h")
                              h = xml.s2().toInt(&ok, base);
                        else if (tag == "l") {
                              if (xml.s2() == "pitch")
                                    l = 0xff;
                              else
                                    l = xml.s2().toInt(&ok, base);
                              }
                        else if (tag == "min")
                              _minVal = xml.s2().toInt(&ok, base);
                        else if (tag == "max")
                              _maxVal = xml.s2().toInt(&ok, base);
                        else if (tag == "init")
                              _initVal = xml.s2().toInt(&ok, base);
                        }
                        break;
                  case Xml::TagStart:
                        xml.unknown("MidiController");
                        break;
                  case Xml::TagEnd:
                        if (tag == "Controller") {
                              _num = (h << 8) + l;
                              switch (t) {
                                    case RPN:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127;
                                          _num |= 0x20000;
                                          break;
                                    case NRPN:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127;
                                          _num |= 0x30000;
                                          break;
                                    case Controller7:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127;
                                          break;
                                    case Controller14:
                                          _num |= 0x10000;
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127 * 127 * 127;
                                          break;
                                    case RPN14:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127 * 127 * 127;
                                          _num |= 0x50000;
                                          break;
                                    case NRPN14:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127 * 127 * 127;
                                          _num |= 0x60000;
                                          break;
                                    case Pitch:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 8191;
                                          if (_minVal == NOT_SET)
                                                _minVal = -8192;
                                          _num = CTRL_PITCH;
                                          break;
                                    case Program:
                                          if (_maxVal == NOT_SET)
                                                _maxVal = 127 * 127 * 127;
                                          _num = CTRL_PROGRAM;
                                          break;
                                    case Velo:        // cannot happen
                                          break;
                                    }
                              if (_minVal == NOT_SET)
                                    _minVal = 0;
                              return;
                              }
                  default:
                        break;
                  }
            }
      }

//---------------------------------------------------------
//   MidiCtrlValList
//---------------------------------------------------------

MidiCtrlValList::MidiCtrlValList(int c)
      {
      ctrlNum = c;
      _hwVal = CTRL_VAL_UNKNOWN;
      }

//---------------------------------------------------------
//   value
//---------------------------------------------------------

int MidiCtrlValList::value(int tick) const
      {
      ciMidiCtrlVal i = lower_bound(tick);
      if (i == end() || i->first != tick) {
            if (i == begin())
                  return CTRL_VAL_UNKNOWN;
            --i;
            }
      return i->second;
      }

//---------------------------------------------------------
//   add
//    return true if new controller event added
//---------------------------------------------------------

bool MidiCtrlValList::add(int tick, int val)
      {
      if (value(tick) == val)       // controller has already right value
            return false;
      iMidiCtrlVal e = find(tick);
      if (e != end()) {
            e->second = val;
            return false;
            }
      insert(std::pair<const int, int> (tick, val));
      return true;
      }

//---------------------------------------------------------
//   del
//---------------------------------------------------------

void MidiCtrlValList::del(int tick)
      {
      iMidiCtrlVal e = find(tick);
      if (e == end()) {
            printf("MidiCtrlValList::del(%d): not found\n", tick);
            return;
            }
      erase(e);
      }

