//=========================================================
//  MusE
//  Linux Music Editor
//  $Id: midiport.cpp,v 1.21 2004/06/18 08:36:43 wschweer Exp $
//
//  (C) Copyright 2000-2004 Werner Schweer (ws@seh.de)
//=========================================================

#include <qpopupmenu.h>
#include "mididev.h"
#include "midiport.h"
#include "midictrl.h"
#include "midi.h"
#include "minstrument.h"
#include "xml.h"
#include "globals.h"
#include "mpevent.h"
#include "synth.h"
#include "app.h"

MidiPort midiPorts[MIDI_PORTS];

//---------------------------------------------------------
//   initMidiPorts
//---------------------------------------------------------

void initMidiPorts()
      {
      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* port = &midiPorts[i];
            port->setInstrument(genericMidiInstrument);
            }
      }

//---------------------------------------------------------
//   MidiPort
//---------------------------------------------------------

MidiPort::MidiPort()
   : _state("not configured")
      {
      _device     = 0;
      _instrument = 0;
      _controller = new MidiCtrlValListList();

      //
      // create minimum set of managed controllers
      // to make midi mixer operational
      //
      for (int i = 0; i < MIDI_CHANNELS; ++i) {
            addManagedController(i, CTRL_PROGRAM);
            addManagedController(i, CTRL_VOLUME);
            addManagedController(i, CTRL_PANPOT);
            _automationType[i] = AUTO_READ;
            }
      }

//---------------------------------------------------------
//   addManagedController
//---------------------------------------------------------

void MidiPort::addManagedController(int channel, int ctrl)
      {
      iMidiCtrlValList cl = _controller->find(channel, ctrl);
      if (cl == _controller->end()) {
            MidiCtrlValList* pvl = new MidiCtrlValList(ctrl);
            _controller->add(channel, pvl);
            }
      }

//---------------------------------------------------------
//   MidiPort
//---------------------------------------------------------

MidiPort::~MidiPort()
      {
      delete _controller;
      }

//---------------------------------------------------------
//   guiVisible
//---------------------------------------------------------

bool MidiPort::guiVisible() const
      {
      return _instrument ? _instrument->guiVisible() : false;
      }

//---------------------------------------------------------
//   hasGui
//---------------------------------------------------------

bool MidiPort::hasGui() const
      {
      return _instrument ? _instrument->hasGui() : false;
      }

//---------------------------------------------------------
//   setDevice
//---------------------------------------------------------

void MidiPort::setMidiDevice(MidiDevice* dev)
      {
      if (_device) {
            if (_device->isSynti())
                  _instrument = genericMidiInstrument;
            _device->setPort(-1);
            _device->close();
            }
      if (dev) {
            for (int i = 0; i < MIDI_PORTS; ++i) {
                  MidiPort* mp = &midiPorts[i];
                  if (mp->device() == dev) {
                        // move device
                        _state = mp->state();
                        mp->clearDevice();
                        break;
                        }
                  }
            _device = dev;
            if (_device->isSynti()) {
                  SynthI* s = (SynthI*)_device;
                  _instrument = s;
                  }
            _state = _device->open();
            _device->setPort(portno());

            // init HW controller state
            for (iMidiCtrlValList i = _controller->begin(); i != _controller->end(); ++i) {
                  int channel = i->first >> 24;
                  int cntrl   = i->first & 0xffffff;
                  int val     = i->second->hwVal();
                  if (val != CTRL_VAL_UNKNOWN) {
                        _device->putEvent(MidiPlayEvent(0, portno(), channel,
                           ME_CONTROLLER, cntrl, val));
                        }
                  }
            }
      else
            clearDevice();
      }

//---------------------------------------------------------
//   clearDevice
//---------------------------------------------------------

void MidiPort::clearDevice()
      {
      _device = 0;
      _state  = "not configured";
      }

//---------------------------------------------------------
//   portno
//---------------------------------------------------------

int MidiPort::portno() const
      {
      for (int i = 0; i < MIDI_PORTS; ++i) {
            if (&midiPorts[i] == this)
                  return i;
            }
      return -1;
      }

//---------------------------------------------------------
//   midiPortsPopup
//---------------------------------------------------------

QPopupMenu* midiPortsPopup(QWidget* parent)
      {
      QPopupMenu* p = new QPopupMenu(parent);
      for (int i = 0; i < MIDI_PORTS; ++i) {
            MidiPort* port = &midiPorts[i];
            MidiDevice* dev = port->device();
            QString name;
            name.sprintf("%d(%s)", port->portno()+1,
                     dev ? dev->name().latin1() : "none");
            p->insertItem(name, i);
            }
      return p;
      }

//---------------------------------------------------------
//   portname
//---------------------------------------------------------

const QString& MidiPort::portname() const
      {
      static const QString none("none");
      if (_device)
            return _device->name();
      else
            return none;
      }

