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

#include <stdlib.h>
#include <sys/ultrasound.h>

#include "defines.h"
#include "structs.h"

#include "defines.h"
#include "Sample.h"
#include "Voice.h"

void periodToNote(int, int *, int *);
void syncTime();

unsigned char Voice::numVoices_ = 0;

short Voice::vibraTable[][NUM_VIBRA] =
{
  /* sine wave */

  {0, 24, 49, 74, 97, 120, 141, 161,
   180, 197, 212, 224, 235, 244, 250, 253,
   255, 253, 250, 244, 235, 224, 212, 197,
   180, 161, 141, 120, 97, 74, 49, 24,
   0, -24, -49, -74, -97, -120, -141, -161,
   -180, -197, -212, -224, -235, -244, -250, -253,
   -255, -253, -250, -244, -235, -224, -212, -197,
   -180, -161, -141, -120, -97, -74, -49, -24},

  /* ramp down wave */

  {0, 8, 16, 24, 32, 40, 48, 56,
   64, 72, 80, 88, 96, 104, 112, 120,
   128, 136, 144, 152, 160, 168, 176, 184,
   192, 200, 208, 216, 224, 232, 240, 248,
   -255, -248, -240, -232, -224, -216, -208, -200,
   -192, -184, -176, -168, -160, -152, -144, -136,
   -128, -120, -112, -104, -96, -88, -80, -72,
   -64, -56, -48, -40, -32, -24, -16, -8},

  /* square wave */

  {255, 255, 255, 255, 255, 255, 255, 255,
   255, 255, 255, 255, 255, 255, 255, 255,
   255, 255, 255, 255, 255, 255, 255, 255,
   255, 255, 255, 255, 255, 255, 255, 255,
   -255, -255, -255, -255, -255, -255, -255, -255,
   -255, -255, -255, -255, -255, -255, -255, -255,
   -255, -255, -255, -255, -255, -255, -255, -255,
   -255, -255, -255, -255, -255, -255, -255, -255},
};


int
Voice::doPreUpdates(int)
{
  return 0;
}

