// -*-C++-*-
// This file is part of the gmod package
// Copyright (C) 1997 by Andrew J. Robinson

#include <sys/types.h>
#include <errno.h>
#include <unistd.h>

#ifdef USE_LOCAL
#include "soundcard.h"
#else
#include <sys/soundcard.h>
#endif

#include <sys/ultrasound.h>

#ifdef AWE_SUPPORT
#include <linux/awe_voice.h>
#endif

#include "VoiceGUS.h"
#ifdef AWE_SUPPORT
#include "VoiceAWE.h"
#endif

#include "commands.h"
#include "defines.h"
#include "structs.h"
#include "protos.h"
#include "globals.h"

#include "Sequencer.h"
#include <deque.h>

#ifdef USE_X
#include "TopShell.h"
#endif

struct OutBuffer
{
	int nBytes;
	int offset;
	char buffer[2048];
};

void
seqbuf_dump()
{
  extern Sequencer *seq;

  seq->dump();
}

Sequencer::Sequencer() : seqfd_(-1), songFinished_(0), voices_(0)
{
  bufferList = new deque<OutBuffer *>;
}

Sequencer::~Sequencer()
{
  if (voices_)
    delete [] voices_;
  
  if (seqfd_ != -1)
    {
      ioctl(seqfd_, SNDCTL_SEQ_RESET, 0);
      ::close(seqfd_);
    }
};

int
Sequencer::bufferSize() const
{
  return bufferList->size();
}

int
Sequencer::doCommand(int channel, int command, int parm, int parm2, int note,
		     struct songInfo *songChar, struct optionsInfo *options)
{
  int processedEffect = 1;

  switch (command)
    {
    case CMD_ARPEG:
      if (parm)
	voices_[channel].arpeg(parm);
      break;

    case CMD_NOP:
      break;

    case CMD_SET_PAN:
      voices_[channel].pan(parm);
      break;

    case CMD_VOLUME:
      voices_[channel].volume(parm);
      break;

    case CMD_VIBRA_WAVE:
      voices_[channel].vibratoWave(parm);
      break;

    case CMD_TREMOLO_WAVE:
      voices_[channel].tremoloWave(parm);
      break;

    case CMD_GLISSANDO:
      voices_[channel].glissando(parm);
      break;

    case CMD_CUT_NOTE:
      voices_[channel].cutNote(parm);
      break;

    case CMD_FINEVOLUP:
      voices_[channel].fineVol(parm);
      break;

    case CMD_VOLSLIDE:
      voices_[channel].volSlide(parm);
      break;

    case CMD_PORTANDVOL:
      voices_[channel].portAndVol(parm);
      break;

    case CMD_VIBRATO:
      voices_[channel].vibrato(parm);
      break;

    case CMD_VIBRASPEED:
      voices_[channel].vibratoSpeed(parm);
      break;
	  
    case CMD_TREMOLO:
      voices_[channel].tremolo(parm);
      break;

    case CMD_TREMOR:
      voices_[channel].tremor(parm);
      break;
	  
    case CMD_SETENV_POS:
      voices_[channel].setEnvelopePos(parm);
      break;

    case CMD_FINETUNE:
      parm &= 0x0f;

      if (parm <= 7)
	voices_[channel].finetune(12.5 * parm);
      else
	voices_[channel].finetune(12.5 * (parm - 16));

      break;

    case CMD_GLOBAL_VOL:
      mainVolume(options->mainVolume * parm / 255);
      break;

    case CMD_SLIDEUP:
      if (parm > 0)
	voices_[channel].slideTo(parm, songChar->highestNote, SLIDE_UP,
				 songChar->slideType, 256);
      break;

    case CMD_SLIDEDOWN:
      if (parm > 0)
	voices_[channel].slideTo(parm, songChar->lowestNote, SLIDE_DOWN,
				 songChar->slideType, 256);
      break;

    case CMD_SLIDETO:
      voices_[channel].slideTo(parm, note, SLIDE_PORT, songChar->slideType,
			       256);
      break;

    case CMD_SETOFFSET:
      voices_[channel].offset(parm * 256);
      break;

    case CMD_SETOFFSET_1024:
      voices_[channel].offset(parm * 1024);
      break;

    case CMD_SETOFFSET_FINE:
      voices_[channel].offset(((parm * 256) + parm2) * 4);
      break;

    case CMD_DELAY_NOTE:
      if (parm == 0)
	parm = 1;
      voices_[channel].delayNote(parm);
      break;

    case CMD_RETRIGGER:
      voices_[channel].retrigger(parm, 0);
      break;

    case CMD_RETRIGVOL:
      voices_[channel].retrigger(parm & 0x0f, (parm >> 4) & 0x0f);
      break;

    case CMD_FINEVOLDOWN:
      voices_[channel].fineVol(-parm);
      break;

    case CMD_FINEPORTUP:
      if (parm > 0)
	voices_[channel].slideTo(parm, songChar->highestNote, SLIDE_ONCE,
				 songChar->slideType, 256);
      break;

    case CMD_FINEPORTDOWN:
      if (parm > 0)
	voices_[channel].slideTo(parm, songChar->lowestNote, SLIDE_ONCE,
				 songChar->slideType, 256);
      break;

    case CMD_XFINEPORTUP:
      if (parm > 0)
	voices_[channel].slideTo(parm, songChar->highestNote, SLIDE_ONCE,
				 songChar->slideType, 64);
      break;

    case CMD_XFINEPORTDOWN:
      if (parm > 0)
	voices_[channel].slideTo(parm, songChar->lowestNote, SLIDE_ONCE,
				 songChar->slideType, 64);
      break;

    case CMD_VIBRAANDVOL:
      voices_[channel].vibrato(0);
      voices_[channel].volSlide(parm);
      break;

    case CMD_PANSLIDE:
      if (parm & 0xf0)
	voices_[channel].panSlide((parm >> 4) & 0x0f);
      else
	voices_[channel].panSlide(-(parm & 0x0f));
      break;

    case CMD_GLOBALVOL_SLIDE:
      if ((parm >> 4) & 0x0f)
	globalVolSlide(VOL_SLIDE_RATE * ((parm >> 4) & 0x0f));
      else
	globalVolSlide(-VOL_SLIDE_RATE * (parm & 0x0f));
      break;

    default:
      processedEffect = 0;
      break;
    }

