
/*
*	gmod.c	- Module player for GUS and Linux.
*		(C) Hannu Savolainen, 1993
*
*	NOTE!	This program doesn't try to be a complete module player.
*		It's just a too I used while developing the driver. In
*		addition it can be used as an example on programming
*		the VoxWare Sound Driver with GUS.
*/

/*
* Many modifications have been done by Andrew J. Robinson.
* Refer to the ChangeLog for details.
*/


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

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

static unsigned char s3m_extend_tab[] =
{CMD_INFO, CMD_GLISSANDO, CMD_FINETUNE, CMD_VIBRA_WAVE,	/* S0 S1 S2 S3 */
 CMD_TREMOLO_WAVE, CMD_INFO, CMD_INFO, CMD_INFO,	/* S4 S5 S6 S7 */
 CMD_INFO, CMD_INFO, CMD_INFO, CMD_PATTERN_LOOP,	/* S8 S9 SA SB */
 CMD_CUT_NOTE, CMD_DELAY_NOTE, CMD_DELAY_PAT, CMD_INFO};	/* SC SD SE SF */

void
cvt_s3m_play (int channel, unsigned char *effect, unsigned char *parm)
{
  int tmp;

  switch (*effect)
    {
    case 0:
      break;
    case CMD_VOLSLIDE:
      if (*parm == 0)
	*parm = voices[channel].last_info;
      else
	voices[channel].last_info = *parm;

      if ((*parm >= 0xf0) && (*parm < 0xff))
	{
	  *effect = CMD_FINEVOLDOWN;
	  *parm &= 0x0f;
	}
      else if ((*parm & 0x0f) == 0x0f)
	{
	  *effect = CMD_FINEVOLUP;
	  *parm = (*parm >> 4) & 0x0f;
	}
      else if (((*parm & 0x0f) != 0) && ((*parm & 0xf0) != 0))
	*parm &= 0x0f;
      break;
    case CMD_SLIDEUP:
      if (*parm == 0)
	*parm = voices[channel].last_info;
      else
	voices[channel].last_info = *parm;

      if (*parm >= 0xf0)
	{
	  *effect = CMD_FINEPORTUP;
	  *parm &= 0x0f;
	}
      else if (*parm >= 0xe0)
	{			/* extra-fine slide, not really supported */
	  *effect = CMD_FINEPORTUP;
	  *parm &= 0x0f;
	  tmp = (int) (*parm) + voices[channel].fineslide_adj;
	  if (tmp >= 0)
	    {
	      voices[channel].fineslide_adj = tmp % 4;
	      *parm = tmp / 4;
	    }
	  else
	    {
	      voices[channel].fineslide_adj = (signed char) (tmp);
	      *parm = 0;
	    }
	}
      break;
    case CMD_SLIDEDOWN:
      if (*parm == 0)
	*parm = voices[channel].last_info;
      else
	voices[channel].last_info = *parm;

      if (*parm >= 0xf0)
	{
	  *effect = CMD_FINEPORTDOWN;
	  *parm &= 0x0f;
	}
      else if (*parm >= 0xe0)
	{			/* extra-fine slide, not really supported */
	  *effect = CMD_FINEPORTDOWN;
	  *parm &= 0x0f;
	  tmp = (int) (*parm) - voices[channel].fineslide_adj;
	  if (tmp >= 0)
	    {
	      voices[channel].fineslide_adj = -(tmp % 4);
	      *parm = tmp / 4;
	    }
	  else
	    {
	      voices[channel].fineslide_adj = (signed char) (-tmp);
	      *parm = 0;
	    }
	}
      break;
    case CMD_ARPEG2:
      if (*parm == 0)
	*parm = voices[channel].last_info;
      else
	voices[channel].last_info = *parm;

      *effect = CMD_ARPEG;
      break;
    case CMD_TREMOR:
    case CMD_VIBRAANDVOL:
    case CMD_PORTANDVOL:
    case CMD_SLIDETO:
    case CMD_VIBRATO:
    case CMD_RETRIGVOL:
      if (*parm == 0)
	*parm = voices[channel].last_info;
      else
	voices[channel].last_info = *parm;
      break;
    case CMD_BREAK:
      voices[channel].last_info = *parm;
      *parm = ((*parm >> 4) & 0x0f) * 10 + (*parm & 0x0f);
      break;
    case CMD_FINETUNE:
      voices[channel].last_info = *parm;
      *parm &= 0x0f;
      if (*parm >= 8)
	*parm -= 8;
      else
	*parm += 8;
      break;
    case CMD_GLOBAL_VOL:
      voices[channel].last_info = *parm;
      if (*parm > 0)
	*parm = (*parm * 4) - 1;
      break;
    case CMD_EXTENDED:
      voices[channel].last_info = *parm;
      *effect = s3m_extend_tab[(*parm & 0xf0) >> 4];
      *parm &= 0x0f;
      break;
    default:
      voices[channel].last_info = *parm;
    }

  if (*effect == CMD_INFO)
    {
      *effect = 0;
      *parm = 0;
    }
}