int
Voice::doTick(int dev, int tickNo, songInfo *songChar)
{
  int scratch;
  
  if (delayCount_ && (delayCount_ == tickNo))
    delayCount_ = 0;

  if (!delayCount_)
    {
      envelopeVol(sample_->volumeEnvelopeY(volumeEnvelopePos_,
					   inVolumeSustain_, &fadeVol_));
      envelopePan(sample_->panEnvelopeY(panEnvelopePos_, inPanSustain_));
      
      if (slidePitch_ && slideRate_ &&
	  (((slidePitch_ == SLIDE_ONCE) && !tickNo) ||
	   ((slidePitch_ != SLIDE_ONCE) && tickNo)))
	{
	  int bend;
	  
	  if (songChar->slideType == SLIDE_PERIOD_LIN)
	    {
	      slidePeriod_ -= slideRate_;
	      periodToNote(slidePeriod_ / 256, &scratch, &bend);
	    }
	  else if (songChar->slideType == SLIDE_FREQ_LIN)
	    {
	      slidePeriod_ = (int)((111978.0 * 256.0) / (111978.0 / (slidePeriod_ / 256) + (slideRate_ / 256.0)));
	      periodToNote(slidePeriod_ / 256, &scratch, &bend);
	    }
	  else /* note linear (specific to XM) */
	    {
	      slidePeriod_ += (100 * slideRate_) / (16 * 256);
	      scratch = slidePeriod_ / 100;
	      bend = slidePeriod_ % 100;
	    }
	  
	  if ((slidePitch_ == SLIDE_PORT) && glissando_)
	    {
	      if (bend > 0)
		scratch += 1;
	      bend = 0;
	    }
	  
	  pitchbender_ = (scratch * 100 + bend) - (note_ * 100);
	  
	  if (((slideRate_ < 0) && (pitchbender_ <= slideGoal_)) ||
	      ((slideRate_ > 0) && (pitchbender_ >= slideGoal_)))
	    {
	      if ((songChar->type != MODULE_S3M) ||
		  (slidePitch_ == SLIDE_PORT))
		{
		  if (slidePitch_ == SLIDE_PORT)
		    lastNote_ = 0;
		  
		  pitchbender_ = slideGoal_;
		  slidePeriod_ = slidePeriodGoal_;
		  slideRate_ = 0;
		  slidePitch_ = 0;
		}
	      else if (!(flagVol_ & SLIDE_VOL) &&
		       (pitchbender_ != slideGoal_))
		{
		  flagVol_ |= SLIDE_VOL;
		  whatChanged_ |= VOL_CHANGED;
		}
	    }
	  else if (flagVol_ & SLIDE_VOL)
	    {
	      flagVol_ &= ~SLIDE_VOL;
	      whatChanged_ |= VOL_CHANGED;
	    }
	  
	  whatChanged_ |= BEND_CHANGED;
	}
      
      if (tremor_)
	{
	  scratch = tickNo % tremTotal_;
	  
	  if (scratch == tremor_)
	    {
	      flagVol_ |= TREMOR_VOL;
	      whatChanged_ |= VOL_CHANGED;
	    }
	  else if (!scratch)
	    {
	      flagVol_ &= ~TREMOR_VOL;
	      whatChanged_ |= VOL_CHANGED;
	    }
	}
      
      if (tickNo)
	{
	  if (tremolo_)
	    {
	      unsigned char vol;
	      
	      vol = vibraTable[tremoloWave_ & 0x03][tremoloPosition_] *
		tremoloDepth_ * VOL_SLIDE_RATE / 128;
	      tremoloPosition_ += tremolo_;
	      tremoloPosition_ %= NUM_VIBRA;
	      
	      if (vol != tremoloVol_)
		{
		  tremoloVol_ = vol;
		  whatChanged_ |= VOL_CHANGED;
		}
	    }
	  
	  if (panSlide_)
	    {
	      scratch = pan_ + panSlide_;
	      
	      if (scratch < 0)
		{
		  scratch = 0;
		  panSlide_ = 0;
		}
	      else if (scratch > 255)
		{
		  scratch = 255;
		  panSlide_ = 0;
		}
	      
	      pan(scratch);
	    }
	  
	  if (globalVolSlide_)
	    {
	      scratch = mainVolume_ + globalVolSlide_;
	      
	      if (scratch < 0)
		{
		  scratch = 0;
		  globalVolSlide_ = 0;
		}
	      else if (scratch > 255)
		{
		  scratch = 255;
		  globalVolSlide_ = 0;
		}
	      
	      mainVolume(scratch);
	    }
	  
	  if (cutCount_ == tickNo)
	    {
	      cutCount_ = 0;
	      volume(0);
	      // stop volume/pan envelopes too?
	    }
	  
	  if (vibraRate_)
	    {
	      // This is always period-linear.  It should really be note-linear
	      // for XM modules.
	      
	      scratch =
		(vibraTable[vibraWave_ & 0x03][vibraPosition_] * vibraDepth_)
		/ 128;
	      
	      if (scratch != vibraBend_)
		{
		  vibraBend_ = scratch;
		  whatChanged_ |= BEND_CHANGED;
		}
	      
	      vibraPosition_ += vibraRate_;
	      vibraPosition_ %= NUM_VIBRA;
	    }
	  
	  if (retrigger_)
	    if (!(tickNo % retrigger_))
	      {
		scratch = volume_;
		
		switch (retrigVol_)
		  {
		  case 0:
		  case 8:
		    break;
		  case 6:
		    scratch = (scratch * 2) / 3;
		    break;
		  case 7:
		    scratch /= 2;
		    break;
		  case 14:
		    scratch = (scratch * 3) / 2;
		    break;
		  case 15:
		    scratch *= 2;
		    break;
		  default:
		    if (retrigVol_ > 8)
		      scratch += ((1 << (retrigVol_ - 9)) * VOL_SLIDE_RATE);
		    else
		      scratch -= ((1 << (retrigVol_ - 1)) * VOL_SLIDE_RATE);
		    break;
		  }
		
		if (scratch > 255)
		  scratch = 255;
		else if (scratch < 0)
		  scratch = 0;
		
		volume(scratch);
		note(note_, SLIDE_NO_TYPE);
	      }
	}

      if (!(fineVol_ && tickNo) &&
	  (volSlide_ && (tickNo || fineVol_ || songChar->volOnZero)))
	{
	  scratch = volume_ + volSlide_;
	  
	  if (scratch > 255)
	    {
	      scratch = 255;
	      volSlide_ = 0;
	    }
	  else if (scratch < 0)
	    {
	      scratch = 0;
	      volSlide_ = 0;
	    }
	  
	  volume(scratch);
	}
      
      if (arpegNum_)
	{
	  arpegCurr_ = tickNo % arpegNum_;
	  
	  if (arpegBend_ != arpegNote_[arpegCurr_])
	    {
	      arpegBend_ = arpegNote_[arpegCurr_];
	      whatChanged_ |= BEND_CHANGED;
	    }
	}
    }

  return doPreUpdates(dev);
}