//---------------------------------------------------------
//   sendGmOn
//    send GM-On message to midi device and keep track
//    of device state
//---------------------------------------------------------

void MidiPort::sendGmOn()
      {
      sendSysex(gmOnMsg, gmOnMsgLen);
      for (int i = 0; i < MIDI_CHANNELS; ++i) {
            setHwCtrlState(i, CTRL_PROGRAM,      0);
            setHwCtrlState(i, CTRL_PITCH,        0);
            setHwCtrlState(i, CTRL_VOLUME,     100);
            setHwCtrlState(i, CTRL_PANPOT,      64);
            setHwCtrlState(i, CTRL_REVERB_SEND, 40);
            setHwCtrlState(i, CTRL_CHORUS_SEND,  0);
            }
      }

//---------------------------------------------------------
//   sendGsOn
//    send Roland GS-On message to midi device and keep track
//    of device state
//---------------------------------------------------------

void MidiPort::sendGsOn()
      {
      static unsigned char data2[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x33, 0x50, 0x3c };
      static unsigned char data3[] = { 0x41, 0x10, 0x42, 0x12, 0x40, 0x01, 0x34, 0x50, 0x3b };

      sendSysex(data2, sizeof(data2));
      sendSysex(data3, sizeof(data3));
      }

//---------------------------------------------------------
//   sendXgOn
//    send Yamaha XG-On message to midi device and keep track
//    of device state
//---------------------------------------------------------

void MidiPort::sendXgOn()
      {
      sendSysex(xgOnMsg, xgOnMsgLen);
      for (int i = 0; i < MIDI_CHANNELS; ++i) {
            setHwCtrlState(i, CTRL_PROGRAM, 0);
            setHwCtrlState(i, CTRL_MODULATION, 0);
            setHwCtrlState(i, CTRL_PORTAMENTO_TIME, 0);
            setHwCtrlState(i, CTRL_VOLUME, 0x64);
            setHwCtrlState(i, CTRL_PANPOT, 0x40);
            setHwCtrlState(i, CTRL_EXPRESSION, 0x7f);
            setHwCtrlState(i, CTRL_SUSTAIN, 0x0);
            setHwCtrlState(i, CTRL_PORTAMENTO, 0x0);
            setHwCtrlState(i, CTRL_SOSTENUTO, 0x0);
            setHwCtrlState(i, CTRL_SOFT_PEDAL, 0x0);
            setHwCtrlState(i, CTRL_HARMONIC_CONTENT, 0x40);
            setHwCtrlState(i, CTRL_RELEASE_TIME, 0x40);
            setHwCtrlState(i, CTRL_ATTACK_TIME, 0x40);
            setHwCtrlState(i, CTRL_BRIGHTNESS, 0x40);
            setHwCtrlState(i, CTRL_REVERB_SEND, 0x28);
            setHwCtrlState(i, CTRL_CHORUS_SEND, 0x0);
            setHwCtrlState(i, CTRL_VARIATION_SEND, 0x0);
            }
      }

//---------------------------------------------------------
//   sendSysex
//    send SYSEX message to midi device
//---------------------------------------------------------

void MidiPort::sendSysex(const unsigned char* p, int n)
      {
      if (_device) {
            MidiPlayEvent event(0, 0, ME_SYSEX, p, n);
           _device->putEvent(event);
            }
      }

//---------------------------------------------------------
//   sendStart
//---------------------------------------------------------

void MidiPort::sendStart()
      {
      if (_device) {
            MidiPlayEvent event(0, 0, ME_START, 0, 0);
           _device->putEvent(event);
            }
      }

//---------------------------------------------------------
//   sendStop
//---------------------------------------------------------

void MidiPort::sendStop()
      {
      if (_device) {
            MidiPlayEvent event(0, 0, 0, ME_STOP, 0, 0);
           _device->putEvent(event);
            }
      }

//---------------------------------------------------------
//   sendClock
//---------------------------------------------------------

void MidiPort::sendClock()
      {
      if (_device) {
            MidiPlayEvent event(0, 0, 0, ME_CLOCK, 0, 0);
           _device->putEvent(event);
            }
      }

//---------------------------------------------------------
//   sendContinue
//---------------------------------------------------------

void MidiPort::sendContinue()
      {
      if (_device) {
            MidiPlayEvent event(0, 0, 0, ME_CONTINUE, 0, 0);
           _device->putEvent(event);
            }
      }

//---------------------------------------------------------
//   sendSongpos
//---------------------------------------------------------

void MidiPort::sendSongpos(int pos)
      {
      if (_device) {
            MidiPlayEvent event(0, 0, 0, ME_SONGPOS, pos, 0);
           _device->putEvent(event);
            }
      }