int
do_command (int channel, int command, int parm, int note, int position, int pattern,
	    struct song_info *song_char, struct effect_info *effects)
  {
    int jump = 0;

    switch (command)
      {

      case CMD_NOP:;
	break;

      case CMD_ARPEG:
	if (parm)
	  set_arpeg (channel, parm);
	break;

      case CMD_JUMP:
	jump = MOVE_JUMP;
	effects->position = parm;
	effects->pattern = 0;
	break;

      case CMD_BREAK:
	jump = MOVE_JUMP;
	effects->position = position + 1;
	if (effects->position < song_char->songlength)
	  if (parm < pattern_len[tune[effects->position]])
	    effects->pattern = parm;
	  else
	    effects->pattern = 0;
	else
	  effects->pattern = 0;
	break;

      case CMD_SET_TICKS:
	if (parm)
	  TICKS_PER_DIVISION (parm);
	else
	  jump = MOVE_EXIT;
	break;

      case CMD_SET_BPM:
	TEMPO (parm);
	break;

      case CMD_SLIDEUP:
	if (parm > 0)
	  {
	    set_slideto (channel, parm, song_char->highest_note, SLIDE_UP);
	  }
	break;

      case CMD_SLIDEDOWN:
	if (parm > 0)
	  {
	    set_slideto (channel, parm, song_char->lowest_note, SLIDE_DOWN);
	  }
	break;

      case CMD_SLIDETO:
	set_slideto (channel, parm, note, SLIDE_PORT);
	break;

      case CMD_SETOFFSET:
	sync_time ();
	/* if there is no note, restart current note to prevent click */
	if (!note)
	  SEQ_START_NOTE (gus_dev, channel, voices[channel].note, voices[channel].volume / 2);
	GUS_VOICE_POS (gus_dev, channel, parm * 256);
	break;

      case CMD_VIBRA_WAVE:
	if ((parm & 0x03) == 0x03)
	  parm -= 1;
	voices[channel].vibra_wave = parm;
	break;

      case CMD_TREMOLO_WAVE:
	if ((parm & 0x03) == 0x03)
	  parm -= 1;
	voices[channel].tremolo_wave = parm;
	break;

      case CMD_GLISSANDO:
	voices[channel].glissando = parm;
	break;

      case CMD_DELAY_PAT:
	effects->delay_notes = parm;
	break;

      case CMD_CUT_NOTE:
	voices[channel].cut_count = parm;
	break;

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

      case CMD_PATTERN_LOOP:
	if (parm == 0)
	  voices[channel].pattern = pattern;
	else
	  {
	    effects->loop_chan = channel;
	    if (voices[channel].loop_times == 0)
	      {
		voices[channel].loop_times = parm;
		effects->pattern = voices[channel].pattern;
		jump = MOVE_LOOP;
	      }
	    else if (--voices[channel].loop_times > 0)
	      {
		effects->pattern = voices[channel].pattern;
		jump = MOVE_LOOP;
	      }
	  }
	break;

      case CMD_RETRIGGER:
	voices[channel].retrigger = parm;
	voices[channel].retrig_vol = 0;
	break;

      case CMD_RETRIGVOL:
	voices[channel].retrigger = parm & 0x0f;
	voices[channel].retrig_vol = (parm >> 4) & 0x0f;
	break;

      case CMD_FINEVOLUP:
	voices[channel].finevol = TRUE;
	voices[channel].volslide = parm * VOL_SLIDE_RATE;
	break;

      case CMD_FINEVOLDOWN:
	voices[channel].finevol = TRUE;
	voices[channel].volslide = -parm * VOL_SLIDE_RATE;
	break;

      case CMD_FINEPORTUP:
	if (parm > 0)
	  {
	    voices[channel].slide_pitch = SLIDE_ONCE;
	    voices[channel].slide_rate = parm;
	  }
	break;

      case CMD_FINEPORTDOWN:
	if (parm > 0)
	  {
	    voices[channel].slide_pitch = SLIDE_ONCE;
	    voices[channel].slide_rate = -parm;
	  }
	break;

      case CMD_VOLSLIDE:
	set_volslide (channel, parm);
	break;

      case CMD_PORTANDVOL:
	set_volslide (channel, parm);
	voices[channel].slide_pitch = 1;
	break;

      case CMD_VIBRATO:
	set_vibrato (channel, parm);
	break;

      case CMD_VIBRAANDVOL:
	set_vibrato (channel, 0);
	set_volslide (channel, parm);
	break;

      case CMD_TREMOLO:
	set_tremolo (channel, parm);
	break;

      case CMD_TREMOR:
	set_tremor (channel, parm);
	break;

      default:
	/* printf ("Command %x %02x\n", pat->command, pat->parm1); */
      }

    return jump;
  }