void
Voice::init(int dev, const Sample *samp)
{
  SEQ_DECLAREBUF();

  SEQ_CONTROL(dev, channel_, CTRL_PITCH_BENDER_RANGE, 8192);
  sample(samp);
  whatChanged_ &= ~(SAMPLE_CHANGED | NOTE_CHANGED);
  SEQ_SET_PATCH(dev, channel_, sample_->number());
}

void
Voice::keyOff()
{
  inVolumeSustain_ = MY_FALSE;
  inPanSustain_ = MY_FALSE;
}

void
Voice::note(int note, int slideType)
{
  extern unsigned short periodTable[];
  whatChanged_ |= NOTE_CHANGED;
  note_ = note;

  if (tremoloWave_ <= 3)
    tremoloPosition_ = 0;

  volumeEnvelopePos_ = 0;
  inVolumeSustain_ = MY_TRUE;
  fadeVol_ = 65535;
  envelopeVol(sample_->volumeEnvelopeY(volumeEnvelopePos_, MY_TRUE,
				       &fadeVol_));
  panEnvelopePos_ = 0;
  inPanSustain_ = MY_TRUE;
  envelopePan(sample_->panEnvelopeY(panEnvelopePos_, MY_TRUE));

  if (slideType != SLIDE_NO_TYPE)
    {
      if (slideType == SLIDE_NOTE_LIN)
	slidePeriod_ = note * 100;
      else
	slidePeriod_ = periodTable[note - NOTE_BASE] * 256;

      if (pitchbender_)
	{
	  pitchbender_ = 0;
	  whatChanged_ |= BEND_CHANGED;
	}

      if (flagVol_ & SLIDE_VOL)
	{
	  flagVol_ &= ~SLIDE_VOL;
	  whatChanged_ |= VOL_CHANGED;
	}
    }

  if (vibraWave_ <= 3)
    vibraPosition_ = 0;
};

void
Voice::offset(int pos)
{
  offset_ = pos / sample_->cutFactor();
  whatChanged_ |= OFFSET_CHANGED;
}

void
Voice::resetEffects(int doReset)
{
  if (doReset) 
    {
      if (tremor_)
	{
	  tremor_ = 0;

	  if (flagVol_ & TREMOR_VOL)
	    {
	      flagVol_ &= ~TREMOR_VOL;
	      whatChanged_ |= VOL_CHANGED;
	    }
	}

      if (tremolo_)
	{
	  tremolo_ = 0;
	  tremoloVol_ = 0;
	  whatChanged_ |= VOL_CHANGED;
	}

      if (vibraBend_)
	{
	  vibraBend_ = 0;
	  whatChanged_ |= BEND_CHANGED;
	}

      if (arpegBend_)
	{
	  arpegBend_ = 0;
	  whatChanged_ |= BEND_CHANGED;
	}

      arpegNum_ = 0;
      cutCount_ = 0;
      delayCount_ = 0;
      fineVol_ = 0;
      retrigger_ = 0;
      slidePitch_ = 0;
      vibraRate_ = sample_->vibratoRate();
      volSlide_ = 0;

      if (sample_->vibratoDepth() >= 0)
	vibraDepth_ = sample_->vibratoDepth();
    }
  else if (slidePitch_ == SLIDE_ONCE)
    slidePitch_ = 0;
}