  return processedEffect;
}

void
Sequencer::dump()
{
  SEQ_DECLAREBUF();
  OutBuffer *bufferEntry;

  if (_seqbufptr > 0)
    {
      bufferEntry = new OutBuffer;
      memcpy(bufferEntry->buffer, _seqbuf, _seqbufptr);
      bufferEntry->nBytes = _seqbufptr;
      bufferEntry->offset = 0;
      bufferList->push_back(bufferEntry);
      _seqbufptr = 0;
    }
}

void
Sequencer::force()
{
  int firstTime = 1;

  while (bufferList->size() != 0)
    {
      if (!firstTime)
	sleep(1);
      else
	firstTime = 0;

      write();
    }
}

void
Sequencer::numVoices(int num, int volType, const Sample *samp)
{
  SEQ_DECLAREBUF();

  numVoices_ = num;

  if (num < 14)
    num = 14;

  sync();
  GUS_NUMVOICES(gusDev_, num);
  SEQ_VOLUME_MODE(gusDev_, VOL_METHOD_LINEAR);

  if (voices_)
    delete [] voices_;

#ifdef AWE_SUPPORT
  if (synthType_ == SAMPLE_TYPE_AWE32)
    voices_ = new VoiceAWE[numVoices_];
  else
#endif
    voices_ = new VoiceGUS[numVoices_];

  for (int i = 0; i < numVoices_; i++)
    {
      voices_[i].init(gusDev_, samp);
      voices_[i].volType(volType);
    }

  force();
  sync();
}