/* do_pre_command:  Commands executed before note is started */

void
do_pre_command (int channel, int command, int parm, int note,
		struct song_info *song_char, struct options_info *options)
{
  int tmp_int;

  switch (command)
    {
    case 0:
    case CMD_NOP:
      break;

    case CMD_FINETUNE:
      parm &= 0x0f;
      if (parm <= 7)
	sample_ft[voices[channel].sample] = 12.5 * parm;
      else
	sample_ft[voices[channel].sample] = 12.5 * (parm - 16);
      break;

    case CMD_SET_PAN:
      if (!note)
	tmp_int = PAN_HARDWARE;
      else
	tmp_int = PAN_NO_HARDWARE;
      set_panning (channel, (parm & 0x0f) * 17 - 128, tmp_int);
      break;

    case CMD_VOLUME:
      {
	int vol = parm;

	if (voices[channel].volume != vol)
	  {
	    voices[channel].volume = vol;
	    if (!note)
	      {
		if (song_char->vol_type == VOL_LOG)
		  vol = vol_log_to_lin ((unsigned char) vol) / 2;
		else
		  vol /= 2;
		sync_time ();
		SEQ_START_NOTE (gus_dev, channel, 255, vol);
	      }
	  }
      }
      break;
    case CMD_GLOBAL_VOL:
      sync_time ();
      SEQ_EXPRESSION (gus_dev, channel, options->main_volume * parm / 255);
      break;
    }
}

int
play_note (int channel, int position, int pattern, struct note_info *pat,
	   struct song_info *song_char, struct effect_info *effects,
	   struct options_info *options)
{
  int jump = 0;
  int sample = pat->sample, note = pat->note, vol, tmp_int;
  int old_sample, old_arpeg, old_vibra, old_tremolo, old_tremor;
  unsigned char command = pat->command[1], param = pat->parm1[1];

  if (song_char->type == MODULE_S3M)
    cvt_s3m_play (channel, &command, &param);

  old_arpeg = voices[channel].arpeg_num;
  old_vibra = voices[channel].vibra_rate;
  old_tremolo = voices[channel].tremolo;
  old_tremor = voices[channel].tremor;

  if ((pat->command[1] != CMD_NOP) || (pat->note != 0))
    {
      voices[channel].arpeg_num = 0;
      voices[channel].vibra_rate = 0;
      voices[channel].tremolo = 0;
      voices[channel].slide_pitch = 0;
      voices[channel].volslide = 0;
      voices[channel].retrigger = 0;
      voices[channel].cut_count = 0;
      voices[channel].delay_count = 0;
      voices[channel].tremor = 0;
    }
  old_sample = voices[channel].sample;

  /* A sample with no note resets the volume to its default, but should not */
  /* retrigger the note.  This really isn't possible with the current */
  /* drivers if the sample changes, so the note is retriggered on a sample */
  /* change */