void
Voice::sample(const Sample *samp)
{
  if (sample_ != samp)
    {
      if (samp)
	{
	  sample_ = samp;

	  if (samp->ok())
	    {
	      if (flagVol_ & SAMPLE_VOL)
		{
		  flagVol_ &= ~SAMPLE_VOL;
		  whatChanged_ |= VOL_CHANGED;
		}

	      // always force note retrigger if the sample changes
	      whatChanged_ |= SAMPLE_CHANGED | NOTE_CHANGED;
	    }
	}

      if (!samp || !samp->ok())
	{
	  if (!(flagVol_ & SAMPLE_VOL))
	    {
	      flagVol_ |= SAMPLE_VOL;
	      whatChanged_ |= VOL_CHANGED;
	    }
	}
    }

  if (sample_->pan() >= 0)
    pan(sample_->pan());
  
  if (sample_->vibratoType() >= 0)
    vibratoWave(sample_->vibratoType());

  finetune(sample_->finetune());
  volume(sample_->volume());
}

void
Voice::setEnvelopePos(int pos)
{
  volumeEnvelopePos_ = pos;
  panEnvelopePos_ = pos;
}

void
Voice::slideTo(int rate, int note, int type, int slideType, int rateMult)
{
  /* slide up/down should never have rate or note set to 0 */
  extern unsigned short periodTable[];
  int size, currNote;
  unsigned char setDir = 1;

  slidePitch_ = type;

  if (rate)
    {
      if (type == SLIDE_PORT)
	lastRate_ = rate;
    }
  else
    rate = lastRate_;

  if (!note)		/* only PORT should have 0 note */
    {
      if (!lastNote_)
	{			/* last port completed */
	  slidePitch_ = 0;
	  return;
	}

      slideRate_ = sample_->slideRate(rate * rateMult, slideDir_);
      note = lastNote_;
      setDir = 0;
    }
  else if (type == SLIDE_PORT)
    lastNote_ = note;

  currNote = note_ * 100 + pitchbender_;
  size = (note * 100) - currNote;

  if (!size)
    {
      slidePitch_ = 0;

      if (type == SLIDE_PORT)
	{
	  lastNote_ = 0;
	  slideDir_ = SLIDE_NEG;
	}

      return;
    }

  if (setDir)
    {
      if (size < 0)
	{
	  if (type == SLIDE_PORT)
	    slideDir_ = SLIDE_NEG;
	  rate = -rate;
	}
      else
	{
	  if (type == SLIDE_PORT)
	    slideDir_ = SLIDE_POS;
	}

      slideRate_ = sample_->slideRate(rate * rateMult, SLIDE_POS);
    }

  slideGoal_ = pitchbender_ + size;

  if (slideType == SLIDE_NOTE_LIN)
    slidePeriodGoal_ = note * 100;
  else
    slidePeriodGoal_ = periodTable[note - NOTE_BASE] * 256;
}

void
Voice::startNote(int dev, int note)
{
  SEQ_DECLAREBUF();
  int vol;

  if (flagVol_)
    vol = 0;
  else
    {
      vol = ((((volume_ * envelopeVol_) / 64) + tremoloVol_) *
	     mainVolume_) / 255;
  
      if (vol > 255)
	vol = 255;
      else if (vol < 0)
	vol = 0;
    }

  if ((note != 255) || (vol != lastVol_))
    {
      lastVol_ = vol;
      
      if (volType_ == VOL_LOG)
	{
	  int newVolume;
	  unsigned char bits;
	  
	  bits = (vol & 0xf0) >> 4;
	  newVolume = 1 << bits;
	  vol &= 0x0f;
	  if (bits >= 4)
	    {
	      newVolume |= (vol << (bits - 4));
	      for (bits -= 4; bits > 0; bits--)
		newVolume |= (1 << (bits - 1));
	    }
	  else
	    newVolume |= (vol >> (4 - bits));
	  
	  vol = (unsigned char)(newVolume / 256);
	}
      else
	vol /= 2;
      
      syncTime();
      SEQ_START_NOTE(dev, channel_, note, (unsigned char)vol);
    }
}

void
Voice::vibrato(int amount)
{
  int depth;

  vibraRate_ = (amount >> 4) & 0x0f;

  if (!vibraRate_)
    vibraRate_ = vibraOldRate_;
  else
    vibraOldRate_ = vibraRate_;

  depth = (amount & 0x0f);

  if (depth)
    vibraDepth_ = depth;
}