int
Sequencer::open()
{
  SEQ_DECLAREBUF();
  int i, n;
  struct synth_info info;
  struct midi_info midi;

#ifndef OSS_GETVERSION
#define OSS_GETVERSION _IOR('M', 118, int)
#endif

  // Versions of the driver prior to 0x0307f0 may have a problem
  // when used with the GUS PnP, so attempt to work around the
  // problem

  if ((ioctl(seqfd_, OSS_GETVERSION, &n) == -1) || (n < 0x0307f0))
    {					
      if ((seqfd_ = ::open("/dev/sequencer", O_WRONLY | O_NONBLOCK, 0)) == -1)
	return -1;

      if (ioctl(seqfd_, SNDCTL_SEQ_NRMIDIS, &n) != -1)
	{
	  int midiDev = -1;

	  for (i = 0; (i < n) && (midiDev == -1); i++)
	    {
	      midi.device = i;

	      if ((ioctl(seqfd_, SNDCTL_MIDI_INFO, &midi) != -1) &&
		  (midi.dev_type == SNDCARD_GUS))
		midiDev = i;
	    }

	  ::close(seqfd_);

	  if (midiDev != -1)
	    {
	      char midiName[12];
	      
	      sprintf(midiName, "/dev/midi%02d", midiDev);
	      ::open(midiName, O_WRONLY | O_NONBLOCK, 0);
	    }
	}
    }

  if ((seqfd_ = ::open("/dev/sequencer", O_RDWR | O_NONBLOCK, 0)) == -1)
    return -1;

  if (ioctl(seqfd_, SNDCTL_SEQ_NRSYNTHS, &n) == -1)
    return -2;

  gusDev_ = -1;

  for (i = 0; (i < n) && (gusDev_ == -1); i++)
    {
      info.device = i;

      if (ioctl(seqfd_, SNDCTL_SYNTH_INFO, &info) == -1)
	return -3;

      if (info.synth_type == SYNTH_TYPE_SAMPLE
	  && (info.synth_subtype == SAMPLE_TYPE_GUS
#ifdef AWE_SUPPORT
	      || info.synth_subtype == SAMPLE_TYPE_AWE32
#endif
	      ))
	{
	  gusDev_ = i;
	  synthType_ = info.synth_subtype;
	}
    }

  if (gusDev_ == -1)
    return -4;

#ifdef USE_X
  writeNotifier_ = new QSocketNotifier(seqfd_, QSocketNotifier::Write);
  writeNotifier_->setEnabled(FALSE);
  QObject::connect(writeNotifier_, SIGNAL(activated(int)), this,
		   SLOT(writeReady()));
  readNotifier_ = new QSocketNotifier(seqfd_, QSocketNotifier::Read);
  readNotifier_->setEnabled(FALSE);
  QObject::connect(readNotifier_, SIGNAL(activated(int)), this,
		   SLOT(readReady()));
#endif
  
  GUS_NUMVOICES(gusDev_, 32);
  dump();
  force();
  ioctl(seqfd_, SNDCTL_SEQ_SYNC, 0);
  ioctl(seqfd_, SNDCTL_SEQ_RESET, 0);

  return (seqfd_);
}

#ifdef USE_X
void
Sequencer::readReady()
{
  unsigned int seqInput;
  extern TopShell *topShell;

  while ((seqInput = procInput()) != ECHO_NONE)
    if (seqInput == ECHO_END)
      {
	songFinished_ = 0;
	topShell->doNext(1);
      }
}
#endif

void
Sequencer::stopPlayback()
{
  SEQ_DECLAREBUF();
  OutBuffer *bufferItem;
  extern double nextTime;

  nextTime = 0;

#ifdef USE_X
  writeEnabled(FALSE);
#endif

  ioctl(seqfd_, SNDCTL_SEQ_RESET, 0);

  while (bufferList->size() != 0)
    {
      bufferItem = bufferList->front();
      delete bufferItem;
      bufferList->pop_front();
    }
	      
  _seqbufptr = 0;
#ifdef USE_X
  songFinished_ = 0;
#else
  SEQ_ECHO_BACK(ECHO_END);
  dump();
  force();
  ioctl(seqfd_, SNDCTL_SEQ_SYNC, 0);
#endif
  
}

int
Sequencer::write()
{
  OutBuffer *bufferItem;
  int result;

  bufferItem = bufferList->front();
      
  if ((result = ::write(seqfd_, bufferItem->buffer + bufferItem->offset, bufferItem->nBytes)) == -1)
    {
      if (errno != EAGAIN)
	{
	  perror("write /dev/sequencer");
	  exit(ERR_SEQUENCER);
	}
    }
  else if (result != bufferItem->nBytes)
    {
      bufferItem->nBytes -= result;
      bufferItem->offset += result;
    }
  else
    {
      delete bufferItem;
      bufferList->pop_front();
    }

  return (result);
}

#ifdef USE_X
void
Sequencer::writeReady()
{
  SEQ_DECLAREBUF();

  do
    {
      while ((bufferSize() < 1) && (!songFinished_))
	if ((songFinished_ = playNextPosition()))
	  {
	    syncTime();
	    SEQ_ECHO_BACK(ECHO_END);
	    dump();
	  }
    }
  while ((bufferSize() > 0) && (write() != -1));
  
  if ((bufferSize() == 0) && (songFinished_))
    {
      writeEnabled(FALSE);
      songFinished_ = 0;
    }
}
#endif


#if defined(USE_X) && !defined(DEPEND)
#include "Sequencer.moc"
#endif