  if (sample)
    {
      voices[channel].sample = --sample;

      if (sample_ok[sample] && (old_sample != sample))
	{
	  sync_time ();
	  SEQ_SET_PATCH (gus_dev, channel, sample);
	}

      if (!note || (pat->command[0] == CMD_SLIDETO) ||
	  (pat->command[1] == CMD_SLIDETO))
	{
	  if (sample_ok[sample])
	    vol = sample_vol[sample];
	  else
	    vol = 0;

	  if (old_sample != sample)
	    {
	      sync_time ();
	      SEQ_START_NOTE (gus_dev, channel, voices[channel].note, 0);
	      voices[channel].volume = 0;
	    }

	  if ((voices[channel].volume != vol) &&
	      (pat->command[0] != CMD_VOLUME) &&
	      (pat->command[1] != CMD_VOLUME))
	    {
	      voices[channel].volume = vol;

	      if (song_char->vol_type == VOL_LOG)
		vol = vol_log_to_lin ((unsigned char) vol) / 2;
	      else
		vol /= 2;
	      sync_time ();
	      SEQ_START_NOTE (gus_dev, channel, 255, vol);
	    }
	}
      else
	voices[channel].volume = sample_vol[sample];
    }
  else
    sample = voices[channel].sample;

  if (note == NOTE_STOP)
    {
      sync_time ();
      GUS_VOICEOFF (gus_dev, channel);
      note = 0;
    }

  do_pre_command (channel, pat->command[0], pat->parm1[0], pat->note,
		  song_char, options);
  do_pre_command (channel, command, param, pat->note,
		  song_char, options);

  if (note && (pat->command[0] != CMD_SLIDETO) &&
      (pat->command[1] != CMD_SLIDETO))
    {
      sync_time ();

      if (sample_ok[sample])
	{
	  if (voices[channel].pitchbender || old_arpeg || old_vibra ||
	      (sample_ft[sample] != voices[channel].finetune))
	    {
	      /* correct for rounding by the driver */
	      tmp_int = sample_ft[sample];
	      if (tmp_int >= 0)
		tmp_int++;
	      else
		tmp_int--;

	      if ((pat->command[0] != CMD_DELAY_NOTE) &&
		  (pat->command[1] != CMD_DELAY_NOTE))
		SEQ_PITCHBEND (gus_dev, channel, tmp_int);

	      voices[channel].pitchbender = 0;
	      voices[channel].finetune = sample_ft[sample];
	      old_arpeg = 0;
	      old_vibra = 0;
	    }

	  vol = voices[channel].volume;

	  if (song_char->vol_type == VOL_LOG)
	    vol = vol_log_to_lin ((unsigned char) vol) / 2;
	  else
	    vol /= 2;

	  if ((pat->command[0] != CMD_DELAY_NOTE) &&
	      (pat->command[1] != CMD_DELAY_NOTE))
	    SEQ_START_NOTE (gus_dev, channel, note, vol);

	  voices[channel].note = note;
	  voices[channel].slide_period = period_table[note - NOTE_BASE];
	  voices[channel].fineslide_adj = 0;

	  if (voices[channel].vibra_wave <= 3)
	    voices[channel].vibra_position = 0;
	  if (voices[channel].tremolo_wave <= 3)
	    voices[channel].tremolo_position = 0;
	}
      else
	GUS_VOICEOFF (gus_dev, channel);
    }

  jump = do_command (channel, pat->command[0], pat->parm1[0], pat->note,
		     position, pattern, song_char, effects);
  jump |= do_command (channel, command, param, pat->note,
		      position, pattern, song_char, effects);

  if (((old_arpeg && !voices[channel].arpeg_num) ||
       (old_vibra && !voices[channel].vibra_rate)) &&
      !pat->note)
    {
      /* correct for rounding by the driver */
      tmp_int = voices[channel].pitchbender + voices[channel].finetune;
      if (tmp_int >= 0)
	tmp_int++;
      else
	tmp_int--;

      sync_time ();
      SEQ_PITCHBEND (gus_dev, channel, tmp_int);
    }

  if (((old_tremolo && !voices[channel].tremolo) ||
       (old_tremor && !voices[channel].tremor)) &&
      (!pat->note || (pat->command[0] == CMD_SLIDETO) ||
       (pat->command[1] == CMD_SLIDETO)))
    {
      if (old_tremor && !voices[channel].tremor)
	voices[channel].volume = voices[channel].trem_volume;

      vol = voices[channel].volume;
      if (song_char->vol_type == VOL_LOG)
	vol = vol_log_to_lin ((unsigned char) vol) / 2;
      else
	vol /= 2;

      sync_time ();
      SEQ_START_NOTE (gus_dev, channel, 255, vol);
    }

  return jump;
}