//---------------------------------------------------------
//   sendEvent
//    return true, if event cannot be delivered
//---------------------------------------------------------

bool MidiPort::sendEvent(const MidiPlayEvent& ev)
      {
      if (ev.type() == ME_CONTROLLER) {
            //
            //  optimize controller settings
            //
            if (hwCtrlState(ev.channel(), ev.dataA()) == ev.dataB()) {
// printf("optimize ctrl %d %x val %d\n", ev.dataA(), ev.dataA(), ev.dataB());
                  return false;
                  }
// printf("set HW Ctrl State ch:%d 0x%x 0x%x\n", ev.channel(), ev.dataA(), ev.dataB());
            setHwCtrlState(ev.channel(), ev.dataA(), ev.dataB());
            }
      if (!_device)
            return true;
      return _device->putEvent(ev);
      }

//---------------------------------------------------------
//   hwCtrlState
//---------------------------------------------------------

int MidiPort::hwCtrlState(int ch, int ctrl) const
      {
      ch &= 0xff;
      iMidiCtrlValList cl = _controller->find(ch, ctrl);
      if (cl == _controller->end()) {
            if (debugMsg)
                  printf("hwCtrlState: ctrl 0x%x not found\n", ctrl);
            return CTRL_VAL_UNKNOWN;
            }
      MidiCtrlValList* vl = cl->second;
      return vl->hwVal();
      }

//---------------------------------------------------------
//   setHwCtrlState
//---------------------------------------------------------

void MidiPort::setHwCtrlState(int ch, int ctrl, int val)
      {
      iMidiCtrlValList cl = _controller->find(ch, ctrl);
      if (cl == _controller->end()) {
            // try to add new controller
//            muse->importController(ch, this, ctrl);
//            iMidiCtrlValList cl = _controller->find(ch, ctrl);
//            if (cl == _controller->end()) {

                  if (debugMsg)
                        printf("setHwCtrlState(%d,0x%x,0x%x): not found\n", ch, ctrl, val);
                  return;
//                  }
            }
      MidiCtrlValList* vl = cl->second;
// printf("setHwCtrlState ch %d  ctrl %x  val %x\n", ch, ctrl, val);
      return vl->setHwVal(val);
      }

//---------------------------------------------------------
//   setCtrl
//    return true if new controller value added
//---------------------------------------------------------

bool MidiPort::setCtrl(int ch, int tick, int ctrl, int val)
      {
      iMidiCtrlValList cl = _controller->find(ch, ctrl);
      if (cl == _controller->end()) {
            if (debugMsg)
                  printf("setCtrl: controller 0x%x for channel %d not found\n", ctrl, ch);
            return false;
            }
      return cl->second->add(tick, val);
      }

//---------------------------------------------------------
//   getCtrl
//---------------------------------------------------------

int MidiPort::getCtrl(int ch, int tick, int ctrl) const
      {
      iMidiCtrlValList cl = _controller->find(ch, ctrl);
      if (cl == _controller->end()) {
            if (debugMsg)
                  printf("getCtrl: controller %d(0x%x) for channel %d not found %d\n",
                     ctrl, ctrl, ch, _controller->size());
            return CTRL_VAL_UNKNOWN;
            }
      return cl->second->value(tick);
      }

//---------------------------------------------------------
//   deleteController
//---------------------------------------------------------

void MidiPort::deleteController(int ch, int tick, int ctrl)
      {
      iMidiCtrlValList cl = _controller->find(ch, ctrl);
      if (cl == _controller->end()) {
//            if (debugMsg)
                  printf("getCtrl: controller %d(0x%x) for channel %d not found %d\n",
                     ctrl, ctrl, ch, _controller->size());
            return;
            }
      cl->second->del(tick);
      }

//---------------------------------------------------------
//   getMidiController
//---------------------------------------------------------

MidiController* MidiPort::midiController(int num) const
      {
      if (_instrument) {
            MidiControllerList* mcl = _instrument->controller();
            for (iMidiController i = mcl->begin(); i != mcl->end(); ++i) {
                  int cn = (*i)->num();
                  if (cn == num)
                        return *i;
                  // wildcard?
                  if (((cn & 0xff) == 0xff) && ((cn & ~0xff) == (num & ~0xff)))
                        return *i;
                  }
            }
      for (iMidiController i = defaultMidiController.begin(); i != defaultMidiController.end(); ++i) {
            int cn = (*i)->num();
            if (cn == num)
                  return *i;
            // wildcard?
            if (((cn & 0xff) == 0xff) && ((cn & ~0xff) == (num & ~0xff)))
                  return *i;
            }
      QString name = midiCtrlName(num);
      int min = 0;
      int max = 128 * 128 - 1;
      MidiController* c = new MidiController(name, num, min, max, 0);
      defaultMidiController.push_back(c);
      return c;
      }

